video_editing_poc/tools/beat_detect.py

44 lines
1.6 KiB
Python

#!/usr/bin/env python3
"""Offline beat detection for a rendered/generated audio track.
Usage:
python beat_detect.py <audio_path>
Prints a single JSON object to stdout:
{"tempo": <bpm float>, "beats": [<seconds float>, ...], "duration": <seconds float>}
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())