arthur/extract.sh

39 lines
1.6 KiB
Bash
Raw Permalink Normal View History

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