#!/usr/bin/env python3 """Offline beat detection for a rendered/generated audio track. Usage: python beat_detect.py Prints a single JSON object to stdout: {"tempo": , "beats": [, ...], "duration": } Local/offline only: librosa + numpy (already pinned for the asset venv). No network, no model downloads. On any failure it prints {"tempo": 0, "beats": [], "duration": 0} and exits 0 so the caller can fail soft to un-synced cuts rather than failing the render. """ import json import sys def main() -> int: if len(sys.argv) < 2: print(json.dumps({"tempo": 0, "beats": [], "duration": 0})) return 0 audio_path = sys.argv[1] try: import librosa # sr=None keeps the file's native rate; mono is enough for beat tracking. y, sr = librosa.load(audio_path, sr=None, mono=True) duration = float(librosa.get_duration(y=y, sr=sr)) tempo, beat_frames = librosa.beat.beat_track(y=y, sr=sr, units="frames") beat_times = librosa.frames_to_time(beat_frames, sr=sr) beats = [round(float(t), 4) for t in beat_times.tolist()] tempo_val = float(tempo[0]) if hasattr(tempo, "__len__") else float(tempo) print(json.dumps({"tempo": round(tempo_val, 2), "beats": beats, "duration": round(duration, 4)})) return 0 except Exception as exc: # noqa: BLE001 - fail soft, never break the render sys.stderr.write("beat_detect failed: %s\n" % exc) print(json.dumps({"tempo": 0, "beats": [], "duration": 0})) return 0 if __name__ == "__main__": raise SystemExit(main())