Compare commits

..

3 Commits

Author SHA1 Message Date
exu
8e5d9d9900 Update imports 2024-06-07 20:50:15 +02:00
exu
5eb6d2885f Remove redundant variable 2024-06-07 20:50:02 +02:00
exu
6716d5003b Implement handling of KeyboardInterrupt
This commit adds handling of KeyboardInterrupts. For this purpose, a
    signal handler is installed for all processes and a global variable
    that calls further cleanup steps. Because processes are used, each
    process receives the SIGINT. To properly stop and not just wait for
    something to handle the SIGINT, each process raises a custom
    `CleanupRequired` error and exits.
2024-06-07 20:45:01 +02:00

View File

@ -10,7 +10,7 @@ import ffmpy
import argparse import argparse
# multiprocessing stuff # multiprocessing stuff
from multiprocessing import Pool from multiprocessing import Pool, Value, parent_process
# executing some commands # executing some commands
import subprocess import subprocess
@ -27,6 +27,7 @@ from random import randint
# typing hints # typing hints
from typing import Any, Optional from typing import Any, Optional
# temporary file/directory management
import tempfile import tempfile
# working with sound files # working with sound files
@ -38,6 +39,9 @@ import pyloudnorm
# file copy # file copy
import shutil import shutil
# signal handling
import signal
""" """
Normalize loudness of all music files in a given directory and its subdirectories. 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") 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): def loudnorm(inputfile: str, outputfile: str):
""" """
Normalize audio to EBU R 128 standard using pyloudnorm Normalize audio to EBU R 128 standard using pyloudnorm
@ -59,6 +75,10 @@ def loudnorm(inputfile: str, outputfile: str):
meter = pyloudnorm.Meter(rate=rate) meter = pyloudnorm.Meter(rate=rate)
loudness = meter.integrated_loudness(data=data) loudness = meter.integrated_loudness(data=data)
# cleanup check
if bool(cleanup_required.value):
raise CleanupRequired()
# normalize audio # normalize audio
file_normalized = pyloudnorm.normalize.loudness( file_normalized = pyloudnorm.normalize.loudness(
data=data, input_loudness=loudness, target_loudness=-18.0 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 inputfile (str): Path to input file
outputfile (str): Path to output file outputfile (str): Path to output file
""" """
# cleanup check
if bool(cleanup_required.value):
raise CleanupRequired()
# convert to wav in temporary directory # convert to wav in temporary directory
with tempfile.TemporaryDirectory() as tempdir: 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) subprocess.run(ff.cmd, shell=True, capture_output=True)
# cleanup check
if bool(cleanup_required.value):
raise CleanupRequired()
# normalize loudness # normalize loudness
loudnorm(inputfile=temp_input, outputfile=temp_output) 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" outputfile: "-c:a libopus" " " "-b:a 192k" " " "-compression_level 10"
} }
# cleanup check
if bool(cleanup_required.value):
raise CleanupRequired()
ff = ffmpy.FFmpeg( ff = ffmpy.FFmpeg(
inputs={temp_output: None}, outputs=outputcmd, global_options=("-y") 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 inputfile (str): Path to input file
outputfile (str): Path to output 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 # store output file as temporary file. FFMPEG can't work on files in-place
with tempfile.NamedTemporaryFile() as temp_audio: with tempfile.NamedTemporaryFile() as temp_audio:
@ -154,10 +188,13 @@ def main(inputfile: str) -> Optional[list[Any]]:
Output: Output:
dynamically normalised audio files (list) dynamically normalised audio files (list)
""" """
# set output folder to parent path + "normalized" # set output folder to parent path + "normalized"
outputfolder = os.path.join(os.path.dirname(inputfile), "normalized") outputfolder = os.path.join(os.path.dirname(inputfile), "normalized")
# cleanup check
if bool(cleanup_required.value):
raise CleanupRequired()
# NOTE create output folder # NOTE create output folder
# because multiple parallel processes are at work here, # because multiple parallel processes are at work here,
# there might be conflicts with one trying to create the directory although it already exists # 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 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 # start time of program
starttime = time.time() starttime = time.time()
@ -242,9 +284,6 @@ if __name__ == "__main__":
# file where last run timestamp is stored # file where last run timestamp is stored
timefile = os.path.join(srcfolder, "run.time") timefile = os.path.join(srcfolder, "run.time")
# list of non-linear normalizations
nonlinear_all: Optional[list[Any]] = []
# get time of previous run # get time of previous run
if reset: if reset:
timeprev = 0 timeprev = 0
@ -265,8 +304,11 @@ if __name__ == "__main__":
if os.path.getmtime(filepath) >= float(timeprev): if os.path.getmtime(filepath) >= float(timeprev):
musicfiles.append(os.path.join(root, file)) musicfiles.append(os.path.join(root, file))
# process pool
with Pool(cpu) as p: 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 # write this run's time into file
with open(timefile, "w") as file: with open(timefile, "w") as file: