Compare commits
3 Commits
8471db3ea1
...
8e5d9d9900
Author | SHA1 | Date | |
---|---|---|---|
8e5d9d9900 | |||
5eb6d2885f | |||
6716d5003b |
@ -10,7 +10,7 @@ import ffmpy
|
||||
import argparse
|
||||
|
||||
# multiprocessing stuff
|
||||
from multiprocessing import Pool
|
||||
from multiprocessing import Pool, Value, parent_process
|
||||
|
||||
# executing some commands
|
||||
import subprocess
|
||||
@ -27,6 +27,7 @@ from random import randint
|
||||
# typing hints
|
||||
from typing import Any, Optional
|
||||
|
||||
# temporary file/directory management
|
||||
import tempfile
|
||||
|
||||
# working with sound files
|
||||
@ -38,6 +39,9 @@ import pyloudnorm
|
||||
# file copy
|
||||
import shutil
|
||||
|
||||
# signal handling
|
||||
import signal
|
||||
|
||||
"""
|
||||
Normalize loudness of all music files in a given directory and its subdirectories.
|
||||
"""
|
||||
@ -45,6 +49,18 @@ Normalize loudness of all music files in a given directory and its subdirectorie
|
||||
musicfile_extensions = (".flac", ".wav", ".mp3", ".m4a", ".aac", ".opus")
|
||||
|
||||
|
||||
class CleanupRequired(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def sigint_handler(signum, frame):
|
||||
# set workers to clean up
|
||||
cleanup_required.value = 1
|
||||
# Output only once
|
||||
if parent_process() is None:
|
||||
print("\nReceived KeyboardInterrupt. Process cleaning up and stopping...")
|
||||
|
||||
|
||||
def loudnorm(inputfile: str, outputfile: str):
|
||||
"""
|
||||
Normalize audio to EBU R 128 standard using pyloudnorm
|
||||
@ -59,6 +75,10 @@ def loudnorm(inputfile: str, outputfile: str):
|
||||
meter = pyloudnorm.Meter(rate=rate)
|
||||
loudness = meter.integrated_loudness(data=data)
|
||||
|
||||
# cleanup check
|
||||
if bool(cleanup_required.value):
|
||||
raise CleanupRequired()
|
||||
|
||||
# normalize audio
|
||||
file_normalized = pyloudnorm.normalize.loudness(
|
||||
data=data, input_loudness=loudness, target_loudness=-18.0
|
||||
@ -76,6 +96,9 @@ def ffmpeg_to_wav(inputfile: str, outputfile: str):
|
||||
inputfile (str): Path to input file
|
||||
outputfile (str): Path to output file
|
||||
"""
|
||||
# cleanup check
|
||||
if bool(cleanup_required.value):
|
||||
raise CleanupRequired()
|
||||
|
||||
# convert to wav in temporary directory
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
@ -99,6 +122,10 @@ def ffmpeg_to_wav(inputfile: str, outputfile: str):
|
||||
|
||||
subprocess.run(ff.cmd, shell=True, capture_output=True)
|
||||
|
||||
# cleanup check
|
||||
if bool(cleanup_required.value):
|
||||
raise CleanupRequired()
|
||||
|
||||
# normalize loudness
|
||||
loudnorm(inputfile=temp_input, outputfile=temp_output)
|
||||
|
||||
@ -107,6 +134,10 @@ def ffmpeg_to_wav(inputfile: str, outputfile: str):
|
||||
outputfile: "-c:a libopus" " " "-b:a 192k" " " "-compression_level 10"
|
||||
}
|
||||
|
||||
# cleanup check
|
||||
if bool(cleanup_required.value):
|
||||
raise CleanupRequired()
|
||||
|
||||
ff = ffmpy.FFmpeg(
|
||||
inputs={temp_output: None}, outputs=outputcmd, global_options=("-y")
|
||||
)
|
||||
@ -123,6 +154,9 @@ def ffmpeg_copy_metadata(inputfile: str, outputfile: str):
|
||||
inputfile (str): Path to input file
|
||||
outputfile (str): Path to output file
|
||||
"""
|
||||
# cleanup check
|
||||
if bool(cleanup_required.value):
|
||||
raise CleanupRequired()
|
||||
|
||||
# store output file as temporary file. FFMPEG can't work on files in-place
|
||||
with tempfile.NamedTemporaryFile() as temp_audio:
|
||||
@ -154,10 +188,13 @@ def main(inputfile: str) -> Optional[list[Any]]:
|
||||
Output:
|
||||
dynamically normalised audio files (list)
|
||||
"""
|
||||
|
||||
# set output folder to parent path + "normalized"
|
||||
outputfolder = os.path.join(os.path.dirname(inputfile), "normalized")
|
||||
|
||||
# cleanup check
|
||||
if bool(cleanup_required.value):
|
||||
raise CleanupRequired()
|
||||
|
||||
# NOTE create output folder
|
||||
# because multiple parallel processes are at work here,
|
||||
# there might be conflicts with one trying to create the directory although it already exists
|
||||
@ -201,6 +238,11 @@ if __name__ == "__main__":
|
||||
"""
|
||||
Handle arguments and other details for interactive usage
|
||||
"""
|
||||
# global cleanup variable
|
||||
cleanup_required = Value("i", 0)
|
||||
|
||||
# handle KeyboardInterrupt
|
||||
signal.signal(signal.SIGINT, sigint_handler)
|
||||
|
||||
# start time of program
|
||||
starttime = time.time()
|
||||
@ -242,9 +284,6 @@ if __name__ == "__main__":
|
||||
# file where last run timestamp is stored
|
||||
timefile = os.path.join(srcfolder, "run.time")
|
||||
|
||||
# list of non-linear normalizations
|
||||
nonlinear_all: Optional[list[Any]] = []
|
||||
|
||||
# get time of previous run
|
||||
if reset:
|
||||
timeprev = 0
|
||||
@ -265,8 +304,11 @@ if __name__ == "__main__":
|
||||
if os.path.getmtime(filepath) >= float(timeprev):
|
||||
musicfiles.append(os.path.join(root, file))
|
||||
|
||||
# process pool
|
||||
with Pool(cpu) as p:
|
||||
nonlinear_all: Optional[list[Any]] = p.map(main, musicfiles)
|
||||
result = p.map_async(main, musicfiles)
|
||||
# wait for all processes to finish
|
||||
result.wait()
|
||||
|
||||
# write this run's time into file
|
||||
with open(timefile, "w") as file:
|
||||
|
Loading…
Reference in New Issue
Block a user