refactor(media-proxy): split into modules and harden streaming (#2437)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-03 22:04:00 +02:00
1 parent 16ae98e189
commit d7d4e8da03
214 files changed
+47213 -18092

No files matched your search

@@ -0,0 +1,111 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use super::images::{apng_header, minimal_gif};
pub fn adversarial_media_bytes() -> Vec<Vec<u8>> {
let mut truncated_apng = apng_header(2);
truncated_apng.truncate(20);
let mut oversized_bmff = Vec::new();
oversized_bmff.extend_from_slice(&u32::MAX.to_be_bytes());
oversized_bmff.extend_from_slice(b"ftyp");
oversized_bmff.extend_from_slice(b"isom");
let mut webp_without_payload = vec![0_u8; 20];
webp_without_payload[0..4].copy_from_slice(b"RIFF");
webp_without_payload[4..8].copy_from_slice(&u32::MAX.to_le_bytes());
webp_without_payload[8..12].copy_from_slice(b"WEBP");
webp_without_payload[12..16].copy_from_slice(b"VP8X");
webp_without_payload[16..20].copy_from_slice(&u32::MAX.to_le_bytes());
vec![
Vec::new(),
vec![0x00],
vec![0x00; 1024],
vec![0xff; 1024],
b"GIF".to_vec(),
b"GIF89a".to_vec(),
b"GIF89a\x01\x00".to_vec(),
minimal_gif(),
b"\x89PNG\r\n\x1a\n".to_vec(),
apng_header(0),
apng_header(u32::MAX),
truncated_apng,
b"RIFF".to_vec(),
webp_without_payload,
oversized_bmff,
b"ftypM4A ".to_vec(),
b"\xff\xd8\xff\xe0".to_vec(),
]
}
pub const ADVERSARIAL_RANGE_HEADERS: &[&str] = &[
"",
" ",
"bytes",
"bytes=",
"bytes=-",
"bytes=--1",
"bytes=0-",
"bytes=-0",
"bytes=0-0",
"bytes=0-1",
"bytes=1-0",
"bytes=18446744073709551615-18446744073709551615",
"bytes=-18446744073709551615",
"bytes=18446744073709551616-",
"bytes=1-2,3-4",
"bytes = 0-1",
"BYTES=0-1",
"items=0-1",
"bytes 0-0/0",
"bytes 0-1/1",
"bytes 1-0/2",
"bytes 0-1/*",
"bytes */2",
"bytes 0-1/18446744073709551616",
"bytes\t0-1/2",
"bytes 0-1/2 ",
];
pub const ADVERSARIAL_TEXT_INPUTS: &[&str] = &[
"",
" ",
"%",
"%0",
"%00",
"%2F",
"%2f",
"%5C",
"%FF",
"%C3",
"%C3%A9",
"%%%%",
"%20%2e%2e%2f",
"a_deadbeef",
"A_DEADBEEF",
"deadbeef",
"deadbee",
"deadbeef0",
"0123456789",
"a_",
"\"quoted\"",
"photo name.png",
"r\u{e9}sum\u{e9}.png",
"token=",
"token=abc&token=def",
"\u{7f}",
"~!@#$^&*()_+",
];
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adversarial_corpora_hold_every_documented_input() {
assert_eq!(17, adversarial_media_bytes().len());
assert_eq!(26, ADVERSARIAL_RANGE_HEADERS.len());
assert_eq!(27, ADVERSARIAL_TEXT_INPUTS.len());
}
}
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pub const REQUIRE_MEDIA_FIXTURES_ENV: &str = "FLUXER_REQUIRE_MEDIA_FIXTURES";
pub fn media_fixtures_are_required() -> bool {
std::env::var_os(REQUIRE_MEDIA_FIXTURES_ENV).is_some_and(|value| !value.is_empty())
}
fn ffmpeg_fixture(description: &str, produced: Option<Vec<u8>>) -> Option<Vec<u8>> {
assert!(
produced.is_some() || !media_fixtures_are_required(),
"{REQUIRE_MEDIA_FIXTURES_ENV} is set but the ffmpeg CLI could not produce {description}"
);
produced
}
pub fn ffmpeg_gen_media(file_name: &str, args: &[&str]) -> Option<Vec<u8>> {
ffmpeg_fixture(file_name, run_ffmpeg_gen_media(file_name, args))
}
fn run_ffmpeg_gen_media(file_name: &str, args: &[&str]) -> Option<Vec<u8>> {
let dir = tempfile::tempdir().ok()?;
let out = dir.path().join(file_name);
let status = std::process::Command::new("ffmpeg")
.args(["-nostdin", "-loglevel", "error", "-y"])
.args(args)
.arg(out.to_str()?)
.status()
.ok()?;
if !status.success() {
return None;
}
std::fs::read(&out).ok()
}
pub fn ffmpeg_gen_mp4(args: &[&str]) -> Option<Vec<u8>> {
ffmpeg_gen_media("fixture.mp4", args)
}
pub fn ffmpeg_gen_rotated_mp4(display_rotation: &str, source_args: &[&str]) -> Option<Vec<u8>> {
ffmpeg_fixture(
"rotated.mp4",
run_ffmpeg_gen_rotated_mp4(display_rotation, source_args),
)
}
fn run_ffmpeg_gen_rotated_mp4(display_rotation: &str, source_args: &[&str]) -> Option<Vec<u8>> {
let dir = tempfile::tempdir().ok()?;
let source = dir.path().join("source.mp4");
let out = dir.path().join("rotated.mp4");
let source_status = std::process::Command::new("ffmpeg")
.args(["-nostdin", "-loglevel", "error", "-y"])
.args(source_args)
.arg(source.to_str()?)
.status()
.ok()?;
if !source_status.success() {
return None;
}
let rotate_status = std::process::Command::new("ffmpeg")
.args(["-nostdin", "-loglevel", "error", "-y", "-noautorotate"])
.args(["-display_rotation", display_rotation])
.args(["-i", source.to_str()?])
.args(["-c", "copy", "-f", "mp4"])
.arg(out.to_str()?)
.status()
.ok()?;
if !rotate_status.success() {
return None;
}
std::fs::read(&out).ok()
}
pub fn ffmpeg_mirror_mp4(source_mp4: &[u8]) -> Option<Vec<u8>> {
ffmpeg_fixture("mirrored.mp4", run_ffmpeg_mirror_mp4(source_mp4))
}
fn run_ffmpeg_mirror_mp4(source_mp4: &[u8]) -> Option<Vec<u8>> {
let dir = tempfile::tempdir().ok()?;
let source = dir.path().join("source.mp4");
let out = dir.path().join("mirrored.mp4");
std::fs::write(&source, source_mp4).ok()?;
let mirror_status = std::process::Command::new("ffmpeg")
.args(["-nostdin", "-loglevel", "error", "-y", "-noautorotate"])
.arg("-display_hflip")
.args(["-i", source.to_str()?])
.args(["-c", "copy", "-f", "mp4"])
.arg(out.to_str()?)
.status()
.ok()?;
if !mirror_status.success() {
return None;
}
std::fs::read(&out).ok()
}
#[cfg(test)]
mod tests {
use super::*;
const FIXTURE_PROBE_TEST: &str =
"test_fixtures::ffmpeg_cli::tests::ffmpeg_fixture_probe_reports_its_outcome";
fn run_fixture_probe(require: bool, empty_path: bool) -> (bool, String) {
let exe = std::env::current_exe().expect("test binary path");
let empty = tempfile::tempdir().expect("empty path directory");
let mut command = std::process::Command::new(exe);
command.args(["--exact", "--ignored", "--nocapture", FIXTURE_PROBE_TEST]);
if require {
command.env(REQUIRE_MEDIA_FIXTURES_ENV, "1");
} else {
command.env_remove(REQUIRE_MEDIA_FIXTURES_ENV);
}
if empty_path {
command.env("PATH", empty.path());
}
let output = command.output().expect("probe run");
let mut text = String::from_utf8_lossy(&output.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&output.stderr));
(output.status.success(), text)
}
fn ffmpeg_cli_is_on_path() -> bool {
std::process::Command::new("ffmpeg")
.arg("-version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
}
#[test]
#[ignore]
fn ffmpeg_fixture_probe_reports_its_outcome() {
let fixture = ffmpeg_gen_mp4(&[
"-f",
"lavfi",
"-i",
"testsrc=size=16x16:rate=1:duration=1",
"-pix_fmt",
"yuv420p",
"-f",
"mp4",
]);
println!(
"FIXTURE_OUTCOME={}",
if fixture.is_some() {
"produced"
} else {
"absent"
}
);
}
#[test]
fn missing_ffmpeg_fixtures_are_fatal_when_the_requirement_is_set() {
let (succeeded, output) = run_fixture_probe(true, true);
assert!(!succeeded, "the probe must fail loudly, got: {output}");
assert!(
output.contains(
"FLUXER_REQUIRE_MEDIA_FIXTURES is set but the ffmpeg CLI could not produce"
),
"the failure must name the requirement, got: {output}"
);
}
#[test]
fn missing_ffmpeg_fixtures_still_skip_when_the_requirement_is_unset() {
let (succeeded, output) = run_fixture_probe(false, true);
assert!(succeeded, "the probe must skip, got: {output}");
assert!(output.contains("FIXTURE_OUTCOME=absent"), "got: {output}");
}
#[test]
fn the_requirement_lets_fixtures_run_when_the_ffmpeg_cli_is_on_path() {
let (succeeded, output) = run_fixture_probe(true, false);
if ffmpeg_cli_is_on_path() {
assert!(succeeded, "got: {output}");
assert!(output.contains("FIXTURE_OUTCOME=produced"), "got: {output}");
} else {
assert!(!succeeded, "got: {output}");
assert!(output.contains(REQUIRE_MEDIA_FIXTURES_ENV), "got: {output}");
}
}
}
@@ -0,0 +1,318 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use crate::{media_process::native_runtime::ensure_vips_init, native};
use base64::Engine as _;
use libc::{c_int, c_void, size_t};
use std::{ffi::CString, ptr};
pub fn minimal_gif() -> Vec<u8> {
vec![
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00,
0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02,
0x02, 0x44, 0x01, 0x00, 0x3b,
]
}
pub fn apng_header(frames: u32) -> Vec<u8> {
let mut data = Vec::new();
data.extend_from_slice(b"\x89PNG\r\n\x1a\n");
data.extend_from_slice(&13_u32.to_be_bytes());
data.extend_from_slice(b"IHDR");
data.extend_from_slice(&2_u32.to_be_bytes());
data.extend_from_slice(&3_u32.to_be_bytes());
data.extend_from_slice(&[8, 6, 0, 0, 0]);
data.extend_from_slice(&[0; 4]);
data.extend_from_slice(&8_u32.to_be_bytes());
data.extend_from_slice(b"acTL");
data.extend_from_slice(&frames.to_be_bytes());
data.extend_from_slice(&0_u32.to_be_bytes());
data.extend_from_slice(&[0; 4]);
data
}
pub fn synthetic_png(width: u32, height: u32) -> Vec<u8> {
ensure_vips_init().unwrap();
let mut pixels = vec![0u8; width as usize * height as usize * 4];
for y in 0..height {
for x in 0..width {
let i = (y as usize * width as usize + x as usize) * 4;
pixels[i] = (x * 255 / width.max(1)) as u8;
pixels[i + 1] = (y * 255 / height.max(1)) as u8;
pixels[i + 2] = 120;
pixels[i + 3] = 255;
}
}
let image = unsafe {
native::fluxer_vips_image_new_from_memory_copy(
pixels.as_ptr().cast(),
pixels.len(),
width as c_int,
height as c_int,
4,
native::fluxer_vips_format_uchar,
)
};
let image = unsafe { native::VipsImageHandle::from_raw_owned(image) }.unwrap();
let suffix = CString::new(".png[strip]").unwrap();
let mut out_ptr: *mut c_void = ptr::null_mut();
let mut out_size: size_t = 0;
let rc = unsafe {
native::fluxer_vips_image_write_to_buffer(
image.as_ptr(),
suffix.as_ptr(),
&mut out_ptr,
&mut out_size,
)
};
assert_eq!(0, rc);
unsafe { native::buffer::NativeBuffer::from_vips_owned(out_ptr, out_size) }
.unwrap()
.try_to_vec()
.unwrap()
}
pub fn synthetic_bmp(width: u32, height: u32) -> Vec<u8> {
assert!(width > 0 && height > 0);
let row_bytes = (width as usize * 3).next_multiple_of(4);
let pixel_bytes = row_bytes * height as usize;
let file_bytes = 54 + pixel_bytes;
let mut bytes = Vec::with_capacity(file_bytes);
bytes.extend_from_slice(b"BM");
bytes.extend_from_slice(&(file_bytes as u32).to_le_bytes());
bytes.extend_from_slice(&0u16.to_le_bytes());
bytes.extend_from_slice(&0u16.to_le_bytes());
bytes.extend_from_slice(&54u32.to_le_bytes());
bytes.extend_from_slice(&40u32.to_le_bytes());
bytes.extend_from_slice(&(width as i32).to_le_bytes());
bytes.extend_from_slice(&(height as i32).to_le_bytes());
bytes.extend_from_slice(&1u16.to_le_bytes());
bytes.extend_from_slice(&24u16.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&(pixel_bytes as u32).to_le_bytes());
bytes.extend_from_slice(&2835i32.to_le_bytes());
bytes.extend_from_slice(&2835i32.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
bytes.extend_from_slice(&0u32.to_le_bytes());
for y in (0..height).rev() {
let mut row = Vec::with_capacity(row_bytes);
for x in 0..width {
row.push(120);
row.push((y * 255 / height) as u8);
row.push((x * 255 / width) as u8);
}
row.resize(row_bytes, 0);
bytes.extend_from_slice(&row);
}
bytes
}
pub fn animated_gif_fixture() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("R0lGODlhIAAgAPEAAAAAAP8AAP///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJFAAAACwAAAAAIAAgAAACPYyPGcDtD5Q8sLY5rcVSV654EJiID4mYDkqpDGu4LyxHtAwv+O3mtb9j1YbEovGITCqXzKbzCY1Kp9QqsQAAIfkECRQAAAAsAAAAACAAIACDAAAAAAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/AAD/////AAAAAAAAAAAAAAAAAAAABFsQyEmrvTjrzbv/YCiOZGmeaKqubOu+MBUIA1EQgxCYBpH8wATBQDoEj7+DyIBsEj8BX/NIACGmTdAAiwRJuUBQARz0ksOf7TlhXbOhX24VxCQ/QUauctRrDiURADs=")
.unwrap()
}
pub fn animated_gif_frames(frames: usize, delay_cs: u16) -> Vec<u8> {
let mut gif = Vec::new();
gif.extend_from_slice(b"GIF89a");
gif.extend_from_slice(&16u16.to_le_bytes());
gif.extend_from_slice(&16u16.to_le_bytes());
gif.extend_from_slice(&[0x80, 0x00, 0x00]);
gif.extend_from_slice(&[0x00, 0x00, 0x00, 0xff, 0xff, 0xff]);
gif.extend_from_slice(b"\x21\xff\x0bNETSCAPE2.0");
gif.extend_from_slice(&[0x03, 0x01, 0x00, 0x00, 0x00]);
for index in 0..frames {
gif.extend_from_slice(&[0x21, 0xf9, 0x04, 0x00]);
gif.extend_from_slice(&delay_cs.to_le_bytes());
gif.extend_from_slice(&[0x00, 0x00]);
gif.extend_from_slice(&[0x2c, 0x00, 0x00, 0x00, 0x00]);
gif.extend_from_slice(&1u16.to_le_bytes());
gif.extend_from_slice(&1u16.to_le_bytes());
gif.extend_from_slice(&[0x00, 0x02, 0x02]);
gif.extend_from_slice(if index % 2 == 0 {
&[0x44, 0x01]
} else {
&[0x4c, 0x01]
});
gif.push(0x00);
}
gif.push(0x3b);
gif
}
pub fn read_u24_le(bytes: &[u8]) -> Option<u32> {
(bytes.len() >= 3)
.then(|| bytes[0] as u32 | ((bytes[1] as u32) << 8) | ((bytes[2] as u32) << 16))
}
pub fn webp_chunk_payloads<'a>(bytes: &'a [u8], fourcc: &[u8; 4]) -> Vec<&'a [u8]> {
if bytes.len() < 12 || &bytes[0..4] != b"RIFF" || &bytes[8..12] != b"WEBP" {
return Vec::new();
}
let mut chunks = Vec::new();
let mut offset = 12usize;
while offset + 8 <= bytes.len() {
let chunk_size = u32::from_le_bytes([
bytes[offset + 4],
bytes[offset + 5],
bytes[offset + 6],
bytes[offset + 7],
]) as usize;
let payload_start = offset + 8;
let Some(payload_end) = payload_start.checked_add(chunk_size) else {
break;
};
if payload_end > bytes.len() {
break;
}
if &bytes[offset..offset + 4] == fourcc {
chunks.push(&bytes[payload_start..payload_end]);
}
offset = payload_end + (chunk_size & 1);
}
chunks
}
pub fn webp_canvas_size(bytes: &[u8]) -> Option<(u32, u32, u8)> {
let vp8x = webp_chunk_payloads(bytes, b"VP8X").into_iter().next()?;
if vp8x.len() < 10 {
return None;
}
let width = read_u24_le(&vp8x[4..7])? + 1;
let height = read_u24_le(&vp8x[7..10])? + 1;
Some((width, height, vp8x[0]))
}
pub fn first_webp_anim_frame_size(bytes: &[u8]) -> Option<(u32, u32)> {
let anmf = webp_chunk_payloads(bytes, b"ANMF").into_iter().next()?;
if anmf.len() < 16 {
return None;
}
let width = read_u24_le(&anmf[6..9])? + 1;
let height = read_u24_le(&anmf[9..12])? + 1;
Some((width, height))
}
pub fn webp_animation_loop_count(bytes: &[u8]) -> Option<u16> {
let anim = webp_chunk_payloads(bytes, b"ANIM").into_iter().next()?;
(anim.len() >= 6).then(|| u16::from_le_bytes([anim[4], anim[5]]))
}
pub fn webp_with_metadata_chunk(bytes: &[u8], fourcc: &[u8; 4], flag: u8) -> Option<Vec<u8>> {
if bytes.len() < 20 || &bytes[0..4] != b"RIFF" || &bytes[8..12] != b"WEBP" {
return None;
}
if &bytes[12..16] != b"VP8X" {
return None;
}
let vp8x_size = u32::from_le_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]) as usize;
let vp8x_end = 20usize.checked_add(vp8x_size + (vp8x_size & 1))?;
if vp8x_end > bytes.len() {
return None;
}
let payload: [u8; 4] = [0x00, 0x01, 0x02, 0x03];
let mut out = Vec::with_capacity(bytes.len() + 12);
out.extend_from_slice(&bytes[..vp8x_end]);
out[20] |= flag;
out.extend_from_slice(fourcc);
out.extend_from_slice(&(payload.len() as u32).to_le_bytes());
out.extend_from_slice(&payload);
out.extend_from_slice(&bytes[vp8x_end..]);
let riff_size = u32::try_from(out.len() - 8).ok()?;
out[4..8].copy_from_slice(&riff_size.to_le_bytes());
Some(out)
}
pub fn gif_frame_delays_cs(bytes: &[u8]) -> Vec<u16> {
if bytes.len() < 13 || (&bytes[..6] != b"GIF89a" && &bytes[..6] != b"GIF87a") {
return Vec::new();
}
let mut offset = 13usize;
if bytes[10] & 0x80 != 0 {
let entries = 1usize << ((bytes[10] & 0x07) + 1);
offset = offset.saturating_add(entries.saturating_mul(3));
}
let mut delays = Vec::new();
while offset < bytes.len() {
match bytes[offset] {
0x21 => {
if offset + 1 >= bytes.len() {
break;
}
if bytes[offset + 1] == 0xf9 {
if offset + 7 >= bytes.len() || bytes[offset + 2] != 4 {
break;
}
delays.push(u16::from_le_bytes([bytes[offset + 4], bytes[offset + 5]]));
offset += 8;
continue;
}
offset += 2;
while offset < bytes.len() {
let len = bytes[offset] as usize;
offset += 1;
if len == 0 {
break;
}
offset = offset.saturating_add(len);
}
}
0x2c => {
if offset + 9 >= bytes.len() {
break;
}
let local_entries = if bytes[offset + 9] & 0x80 != 0 {
1usize << ((bytes[offset + 9] & 0x07) + 1)
} else {
0
};
offset += 10 + local_entries * 3 + 1;
while offset < bytes.len() {
let len = bytes[offset] as usize;
offset += 1;
if len == 0 {
break;
}
offset = offset.saturating_add(len);
}
}
0x3b => break,
_ => break,
}
}
delays
}
pub fn gif_loop_count(bytes: &[u8]) -> Option<u16> {
if bytes.len() < 13 || (&bytes[..6] != b"GIF89a" && &bytes[..6] != b"GIF87a") {
return None;
}
let mut offset = 13usize;
if bytes[10] & 0x80 != 0 {
let entries = 1usize << ((bytes[10] & 0x07) + 1);
offset = offset.saturating_add(entries.saturating_mul(3));
}
while offset + 19 <= bytes.len() {
if bytes[offset] == 0x21
&& bytes[offset + 1] == 0xff
&& bytes[offset + 2] == 11
&& (&bytes[offset + 3..offset + 14] == b"NETSCAPE2.0"
|| &bytes[offset + 3..offset + 14] == b"ANIMEXTS1.0")
&& bytes[offset + 14] == 3
&& bytes[offset + 15] == 1
{
return Some(u16::from_le_bytes([bytes[offset + 16], bytes[offset + 17]]));
}
offset += 1;
}
None
}
pub fn png_dimensions(bytes: &[u8]) -> Option<(u32, u32)> {
if bytes.len() < 24 || &bytes[..8] != b"\x89PNG\r\n\x1a\n" || &bytes[12..16] != b"IHDR" {
return None;
}
let width = u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
let height = u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]);
Some((width, height))
}
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
use base64::Engine as _;
pub fn synthetic_wav() -> Vec<u8> {
let sample_rate = 8_000u32;
let channels = 1u16;
let bits_per_sample = 8u16;
let data_len = sample_rate;
let byte_rate = sample_rate * u32::from(channels) * u32::from(bits_per_sample) / 8;
let block_align = channels * bits_per_sample / 8;
let mut wav = Vec::with_capacity(44 + data_len as usize);
wav.extend_from_slice(b"RIFF");
wav.extend_from_slice(&(36 + data_len).to_le_bytes());
wav.extend_from_slice(b"WAVEfmt ");
wav.extend_from_slice(&16u32.to_le_bytes());
wav.extend_from_slice(&1u16.to_le_bytes());
wav.extend_from_slice(&channels.to_le_bytes());
wav.extend_from_slice(&sample_rate.to_le_bytes());
wav.extend_from_slice(&byte_rate.to_le_bytes());
wav.extend_from_slice(&block_align.to_le_bytes());
wav.extend_from_slice(&bits_per_sample.to_le_bytes());
wav.extend_from_slice(b"data");
wav.extend_from_slice(&data_len.to_le_bytes());
wav.extend(std::iter::repeat_n(128u8, data_len as usize));
wav
}
pub fn fixture_audio_only_mp4() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAxptb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAA+gABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACRXRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAA+gAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAPoAAAQAAAEAAAAAAb1tZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAB9AAAAL0FXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAFobWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEsc3RibAAAAH5zdHNkAAAAAAAAAAEAAABubXA0YQAAAAAAAAABAAAAAAAAAAAAAQAQAAAAAB9AAAAAAAA2ZXNkcwAAAAADgICAJQABAASAgIAXQBUAAAAAAD6AAAACZQWAgIAFFYhW5QAGgICAAQIAAAAUYnRydAAAAAAAAD6AAAACZQAAACBzdHRzAAAAAAAAAAIAAAACAAAEAAAAAAEAAAPQAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAADAAAAAQAAACBzdHN6AAAAAAAAAAAAAAADAAAAFQAAAAQAAAAEAAAAFHN0Y28AAAAAAAAAAQAAA0YAAAAac2dwZAEAAAByb2xsAAAAAgAAAAH//wAAABxzYmdwAAAAAHJvbGwAAAABAAAAAwAAAAEAAABhdWR0YQAAAFltZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAACxpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYxLjcuMTAyAAAACGZyZWUAAAAlbWRhdN4CAExhdmM2MS4xOS4xMDEAAjBADgEYIAcBGCAH")
.unwrap()
}
pub fn fixture_audio_mp4_with_attached_picture() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAABBNtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAA+gABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACRXRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAA+gAAAAAAAAAAAAAAAQEAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAPoAAAQAAAEAAAAAAb1tZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAB9AAAAL0FXEAAAAAAAtaGRscgAAAAAAAAAAc291bgAAAAAAAAAAAAAAAFNvdW5kSGFuZGxlcgAAAAFobWluZgAAABBzbWhkAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEsc3RibAAAAH5zdHNkAAAAAAAAAAEAAABubXA0YQAAAAAAAAABAAAAAAAAAAAAAQAQAAAAAB9AAAAAAAA2ZXNkcwAAAAADgICAJQABAASAgIAXQBUAAAAAAAJlAAACZQWAgIAFFYhW5QAGgICAAQIAAAAUYnRydAAAAAAAAAJlAAACZQAAACBzdHRzAAAAAAAAAAIAAAACAAAEAAAAAAEAAAPQAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAADAAAAAQAAACBzdHN6AAAAAAAAAAAAAAADAAAAFQAAAAQAAAAEAAAAFHN0Y28AAAAAAAAAAQAABD8AAAAac2dwZAEAAAByb2xsAAAAAgAAAAH//wAAABxzYmdwAAAAAHJvbGwAAAABAAAAAwAAAAEAAAFadWR0YQAAAVJtZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAASVpbHN0AAAAJKl0b28AAAAcZGF0YQAAAAEAAAAATGF2ZjYxLjcuMTAyAAAA+WNvdnIAAADxZGF0YQAAAA0AAAAA/9j/4AAQSkZJRgABAgAAAQABAAD//gAQTGF2YzYxLjE5LjEwMQD/2wBDAAgEBAQEBAUFBQUFBQYGBgYGBgYGBgYGBgYHBwcICAgHBwcGBgcHCAgICAkJCQgICAgJCQoKCgwMCwsODg4RERT/xABMAAEBAAAAAAAAAAAAAAAAAAAABgEBAQAAAAAAAAAAAAAAAAAABgcQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/wAARCAACAAIDASIAAhEAAxEA/9oADAMBAAIRAxEAPwCLAFF/f//ZAAAACGZyZWUAAAAlbWRhdN4CAExhdmM2MS4xOS4xMDEAAjBADgEYIAcBGCAH")
.unwrap()
}
pub fn fixture_audio_mp3_with_png_cover_art() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("SUQzAwAAAAABGFRTU0UAAAAPAAAATGF2ZjYyLjEyLjEwMgBBUElDAAAAawAAAGltYWdlL3BuZwAAAIlQTkcNChoKAAAADUlIRFIAAAACAAAAAggCAAAA/dSacwAAAAlwSFlzAAAAAQAAAAEATyXE1gAAABBJREFUeJxj/MMAAixgkgEADQQBAr9QFbMAAAAASUVORK5CYIIAAAAAAAAAAAAA//tAwAAAAAAAAAAAAAAAAAAAAAAASW5mbwAAAA8AAAACAAABhwC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7//////////////////////////////////////////////////////////////////8AAAAATGF2YzYyLjI4AAAAAAAAAAAAAAAAJAJAAAAAAAAAAYfrk5ZRAAAAAAD/+xDEAAAEdBNVVJCAMKYJrzcaIAIAAa05QAABWTo9UFAIBgkB8HwfB8oCAIBhEHwf1Ag7E4f4g3AEk/bAYDgcDgAAAAAAKIkqmRRkCOkCSBaj94UB8BMb8CKUL6gaEvwkDSoAAFON4P/7EsQCgESkLVOdkwA4kAVoQPwwGQAEbhuG4bgcx5yaFqMmo5FPRenp43T04AIECAAAK/mHqYbyYxdYDKFIny4QlL1iQJBUZGRkuXLlq1aCgoKFBQUFBIKC1UxBTUU0LjBVVVVVVVVVVQ==")
.unwrap()
}
pub fn fixture_jpeg() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("/9j/2wBDAAoHBwgHBgoICAgLCgoLDhgQDg0NDh0VFhEYIx8lJCIfIiEmKzcvJik0KSEiMEExNDk7Pj4+JS5ESUM8SDc9Pjv/2wBDAQoLCw4NDhwQEBw7KCIoOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozv/wAARCAAbADADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwCLyfakMB9Kd/a8B+7Y3B/Kj+1rcplrO5B9lFe77VHgcnmQtD61XeP2q3PqlnHCZPs9ycYBAjB5P44qtPqUC3QgjtpZcjkqORxnp/8AXqfbw7lqjNq5WeP2qB4TV5b2OQgfY5F3EhS5Cg468n60yxheaaR1QGFizHL8/gOKmWJhFlRoSktC0tlcEZa5Ix7mmTw3W1Y4b2NVByxOQW+pweKLm6mjVtj4x04Bq/YSvPp8MkjbmYHJ6ZrzalRuNzWlT5XcoxWZkRXkkEjR/wDPLkdf7vrU7Wz6ltaUynyycK4Ix71fUc4ycD3rL8TzyxaJIsblQ7qjY7g9axdRvQ6Iwv1Kt/qul6aDGG+0SLwUiIIGfU9PyrJHiooT5dhGsZ52lufrWckafZAdozj/ABqrdHBwOwFPmbNY0oLof//Z")
.unwrap()
}
pub fn fixture_h264_mp4() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("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")
.unwrap()
}
pub fn fixture_mkv_with_png_video_stream() -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.decode("GkXfo6NChoEBQveBAULygQRC84EIQoKIbWF0cm9za2FCh4EEQoWBAhhTgGcBAAAAAAADKRFNm3TAv4TRygGCTbuLU6uEFUmpZlOsgaFNu4tTq4QWVK5rU6yB8U27jFOrhBJUw2dTrIIBjU27jFOrhBxTu2tTrIIC++wBAAAAAAAAUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFUmpZsu/hDfQZgQq17GDD0JATYCNTGF2ZjYyLjEyLjEwMldBjUxhdmY2Mi4xMi4xMDJzpJCjpT5oFuxsQH+IIFgtzSyDRImIQI9AAAAAAAAWVK5rQJa/hBL4yYmuAQAAAAAAAIfXgQFzxYhQd4Ha+R0+q5yBACK1nIN1bmSIgQCDgQEj44OEHc1lAIaPVl9NUy9WRlcvRk9VUkND4JSwgRC6gRCagQJVsIhVsYEAVbmBAlXugQDsAQAAAAAAAAIAAGOiqCgAAAAQAAAAEAAAAAEAGABNUE5HAAMAAAAAAAAAAAAAAAAAAAAAAAASVMNnQH+/hDz+Y4Vzc6BjwIBnyJpFo4dFTkNPREVSRIeNTGF2ZjYyLjEyLjEwMnNz02PAi2PFiFB3gdr5HT6rZ8ieRaOHRU5DT0RFUkSHkUxhdmM2Mi4yOC4xMDIgcG5nZ8ihRaOIRFVSQVRJT05Eh5MwMDowMDowMS4wMDAwMDAwMDAAH0O2dUDjv4S0YLXX54EAo+uBAACAiVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAABAAAAAQBPJcTWAAAAGUlEQVR4nGNkYPjPQApgIUn1qIZRDUNKAwBsIAE94xryDgAAAABJRU5ErkJggqPrgQH0gIlQTkcNChoKAAAADUlIRFIAAAAQAAAAEAgCAAAAkJFoNgAAAAlwSFlzAAAAAQAAAAEATyXE1gAAABlJREFUeJxjZGD4z0AKYCFJ9aiGUQ1DSgMAbCABPeMa8g4AAAAASUVORK5CYIIcU7trqb+E2z26zruPs4EAt4r3gQHxggIS8IEJu5CzggH0t4r3gQHxggIS8IF2")
.unwrap()
}
pub fn fixture_mp4_with_undecodable_video() -> Vec<u8> {
let mut mp4 = fixture_h264_mp4();
let mdat = mp4
.windows(4)
.position(|window| window == b"mdat")
.filter(|offset| *offset >= 4)
.expect("fixture mp4 has an mdat box");
let box_start = mdat - 4;
let box_size = u32::from_be_bytes([
mp4[box_start],
mp4[box_start + 1],
mp4[box_start + 2],
mp4[box_start + 3],
]) as usize;
let payload_end = box_start + box_size;
assert!(payload_end <= mp4.len() && box_size > 8);
mp4[mdat + 4..payload_end].fill(0);
mp4
}
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
mod adversarial;
mod ffmpeg_cli;
mod images;
mod media;
pub use adversarial::{
ADVERSARIAL_RANGE_HEADERS, ADVERSARIAL_TEXT_INPUTS, adversarial_media_bytes,
};
pub use ffmpeg_cli::{ffmpeg_gen_media, ffmpeg_gen_mp4, ffmpeg_gen_rotated_mp4, ffmpeg_mirror_mp4};
pub use images::{
animated_gif_fixture, animated_gif_frames, apng_header, first_webp_anim_frame_size,
gif_frame_delays_cs, gif_loop_count, minimal_gif, png_dimensions, synthetic_bmp, synthetic_png,
webp_animation_loop_count, webp_canvas_size, webp_chunk_payloads, webp_with_metadata_chunk,
};
pub use media::{
fixture_audio_mp3_with_png_cover_art, fixture_audio_mp4_with_attached_picture,
fixture_audio_only_mp4, fixture_h264_mp4, fixture_jpeg, fixture_mkv_with_png_video_stream,
fixture_mp4_with_undecodable_video, synthetic_wav,
};