#!/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)"