Lip sync cannot be judged silently. extract.sh now pulls the audio track and writes manifest.json alongside the frames; the page reads the true extraction rate from it rather than assuming one, since a guessed fps desynchronises picture from sound - the one thing this view exists to show. Audio is the clock: frame = floor(currentTime * fps). A slow render loop drops frames instead of drifting, and half/quarter speed work via playbackRate with the picture following for free. Scrubbing, stepping and clicking a thumbnail all seek the audio too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
38 lines
1.6 KiB
Bash
Executable file
38 lines
1.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Extract a clip to a PNG sequence + audio + manifest for the take builder.
|
|
#
|
|
# Frames are pre-extracted rather than decoded in the page on purpose: browser
|
|
# video seeking by currentTime is approximate and requestVideoFrameCallback only
|
|
# delivers frames at playback speed, so neither gives a deterministic per-frame
|
|
# pass. A PNG sequence is exact, instantly seekable, and reproducible.
|
|
#
|
|
# Audio comes out alongside because the page uses it as the PLAYBACK CLOCK -
|
|
# frame = floor(audio.currentTime * fps) - so picture and sound cannot drift
|
|
# apart no matter how long the shot is or how slow the render loop runs.
|
|
set -euo pipefail
|
|
|
|
src="${1:?usage: ./extract.sh CLIP [FPS] [OUTDIR]}"
|
|
fps="${2:-12}"
|
|
out="${3:-frames}"
|
|
|
|
rm -rf "$out"; mkdir -p "$out"
|
|
ffmpeg -hide_banner -loglevel warning -i "$src" -vf "fps=$fps" "$out/%04d.png"
|
|
count=$(ls -1 "$out" | wc -l)
|
|
|
|
# Mono is enough for judging sync and halves the file. Absent audio is not fatal.
|
|
if ffprobe -v error -select_streams a:0 -show_entries stream=codec_type \
|
|
-of csv=p=0 "$src" 2>/dev/null | grep -q audio; then
|
|
ffmpeg -hide_banner -loglevel warning -y -i "$src" -vn -ac 1 -ar 44100 audio.wav
|
|
audio='"audio.wav"'
|
|
echo "audio -> audio.wav"
|
|
else
|
|
audio='null'
|
|
echo "no audio stream"
|
|
fi
|
|
|
|
# The page must know the true extraction rate: if it guessed, audio and picture
|
|
# would drift. Source of truth lives here, next to the frames it describes.
|
|
printf '{"fps":%s,"frames":%s,"dir":"%s","audio":%s,"source":"%s"}\n' \
|
|
"$fps" "$count" "$out" "$audio" "$(basename "$src")" > manifest.json
|
|
|
|
echo "$count frames at ${fps}fps -> $out/ (manifest.json written)"
|