"""Audio file utility functions shared across audio processing services.""" import hashlib from pathlib import Path from typing import TYPE_CHECKING import ffmpeg # type: ignore[import-untyped] from app.core.logging import get_logger if TYPE_CHECKING: from app.models.sound import Sound logger = get_logger(__name__) def get_file_hash(file_path: Path) -> str: """Calculate SHA-256 hash of a file.""" hash_sha256 = hashlib.sha256() with file_path.open("rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_sha256.update(chunk) return hash_sha256.hexdigest() def get_file_size(file_path: Path) -> int: """Get file size in bytes.""" return file_path.stat().st_size def get_audio_duration(file_path: Path) -> int: """Get audio duration in milliseconds using ffmpeg.""" try: probe = ffmpeg.probe(str(file_path)) duration = float(probe["format"]["duration"]) return int(duration * 1000) # Convert to milliseconds except Exception as e: logger.warning("Failed to get duration for %s: %s", file_path, e) return 0 def get_sound_file_path(sound: "Sound") -> Path: """Get the file path for a sound based on its type and normalization status. Args: sound: The Sound object to get the path for Returns: Path: The full path to the sound file """ # Determine the correct subdirectory based on sound type if sound.type.upper() == "EXT": subdir = "extracted" elif sound.type.upper() == "SDB": subdir = "soundboard" elif sound.type.upper() == "TTS": subdir = "text_to_speech" else: # Fallback to lowercase type subdir = sound.type.lower() # Use normalized file if available, otherwise original if sound.is_normalized and sound.normalized_filename: return Path("sounds/normalized") / subdir / sound.normalized_filename return Path("sounds/originals") / subdir / sound.filename