cut.py --short builds a social-length version from the same takes: one line per witness cut inside their takes, then the challengers beat, "Quiet, please", "Play", the splat and Reg's stinger. Each cut keeps its own build folder and output file, so cut 2, cut 3 and the short all stay. words.py gives local Whisper word timestamps for cutting inside lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 lines
1.1 KiB
Python
20 lines
1.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Word timestamps for takes, from a local Whisper — the cutdown cuts INSIDE lines, and the QA
|
|
listener's timestamps drift too much for that.
|
|
|
|
uv run --with faster-whisper python tools/trailer/words.py <take|file>... (take names in promo/trailer/clips)
|
|
"""
|
|
import os, subprocess, sys
|
|
import numpy as np
|
|
from faster_whisper import WhisperModel
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
m = WhisperModel('small.en', compute_type='int8')
|
|
for name in sys.argv[1:]:
|
|
# decode with ffmpeg ourselves: faster-whisper's PyAV path breaks on newer av releases
|
|
src = name if os.path.exists(name) else os.path.join(ROOT, 'promo', 'trailer', 'clips', name + '.mp4')
|
|
raw = subprocess.run(['ffmpeg', '-v', 'error', '-i', src,
|
|
'-ac', '1', '-ar', '16000', '-f', 's16le', '-'], capture_output=True).stdout
|
|
segs, _ = m.transcribe(np.frombuffer(raw, np.int16).astype(np.float32) / 32768, word_timestamps=True)
|
|
print(name + ':', ' '.join(f'{w.word.strip()}[{w.start:.2f}-{w.end:.2f}]' for s in segs for w in s.words))
|