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.
This commit is contained in:
exu 2024-06-07 20:45:01 +02:00
parent 8471db3ea1
commit 6716d5003b

View File

@ -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
@ -38,6 +38,12 @@ import pyloudnorm
# file copy
import shutil
# signal handling
import signal
# exiting
import sys
"""
Normalize loudness of all music files in a given directory and its subdirectories.
"""
@ -45,6 +51,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 +77,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 +98,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 +124,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 +136,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 +156,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 +190,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 +240,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()
@ -265,8 +309,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: