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