import tempfile import zipfile from pathlib import Path import pandas as pd from . import pipeline from .logger import log SUPPORTED_EXTS = {".wav", ".mp3", ".ogg", ".flac", ".m4a"} def _resolve_root(input_path: str) -> Path: p = Path(input_path) if p.is_dir(): return p if p.suffix.lower() == ".zip": tmp = Path(tempfile.mkdtemp()) with zipfile.ZipFile(p) as zf: zf.extractall(tmp) entries = list(tmp.iterdir()) if len(entries) == 1 and entries[0].is_dir(): return entries[0] return tmp raise ValueError(f"Expected a folder or .zip file, got: {input_path}") def process_batch(input_path: str, on_progress=None) -> tuple[pd.DataFrame, list[dict]]: root = _resolve_root(input_path) errors: list[dict] = [] csv_files = list(root.glob("*.csv")) manifest_names: set[str] | None = None if csv_files: manifest = pd.read_csv(csv_files[0]) if "name" in manifest.columns: manifest_names = set(manifest["name"]) else: errors.append( {"file": csv_files[0].name, "error": "manifest is missing the required 'name' column"} ) audio_files = sorted(f for f in root.iterdir() if f.suffix.lower() in SUPPORTED_EXTS) if not audio_files: errors.append({"file": "", "error": "no supported audio files found in the batch"}) if manifest_names is not None: found_names = {f.name for f in audio_files} for missing in sorted(manifest_names - found_names): errors.append({"file": missing, "error": "listed in manifest but not found in the uploaded batch"}) rows = [] for i, f in enumerate(audio_files, start=1): try: result = pipeline.analyze_one(str(f)) rows.append({"name": f.name, **result.model_dump(mode="json")}) except Exception as e: log.exception("failed to process %s: %s", f.name, e) errors.append({"file": f.name, "error": f"{type(e).__name__}: {e}"}) if on_progress: on_progress(i, len(audio_files), f.name) return pd.DataFrame(rows), errors