Files
arr/scripts/make-probe-fixtures.sh
T
naps62-yolo 403593cda1
ci / rust (push) Successful in 1m23s
ci / web (push) Successful in 29s
e2e / e2e (push) Successful in 1m33s
test(probe): add HDR fixture coverage (#72)
2026-08-22 21:50:13 +01:00

174 lines
6.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# Regenerate the ffprobe fixture clips in crates/arr-probe/tests/fixtures.
#
# The clips are committed (a few KB each, DESIGN.md §12) so the test suite
# never needs ffmpeg. This script exists to document how they were made and to
# rebuild them if the set ever needs to change.
#
# The two Dolby Vision clips cannot be encoded directly: x265 refuses to write
# a DV stream without an RPU file. Instead a real HEVC clip is produced and the
# `dvcC`/`dvvC` configuration box is spliced into its sample entry, which is
# exactly what ffprobe reads to report `dv_profile`.
set -euo pipefail
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
out="$root/crates/arr-probe/tests/fixtures"
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
mkdir -p "$out"
# 1080p feature: three audio tracks covering all three §5.2 signals, plus two
# subtitle tracks. Three seconds, so the runtime sanity check has something to
# compare against.
printf '1\n00:00:00,000 --> 00:00:01,000\nhello\n\n' > "$work/en.srt"
printf '1\n00:00:00,000 --> 00:00:01,000\nolá\n\n' > "$work/pt.srt"
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "color=c=black:s=1920x1080:r=24:d=3" \
-f lavfi -i "sine=frequency=440:duration=3" \
-f lavfi -i "sine=frequency=660:duration=3" \
-f lavfi -i "sine=frequency=880:duration=3" \
-i "$work/en.srt" -i "$work/pt.srt" \
-map 0:v -map 1:a -map 2:a -map 3:a -map 4:s -map 5:s \
-c:v libx264 -preset ultrafast -crf 51 -pix_fmt yuv420p \
-c:a aac -b:a 8k -c:s srt \
-metadata:s:a:0 language=eng -metadata:s:a:0 title="English" \
-metadata:s:a:1 language=por -metadata:s:a:1 title="Português (Brasil)" \
-metadata:s:a:1 handler_name="Portuguese (Brazil)" \
-metadata:s:a:2 language=pt-PT \
-metadata:s:s:0 language=eng -metadata:s:s:1 language=por \
"$out/movie-1080p.mkv"
# The extras reel trap: larger on disk than the feature (noise compresses
# badly) but a third of its runtime.
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "nullsrc=s=640x360:r=12:d=1,geq=random(1)*255:128:128" \
-an -c:v libx264 -preset ultrafast -crf 51 -pix_fmt yuv420p \
"$out/extras-360p.mkv"
# HDR10: bt2020 primaries and the PQ transfer, which is all ffprobe reports.
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "color=c=black:s=3840x2160:r=24:d=1" -an \
-vf "format=yuv420p10le,setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc" \
-c:v libx265 -preset ultrafast -crf 51 \
"$out/hdr10-2160p.mkv"
# HLG and SDR both use HEVC here so transfer function, rather than codec bit
# depth, is what distinguishes their probe results.
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "color=c=black:s=1280x720:r=24:d=1" -an \
-vf "format=yuv420p10le,setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc" \
-c:v libx265 -preset ultrafast -crf 51 \
"$out/hlg-720p.mkv"
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "color=c=black:s=1280x720:r=24:d=1" -an \
-vf "format=yuv420p10le" \
-c:v libx265 -preset ultrafast -crf 51 \
"$out/sdr-10bit-hevc-720p.mkv"
# HEVC base layer for the two Dolby Vision clips.
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "color=c=black:s=1280x720:r=24:d=1" -an \
-vf "format=yuv420p10le,setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc" \
-c:v libx265 -preset ultrafast -crf 51 -tag:v hvc1 \
"$work/base.mp4"
cat > "$work/dovi.py" <<'PY'
"""Splice a DolbyVisionConfigurationBox into an MP4 HEVC sample entry."""
import struct
import sys
CONTAINERS = {"moov", "trak", "mdia", "minf", "stbl", "stsd"}
def children(buf, start, end):
boxes = []
off = start
while off + 8 <= end:
size = struct.unpack(">I", buf[off : off + 4])[0]
if size == 0:
size = end - off
boxes.append((off, size, buf[off + 4 : off + 8].decode("latin1")))
off += size
return boxes
def first_child_offset(off, kind):
# stsd carries a version/flags word and an entry count before its children.
return off + (16 if kind == "stsd" else 8)
def find(buf, start, end, path):
for off, size, kind in children(buf, start, end):
if kind != path[0]:
continue
if len(path) == 1:
return off, size
return find(buf, first_child_offset(off, kind), off + size, path[1:])
return None
def configuration(profile, compatibility_id, level=6):
bits = (profile & 0x7F) << 41
bits |= (level & 0x3F) << 35
bits |= 1 << 34 # rpu_present_flag
bits |= 1 << 32 # bl_present_flag
bits |= (compatibility_id & 0xF) << 28
return bytes([1, 0]) + bits.to_bytes(6, "big") + bytes(16)
def grow_ancestors(buf, target, growth):
def walk(start, end):
for off, size, kind in children(buf, start, end):
if not off <= target < off + size:
continue
struct.pack_into(">I", buf, off, size + growth)
if kind in CONTAINERS:
walk(first_child_offset(off, kind), off + size)
return
walk(0, len(buf))
def main(source, destination, profile, compatibility_id):
buf = bytearray(open(source, "rb").read())
entry = find(buf, 0, len(buf), ["moov", "trak", "mdia", "minf", "stbl", "stsd", "hvc1"])
if entry is None:
sys.exit("no hvc1 sample entry")
off, size = entry
payload = configuration(profile, compatibility_id)
kind = b"dvvC" if profile == 5 else b"dvcC"
box = struct.pack(">I", 8 + len(payload)) + kind + payload
buf[off + 4 : off + 8] = b"dvh1"
buf[off + size : off + size] = box
grow_ancestors(buf, off, len(box))
open(destination, "wb").write(bytes(buf))
if __name__ == "__main__":
main(sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4]))
PY
python3 "$work/dovi.py" "$work/base.mp4" "$out/dv-profile5.mp4" 5 0
python3 "$work/dovi.py" "$work/base.mp4" "$out/dv-profile8-1.mp4" 8 1
# An audio file with embedded artwork. ffprobe reports the cover as a video
# stream, so this is what stops a soundtrack passing for a feature.
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "color=c=red:s=64x64:d=1" -frames:v 1 "$work/cover.jpg"
ffmpeg -hide_banner -loglevel error -y \
-f lavfi -i "sine=frequency=440:duration=2" -i "$work/cover.jpg" \
-map 0:a -map 1:v -c:a aac -b:a 8k -c:v mjpeg -disposition:v attached_pic \
"$out/audio-with-cover.m4a"
# Not media at all: the thing a multi-file torrent is full of.
printf 'Release notes. Not a video file.\n' > "$out/notes.nfo"
ls -l "$out"