149 lines
5.0 KiB
Bash
149 lines
5.0 KiB
Bash
#!/usr/bin/env bash
|
|
# Synthetic credits-detection corpus with exact ground truth.
|
|
#
|
|
# Every clip is 25fps, 640x360, with audio, and every positive puts credits_start
|
|
# at exactly frame 1534 = 61.360s. That figure is deliberately not round: at a
|
|
# 750ms fine-pass interval anchored to the window start, a boundary at 60.000s is
|
|
# itself a sample point, so a detector sampling on that grid scores a perfect zero
|
|
# for reasons that have nothing to do with how well it found anything. Off-grid
|
|
# truth is what makes the reported error the real error.
|
|
#
|
|
# Segments are encoded separately and concatenated, so the boundary is a genuine
|
|
# frame boundary rather than something a filter approximated.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")"
|
|
OUT=corpus
|
|
rm -rf "$OUT" parts
|
|
mkdir -p "$OUT" parts
|
|
FONT="C\\:/Windows/Fonts/arial.ttf"
|
|
ENC="-c:v libx264 -preset veryfast -pix_fmt yuv420p -g 50 -r 25 -c:a aac -b:a 96k -ar 48000 -ac 2"
|
|
Q="-hide_banner -loglevel error -y"
|
|
|
|
# Frame-exact durations. 1534 frames at 25fps is 61.360s.
|
|
D_PROG=61.36 # programme before the credits
|
|
D_PROG_A=46.36 # programme before a dark closing scene
|
|
D_DARK=15.00 # the dark closing scene
|
|
D_CRED=30.00
|
|
D_CRED_SHORT=20.00
|
|
D_POST=10.00
|
|
D_DARKTAIL=30.00
|
|
TRUTH_MS=61360
|
|
|
|
cat > parts/credits.txt <<'EOF'
|
|
DIRECTED BY
|
|
ALEX MERRIWEATHER
|
|
WRITTEN BY
|
|
JORDAN HALE
|
|
PRODUCED BY
|
|
SAM OKONKWO
|
|
CAST
|
|
RILEY BRENNAN
|
|
DANA VOSS
|
|
KIT ARMITAGE
|
|
MARGO SEELEY
|
|
DIRECTOR OF PHOTOGRAPHY
|
|
NOOR HADDAD
|
|
EDITED BY
|
|
TOBY LINDQVIST
|
|
MUSIC BY
|
|
PRIYA RAGHAVAN
|
|
PRODUCTION DESIGNER
|
|
ELLIOT SHAW
|
|
COSTUME DESIGNER
|
|
FRANCES ADEYEMI
|
|
CASTING BY
|
|
WES TANAKA
|
|
UNIT PRODUCTION MANAGER
|
|
HANNAH DELACROIX
|
|
FIRST ASSISTANT DIRECTOR
|
|
OSCAR BRENNAN
|
|
EOF
|
|
|
|
# Audio beds. Programme is broadband and non-stationary the way speech is;
|
|
# credits are a steady two-note pad. The distinction a detector can find here is
|
|
# stationarity and spectral shape, which is the same distinction real end-credit
|
|
# music offers against dialogue — synthetic, but not a different mechanism.
|
|
A_PROG="anoisesrc=color=brown:amplitude=0.35:r=48000,tremolo=f=3.5:d=0.8"
|
|
A_CRED="sine=frequency=220:r=48000,volume=0.3"
|
|
|
|
# $1 out $2 duration $3 video lavfi $4 audio lavfi [$5 extra -vf]
|
|
seg() {
|
|
local extra="${5:-null}"
|
|
ffmpeg $Q -f lavfi -i "$3" -f lavfi -i "$4" -t "$2" -vf "$extra" $ENC "parts/$1.mp4"
|
|
}
|
|
|
|
# scrolling credits over a supplied background. $1 out $2 dur $3 vsrc $4 fontcolour
|
|
credits_over() {
|
|
seg "$1" "$2" "$3" "$A_CRED" \
|
|
"drawtext=fontfile='$FONT':textfile=parts/credits.txt:fontcolor=$4:fontsize=15:line_spacing=10:x=(w-tw)/2:y=h-35*t"
|
|
}
|
|
|
|
join() { # $1 out name, rest: part names
|
|
local out="$1"; shift
|
|
: > parts/list.txt
|
|
# Relative to the list file's own directory: a POSIX $(pwd) from Git Bash is
|
|
# not a path native ffmpeg can open.
|
|
for p in "$@"; do echo "file '$p.mp4'" >> parts/list.txt; done
|
|
ffmpeg $Q -f concat -safe 0 -i parts/list.txt -c copy "$OUT/$out.mp4"
|
|
}
|
|
|
|
PROG="testsrc2=s=640x360:r=25"
|
|
DARKPROG="color=c=#0d0d10:s=640x360:r=25"
|
|
BLACK="color=c=black:s=640x360:r=25"
|
|
WHITE="color=c=white:s=640x360:r=25"
|
|
|
|
echo "building parts..."
|
|
seg prog $D_PROG "$PROG" "$A_PROG"
|
|
seg progA $D_PROG_A "$PROG" "$A_PROG"
|
|
seg dark15 $D_DARK "$DARKPROG" "$A_PROG"
|
|
seg darktail $D_DARKTAIL "$DARKPROG" "$A_PROG"
|
|
seg post10 $D_POST "$PROG" "$A_PROG"
|
|
seg progfade $D_PROG "$PROG" "$A_PROG" "fade=t=out:st=59.86:d=1.5"
|
|
|
|
credits_over cred_black $D_CRED "$BLACK" white
|
|
credits_over cred_short $D_CRED_SHORT "$BLACK" white
|
|
credits_over cred_over $D_CRED "$PROG" white
|
|
credits_over cred_white $D_CRED "$WHITE" black
|
|
|
|
# static centred card credits (no scroll)
|
|
seg cred_static $D_CRED "$BLACK" "$A_CRED" \
|
|
"drawtext=fontfile='$FONT':textfile=parts/credits.txt:fontcolor=white:fontsize=13:line_spacing=6:x=(w-tw)/2:y=(h-th)/2"
|
|
|
|
echo "assembling clips..."
|
|
join hard-cut-black prog cred_black
|
|
join fade-to-black progfade cred_black
|
|
join credits-over-footage prog cred_over
|
|
join bright-credits prog cred_white
|
|
join static-card-credits prog cred_static
|
|
join dark-scene-then-credits progA dark15 cred_black
|
|
join short-credits-postcred prog cred_short post10
|
|
join negative-dark-ending progA dark15 darktail
|
|
|
|
# Ground truth travels with the corpus rather than being written into the
|
|
# harness: a truth table kept apart from the media it describes is one that
|
|
# silently stops matching when a clip is regenerated.
|
|
{
|
|
echo '['
|
|
first=1
|
|
for f in "$OUT"/*.mp4; do
|
|
n=$(basename "$f" .mp4)
|
|
[ $first -eq 1 ] || echo ','
|
|
first=0
|
|
if [ "$n" = "negative-dark-ending" ]; then
|
|
printf ' {"clip":"%s","creditsStartMs":-1}' "$n"
|
|
else
|
|
printf ' {"clip":"%s","creditsStartMs":%s}' "$n" "$TRUTH_MS"
|
|
fi
|
|
done
|
|
echo; echo ']'
|
|
} > "$OUT/truth.json"
|
|
|
|
echo
|
|
printf '%-28s %-9s %s\n' CLIP DURATION TRUTH
|
|
for f in "$OUT"/*.mp4; do
|
|
n=$(basename "$f" .mp4)
|
|
d=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$f")
|
|
if [ "$n" = "negative-dark-ending" ]; then t="none"; else t="61.360"; fi
|
|
printf '%-28s %-9.2f %s\n' "$n" "$d" "$t"
|
|
done
|