316 lines
8.3 KiB
Bash
316 lines
8.3 KiB
Bash
#!/bin/sh
|
|
|
|
set -eu
|
|
export LC_ALL=C
|
|
export TZ=UTC
|
|
|
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)
|
|
project_root=$(CDPATH= cd -- "$script_dir/.." && pwd -P)
|
|
output_dir="$project_root/tests/fixtures/generated"
|
|
force=false
|
|
|
|
if [ "${1:-}" = "--force" ]; then
|
|
force=true
|
|
shift
|
|
fi
|
|
|
|
if [ "$#" -ne 0 ]; then
|
|
echo "Usage: scripts/generate-fixtures.sh [--force]" >&2
|
|
exit 2
|
|
fi
|
|
|
|
for command_name in ffmpeg ffprobe head sha256sum mktemp; do
|
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
|
echo "Required development command is unavailable: $command_name" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
fixture_names="
|
|
pattern-av.mp4
|
|
compatible-a.mp4
|
|
compatible-b.mp4
|
|
different-dimensions.mp4
|
|
tone.wav
|
|
tone.mp3
|
|
tone.flac
|
|
pattern.webm
|
|
subtitle.srt
|
|
subtitle.vtt
|
|
subtitle.ass
|
|
metadata.mp4
|
|
chapters.mp4
|
|
attached-cover.mp3
|
|
truncated.mp4
|
|
SHA256SUMS
|
|
"
|
|
|
|
mkdir -p "$output_dir"
|
|
if [ -L "$output_dir" ] || [ ! -d "$output_dir" ]; then
|
|
echo "Fixture output must be a real directory: $output_dir" >&2
|
|
exit 1
|
|
fi
|
|
canonical_output_dir=$(CDPATH= cd -- "$output_dir" && pwd -P)
|
|
if [ "$canonical_output_dir" != "$project_root/tests/fixtures/generated" ]; then
|
|
echo "Fixture output resolves outside the expected project path." >&2
|
|
exit 1
|
|
fi
|
|
for fixture_name in $fixture_names; do
|
|
fixture_path="$output_dir/$fixture_name"
|
|
if [ -e "$fixture_path" ] && [ "$force" != true ]; then
|
|
echo "Fixture already exists: $fixture_path" >&2
|
|
echo "Pass --force to replace only the known generated fixture set." >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [ "$force" = true ]; then
|
|
for fixture_name in $fixture_names; do
|
|
rm -f -- "$output_dir/$fixture_name"
|
|
done
|
|
fi
|
|
|
|
temporary_dir=$(mktemp -d "/tmp/av-tools-fixtures.XXXXXX")
|
|
case "$temporary_dir" in
|
|
/tmp/av-tools-fixtures.*) ;;
|
|
*)
|
|
echo "Refusing unexpected temporary path: $temporary_dir" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
trap 'rm -rf -- "$temporary_dir"' EXIT HUP INT TERM
|
|
|
|
run_ffmpeg() {
|
|
ffmpeg \
|
|
-hide_banner \
|
|
-loglevel error \
|
|
-y \
|
|
-fflags +bitexact \
|
|
-filter_threads 1 \
|
|
-filter_complex_threads 1 \
|
|
"$@"
|
|
}
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "testsrc2=size=160x90:rate=12" \
|
|
-f lavfi -i "sine=frequency=440:sample_rate=16000" \
|
|
-t 2 -shortest \
|
|
-map 0:v:0 -map 1:a:0 \
|
|
-c:v libx264 -preset ultrafast -crf 35 -pix_fmt yuv420p -g 12 -threads 1 \
|
|
-c:a aac -b:a 32k -ar 16000 -ac 1 \
|
|
-flags:v +bitexact -flags:a +bitexact \
|
|
-metadata title="Generated AV fixture" \
|
|
-metadata creation_time="2020-01-01T00:00:00Z" \
|
|
-movflags +faststart \
|
|
"$output_dir/pattern-av.mp4"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "color=c=red:size=128x72:rate=10" \
|
|
-f lavfi -i "sine=frequency=330:sample_rate=16000" \
|
|
-t 1 -shortest \
|
|
-map 0:v:0 -map 1:a:0 \
|
|
-c:v libx264 -preset ultrafast -crf 35 -pix_fmt yuv420p -g 10 -threads 1 \
|
|
-c:a aac -b:a 32k -ar 16000 -ac 1 \
|
|
-flags:v +bitexact -flags:a +bitexact \
|
|
-metadata creation_time="2020-01-01T00:00:00Z" \
|
|
-movflags +faststart \
|
|
"$output_dir/compatible-a.mp4"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "color=c=blue:size=128x72:rate=10" \
|
|
-f lavfi -i "sine=frequency=550:sample_rate=16000" \
|
|
-t 1 -shortest \
|
|
-map 0:v:0 -map 1:a:0 \
|
|
-c:v libx264 -preset ultrafast -crf 35 -pix_fmt yuv420p -g 10 -threads 1 \
|
|
-c:a aac -b:a 32k -ar 16000 -ac 1 \
|
|
-flags:v +bitexact -flags:a +bitexact \
|
|
-metadata creation_time="2020-01-01T00:00:00Z" \
|
|
-movflags +faststart \
|
|
"$output_dir/compatible-b.mp4"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "testsrc2=size=96x96:rate=10" \
|
|
-f lavfi -i "sine=frequency=660:sample_rate=16000" \
|
|
-t 1 -shortest \
|
|
-map 0:v:0 -map 1:a:0 \
|
|
-c:v libx264 -preset ultrafast -crf 35 -pix_fmt yuv420p -g 10 -threads 1 \
|
|
-c:a aac -b:a 32k -ar 16000 -ac 1 \
|
|
-flags:v +bitexact -flags:a +bitexact \
|
|
-metadata creation_time="2020-01-01T00:00:00Z" \
|
|
-movflags +faststart \
|
|
"$output_dir/different-dimensions.mp4"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "sine=frequency=440:sample_rate=16000:duration=1" \
|
|
-c:a pcm_s16le -flags:a +bitexact \
|
|
-metadata title="Generated tone" \
|
|
"$output_dir/tone.wav"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "sine=frequency=440:sample_rate=16000:duration=1" \
|
|
-c:a libmp3lame -b:a 32k -ar 16000 -ac 1 -flags:a +bitexact \
|
|
-id3v2_version 3 -write_xing 0 \
|
|
-metadata title="Generated tone" \
|
|
"$output_dir/tone.mp3"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "sine=frequency=440:sample_rate=16000:duration=1" \
|
|
-c:a flac -compression_level 5 -flags:a +bitexact \
|
|
-metadata title="Generated tone" \
|
|
"$output_dir/tone.flac"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "testsrc2=size=128x72:rate=10" \
|
|
-f lavfi -i "sine=frequency=440:sample_rate=16000" \
|
|
-t 1 -shortest \
|
|
-map 0:v:0 -map 1:a:0 \
|
|
-c:v libvpx -deadline good -cpu-used 8 -crf 50 -b:v 80k -threads 1 \
|
|
-c:a libvorbis -b:a 32k -ar 16000 -ac 1 \
|
|
-flags:v +bitexact -flags:a +bitexact \
|
|
-metadata title="Generated WebM fixture" \
|
|
-fflags +bitexact \
|
|
"$output_dir/pattern.webm"
|
|
|
|
printf '%s\n' \
|
|
'1' \
|
|
'00:00:00,100 --> 00:00:00,700' \
|
|
'Generated subtitle' \
|
|
>"$output_dir/subtitle.srt"
|
|
|
|
printf '%s\n' \
|
|
'WEBVTT' \
|
|
'' \
|
|
'00:00:00.100 --> 00:00:00.700' \
|
|
'Generated subtitle' \
|
|
>"$output_dir/subtitle.vtt"
|
|
|
|
printf '%s\n' \
|
|
'[Script Info]' \
|
|
'ScriptType: v4.00+' \
|
|
'PlayResX: 128' \
|
|
'PlayResY: 72' \
|
|
'' \
|
|
'[V4+ Styles]' \
|
|
'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding' \
|
|
'Style: Default,sans-serif,12,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,1,0,2,8,8,8,1' \
|
|
'' \
|
|
'[Events]' \
|
|
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text' \
|
|
'Dialogue: 0,0:00:00.10,0:00:00.70,Default,,0,0,0,,Generated subtitle' \
|
|
>"$output_dir/subtitle.ass"
|
|
|
|
run_ffmpeg \
|
|
-i "$output_dir/compatible-a.mp4" \
|
|
-map 0 -c copy \
|
|
-metadata title="Metadata fixture" \
|
|
-metadata artist="add ideas test generator" \
|
|
-metadata comment="Local generated media" \
|
|
-metadata creation_time="2020-01-01T00:00:00Z" \
|
|
"$output_dir/metadata.mp4"
|
|
|
|
printf '%s\n' \
|
|
';FFMETADATA1' \
|
|
'title=Chapter fixture' \
|
|
'[CHAPTER]' \
|
|
'TIMEBASE=1/1000' \
|
|
'START=0' \
|
|
'END=500' \
|
|
'title=First' \
|
|
'[CHAPTER]' \
|
|
'TIMEBASE=1/1000' \
|
|
'START=500' \
|
|
'END=1000' \
|
|
'title=Second' \
|
|
>"$temporary_dir/chapters.ffmeta"
|
|
|
|
run_ffmpeg \
|
|
-i "$output_dir/compatible-b.mp4" \
|
|
-i "$temporary_dir/chapters.ffmeta" \
|
|
-map 0 -map_metadata 1 -map_chapters 1 -c copy \
|
|
-metadata creation_time="2020-01-01T00:00:00Z" \
|
|
"$output_dir/chapters.mp4"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "color=c=yellow:size=32x32:rate=1" \
|
|
-frames:v 1 -c:v png -threads 1 -flags:v +bitexact \
|
|
"$temporary_dir/cover.png"
|
|
|
|
run_ffmpeg \
|
|
-f lavfi -i "sine=frequency=440:sample_rate=16000:duration=1" \
|
|
-i "$temporary_dir/cover.png" \
|
|
-map 0:a:0 -map 1:v:0 \
|
|
-c:a libmp3lame -b:a 32k -ar 16000 -ac 1 \
|
|
-c:v png -threads 1 -disposition:v:0 attached_pic \
|
|
-id3v2_version 3 -write_xing 0 \
|
|
-metadata title="Attached cover fixture" \
|
|
-metadata:s:v:0 title="Generated cover" \
|
|
-metadata:s:v:0 comment="Cover (front)" \
|
|
"$output_dir/attached-cover.mp3"
|
|
|
|
head -c 128 "$output_dir/compatible-a.mp4" >"$output_dir/truncated.mp4"
|
|
|
|
for media_name in \
|
|
attached-cover.mp3 \
|
|
chapters.mp4 \
|
|
compatible-a.mp4 \
|
|
compatible-b.mp4 \
|
|
different-dimensions.mp4 \
|
|
metadata.mp4 \
|
|
pattern-av.mp4 \
|
|
pattern.webm \
|
|
tone.flac \
|
|
tone.mp3 \
|
|
tone.wav
|
|
do
|
|
ffprobe \
|
|
-v error \
|
|
-show_entries format=format_name,duration:stream=index,codec_name,codec_type \
|
|
-of json \
|
|
"$output_dir/$media_name" \
|
|
>/dev/null
|
|
done
|
|
|
|
truncated_streams=$(
|
|
ffprobe \
|
|
-v error \
|
|
-show_entries stream=index \
|
|
-of csv=p=0 \
|
|
"$output_dir/truncated.mp4"
|
|
)
|
|
if [ -n "$truncated_streams" ]; then
|
|
echo "Truncated fixture unexpectedly contains a usable stream." >&2
|
|
exit 1
|
|
fi
|
|
|
|
(
|
|
cd "$output_dir"
|
|
sha256sum \
|
|
attached-cover.mp3 \
|
|
chapters.mp4 \
|
|
compatible-a.mp4 \
|
|
compatible-b.mp4 \
|
|
different-dimensions.mp4 \
|
|
metadata.mp4 \
|
|
pattern-av.mp4 \
|
|
pattern.webm \
|
|
subtitle.ass \
|
|
subtitle.srt \
|
|
subtitle.vtt \
|
|
tone.flac \
|
|
tone.mp3 \
|
|
tone.wav \
|
|
truncated.mp4 \
|
|
>SHA256SUMS
|
|
)
|
|
|
|
for fixture_name in $fixture_names; do
|
|
chmod 0644 "$output_dir/$fixture_name"
|
|
done
|
|
|
|
echo "Generated fixtures in $output_dir"
|
|
ffmpeg -version | sed -n '1p'
|
|
(
|
|
cd "$output_dir"
|
|
sha256sum -c SHA256SUMS
|
|
)
|