`wai inspect` reads envelopes; most files a toolchain meets are not envelopes, and the first question about one is what it is. `wai identify <file>` (operation media.inspect, extensions/media-identify.md) answers it for any file: - the formats the bytes fit, each with the offsets of the bytes that say so, how far its structure was followed and where and why that stopped; - a container's tracks as it states them: codec, size, channels, rate, protection; - the registered capabilities they map to, and what a format probe reads of them; - signatures found inside the file; - where no format is recognised, measures that still hold: how the byte values are spread, uniform windows, and the lags at which the bytes repeat. The verdict is identified, ambiguous, signature_only or unknown, and there is no confidence figure: a candidate's strength says what was checked. Nothing is decoded, no protected track is opened, and unknown stays unknown. Walks cover ISO media, Matroska, transport streams, Ogg, FLAC, RIFF, MPEG audio, ADTS, PNG, JPEG, GIF, IVF, YUV4MPEG2, FLV, AV1 OBU and Annex B streams, and HLS and DASH text; 35 more formats are known by their signature. Every walk is bounded, reads a file where its lengths lead (a large file is never held whole), and has been given hostile bytes; it builds for wasm32. The agent door has it as media_inspect, its results marked untrusted_content: a file's author chooses the strings in it. identify-conformance/ holds files written by real muxers and encoders from test patterns and tones, what an independent media prober reads of them (prober.json), and generated cases sitting on each rule's edge. identify_verify.py is a second implementation written from the text; it reads the extension's tables, gives every file its whole result, and holds ext/ to the prober's reading. tests/identify.rs holds the reference's tables to the extension's. Co-Authored-By: Claude Opus 5.5 <[email protected]>
89 lines
4.4 KiB
Shell
89 lines
4.4 KiB
Shell
#!/bin/bash
|
|
# Re-creates ext/, files written by real muxers and encoders from synthetic pictures and tones, and
|
|
# prober.json, what an independent media prober reads of each.
|
|
#
|
|
# bash tools/make_external.sh (from wai/identify-conformance)
|
|
#
|
|
# Every input is a test pattern or a sine wave, so no captured content enters. The protected file
|
|
# uses an all-zero test key and protects nothing of worth. Not run in CI, since it needs the tools;
|
|
# its outputs are committed, and wai-rs and identify_verify.py check them on every change.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
rm -rf ext prober.json
|
|
mkdir -p ext
|
|
WORK=$(mktemp -d)
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
V=(-f lavfi -i testsrc2=s=32x32:r=10:d=0.3)
|
|
A=(-f lavfi -i sine=f=440:r=48000:d=0.2)
|
|
X264=(-c:v libx264 -preset ultrafast -g 3 -pix_fmt yuv420p -x264-params no-info=1)
|
|
X265=(-c:v libx265 -preset ultrafast -g 3 -pix_fmt yuv420p -x265-params log-level=none:info=0)
|
|
ff() { local a=("$@") n=$#; ffmpeg -hide_banner -loglevel error -y "${a[@]:0:n-1}" -bitexact -map_metadata -1 "${a[n-1]}" </dev/null; }
|
|
|
|
# Containers with tracks.
|
|
ff "${V[@]}" "${A[@]}" "${X264[@]}" -c:a aac -b:a 32k ext/avc-aac.mp4
|
|
ff "${V[@]}" "${X265[@]}" -tag:v hvc1 ext/hevc-hvc1.mp4
|
|
ff "${V[@]}" "${X265[@]}" -tag:v hev1 -movflags frag_keyframe+empty_moov+default_base_moof ext/hevc-fragmented.mp4
|
|
AV1=(-c:v libsvtav1 -preset 12 -g 3 -pix_fmt yuv420p)
|
|
W=(-f lavfi -i testsrc2=s=64x64:r=10:d=0.3)
|
|
ff "${W[@]}" "${AV1[@]}" ext/av1.mp4
|
|
ff "${V[@]}" -c:v libvpx-vp9 -cpu-used 8 -g 3 -pix_fmt yuv420p ext/vp9.mp4
|
|
ff "${A[@]}" -c:a libopus -b:a 32k -f mp4 ext/opus.m4a
|
|
ff "${A[@]}" -c:a flac -strict -2 ext/flac.mp4
|
|
ff "${V[@]}" "${X264[@]}" -encryption_scheme cenc-aes-ctr -encryption_key 00000000000000000000000000000000 -encryption_kid 00000000000000000000000000000000 ext/avc-protected.mp4
|
|
ff "${V[@]}" "${A[@]}" -c:v libvpx-vp9 -cpu-used 8 -g 3 -pix_fmt yuv420p -c:a libopus -b:a 32k ext/vp9-opus.webm
|
|
ff "${V[@]}" "${A[@]}" "${X265[@]}" -c:a flac ext/hevc-flac.mkv
|
|
ff "${V[@]}" "${A[@]}" "${X264[@]}" -c:a aac -b:a 32k ext/avc-aac.ts
|
|
ff "${V[@]}" "${X265[@]}" ext/hevc.ts
|
|
ff "${V[@]}" "${X264[@]}" -f mpegts -mpegts_m2ts_mode 1 ext/avc.m2ts
|
|
ff "${V[@]}" "${A[@]}" "${X264[@]}" -c:a aac -b:a 32k -f flv ext/avc-aac.flv
|
|
|
|
# Audio.
|
|
ff "${A[@]}" -c:a libopus -b:a 32k ext/opus.ogg
|
|
ff "${A[@]}" -c:a pcm_s16le "$WORK/in.wav"
|
|
oggenc -Q -q 1 -o ext/vorbis.ogg --serial 1 "$WORK/in.wav"
|
|
ff "${A[@]}" -c:a flac ext/a.flac
|
|
ff "${A[@]}" -c:a pcm_s16le ext/a.wav
|
|
ff "${A[@]}" -c:a libmp3lame -b:a 32k -id3v2_version 0 -write_xing 0 ext/a.mp3
|
|
ff "${A[@]}" -c:a libmp3lame -b:a 32k -write_xing 0 ext/id3.mp3
|
|
ff "${A[@]}" -c:a aac -b:a 32k -f adts ext/a.aac
|
|
ff "${A[@]}" -c:a pcm_s16be ext/a.aiff
|
|
ff "${A[@]}" -c:a pcm_s16le ext/a.caf
|
|
|
|
# Pictures.
|
|
P=(-f lavfi -i testsrc2=s=16x16:r=1:d=1 -frames:v 1)
|
|
ff "${P[@]}" ext/p.png
|
|
ff "${P[@]}" -c:v mjpeg -q:v 5 -huffman default -strict -2 ext/p.jpg
|
|
cwebp -quiet -lossless ext/p.png -o ext/lossless.webp
|
|
cwebp -quiet -q 50 ext/p.png -o ext/lossy.webp
|
|
ff "${P[@]}" ext/p.gif
|
|
ff "${P[@]}" -c:v tiff ext/p.tif
|
|
cjxl -q 80 --quiet ext/p.png ext/p.jxl
|
|
cjxl -q 80 --quiet --container=1 ext/p.png ext/container.jxl
|
|
ff "${P[@]}" -c:v jpeg2000 -format j2k -f image2 ext/p.j2k
|
|
ff "${P[@]}" -c:v jpeg2000 -f image2 ext/p.jp2
|
|
ff -f lavfi -i testsrc2=s=64x64:r=1:d=1 -frames:v 1 "${AV1[@]}" -f avif ext/p.avif
|
|
|
|
# Elementary streams and raw framing.
|
|
S=(-f lavfi -i testsrc2=s=16x16:r=10:d=0.3)
|
|
ff "${S[@]}" -pix_fmt yuv420p ext/v.y4m
|
|
ff "${V[@]}" -c:v libvpx-vp9 -cpu-used 8 -g 3 -pix_fmt yuv420p ext/vp9.ivf
|
|
ff "${W[@]}" "${AV1[@]}" ext/av1.ivf
|
|
ff "${V[@]}" "${X264[@]}" -f h264 ext/avc.h264
|
|
ff "${V[@]}" "${X265[@]}" -f hevc ext/hevc.h265
|
|
ff "${W[@]}" "${AV1[@]}" -f obu ext/av1.obu
|
|
ff "${V[@]}" -c:v mpeg2video -g 3 -f mpeg2video ext/mpeg2.m2v
|
|
ff "${V[@]}" -c:v mpeg2video -g 3 -f mpeg ext/mpeg2.mpg
|
|
|
|
# Playlists, written by a packager, with their segments left out.
|
|
ff "${V[@]}" "${X264[@]}" -f hls -hls_time 1 -hls_playlist_type vod -hls_segment_filename "$WORK/s%d.ts" "$WORK/media.m3u8"
|
|
tr -d '\r' < "$WORK/media.m3u8" > ext/media.m3u8
|
|
ff "${V[@]}" "${X264[@]}" -f dash -init_seg_name "i.m4s" -media_seg_name "s.m4s" "$WORK/manifest.mpd"
|
|
grep -v '<!-- MPEG-DASH\|availabilityStartTime' "$WORK/manifest.mpd" > ext/manifest.mpd || true
|
|
|
|
# Compressed and captured.
|
|
gzip -nc ext/a.wav > ext/a.wav.gz
|
|
zstd -q -c --no-check ext/a.wav > ext/a.wav.zst
|
|
printf 'WEBVTT\n\n00:00.000 --> 00:01.000\nA line.\n' > ext/t.vtt
|
|
|
|
python3 tools/oracle.py ext > prober.json
|