import queue import shutil import tempfile import threading import time from pathlib import Path from typing import Callable, Optional import structlog from ffmpeg_normalize import FFmpegNormalize from settings import Settings DELETE_INPUTS = Settings().delete_consume_files CONSUME_DELAY = Settings().consume_delay log = structlog.get_logger() class AudioProcessor: def __init__(self, generate_callback: Callable[[], None]): self.queue: queue.Queue[(Path, Path)] = queue.Queue() self.is_running = False self.processor_thread: Optional[threading.Thread] = None self.generate_callback = generate_callback def add_file(self, input_filename: Path, output_filename: Path) -> None: self.queue.put((input_filename, output_filename)) log.debug( "Added file for processing", input_filename=input_filename, output_filename=output_filename, ) def start_processing(self) -> None: if self.is_running: return self.is_running = True self.processor_thread = threading.Thread(target=self._process_queue) self.processor_thread.daemon = True self.processor_thread.start() log.info("Started audio processing queue") def stop_processing(self) -> None: self.is_running = False def process_audio(self, input_filename: Path, output_filename: Path) -> None: log.info( "Processing file", input_filename=input_filename, output_filename=output_filename, ) if not input_filename.is_file(): log.error("Could not process non-file", input_filename=input_filename) return # wait for file to finish uploading current_size = input_filename.stat().st_size while True: time.sleep(CONSUME_DELAY) if input_filename.stat().st_size != current_size: log.debug( "Waiting for file to finish uploading", input_filename=input_filename, ) current_size = input_filename.stat().st_size continue break with tempfile.TemporaryDirectory() as tmp: input_temp_path = Path(tmp) / input_filename.name output_temp_path = Path(tmp) / output_filename.name # copy to temp directory shutil.move(input_filename, input_temp_path) ffmpeg_normalize = FFmpegNormalize( "ebu", audio_codec="aac", audio_bitrate="192k" ) ffmpeg_normalize.add_media_file(str(input_temp_path), str(output_temp_path)) ffmpeg_normalize.run_normalization() shutil.move(output_temp_path, output_filename) self.generate_callback() def _process_queue(self) -> None: while self.is_running: try: (input_filename, output_filename) = self.queue.get(timeout=1.0) try: self.process_audio(input_filename, output_filename) log.info("Finished processing", output_filename=output_filename) except Exception as e: log.error( "Failed processing", input_filename=input_filename, output_filename=output_filename, error=e, ) self.queue.task_done() except queue.Empty: continue