Files
av-tools/scripts/generate-fixtures.sh

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
)