From 86096d309b55263534fad830c24b422e781d5092 Mon Sep 17 00:00:00 2001 From: bakgio <76126058+bakgio@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:51:36 +0300 Subject: [PATCH 1/2] release: bump mp4forge to 0.4.0 --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- CHANGELOG.md | 10 + Cargo.toml | 10 +- README.md | 59 +- examples/dump_selected_paths.rs | 21 + examples/dump_structured_fields.rs | 23 + examples/probe_codec_details.rs | 27 + examples/probe_lightweight.rs | 30 + examples/probe_media_characteristics.rs | 41 + examples/pssh_report.rs | 22 + examples/pssh_report_filtered.rs | 33 + examples/validate_divide_layout.rs | 35 + fuzz/Cargo.toml | 21 + fuzz/fuzz_targets/dump_reports.rs | 119 + fuzz/fuzz_targets/probe_reports.rs | 132 + fuzz/fuzz_targets/support.rs | 165 ++ fuzz/fuzz_targets/typed_rewrites.rs | 146 ++ src/cli/divide.rs | 490 +++- src/cli/dump.rs | 1346 +++++++++- src/cli/edit.rs | 233 +- src/cli/extract.rs | 105 +- src/cli/mod.rs | 4 +- src/cli/probe.rs | 2230 +++++++++++++++- src/cli/pssh.rs | 765 +++++- src/codec.rs | 5 + src/fourcc.rs | 55 + src/probe.rs | 1702 +++++++++++- src/stringify.rs | 94 +- tests/cli_dispatch.rs | 6 +- tests/cli_divide.rs | 442 +++- tests/cli_dump.rs | 539 +++- tests/cli_edit.rs | 183 +- tests/cli_extract.rs | 75 +- tests/cli_probe.rs | 499 +++- tests/cli_psshdump.rs | 417 ++- tests/fixture_probe_coverage.rs | 251 ++ tests/fixtures/aac_audio.mp4 | Bin 0 -> 12918 bytes tests/fixtures/av1_opus.mp4 | Bin 0 -> 308559 bytes tests/fixtures/opus_audio.mp4 | Bin 0 -> 12159 bytes tests/fixtures/pcm_audio.mp4 | Bin 0 -> 192746 bytes tests/fixtures/vp9_opus.mp4 | Bin 0 -> 134436 bytes .../cli_divide/sample_fragmented/master.m3u8 | 2 +- tests/golden/cli_dump/sample.json | 2308 +++++++++++++++++ tests/golden/cli_dump/sample.yaml | 1666 ++++++++++++ tests/golden/cli_probe/sample.json | 35 +- tests/golden/cli_probe/sample.yaml | 27 + tests/golden/cli_probe/sample_light.json | 65 + tests/golden/cli_probe/sample_light.yaml | 53 + tests/golden/cli_psshdump/filtered_kid.yaml | 16 + tests/golden/cli_psshdump/filtered_path.json | 23 + .../cli_psshdump/filtered_system_id.json | 23 + tests/golden/cli_psshdump/sample_init.json | 21 + tests/golden/cli_psshdump/sample_init.yaml | 14 + tests/parity_harness.rs | 59 +- tests/probe.rs | 1292 ++++++++- tests/serde_reports.rs | 273 ++ 56 files changed, 15774 insertions(+), 440 deletions(-) create mode 100644 examples/dump_selected_paths.rs create mode 100644 examples/dump_structured_fields.rs create mode 100644 examples/probe_codec_details.rs create mode 100644 examples/probe_lightweight.rs create mode 100644 examples/probe_media_characteristics.rs create mode 100644 examples/pssh_report.rs create mode 100644 examples/pssh_report_filtered.rs create mode 100644 examples/validate_divide_layout.rs create mode 100644 fuzz/fuzz_targets/dump_reports.rs create mode 100644 fuzz/fuzz_targets/probe_reports.rs create mode 100644 fuzz/fuzz_targets/typed_rewrites.rs create mode 100644 tests/fixture_probe_coverage.rs create mode 100644 tests/fixtures/aac_audio.mp4 create mode 100644 tests/fixtures/av1_opus.mp4 create mode 100644 tests/fixtures/opus_audio.mp4 create mode 100644 tests/fixtures/pcm_audio.mp4 create mode 100644 tests/fixtures/vp9_opus.mp4 create mode 100644 tests/golden/cli_dump/sample.json create mode 100644 tests/golden/cli_dump/sample.yaml create mode 100644 tests/golden/cli_probe/sample_light.json create mode 100644 tests/golden/cli_probe/sample_light.yaml create mode 100644 tests/golden/cli_psshdump/filtered_kid.yaml create mode 100644 tests/golden/cli_psshdump/filtered_path.json create mode 100644 tests/golden/cli_psshdump/filtered_system_id.json create mode 100644 tests/golden/cli_psshdump/sample_init.json create mode 100644 tests/golden/cli_psshdump/sample_init.yaml create mode 100644 tests/serde_reports.rs diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 8343118..966f12e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -13,7 +13,7 @@ body: attributes: label: mp4forge Version description: Which version are you using? - placeholder: "0.3.0" + placeholder: "0.4.0" validations: required: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a10c19..226cc4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 0.4.0 (April 22, 2026) + +- Added richer additive probe surfaces for broader codec families, codec-specific details, media-characteristics reporting, and lighter-weight probe controls for large-file inspection +- Added deterministic structured dump and `psshdump` JSON/YAML export, field-level dump payload reporting, and repeatable path or protection filters shared across text and structured output +- Expanded CLI path ergonomics with parsed-path extraction, subtree-scoped dump selection, path-scoped typed edit flows, and richer `psshdump` filtering by box path, system ID, and KID +- Improved `divide` by deriving playlist signaling from probed metadata and adding a first-class validation mode for unsupported fragmented layouts before any output is written +- Added optional `serde` support for reusable report types, including nested probe and dump companion data intended for library-side embedding +- Expanded checked-in fixture coverage for AV1, VP9, AAC, Opus, and PCM, and added dedicated high-level fuzz targets for probe, structured dump, and typed rewrite surfaces +- Refined README guidance, examples, tests, and goldens across the newer higher-level library and CLI workflows while preserving the existing low-level usage paths + # 0.3.0 (April 22, 2026) - Added byte-slice convenience helpers for typed extract, rewrite, and probe workflows so higher-level integrations can stay in-memory without dropping to the lower-level APIs diff --git a/Cargo.toml b/Cargo.toml index 5652f91..7f816c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mp4forge" -version = "0.3.0" +version = "0.4.0" edition = "2024" rust-version = "1.88" authors = ["bakgio"] @@ -16,5 +16,13 @@ exclude = [".github/**", "fuzz/**", "tests/**"] all-features = true rustdoc-args = ["--cfg", "docsrs"] +[features] +default = [] +serde = ["dep:serde"] + [dependencies] +serde = { version = "1", features = ["derive"], optional = true } terminal_size = "0.4" + +[dev-dependencies] +serde_json = "1" diff --git a/README.md b/README.md index 37c793b..2f0725d 100644 --- a/README.md +++ b/README.md @@ -20,13 +20,16 @@ - Low-level traversal, extraction, stringify, probe, and writer APIs - Thin typed path-based helpers and byte-slice convenience wrappers for common extraction, rewrite, and probe flows - Built-in CLI for `dump`, `extract`, `probe`, `psshdump`, `edit`, and `divide` -- Shared-fixture coverage for regular MP4, fragmented MP4, encrypted init segments, and QuickTime-style metadata cases +- Shared-fixture coverage for regular MP4, fragmented MP4, encrypted init segments, QuickTime-style metadata cases, and derived real codec fixtures for additional codec-family coverage ## Installation ```toml [dependencies] -mp4forge = "0.3.0" +mp4forge = "0.4.0" + +# With optional features: +# mp4forge = { version = "0.4.0", features = ["serde"] } ``` Install the CLI from crates.io: @@ -43,6 +46,17 @@ cargo install --path . --locked The published crate includes both the library and the `mp4forge` binary from `src/bin/mp4forge.rs`. +## Feature Flags + +`mp4forge` keeps the default dependency surface minimal and currently exposes one optional public +feature flag: + +- `serde`: derives `Serialize` and `Deserialize` for the reusable public report structs under + `mp4forge::cli::probe` and `mp4forge::cli::dump`, along with their nested public codec-detail, + media-characteristics, `FieldValue`, and `FourCc` data. This is intended for library-side report + embedding and uses the Rust field names of those public structs; the CLI `-format` outputs keep + their existing hand-authored JSON and YAML schemas. + ## CLI ```text @@ -52,22 +66,39 @@ COMMAND: divide split a fragmented MP4 into track playlists dump display the MP4 box tree edit rewrite selected boxes - extract extract raw boxes by type + extract extract raw boxes by type or path psshdump summarize pssh boxes probe summarize an MP4 file ``` -For example: - -```sh -mp4forge dump input.mp4 -mp4forge probe input.mp4 -mp4forge psshdump encrypted_init.mp4 -``` - -## Feature Flags - -`mp4forge` currently ships without public Cargo feature flags. +`divide` currently targets fragmented inputs with up to one AVC video track and one MP4A audio +track, including encrypted wrappers that preserve those original sample-entry formats. Pass +`-validate` when you want the same probe-driven layout checks without creating any output files. + +`dump` defaults to the existing human-readable tree view. Pass `-format json` or `-format yaml` for +deterministic structured tree export with stable `payload_fields` for supported boxes; `-full` and +`-a` still control when large raw or unsupported payloads expand beyond the default summary-oriented +view. Add repeatable `-path ` filters when you want text or structured output rooted at +only the matched parsed subtrees instead of the whole file. + +`edit` keeps the existing global `tfdt` replacement and `-drop` behavior, and now also accepts +repeatable `-path` filters when you want `-base_media_decode_time` to target only matching parsed +box paths. + +`psshdump` defaults to the existing human-readable protection summary. Pass `-format json` or +`-format yaml` for deterministic structured reports with box offsets, system IDs, KIDs, `Data` +bytes, and the legacy raw-box base64 payload. Add repeatable `-path `, `-system-id +`, or `-kid ` filters when you want text and structured reports to return only the +matching protection boxes. + +`probe` defaults to structured JSON output. When the input carries parsed codec-configuration +boxes, the report now includes a nested `codec_details` object per track for families such as AVC, +HEVC, AV1, VP8/VP9, MP4A, Opus, AC-3, PCM, XML subtitles, text subtitles, and WebVTT. When sample +entries carry `btrt`, `colr`, `pasp`, or `fiel`, the richer CLI path also emits nested +`media_characteristics` data such as declared bitrate, colorimetry, pixel aspect ratio, and +field-order hints. Pass `-detail light` for a lighter-weight probe that skips per-sample, +per-chunk, bitrate, and IDR aggregation, or use `mp4forge::probe::ProbeOptions` from the library +when you need the same control programmatically. > See the [`examples/`](./examples) directory for the crate's low-level and high-level API usage patterns. diff --git a/examples/dump_selected_paths.rs b/examples/dump_selected_paths.rs new file mode 100644 index 0000000..d609bab --- /dev/null +++ b/examples/dump_selected_paths.rs @@ -0,0 +1,21 @@ +use std::env; +use std::fs::File; + +use mp4forge::cli::dump::{DumpOptions, build_field_structured_report_paths}; +use mp4forge::walk::BoxPath; + +fn main() -> Result<(), Box> { + let input_path = env::args() + .nth(1) + .expect("usage: cargo run --example dump_selected_paths -- "); + + let mut file = File::open(input_path)?; + let paths = [BoxPath::parse("moov/trak")?]; + let report = build_field_structured_report_paths(&mut file, &DumpOptions::default(), &paths)?; + + for entry in report.boxes { + println!("{} children={}", entry.path, entry.children.len()); + } + + Ok(()) +} diff --git a/examples/dump_structured_fields.rs b/examples/dump_structured_fields.rs new file mode 100644 index 0000000..2f50c94 --- /dev/null +++ b/examples/dump_structured_fields.rs @@ -0,0 +1,23 @@ +use std::env; +use std::fs::File; +use std::io; + +use mp4forge::cli::dump::{DumpOptions, build_field_structured_report}; + +fn main() -> Result<(), Box> { + let input_path = env::args().nth(1).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "usage: dump_structured_fields INPUT.mp4", + ) + })?; + + let mut file = File::open(&input_path)?; + let report = build_field_structured_report(&mut file, &DumpOptions::default())?; + + for root in &report.boxes { + println!("{} {}", root.path, root.payload_fields.len()); + } + + Ok(()) +} diff --git a/examples/probe_codec_details.rs b/examples/probe_codec_details.rs new file mode 100644 index 0000000..2171344 --- /dev/null +++ b/examples/probe_codec_details.rs @@ -0,0 +1,27 @@ +use std::env; +use std::fs::File; +use std::io; + +use mp4forge::probe::probe_codec_detailed; + +fn main() -> Result<(), Box> { + let input_path = env::args().nth(1).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "usage: probe_codec_details INPUT.mp4", + ) + })?; + + let mut file = File::open(&input_path)?; + let summary = probe_codec_detailed(&mut file)?; + + for track in &summary.tracks { + println!( + "track {} family {:?}", + track.summary.summary.track_id, track.summary.codec_family + ); + println!(" details: {:?}", track.codec_details); + } + + Ok(()) +} diff --git a/examples/probe_lightweight.rs b/examples/probe_lightweight.rs new file mode 100644 index 0000000..eefd587 --- /dev/null +++ b/examples/probe_lightweight.rs @@ -0,0 +1,30 @@ +use std::env; +use std::fs::File; +use std::io; + +use mp4forge::probe::{ProbeOptions, probe_codec_detailed_with_options}; + +fn main() -> Result<(), Box> { + let input_path = env::args().nth(1).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "usage: probe_lightweight INPUT.mp4", + ) + })?; + + let mut file = File::open(&input_path)?; + let summary = probe_codec_detailed_with_options(&mut file, ProbeOptions::lightweight())?; + + println!("fast start: {}", summary.fast_start); + println!("track num: {}", summary.tracks.len()); + for track in &summary.tracks { + println!( + "track {} family {:?} expanded samples {}", + track.summary.summary.track_id, + track.summary.codec_family, + track.summary.summary.samples.len() + ); + } + + Ok(()) +} diff --git a/examples/probe_media_characteristics.rs b/examples/probe_media_characteristics.rs new file mode 100644 index 0000000..63aa14b --- /dev/null +++ b/examples/probe_media_characteristics.rs @@ -0,0 +1,41 @@ +use std::env; +use std::fs::File; + +use mp4forge::probe::probe_media_characteristics; + +fn main() -> Result<(), Box> { + let Some(input_path) = env::args().nth(1) else { + eprintln!("usage: cargo run --example probe_media_characteristics -- "); + std::process::exit(1); + }; + + let mut file = File::open(input_path)?; + let summary = probe_media_characteristics(&mut file)?; + + for track in &summary.tracks { + println!( + "track {} codec_family={:?}", + track.summary.summary.track_id, track.summary.codec_family + ); + if let Some(declared) = track.media_characteristics.declared_bitrate.as_ref() { + println!( + " declared bitrate: avg={} max={} buffer={}", + declared.avg_bitrate, declared.max_bitrate, declared.buffer_size_db + ); + } + if let Some(color) = track.media_characteristics.color.as_ref() { + println!(" color type: {}", color.colour_type); + } + if let Some(par) = track.media_characteristics.pixel_aspect_ratio.as_ref() { + println!(" pixel aspect ratio: {}/{}", par.h_spacing, par.v_spacing); + } + if let Some(field_order) = track.media_characteristics.field_order.as_ref() { + println!( + " field order: count={} ordering={} interlaced={}", + field_order.field_count, field_order.field_ordering, field_order.interlaced + ); + } + } + + Ok(()) +} diff --git a/examples/pssh_report.rs b/examples/pssh_report.rs new file mode 100644 index 0000000..89505d7 --- /dev/null +++ b/examples/pssh_report.rs @@ -0,0 +1,22 @@ +use std::env; +use std::fs::File; + +use mp4forge::cli::pssh::build_pssh_report; + +fn main() -> Result<(), Box> { + let input_path = env::args() + .nth(1) + .expect("usage: cargo run --example pssh_report -- "); + + let mut file = File::open(input_path)?; + let report = build_pssh_report(&mut file)?; + + for entry in report.entries { + println!( + "{} offset={} system_id={} kid_count={} data_size={}", + entry.path, entry.offset, entry.system_id, entry.kid_count, entry.data_size + ); + } + + Ok(()) +} diff --git a/examples/pssh_report_filtered.rs b/examples/pssh_report_filtered.rs new file mode 100644 index 0000000..7392b8e --- /dev/null +++ b/examples/pssh_report_filtered.rs @@ -0,0 +1,33 @@ +use std::env; +use std::fs::File; + +use mp4forge::cli::pssh::{PsshReportFilter, build_pssh_report_with_filters}; +use mp4forge::walk::BoxPath; + +fn main() -> Result<(), Box> { + let input_path = env::args() + .nth(1) + .expect("usage: cargo run --example pssh_report_filtered -- "); + + let mut file = File::open(input_path)?; + let report = build_pssh_report_with_filters( + &mut file, + &PsshReportFilter { + paths: vec![BoxPath::parse("moov")?], + system_ids: vec![[ + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, + 0xfb, 0x4b, + ]], + kids: Vec::new(), + }, + )?; + + for entry in report.entries { + println!( + "{} system_id={} kid_count={} data_size={}", + entry.path, entry.system_id, entry.kid_count, entry.data_size + ); + } + + Ok(()) +} diff --git a/examples/validate_divide_layout.rs b/examples/validate_divide_layout.rs new file mode 100644 index 0000000..a8761d6 --- /dev/null +++ b/examples/validate_divide_layout.rs @@ -0,0 +1,35 @@ +use std::env; +use std::fs::File; +use std::io; + +use mp4forge::cli::divide::{DivideTrackRole, validate_divide_reader}; + +fn main() -> Result<(), Box> { + let input_path = env::args().nth(1).ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "usage: validate_divide_layout INPUT.mp4", + ) + })?; + + let mut file = File::open(&input_path)?; + let report = validate_divide_reader(&mut file)?; + + for track in &report.tracks { + let role = match track.role { + DivideTrackRole::Video => "video", + DivideTrackRole::Audio => "audio", + }; + let codec = track + .original_format + .or(track.sample_entry_type) + .map(|value| value.to_string()) + .unwrap_or_else(|| format!("{:?}", track.codec_family)); + println!( + "track {} role {} codec {} segments {}", + track.track_id, role, codec, track.segment_count + ); + } + + Ok(()) +} diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index e51b614..aba7301 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -46,4 +46,25 @@ test = false doc = false bench = false +[[bin]] +name = "probe_reports" +path = "fuzz_targets/probe_reports.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "dump_reports" +path = "fuzz_targets/dump_reports.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "typed_rewrites" +path = "fuzz_targets/typed_rewrites.rs" +test = false +doc = false +bench = false + [workspace] diff --git a/fuzz/fuzz_targets/dump_reports.rs b/fuzz/fuzz_targets/dump_reports.rs new file mode 100644 index 0000000..3bb5485 --- /dev/null +++ b/fuzz/fuzz_targets/dump_reports.rs @@ -0,0 +1,119 @@ +#![no_main] + +mod support; + +use std::collections::BTreeSet; +use std::io::Cursor; + +use libfuzzer_sys::fuzz_target; +use mp4forge::FourCc; +use mp4forge::cli::dump::{ + DumpOptions, StructuredDumpFormat, build_field_structured_report_paths, + build_structured_report_paths, dump_reader_field_structured_paths, + dump_reader_structured_paths, write_field_structured_report, write_structured_report, +}; +use mp4forge::walk::BoxPath; + +use support::{FuzzInput, seeded_small_mp4_bytes}; + +const FTYP: FourCc = FourCc::from_bytes(*b"ftyp"); +const FREE: FourCc = FourCc::from_bytes(*b"free"); +const MDAT: FourCc = FourCc::from_bytes(*b"mdat"); +const MDHD: FourCc = FourCc::from_bytes(*b"mdhd"); +const MDIA: FourCc = FourCc::from_bytes(*b"mdia"); +const MINF: FourCc = FourCc::from_bytes(*b"minf"); +const MOOF: FourCc = FourCc::from_bytes(*b"moof"); +const MOOV: FourCc = FourCc::from_bytes(*b"moov"); +const MVHD: FourCc = FourCc::from_bytes(*b"mvhd"); +const PSSH: FourCc = FourCc::from_bytes(*b"pssh"); +const SKIP: FourCc = FourCc::from_bytes(*b"skip"); +const STBL: FourCc = FourCc::from_bytes(*b"stbl"); +const STSD: FourCc = FourCc::from_bytes(*b"stsd"); +const TFDT: FourCc = FourCc::from_bytes(*b"tfdt"); +const TRAF: FourCc = FourCc::from_bytes(*b"traf"); +const TRAK: FourCc = FourCc::from_bytes(*b"trak"); +const TRUN: FourCc = FourCc::from_bytes(*b"trun"); + +const FULL_BOX_TYPES: [FourCc; 10] = [FTYP, FREE, MDAT, MDHD, MVHD, PSSH, SKIP, STSD, TFDT, TRUN]; + +fuzz_target!(|data: &[u8]| { + let mut input = FuzzInput::new(data); + let bytes = seeded_small_mp4_bytes(&mut input); + let options = take_dump_options(&mut input); + let paths = take_dump_paths(&mut input); + let format = take_dump_format(&mut input); + + if let Ok(report) = + build_structured_report_paths(&mut Cursor::new(bytes.as_slice()), &options, &paths) + { + let mut rendered = Vec::new(); + let _ = write_structured_report(&mut rendered, &report, format); + } + + if let Ok(report) = + build_field_structured_report_paths(&mut Cursor::new(bytes.as_slice()), &options, &paths) + { + let mut rendered = Vec::new(); + let _ = write_field_structured_report(&mut rendered, &report, format); + } + + let mut structured_output = Vec::new(); + let _ = dump_reader_structured_paths( + &mut Cursor::new(bytes.as_slice()), + &options, + &paths, + format, + &mut structured_output, + ); + + let mut field_output = Vec::new(); + let _ = dump_reader_field_structured_paths( + &mut Cursor::new(bytes.as_slice()), + &options, + &paths, + format, + &mut field_output, + ); +}); + +fn take_dump_options(input: &mut FuzzInput<'_>) -> DumpOptions { + let mut full_box_types = BTreeSet::new(); + for _ in 0..input.take_usize(4) { + full_box_types.insert(input.choose_fourcc(&FULL_BOX_TYPES)); + } + + DumpOptions { + full_box_types, + show_all: input.take_bool(), + show_offset: input.take_bool(), + hex: input.take_bool(), + terminal_width: input.take_usize(240).max(16), + } +} + +fn take_dump_paths(input: &mut FuzzInput<'_>) -> Vec { + let known_paths = vec![ + BoxPath::empty(), + BoxPath::from([FTYP]), + BoxPath::from([MOOV]), + BoxPath::from([MOOV, MVHD]), + BoxPath::from([MOOV, TRAK]), + BoxPath::from([MOOV, TRAK, MDIA]), + BoxPath::from([MOOV, FourCc::ANY, MDIA]), + BoxPath::from([MOOV, TRAK, MDIA, MINF, STBL, STSD]), + BoxPath::from([MOOV, TRAK, MDIA, MINF, STBL, FourCc::ANY]), + BoxPath::from([MOOF]), + BoxPath::from([MOOF, TRAF]), + BoxPath::from([MOOF, TRAF, TFDT]), + BoxPath::from([MOOF, TRAF, TRUN]), + ]; + input.take_paths_from_table(&known_paths, 4) +} + +fn take_dump_format(input: &mut FuzzInput<'_>) -> StructuredDumpFormat { + if input.take_bool() { + StructuredDumpFormat::Json + } else { + StructuredDumpFormat::Yaml + } +} diff --git a/fuzz/fuzz_targets/probe_reports.rs b/fuzz/fuzz_targets/probe_reports.rs new file mode 100644 index 0000000..58055d3 --- /dev/null +++ b/fuzz/fuzz_targets/probe_reports.rs @@ -0,0 +1,132 @@ +#![no_main] + +mod support; + +use std::io::Cursor; + +use libfuzzer_sys::fuzz_target; +use mp4forge::cli::probe::{ + ProbeFormat, ProbeReportOptions, build_codec_detailed_report_with_options, + build_detailed_report_with_options, build_media_characteristics_report_with_options, + build_report_with_options, write_codec_detailed_report, write_detailed_report, + write_media_characteristics_report, write_report, +}; +use mp4forge::probe::{ + ProbeOptions, SegmentInfo, TrackInfo, average_sample_bitrate, average_segment_bitrate, + find_idr_frames, max_sample_bitrate, max_segment_bitrate, probe_bytes_with_options, + probe_codec_detailed_bytes_with_options, probe_codec_detailed_with_options, + probe_detailed_bytes_with_options, probe_detailed_with_options, probe_fra, + probe_fra_codec_detailed, probe_fra_detailed, probe_fra_media_characteristics, + probe_media_characteristics_bytes_with_options, probe_media_characteristics_with_options, + probe_with_options, +}; + +use support::{FuzzInput, seeded_any_mp4_bytes}; + +fuzz_target!(|data: &[u8]| { + let mut input = FuzzInput::new(data); + let bytes = seeded_any_mp4_bytes(&mut input); + let options = take_report_options(&mut input); + let format = take_probe_format(&mut input); + + match input.take_u8() % 4 { + 0 => exercise_coarse_probe_surface(&bytes, options, format), + 1 => exercise_detailed_probe_surface(&bytes, options, format), + 2 => exercise_codec_detailed_probe_surface(&bytes, options, format), + _ => exercise_media_characteristics_probe_surface(&bytes, options, format), + } +}); + +fn exercise_coarse_probe_surface(bytes: &[u8], options: ProbeReportOptions, format: ProbeFormat) { + if let Ok(summary) = probe_with_options(&mut Cursor::new(bytes), options.probe) { + exercise_track_metrics(bytes, &summary.tracks, &summary.segments); + } + + let _ = probe_bytes_with_options(bytes, options.probe); + let _ = probe_fra(&mut Cursor::new(bytes)); + + if let Ok(report) = build_report_with_options(&mut Cursor::new(bytes), options) { + let mut rendered = Vec::new(); + let _ = write_report(&mut rendered, &report, format); + } +} + +fn exercise_detailed_probe_surface(bytes: &[u8], options: ProbeReportOptions, format: ProbeFormat) { + let _ = probe_detailed_with_options(&mut Cursor::new(bytes), options.probe); + let _ = probe_detailed_bytes_with_options(bytes, options.probe); + let _ = probe_fra_detailed(&mut Cursor::new(bytes)); + + if let Ok(report) = build_detailed_report_with_options(&mut Cursor::new(bytes), options) { + let mut rendered = Vec::new(); + let _ = write_detailed_report(&mut rendered, &report, format); + } +} + +fn exercise_codec_detailed_probe_surface( + bytes: &[u8], + options: ProbeReportOptions, + format: ProbeFormat, +) { + let _ = probe_codec_detailed_with_options(&mut Cursor::new(bytes), options.probe); + let _ = probe_codec_detailed_bytes_with_options(bytes, options.probe); + let _ = probe_fra_codec_detailed(&mut Cursor::new(bytes)); + + if let Ok(report) = build_codec_detailed_report_with_options(&mut Cursor::new(bytes), options) { + let mut rendered = Vec::new(); + let _ = write_codec_detailed_report(&mut rendered, &report, format); + } +} + +fn exercise_media_characteristics_probe_surface( + bytes: &[u8], + options: ProbeReportOptions, + format: ProbeFormat, +) { + let _ = probe_media_characteristics_with_options(&mut Cursor::new(bytes), options.probe); + let _ = probe_media_characteristics_bytes_with_options(bytes, options.probe); + let _ = probe_fra_media_characteristics(&mut Cursor::new(bytes)); + + if let Ok(report) = + build_media_characteristics_report_with_options(&mut Cursor::new(bytes), options) + { + let mut rendered = Vec::new(); + let _ = write_media_characteristics_report(&mut rendered, &report, format); + } +} + +fn exercise_track_metrics(bytes: &[u8], tracks: &[TrackInfo], segments: &[SegmentInfo]) { + for track in tracks { + let _ = average_sample_bitrate(&track.samples, track.timescale); + let _ = max_sample_bitrate(&track.samples, track.timescale, 1); + let _ = average_segment_bitrate(segments, track.track_id, track.timescale); + let _ = max_segment_bitrate(segments, track.track_id, track.timescale); + + if track.avc.is_some() && !track.samples.is_empty() && !track.chunks.is_empty() { + let _ = find_idr_frames(&mut Cursor::new(bytes), track); + } + } +} + +fn take_probe_format(input: &mut FuzzInput<'_>) -> ProbeFormat { + if input.take_bool() { + ProbeFormat::Json + } else { + ProbeFormat::Yaml + } +} + +fn take_probe_options(input: &mut FuzzInput<'_>) -> ProbeOptions { + ProbeOptions { + expand_samples: input.take_bool(), + expand_chunks: input.take_bool(), + include_segments: input.take_bool(), + } +} + +fn take_report_options(input: &mut FuzzInput<'_>) -> ProbeReportOptions { + ProbeReportOptions { + probe: take_probe_options(input), + include_bitrate: input.take_bool(), + include_idr_frame_count: input.take_bool(), + } +} diff --git a/fuzz/fuzz_targets/support.rs b/fuzz/fuzz_targets/support.rs index a65d72d..812d77a 100644 --- a/fuzz/fuzz_targets/support.rs +++ b/fuzz/fuzz_targets/support.rs @@ -1,8 +1,49 @@ #![allow(dead_code)] use mp4forge::FourCc; +use mp4forge::header::BoxInfo; use mp4forge::walk::BoxPath; +const SAMPLE_MP4: &[u8] = include_bytes!("../../tests/fixtures/sample.mp4"); +const SAMPLE_FRAGMENTED_MP4: &[u8] = include_bytes!("../../tests/fixtures/sample_fragmented.mp4"); +const SAMPLE_INIT_ENCA_MP4: &[u8] = include_bytes!("../../tests/fixtures/sample_init.enca.mp4"); +const SAMPLE_INIT_ENCV_MP4: &[u8] = include_bytes!("../../tests/fixtures/sample_init.encv.mp4"); +const SAMPLE_QT_MP4: &[u8] = include_bytes!("../../tests/fixtures/sample_qt.mp4"); +const AAC_AUDIO_MP4: &[u8] = include_bytes!("../../tests/fixtures/aac_audio.mp4"); +const OPUS_AUDIO_MP4: &[u8] = include_bytes!("../../tests/fixtures/opus_audio.mp4"); +const PCM_AUDIO_MP4: &[u8] = include_bytes!("../../tests/fixtures/pcm_audio.mp4"); +const VP9_OPUS_MP4: &[u8] = include_bytes!("../../tests/fixtures/vp9_opus.mp4"); +const AV1_OPUS_MP4: &[u8] = include_bytes!("../../tests/fixtures/av1_opus.mp4"); + +const ANY_FIXTURES: [&[u8]; 10] = [ + SAMPLE_MP4, + SAMPLE_FRAGMENTED_MP4, + SAMPLE_INIT_ENCA_MP4, + SAMPLE_INIT_ENCV_MP4, + SAMPLE_QT_MP4, + AAC_AUDIO_MP4, + OPUS_AUDIO_MP4, + PCM_AUDIO_MP4, + VP9_OPUS_MP4, + AV1_OPUS_MP4, +]; + +const SMALL_FIXTURES: [&[u8]; 6] = [ + SAMPLE_MP4, + SAMPLE_FRAGMENTED_MP4, + SAMPLE_INIT_ENCA_MP4, + SAMPLE_INIT_ENCV_MP4, + AAC_AUDIO_MP4, + OPUS_AUDIO_MP4, +]; + +const REWRITE_FIXTURES: [&[u8]; 4] = [ + SAMPLE_MP4, + SAMPLE_FRAGMENTED_MP4, + SAMPLE_INIT_ENCA_MP4, + SAMPLE_INIT_ENCV_MP4, +]; + pub struct FuzzInput<'a> { data: &'a [u8], offset: usize, @@ -29,6 +70,10 @@ impl<'a> FuzzInput<'a> { u32::from_be_bytes(self.take_exact()) } + pub fn take_u64(&mut self) -> u64 { + u64::from_be_bytes(self.take_exact()) + } + pub fn take_exact(&mut self) -> [u8; N] { let mut bytes = [0_u8; N]; for byte in &mut bytes { @@ -77,4 +122,124 @@ impl<'a> FuzzInput<'a> { pub fn choose_fourcc(&mut self, table: &[FourCc]) -> FourCc { table[self.take_usize(table.len() - 1)] } + + pub fn take_path_from_table(&mut self, table: &[BoxPath]) -> BoxPath { + table[self.take_usize(table.len() - 1)].clone() + } + + pub fn take_paths_from_table(&mut self, table: &[BoxPath], max_len: usize) -> Vec { + let len = self.take_usize(max_len); + let mut paths = Vec::with_capacity(len); + for _ in 0..len { + paths.push(self.take_path_from_table(table)); + } + paths + } +} + +pub fn seeded_any_mp4_bytes(input: &mut FuzzInput<'_>) -> Vec { + seeded_mp4_bytes_from(input, &ANY_FIXTURES, 384 * 1024) +} + +pub fn seeded_small_mp4_bytes(input: &mut FuzzInput<'_>) -> Vec { + seeded_mp4_bytes_from(input, &SMALL_FIXTURES, 64 * 1024) +} + +pub fn seeded_rewrite_mp4_bytes(input: &mut FuzzInput<'_>) -> Vec { + seeded_mp4_bytes_from(input, &REWRITE_FIXTURES, 96 * 1024) +} + +fn seeded_mp4_bytes_from(input: &mut FuzzInput<'_>, fixtures: &[&[u8]], max_len: usize) -> Vec { + let mut bytes = select_seed_bytes(input, fixtures, max_len); + mutate_seed_bytes(input, &mut bytes, max_len); + if bytes.is_empty() { + bytes = malformed_truncated_mvhd_payload(); + } + bytes +} + +fn select_seed_bytes(input: &mut FuzzInput<'_>, fixtures: &[&[u8]], max_len: usize) -> Vec { + let malformed_seed_count = 4; + match input.take_usize(fixtures.len() + malformed_seed_count) { + index if index < fixtures.len() => fixtures[index].to_vec(), + index if index == fixtures.len() => malformed_truncated_child_header(), + index if index == fixtures.len() + 1 => malformed_huge_supported_payload(), + index if index == fixtures.len() + 2 => malformed_oversized_child_box(), + index if index == fixtures.len() + 3 => malformed_truncated_mvhd_payload(), + _ => input.take_bytes(max_len.min(4096)), + } +} + +fn mutate_seed_bytes(input: &mut FuzzInput<'_>, bytes: &mut Vec, max_len: usize) { + let steps = input.take_usize(8); + for _ in 0..steps { + match input.take_u8() % 6 { + 0 => { + if !bytes.is_empty() { + let index = input.take_usize(bytes.len() - 1); + bytes[index] ^= input.take_u8(); + } + } + 1 => { + if bytes.len() < max_len { + let index = input.take_usize(bytes.len()); + bytes.insert(index, input.take_u8()); + } + } + 2 => { + if !bytes.is_empty() { + let index = input.take_usize(bytes.len() - 1); + bytes.remove(index); + } + } + 3 => { + if !bytes.is_empty() { + let truncate_to = input.take_usize(bytes.len() - 1); + bytes.truncate(truncate_to); + } + } + 4 => { + let available = max_len.saturating_sub(bytes.len()).min(32); + if available != 0 { + bytes.extend(input.take_bytes(available)); + } + } + _ => { + if bytes.len() >= 2 { + let lhs = input.take_usize(bytes.len() - 1); + let rhs = input.take_usize(bytes.len() - 1); + bytes.swap(lhs, rhs); + } + } + } + } + + if bytes.len() > max_len { + bytes.truncate(max_len); + } +} + +fn malformed_truncated_child_header() -> Vec { + let mut bytes = BoxInfo::new(FourCc::from_bytes(*b"moov"), 16).encode(); + bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x0c]); + bytes +} + +fn malformed_huge_supported_payload() -> Vec { + let mut bytes = BoxInfo::new(FourCc::from_bytes(*b"styp"), u64::from(u32::MAX)).encode(); + bytes.extend_from_slice(b"isom"); + bytes.extend_from_slice(&0_u32.to_be_bytes()); + bytes +} + +fn malformed_oversized_child_box() -> Vec { + let mut bytes = BoxInfo::new(FourCc::from_bytes(*b"moov"), 16).encode(); + bytes.extend_from_slice(&BoxInfo::new(FourCc::from_bytes(*b"free"), 12).encode()); + bytes +} + +fn malformed_truncated_mvhd_payload() -> Vec { + let mut bytes = BoxInfo::new(FourCc::from_bytes(*b"mvhd"), 12).encode(); + bytes.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); + bytes } diff --git a/fuzz/fuzz_targets/typed_rewrites.rs b/fuzz/fuzz_targets/typed_rewrites.rs new file mode 100644 index 0000000..1ebd07a --- /dev/null +++ b/fuzz/fuzz_targets/typed_rewrites.rs @@ -0,0 +1,146 @@ +#![no_main] + +mod support; + +use std::collections::BTreeSet; +use std::io::Cursor; + +use libfuzzer_sys::fuzz_target; +use mp4forge::FourCc; +use mp4forge::boxes::iso14496_12::{Ftyp, Mvhd, Tfdt}; +use mp4forge::cli::dump::{DumpOptions, build_field_structured_report}; +use mp4forge::cli::edit::{EditOptions, edit_reader}; +use mp4forge::codec::ImmutableBox; +use mp4forge::probe::{ProbeOptions, probe_with_options}; +use mp4forge::rewrite::{rewrite_box_as_bytes, rewrite_boxes_as_bytes}; +use mp4forge::walk::{BoxPath, WalkControl, walk_structure}; + +use support::{FuzzInput, seeded_rewrite_mp4_bytes}; + +const FREE: FourCc = FourCc::from_bytes(*b"free"); +const FTYP: FourCc = FourCc::from_bytes(*b"ftyp"); +const MDAT: FourCc = FourCc::from_bytes(*b"mdat"); +const MDHD: FourCc = FourCc::from_bytes(*b"mdhd"); +const MOOF: FourCc = FourCc::from_bytes(*b"moof"); +const MOOV: FourCc = FourCc::from_bytes(*b"moov"); +const MVHD: FourCc = FourCc::from_bytes(*b"mvhd"); +const PSSH: FourCc = FourCc::from_bytes(*b"pssh"); +const SKIP: FourCc = FourCc::from_bytes(*b"skip"); +const TFDT: FourCc = FourCc::from_bytes(*b"tfdt"); +const TRAF: FourCc = FourCc::from_bytes(*b"traf"); +const TRAK: FourCc = FourCc::from_bytes(*b"trak"); +const TRUN: FourCc = FourCc::from_bytes(*b"trun"); + +const DROP_BOX_TYPES: [FourCc; 6] = [FREE, MDAT, MDHD, PSSH, SKIP, TRUN]; + +fuzz_target!(|data: &[u8]| { + let mut input = FuzzInput::new(data); + let bytes = seeded_rewrite_mp4_bytes(&mut input); + + match input.take_u8() % 3 { + 0 => exercise_ftyp_rewrite(&mut input, &bytes), + 1 => exercise_mvhd_rewrite(&mut input, &bytes), + _ => exercise_tfdt_rewrite(&mut input, &bytes), + } + + exercise_edit_flow(&mut input, &bytes); +}); + +fn exercise_ftyp_rewrite(input: &mut FuzzInput<'_>, bytes: &[u8]) { + let known_paths = vec![ + BoxPath::empty(), + BoxPath::from([FTYP]), + BoxPath::from([MOOV]), + BoxPath::from([MOOV, MVHD]), + BoxPath::from([MOOF, TRAF, TFDT]), + ]; + let path = input.take_path_from_table(&known_paths); + if let Ok(rewritten) = rewrite_box_as_bytes::(bytes, path, |ftyp| { + ftyp.major_brand = input.take_fourcc(); + ftyp.minor_version ^= input.take_u32(); + ftyp.add_compatible_brand(input.take_fourcc()); + if input.take_bool() && !ftyp.compatible_brands.is_empty() { + let brand = ftyp.compatible_brands[input.take_usize(ftyp.compatible_brands.len() - 1)]; + ftyp.remove_compatible_brand(brand); + } + }) { + exercise_rewritten_bytes(&rewritten); + } +} + +fn exercise_mvhd_rewrite(input: &mut FuzzInput<'_>, bytes: &[u8]) { + let known_paths = vec![ + BoxPath::empty(), + BoxPath::from([FTYP]), + BoxPath::from([MOOV]), + BoxPath::from([MOOV, MVHD]), + BoxPath::from([MOOV, TRAK]), + ]; + let paths = input.take_paths_from_table(&known_paths, 3); + if let Ok(rewritten) = rewrite_boxes_as_bytes::(bytes, &paths, |mvhd| { + mvhd.timescale = input.take_u32().max(1); + if mvhd.version() == 0 { + mvhd.duration_v0 = input.take_u32(); + } else { + mvhd.duration_v1 = input.take_u64(); + } + mvhd.next_track_id = input.take_u32(); + }) { + exercise_rewritten_bytes(&rewritten); + } +} + +fn exercise_tfdt_rewrite(input: &mut FuzzInput<'_>, bytes: &[u8]) { + let known_paths = vec![ + BoxPath::empty(), + BoxPath::from([MOOV, MVHD]), + BoxPath::from([MOOF]), + BoxPath::from([MOOF, TRAF]), + BoxPath::from([MOOF, TRAF, TFDT]), + BoxPath::from([MOOF, FourCc::ANY, TFDT]), + ]; + let paths = input.take_paths_from_table(&known_paths, 4); + let decode_time = input.take_u64(); + if let Ok(rewritten) = rewrite_boxes_as_bytes::(bytes, &paths, |tfdt| { + if tfdt.version() == 0 { + tfdt.base_media_decode_time_v0 = decode_time as u32; + } else { + tfdt.base_media_decode_time_v1 = decode_time; + } + }) { + exercise_rewritten_bytes(&rewritten); + } +} + +fn exercise_edit_flow(input: &mut FuzzInput<'_>, bytes: &[u8]) { + let mut drop_boxes = BTreeSet::new(); + for _ in 0..input.take_usize(4) { + drop_boxes.insert(input.choose_fourcc(&DROP_BOX_TYPES)); + } + + let options = EditOptions { + base_media_decode_time: if input.take_bool() { + Some(input.take_u64()) + } else { + None + }, + drop_boxes, + }; + + let mut rewritten = Cursor::new(Vec::new()); + if edit_reader(&mut Cursor::new(bytes), &mut rewritten, &options).is_ok() { + exercise_rewritten_bytes(rewritten.get_ref().as_slice()); + } +} + +fn exercise_rewritten_bytes(bytes: &[u8]) { + let _ = probe_with_options(&mut Cursor::new(bytes), ProbeOptions::lightweight()); + let _ = build_field_structured_report(&mut Cursor::new(bytes), &DumpOptions::default()); + let _ = walk_structure(&mut Cursor::new(bytes), |handle| { + Ok(if handle.is_supported_type() { + WalkControl::Descend + } else { + WalkControl::Continue + }) + }); +} diff --git a/src/cli/divide.rs b/src/cli/divide.rs index 451edfd..49e0c3b 100644 --- a/src/cli/divide.rs +++ b/src/cli/divide.rs @@ -1,6 +1,6 @@ //! Fragmented-file split command support. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::error::Error; use std::fmt; use std::fs::{self, File}; @@ -11,7 +11,9 @@ use crate::FourCc; use crate::boxes::iso14496_12::{Tfhd, Tkhd}; use crate::extract::{ExtractError, extract_boxes_with_payload}; use crate::header::{BoxInfo, HeaderError}; -use crate::probe::{ProbeError, ProbeInfo, TrackCodec, probe}; +use crate::probe::{ + DetailedProbeInfo, DetailedTrackInfo, ProbeError, TrackCodecFamily, probe_detailed, +}; use crate::walk::BoxPath; use crate::writer::{Writer, WriterError}; @@ -32,14 +34,24 @@ const VIDEO_ENC_DIR: &str = "video_enc"; const AUDIO_ENC_DIR: &str = "audio_enc"; const INIT_FILE_NAME: &str = "init.mp4"; const PLAYLIST_FILE_NAME: &str = "playlist.m3u8"; -const MASTER_PLAYLIST_CODECS: &str = "avc1.64001f,mp4a.40.2"; /// Runs the divide subcommand with `args`, writing files under `OUTPUT_DIR`. pub fn run(args: &[String], stderr: &mut E) -> i32 where E: Write, { - match run_inner(args) { + let mut stdout = io::sink(); + run_with_output(args, &mut stdout, stderr) +} + +/// Runs the divide subcommand with `args`, writing validation output to `stdout` when requested +/// and errors to `stderr`. +pub fn run_with_output(args: &[String], stdout: &mut W, stderr: &mut E) -> i32 +where + W: Write, + E: Write, +{ + match run_inner(args, stdout) { Ok(()) => 0, Err(DivideError::UsageRequested) => { let _ = write_usage(stderr); @@ -57,30 +69,97 @@ pub fn write_usage(writer: &mut W) -> io::Result<()> where W: Write, { - writeln!(writer, "USAGE: mp4forge divide INPUT.mp4 OUTPUT_DIR") + writeln!(writer, "USAGE: mp4forge divide INPUT.mp4 OUTPUT_DIR")?; + writeln!(writer, " mp4forge divide -validate INPUT.mp4")?; + writeln!(writer)?; + writeln!(writer, "OPTIONS:")?; + writeln!( + writer, + " -validate Validate the fragmented divide layout without writing output files" + )?; + writeln!(writer)?; + writeln!( + writer, + "Currently supports fragmented inputs with up to one AVC video track and one MP4A audio track," + )?; + writeln!( + writer, + "including encrypted wrappers that preserve those original sample-entry formats." + ) } -fn run_inner(args: &[String]) -> Result<(), DivideError> { - if args.len() != 2 { - return Err(DivideError::UsageRequested); +#[derive(Debug)] +struct ParsedDivideArgs<'a> { + validate_only: bool, + input_path: &'a Path, + output_dir: Option<&'a Path>, +} + +fn run_inner(args: &[String], stdout: &mut W) -> Result<(), DivideError> +where + W: Write, +{ + let parsed = parse_args(args)?; + let mut input = File::open(parsed.input_path)?; + if parsed.validate_only { + let report = validate_divide_reader(&mut input)?; + write_validation_report(stdout, &report)?; + return Ok(()); } - let input_path = Path::new(&args[0]); - let output_dir = Path::new(&args[1]); - let mut input = File::open(input_path)?; - divide_reader(&mut input, output_dir) + divide_reader( + &mut input, + parsed.output_dir.ok_or(DivideError::UsageRequested)?, + ) +} + +fn parse_args(args: &[String]) -> Result, DivideError> { + let mut validate_only = false; + let mut positional = Vec::new(); + let mut index = 0usize; + while index < args.len() { + match args[index].as_str() { + "-validate" | "--validate" => { + validate_only = true; + index += 1; + } + "-h" | "--help" => return Err(DivideError::UsageRequested), + value if value.starts_with('-') => { + return Err(invalid_input(format!("unknown divide option: {value}"))); + } + value => { + positional.push(Path::new(value)); + index += 1; + } + } + } + + match (validate_only, positional.as_slice()) { + (true, [input_path]) => Ok(ParsedDivideArgs { + validate_only, + input_path, + output_dir: None, + }), + (false, [input_path, output_dir]) => Ok(ParsedDivideArgs { + validate_only, + input_path, + output_dir: Some(output_dir), + }), + _ => Err(DivideError::UsageRequested), + } } /// Splits a fragmented MP4 reader into per-track outputs under `output_dir`. +/// +/// The current `divide` surface supports fragmented inputs with at most one AVC video track and +/// one MP4A audio track, including encrypted `encv` and `enca` wrappers when the original format +/// is still `avc1` or `mp4a`. pub fn divide_reader(reader: &mut R, output_dir: &Path) -> Result<(), DivideError> where R: Read + Seek, { - let summary = probe(reader)?; - let mut tracks = build_track_outputs(&summary, output_dir)?; - if tracks.is_empty() { - return Err(DivideError::NoSupportedTracks); - } + let plans = validate_divide_track_plans(reader)?; + let mut tracks = build_track_outputs(&plans, output_dir)?; reader.seek(SeekFrom::Start(0))?; write_init_segments(reader, &mut tracks)?; @@ -98,8 +177,52 @@ enum TrackKind { EncryptedAudio, } +/// High-level role assigned to one active track in the currently supported divide layout. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DivideTrackRole { + Video, + Audio, +} + +/// Validation summary for one active fragmented track accepted by `divide`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DivideValidationTrack { + /// Track identifier selected from `tkhd` and referenced by fragmented runs. + pub track_id: u32, + /// Role assigned by the current divide layout rules. + pub role: DivideTrackRole, + /// Whether the selected track uses an encrypted sample-entry wrapper. + pub encrypted: bool, + /// Normalized codec family derived from the sample entry or protected original format. + pub codec_family: TrackCodecFamily, + /// Sample-entry box type selected from `stsd`, including encrypted wrappers such as `encv`. + pub sample_entry_type: Option, + /// Original-format sample-entry type from `frma` when the track is protected. + pub original_format: Option, + /// Number of fragmented media segments currently associated with the track. + pub segment_count: usize, +} + +/// Additive divide preflight report returned when the fragmented layout is currently supported. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct DivideValidationReport { + /// Active fragmented tracks accepted by the current divide layout rules. + pub tracks: Vec, +} + +struct TrackLayout { + role: DivideTrackRole, + kind: TrackKind, + codecs: String, + audio_channels: Option, + width: Option, + height: Option, +} + struct TrackOutput { kind: TrackKind, + codecs: String, + audio_channels: Option, width: Option, height: Option, segment_durations: Vec, @@ -109,70 +232,213 @@ struct TrackOutput { next_segment_index: usize, } +struct ValidatedTrackPlan { + validation: DivideValidationTrack, + layout: TrackLayout, + segment_durations: Vec, +} + +/// Validates whether `reader` matches the fragmented layout currently supported by +/// [`divide_reader`] without creating any output files. +/// +/// On success, the returned report lists the active fragmented tracks that would participate in +/// the divide output. On failure, the returned [`DivideError`] explains why the current layout is +/// unsupported. +pub fn validate_divide_reader(reader: &mut R) -> Result +where + R: Read + Seek, +{ + let plans = validate_divide_track_plans(reader)?; + Ok(DivideValidationReport { + tracks: plans.into_iter().map(|plan| plan.validation).collect(), + }) +} + +fn validate_divide_track_plans(reader: &mut R) -> Result, DivideError> +where + R: Read + Seek, +{ + reader.seek(SeekFrom::Start(0))?; + let summary = probe_detailed(reader)?; + collect_track_plans(&summary) +} + fn build_track_outputs( - summary: &ProbeInfo, + plans: &[ValidatedTrackPlan], output_dir: &Path, ) -> Result, DivideError> { let mut tracks = BTreeMap::new(); - for track in &summary.tracks { - let Some((kind, dir_name, width, height)) = track_layout(track) else { - continue; - }; - let track_dir = output_dir.join(dir_name); + for plan in plans { + let track_dir = output_dir.join(relative_dir(plan.layout.kind)); fs::create_dir_all(&track_dir)?; let init_writer = Writer::new(File::create(track_dir.join(INIT_FILE_NAME))?); + tracks.insert( + plan.validation.track_id, + TrackOutput { + kind: plan.layout.kind, + codecs: plan.layout.codecs.clone(), + audio_channels: plan.layout.audio_channels, + width: plan.layout.width, + height: plan.layout.height, + segment_durations: plan.segment_durations.clone(), + bandwidth: 0, + output_dir: track_dir, + init_writer, + next_segment_index: 0, + }, + ); + } + + Ok(tracks) +} + +fn collect_track_plans( + summary: &DetailedProbeInfo, +) -> Result, DivideError> { + let active_track_ids = summary + .segments + .iter() + .map(|segment| segment.track_id) + .collect::>(); + let known_track_ids = summary + .tracks + .iter() + .map(|track| track.summary.track_id) + .collect::>(); + + if let Some(track_id) = active_track_ids.difference(&known_track_ids).next() { + return Err(DivideError::UnknownTrack(*track_id)); + } + + let mut tracks = BTreeMap::new(); + let mut selected_video_track_id = None; + let mut selected_audio_track_id = None; + + for track in &summary.tracks { + if !active_track_ids.contains(&track.summary.track_id) { + continue; + } + let layout = track_layout(track)?; + match layout.role { + DivideTrackRole::Video => { + if let Some(existing_track_id) = + selected_video_track_id.replace(track.summary.track_id) + { + return Err(invalid_input(format!( + "{}; found multiple fragmented video tracks ({existing_track_id} and {}).", + supported_scope_message(), + track.summary.track_id + ))); + } + } + DivideTrackRole::Audio => { + if let Some(existing_track_id) = + selected_audio_track_id.replace(track.summary.track_id) + { + return Err(invalid_input(format!( + "{}; found multiple fragmented audio tracks ({existing_track_id} and {}).", + supported_scope_message(), + track.summary.track_id + ))); + } + } + } + let segment_durations = summary .segments .iter() - .filter(|segment| segment.track_id == track.track_id) + .filter(|segment| segment.track_id == track.summary.track_id) .map(|segment| { - if track.timescale == 0 { + if track.summary.timescale == 0 { 0.0 } else { - segment.duration as f64 / f64::from(track.timescale) + segment.duration as f64 / f64::from(track.summary.timescale) } }) .collect::>(); tracks.insert( - track.track_id, - TrackOutput { - kind, - width, - height, + track.summary.track_id, + ValidatedTrackPlan { + validation: DivideValidationTrack { + track_id: track.summary.track_id, + role: layout.role, + encrypted: track.summary.encrypted, + codec_family: track.codec_family, + sample_entry_type: track.sample_entry_type, + original_format: track.original_format, + segment_count: segment_durations.len(), + }, + layout, segment_durations, - bandwidth: 0, - output_dir: track_dir, - init_writer, - next_segment_index: 0, }, ); } - Ok(tracks) + let plans = tracks.into_values().collect::>(); + if plans.is_empty() { + return Err(DivideError::NoSupportedTracks); + } + + Ok(plans) } -fn track_layout( - track: &crate::probe::TrackInfo, -) -> Option<(TrackKind, &'static str, Option, Option)> { - match (track.codec, track.encrypted) { - (TrackCodec::Avc1, false) => Some(( - TrackKind::Video, - VIDEO_DIR, - track.avc.as_ref().map(|avc| avc.width), - track.avc.as_ref().map(|avc| avc.height), - )), - (TrackCodec::Avc1, true) => Some(( - TrackKind::EncryptedVideo, - VIDEO_ENC_DIR, - track.avc.as_ref().map(|avc| avc.width), - track.avc.as_ref().map(|avc| avc.height), - )), - (TrackCodec::Mp4a, false) => Some((TrackKind::Audio, AUDIO_DIR, None, None)), - (TrackCodec::Mp4a, true) => Some((TrackKind::EncryptedAudio, AUDIO_ENC_DIR, None, None)), - (TrackCodec::Unknown, _) => None, +fn track_layout(track: &DetailedTrackInfo) -> Result { + match track.codec_family { + TrackCodecFamily::Avc => { + let avc = track.summary.avc.as_ref().ok_or_else(|| { + invalid_input(format!( + "track {} is missing the AVC decoder configuration needed for divide playlist signaling.", + track.summary.track_id + )) + })?; + Ok(TrackLayout { + role: DivideTrackRole::Video, + kind: if track.summary.encrypted { + TrackKind::EncryptedVideo + } else { + TrackKind::Video + }, + codecs: format!( + "avc1.{:02x}{:02x}{:02x}", + avc.profile, avc.profile_compatibility, avc.level + ), + audio_channels: None, + width: track.display_width.or(Some(avc.width)), + height: track.display_height.or(Some(avc.height)), + }) + } + TrackCodecFamily::Mp4Audio => { + let mp4a = track.summary.mp4a.as_ref().ok_or_else(|| { + invalid_input(format!( + "track {} is missing the MP4A decoder configuration needed for divide playlist signaling.", + track.summary.track_id + )) + })?; + Ok(TrackLayout { + role: DivideTrackRole::Audio, + kind: if track.summary.encrypted { + TrackKind::EncryptedAudio + } else { + TrackKind::Audio + }, + codecs: mp4a_codec_string(mp4a.object_type_indication, mp4a.audio_object_type), + audio_channels: track + .channel_count + .or(Some(mp4a.channel_count)) + .filter(|value| *value != 0), + width: None, + height: None, + }) + } + _ => Err(invalid_input(format!( + "track {} uses unsupported codec `{}`; {}", + track.summary.track_id, + track_codec_label(track), + supported_scope_message() + ))), } } @@ -332,18 +598,23 @@ fn write_playlists( let mut master = File::create(output_dir.join(PLAYLIST_FILE_NAME))?; writeln!(master, "#EXTM3U")?; if let Some(audio) = audio { - writeln!( + write!( master, - "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"{}/{}\",GROUP-ID=\"audio\",NAME=\"audio\",AUTOSELECT=YES,CHANNELS=\"2\"", + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"{}/{}\",GROUP-ID=\"audio\",NAME=\"audio\",AUTOSELECT=YES", relative_dir(audio.kind), PLAYLIST_FILE_NAME )?; + if let Some(channels) = audio.audio_channels { + write!(master, ",CHANNELS=\"{channels}\"")?; + } + writeln!(master)?; } write!( master, "#EXT-X-STREAM-INF:BANDWIDTH={},CODECS=\"{}\"", - video.bandwidth, MASTER_PLAYLIST_CODECS + video.bandwidth, + master_playlist_codecs(video, audio) )?; if let (Some(width), Some(height)) = (video.width, video.height) { write!(master, ",RESOLUTION={}x{}", width, height)?; @@ -398,6 +669,103 @@ fn segment_file_name(index: usize) -> String { format!("{index}.mp4") } +fn master_playlist_codecs(video: &TrackOutput, audio: Option<&TrackOutput>) -> String { + match audio { + Some(audio) => format!("{},{}", video.codecs, audio.codecs), + None => video.codecs.clone(), + } +} + +fn mp4a_codec_string(object_type_indication: u8, audio_object_type: u8) -> String { + if object_type_indication == 0 { + "mp4a".to_string() + } else if audio_object_type == 0 { + format!("mp4a.{object_type_indication:x}") + } else { + format!("mp4a.{object_type_indication:x}.{audio_object_type}") + } +} + +fn write_validation_report( + writer: &mut W, + report: &DivideValidationReport, +) -> Result<(), DivideError> +where + W: Write, +{ + writeln!(writer, "supported fragmented divide layout")?; + for track in &report.tracks { + writeln!( + writer, + "track {}: role={} codec={} segments={}", + track.track_id, + validation_role_label(track.role), + validation_codec_label(track), + track.segment_count + )?; + } + Ok(()) +} + +fn validation_role_label(role: DivideTrackRole) -> &'static str { + match role { + DivideTrackRole::Video => "video", + DivideTrackRole::Audio => "audio", + } +} + +fn validation_codec_label(track: &DivideValidationTrack) -> String { + track + .original_format + .or(track.sample_entry_type) + .map(|value| value.to_string()) + .unwrap_or_else(|| match track.codec_family { + TrackCodecFamily::Unknown => "unknown".to_string(), + TrackCodecFamily::Avc => "avc".to_string(), + TrackCodecFamily::Hevc => "hevc".to_string(), + TrackCodecFamily::Av1 => "av1".to_string(), + TrackCodecFamily::Vp8 => "vp8".to_string(), + TrackCodecFamily::Vp9 => "vp9".to_string(), + TrackCodecFamily::Mp4Audio => "mp4a".to_string(), + TrackCodecFamily::Opus => "opus".to_string(), + TrackCodecFamily::Ac3 => "ac-3".to_string(), + TrackCodecFamily::Pcm => "pcm".to_string(), + TrackCodecFamily::XmlSubtitle => "stpp".to_string(), + TrackCodecFamily::TextSubtitle => "sbtt".to_string(), + TrackCodecFamily::WebVtt => "wvtt".to_string(), + }) +} + +fn track_codec_label(track: &DetailedTrackInfo) -> String { + track + .original_format + .or(track.sample_entry_type) + .map(|value| value.to_string()) + .unwrap_or_else(|| match track.codec_family { + TrackCodecFamily::Unknown => "unknown".to_string(), + TrackCodecFamily::Avc => "avc".to_string(), + TrackCodecFamily::Hevc => "hevc".to_string(), + TrackCodecFamily::Av1 => "av1".to_string(), + TrackCodecFamily::Vp8 => "vp8".to_string(), + TrackCodecFamily::Vp9 => "vp9".to_string(), + TrackCodecFamily::Mp4Audio => "mp4a".to_string(), + TrackCodecFamily::Opus => "opus".to_string(), + TrackCodecFamily::Ac3 => "ac-3".to_string(), + TrackCodecFamily::Pcm => "pcm".to_string(), + TrackCodecFamily::XmlSubtitle => "stpp".to_string(), + TrackCodecFamily::TextSubtitle => "sbtt".to_string(), + TrackCodecFamily::WebVtt => "wvtt".to_string(), + }) +} + +fn supported_scope_message() -> &'static str { + "divide currently supports fragmented inputs with at most one AVC video track and one MP4A audio track" +} + +fn invalid_input(message: String) -> DivideError { + DivideError::Io(io::Error::new(io::ErrorKind::InvalidInput, message)) +} + fn trak_track_id(reader: &mut R, trak: &BoxInfo) -> Result where R: Read + Seek, @@ -490,7 +858,11 @@ impl fmt::Display for DivideError { Self::MissingTrackId => f.write_str("track id not found"), Self::UnknownTrack(track_id) => write!(f, "unknown track id: {track_id}"), Self::UnexpectedMdat => f.write_str("mdat appeared without a preceding moof"), - Self::NoSupportedTracks => f.write_str("no supported tracks found"), + Self::NoSupportedTracks => write!( + f, + "no supported fragmented tracks found; {}", + supported_scope_message() + ), Self::NumericOverflow => f.write_str("numeric value does not fit in memory"), Self::UsageRequested => f.write_str("usage requested"), } diff --git a/src/cli/dump.rs b/src/cli/dump.rs index b4689c0..250bb3a 100644 --- a/src/cli/dump.rs +++ b/src/cli/dump.rs @@ -9,10 +9,10 @@ use std::io::{self, Read, Seek, Write}; use terminal_size::{Width, terminal_size}; use crate::FourCc; -use crate::codec::CodecError; +use crate::codec::{CodecError, FieldValue}; use crate::header::HeaderError; -use crate::stringify::{StringifyError, stringify}; -use crate::walk::{WalkControl, WalkError, walk_structure}; +use crate::stringify::{StringifyError, collect_structured_fields, stringify}; +use crate::walk::{BoxPath, WalkControl, WalkError, WalkHandle, walk_structure}; use super::util::should_have_no_children; @@ -21,6 +21,128 @@ const FREE: FourCc = FourCc::from_bytes(*b"free"); const MDAT: FourCc = FourCc::from_bytes(*b"mdat"); const SKIP: FourCc = FourCc::from_bytes(*b"skip"); +/// Structured output format supported by the dump command. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StructuredDumpFormat { + /// Pretty-printed JSON output. + Json, + /// Simple YAML output with stable field order. + Yaml, +} + +impl StructuredDumpFormat { + fn parse(value: &str) -> Result, DumpError> { + match value { + "text" => Ok(None), + "json" => Ok(Some(Self::Json)), + "yaml" => Ok(Some(Self::Yaml)), + other => Err(DumpError::InvalidArgument(format!( + "unsupported dump format: {other}" + ))), + } + } +} + +/// Structured payload state recorded for one dumped box. +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(rename_all = "snake_case") +)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum DumpPayloadStatus { + /// The box payload rendered as a descriptor-backed summary string. + Summary, + /// The box payload was empty or has no visible summary fields. + #[default] + Empty, + /// Raw payload bytes were included in the structured report. + Bytes, + /// Payload bytes were intentionally omitted until `-full` or `-a` is requested. + Omitted, + /// The box type is known, but the encoded version is not currently supported. + UnsupportedVersion, +} + +/// Top-level structured dump report used by JSON and YAML tree export. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct StructuredDumpReport { + /// Top-level boxes in file order. + pub boxes: Vec, +} + +/// One node in the structured dump tree. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct StructuredDumpBoxReport { + /// Four-character box identifier. + pub box_type: String, + /// Slash-delimited path from the file root to this box. + pub path: String, + /// Absolute file offset of the box header. + pub offset: u64, + /// Total box size including the header. + pub size: u64, + /// Whether the current box type is registered in the active lookup context. + pub supported: bool, + /// Summary of how payload detail is represented in this report node. + pub payload_status: DumpPayloadStatus, + /// Descriptor-backed payload summary when one was rendered. + pub payload_summary: Option, + /// Raw payload bytes when full raw expansion was requested. + pub payload_bytes: Option>, + /// Direct child boxes in file order. + pub children: Vec, +} + +/// Additive structured dump report that includes field-level payload data for supported boxes. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct FieldStructuredDumpReport { + /// Top-level boxes in file order. + pub boxes: Vec, +} + +/// One node in the field-level structured dump tree. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct FieldStructuredDumpBoxReport { + /// Four-character box identifier. + pub box_type: String, + /// Slash-delimited path from the file root to this box. + pub path: String, + /// Absolute file offset of the box header. + pub offset: u64, + /// Total box size including the header. + pub size: u64, + /// Whether the current box type is registered in the active lookup context. + pub supported: bool, + /// Summary of how payload detail is represented in this report node. + pub payload_status: DumpPayloadStatus, + /// Deterministic field-level payload data for supported boxes when it is available. + pub payload_fields: Vec, + /// Descriptor-backed payload summary when one was rendered. + pub payload_summary: Option, + /// Raw payload bytes when full raw expansion was requested. + pub payload_bytes: Option>, + /// Direct child boxes in file order. + pub children: Vec, +} + +/// One field entry in the field-level structured dump payload report. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct StructuredDumpFieldReport { + /// Stable field name from the active descriptor table. + pub name: String, + /// Machine-readable field value. + pub value: FieldValue, + /// Optional human-oriented display projection when the field uses a display override or + /// non-default formatting. + pub display_value: Option, +} + /// Formatting controls for the dump command. #[derive(Clone, Debug, PartialEq, Eq)] pub struct DumpOptions { @@ -96,6 +218,14 @@ where writer, " -a Show full content for supported boxes" )?; + writeln!( + writer, + " -format Output format (default: text)" + )?; + writeln!( + writer, + " -path Dump only matched parsed subtrees (repeatable)" + )?; writeln!( writer, " -mdat Deprecated shorthand for -full mdat" @@ -118,14 +248,39 @@ pub fn dump_reader( options: &DumpOptions, writer: &mut W, ) -> Result<(), DumpError> +where + R: Read + Seek, + W: Write, +{ + dump_reader_paths(reader, options, &[], writer) +} + +/// Dumps only the subtrees that match any parsed `paths` using the provided formatting +/// `options`. +/// +/// Paths use the existing [`BoxPath`] parser, including slash-delimited segments, `*` wildcards, +/// and the `` marker. Matching roots become the top-level boxes in the rendered text view, +/// so descendants are indented relative to the selected subtree instead of the original file root. +/// When `paths` is empty, this behaves the same as [`dump_reader`]. +pub fn dump_reader_paths( + reader: &mut R, + options: &DumpOptions, + paths: &[BoxPath], + writer: &mut W, +) -> Result<(), DumpError> where R: Read + Seek, W: Write, { let mut dump_error = None; let result = walk_structure(reader, |handle| { + let selection = match_dump_paths(paths, handle.path()); + if !selection.include { + return continue_dump_search(handle, selection.descend); + } + let info = *handle.info(); - let mut line = " ".repeat(handle.path().len().saturating_sub(1) * 2); + let mut line = " ".repeat(selection.relative_depth(handle.path()).unwrap_or(0) * 2); line.push('['); line.push_str(&info.box_type().to_string()); line.push(']'); @@ -225,15 +380,238 @@ where Ok(()) } +/// Builds a structured dump tree from one MP4 reader using the provided formatting `options`. +pub fn build_structured_report( + reader: &mut R, + options: &DumpOptions, +) -> Result +where + R: Read + Seek, +{ + build_structured_report_paths(reader, options, &[]) +} + +/// Builds a structured dump tree from only the subtrees that match any parsed `paths`. +/// +/// Matching roots become the top-level boxes in the returned report while each node keeps its +/// original full file path. When `paths` is empty, this behaves the same as +/// [`build_structured_report`]. +pub fn build_structured_report_paths( + reader: &mut R, + options: &DumpOptions, + paths: &[BoxPath], +) -> Result +where + R: Read + Seek, +{ + let mut roots = Vec::new(); + let mut stack = Vec::new(); + let mut dump_error = None; + + let result = walk_structure(reader, |handle| { + let selection = match_dump_paths(paths, handle.path()); + if !selection.include { + return continue_dump_search(handle, selection.descend); + } + + finalize_completed_boxes( + selection.relative_depth(handle.path()).unwrap_or(0), + &mut stack, + &mut roots, + ); + let (node, control) = build_structured_box_report(handle, options, &mut dump_error)?; + stack.push(node); + Ok(control) + }); + + if let Some(error) = dump_error { + return Err(error); + } + result?; + finalize_completed_boxes(0, &mut stack, &mut roots); + + Ok(StructuredDumpReport { boxes: roots }) +} + +/// Builds an additive field-level structured dump tree from one MP4 reader using the provided +/// formatting `options`. +pub fn build_field_structured_report( + reader: &mut R, + options: &DumpOptions, +) -> Result +where + R: Read + Seek, +{ + build_field_structured_report_paths(reader, options, &[]) +} + +/// Builds an additive field-level structured dump tree from only the subtrees that match any +/// parsed `paths`. +/// +/// Matching roots become the top-level boxes in the returned report while each node keeps its +/// original full file path. When `paths` is empty, this behaves the same as +/// [`build_field_structured_report`]. +pub fn build_field_structured_report_paths( + reader: &mut R, + options: &DumpOptions, + paths: &[BoxPath], +) -> Result +where + R: Read + Seek, +{ + let mut roots = Vec::new(); + let mut stack = Vec::new(); + let mut dump_error = None; + + let result = walk_structure(reader, |handle| { + let selection = match_dump_paths(paths, handle.path()); + if !selection.include { + return continue_dump_search(handle, selection.descend); + } + + finalize_completed_field_boxes( + selection.relative_depth(handle.path()).unwrap_or(0), + &mut stack, + &mut roots, + ); + let (node, control) = build_field_structured_box_report(handle, options, &mut dump_error)?; + stack.push(node); + Ok(control) + }); + + if let Some(error) = dump_error { + return Err(error); + } + result?; + finalize_completed_field_boxes(0, &mut stack, &mut roots); + + Ok(FieldStructuredDumpReport { boxes: roots }) +} + +/// Writes a structured dump `report` in the selected `format`. +pub fn write_structured_report( + writer: &mut W, + report: &StructuredDumpReport, + format: StructuredDumpFormat, +) -> Result<(), DumpError> +where + W: Write, +{ + match format { + StructuredDumpFormat::Json => { + write_json_structured_report(writer, report).map_err(DumpError::Io) + } + StructuredDumpFormat::Yaml => { + write_yaml_structured_report(writer, report).map_err(DumpError::Io) + } + } +} + +/// Writes a field-level structured dump `report` in the selected `format`. +pub fn write_field_structured_report( + writer: &mut W, + report: &FieldStructuredDumpReport, + format: StructuredDumpFormat, +) -> Result<(), DumpError> +where + W: Write, +{ + match format { + StructuredDumpFormat::Json => { + write_json_field_structured_report(writer, report).map_err(DumpError::Io) + } + StructuredDumpFormat::Yaml => { + write_yaml_field_structured_report(writer, report).map_err(DumpError::Io) + } + } +} + +/// Dumps one MP4 reader as a structured JSON or YAML tree using the provided `options`. +pub fn dump_reader_structured( + reader: &mut R, + options: &DumpOptions, + format: StructuredDumpFormat, + writer: &mut W, +) -> Result<(), DumpError> +where + R: Read + Seek, + W: Write, +{ + dump_reader_structured_paths(reader, options, &[], format, writer) +} + +/// Dumps only the subtrees that match any parsed `paths` as a structured JSON or YAML tree. +/// +/// When `paths` is empty, this behaves the same as [`dump_reader_structured`]. +pub fn dump_reader_structured_paths( + reader: &mut R, + options: &DumpOptions, + paths: &[BoxPath], + format: StructuredDumpFormat, + writer: &mut W, +) -> Result<(), DumpError> +where + R: Read + Seek, + W: Write, +{ + let report = build_structured_report_paths(reader, options, paths)?; + write_structured_report(writer, &report, format) +} + +/// Dumps one MP4 reader as an additive field-level structured JSON or YAML tree using the +/// provided `options`. +pub fn dump_reader_field_structured( + reader: &mut R, + options: &DumpOptions, + format: StructuredDumpFormat, + writer: &mut W, +) -> Result<(), DumpError> +where + R: Read + Seek, + W: Write, +{ + dump_reader_field_structured_paths(reader, options, &[], format, writer) +} + +/// Dumps only the subtrees that match any parsed `paths` as an additive field-level structured +/// JSON or YAML tree. +/// +/// When `paths` is empty, this behaves the same as [`dump_reader_field_structured`]. +pub fn dump_reader_field_structured_paths( + reader: &mut R, + options: &DumpOptions, + paths: &[BoxPath], + format: StructuredDumpFormat, + writer: &mut W, +) -> Result<(), DumpError> +where + R: Read + Seek, + W: Write, +{ + let report = build_field_structured_report_paths(reader, options, paths)?; + write_field_structured_report(writer, &report, format) +} + fn run_inner(args: &[String], stdout: &mut W) -> Result<(), DumpError> where W: Write, { let mut options = DumpOptions::default(); + let mut format = None; + let mut paths = Vec::new(); let mut input_path = None; let mut index = 0usize; while index < args.len() { match args[index].as_str() { + "-format" | "--format" => { + let Some(value) = args.get(index + 1) else { + return Err(DumpError::InvalidArgument( + "missing value for -format".to_string(), + )); + }; + format = StructuredDumpFormat::parse(value)?; + index += 2; + } "-full" | "--full" => { let Some(value) = args.get(index + 1) else { return Err(DumpError::InvalidArgument( @@ -243,6 +621,18 @@ where parse_full_box_types(value, &mut options.full_box_types)?; index += 2; } + "-path" | "--path" => { + let Some(value) = args.get(index + 1) else { + return Err(DumpError::InvalidArgument( + "missing value for -path".to_string(), + )); + }; + let path = BoxPath::parse(value).map_err(|error| { + DumpError::InvalidArgument(format!("invalid box path: {error}")) + })?; + paths.push(path); + index += 2; + } "-a" | "--a" => { options.show_all = true; index += 1; @@ -287,7 +677,291 @@ where }; let mut file = File::open(input_path)?; - dump_reader(&mut file, &options, stdout) + match format { + Some(format) => { + dump_reader_field_structured_paths(&mut file, &options, &paths, format, stdout) + } + None => dump_reader_paths(&mut file, &options, &paths, stdout), + } +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +struct DumpPathMatch { + include: bool, + descend: bool, + display_depth_base: Option, +} + +impl DumpPathMatch { + fn relative_depth(self, path: &BoxPath) -> Option { + self.display_depth_base + .map(|base| path.len().saturating_sub(base)) + } +} + +fn match_dump_paths(paths: &[BoxPath], current: &BoxPath) -> DumpPathMatch { + if paths.is_empty() { + return DumpPathMatch { + include: true, + descend: true, + display_depth_base: Some(1), + }; + } + + let mut matched = DumpPathMatch::default(); + for path in paths { + let current_vs_selected = current.compare_with(path); + if current_vs_selected.forward_match { + matched.descend = true; + } + + let selected_vs_current = path.compare_with(current); + if selected_vs_current.exact_match || selected_vs_current.forward_match { + matched.include = true; + matched.descend = true; + let display_depth_base = dump_display_depth_base(path); + matched.display_depth_base = Some( + matched + .display_depth_base + .map_or(display_depth_base, |base| base.min(display_depth_base)), + ); + } + } + + matched +} + +fn dump_display_depth_base(path: &BoxPath) -> usize { + if path.is_empty() { 1 } else { path.len() } +} + +fn continue_dump_search( + handle: &mut WalkHandle<'_, R>, + should_descend: bool, +) -> Result +where + R: Read + Seek, +{ + if !should_descend { + return Ok(WalkControl::Continue); + } + + if !handle.is_supported_type() { + return Ok(WalkControl::Continue); + } + + if handle.info().payload_size()? >= 256 && should_have_no_children(handle.info().box_type()) { + return Ok(WalkControl::Continue); + } + + match handle.read_payload() { + Ok(_) => Ok(WalkControl::Descend), + Err(WalkError::Codec(CodecError::UnsupportedVersion { .. })) => Ok(WalkControl::Continue), + Err(error) => Err(error), + } +} + +fn build_structured_box_report( + handle: &mut WalkHandle<'_, R>, + options: &DumpOptions, + dump_error: &mut Option, +) -> Result<(StructuredDumpBoxReport, WalkControl), WalkError> +where + R: Read + Seek, +{ + let info = *handle.info(); + let box_type = info.box_type(); + let is_full = options.show_all || options.is_full(box_type); + let mut node = StructuredDumpBoxReport { + box_type: box_type.to_string(), + path: handle.path().to_string(), + offset: info.offset(), + size: info.size(), + supported: handle.is_supported_type(), + payload_status: DumpPayloadStatus::Empty, + payload_summary: None, + payload_bytes: None, + children: Vec::new(), + }; + + if !is_full && matches!(box_type, MDAT | FREE | SKIP) { + node.payload_status = DumpPayloadStatus::Omitted; + return Ok((node, WalkControl::Continue)); + } + + if handle.is_supported_type() { + if !is_full && info.payload_size()? >= 64 && should_have_no_children(box_type) { + node.payload_status = DumpPayloadStatus::Omitted; + return Ok((node, WalkControl::Continue)); + } + + match handle.read_payload() { + Ok((payload, _)) => { + let rendered = match stringify(payload.as_ref(), None) { + Ok(rendered) => rendered, + Err(error) => { + *dump_error = Some(error.into()); + return Err(io::Error::other("dump stringify failed").into()); + } + }; + if rendered.is_empty() { + node.payload_status = DumpPayloadStatus::Empty; + } else { + node.payload_status = DumpPayloadStatus::Summary; + node.payload_summary = Some(rendered); + } + return Ok((node, WalkControl::Descend)); + } + Err(WalkError::Codec(CodecError::UnsupportedVersion { .. })) => { + node.payload_status = DumpPayloadStatus::UnsupportedVersion; + } + Err(error) => return Err(error), + } + } + + if is_full { + let capacity = match usize::try_from(info.payload_size()?) { + Ok(capacity) => capacity, + Err(_) => { + *dump_error = Some(DumpError::NumericOverflow); + return Err(io::Error::other("dump payload too large").into()); + } + }; + let mut bytes = Vec::with_capacity(capacity); + handle.read_data(&mut bytes)?; + if !matches!(node.payload_status, DumpPayloadStatus::UnsupportedVersion) { + node.payload_status = DumpPayloadStatus::Bytes; + } + node.payload_bytes = Some(bytes); + } else if !matches!(node.payload_status, DumpPayloadStatus::UnsupportedVersion) { + node.payload_status = DumpPayloadStatus::Omitted; + } + + Ok((node, WalkControl::Continue)) +} + +fn build_field_structured_box_report( + handle: &mut WalkHandle<'_, R>, + options: &DumpOptions, + dump_error: &mut Option, +) -> Result<(FieldStructuredDumpBoxReport, WalkControl), WalkError> +where + R: Read + Seek, +{ + let info = *handle.info(); + let box_type = info.box_type(); + let is_full = options.show_all || options.is_full(box_type); + let mut node = FieldStructuredDumpBoxReport { + box_type: box_type.to_string(), + path: handle.path().to_string(), + offset: info.offset(), + size: info.size(), + supported: handle.is_supported_type(), + payload_status: DumpPayloadStatus::Empty, + payload_fields: Vec::new(), + payload_summary: None, + payload_bytes: None, + children: Vec::new(), + }; + + if !is_full && matches!(box_type, MDAT | FREE | SKIP) { + node.payload_status = DumpPayloadStatus::Omitted; + return Ok((node, WalkControl::Continue)); + } + + if handle.is_supported_type() { + match handle.read_payload() { + Ok((payload, _)) => { + let rendered = match stringify(payload.as_ref(), None) { + Ok(rendered) => rendered, + Err(error) => { + *dump_error = Some(error.into()); + return Err(io::Error::other("dump stringify failed").into()); + } + }; + let fields = match collect_structured_fields(payload.as_ref(), None) { + Ok(fields) => fields, + Err(error) => { + *dump_error = Some(error.into()); + return Err(io::Error::other("dump field collection failed").into()); + } + }; + node.payload_fields = fields + .into_iter() + .map(|field| StructuredDumpFieldReport { + name: field.name.to_string(), + value: field.value, + display_value: field.include_display_value.then_some(field.rendered_value), + }) + .collect(); + if rendered.is_empty() { + node.payload_status = if node.payload_fields.is_empty() { + DumpPayloadStatus::Empty + } else { + DumpPayloadStatus::Summary + }; + } else { + node.payload_status = DumpPayloadStatus::Summary; + node.payload_summary = Some(rendered); + } + return Ok((node, WalkControl::Descend)); + } + Err(WalkError::Codec(CodecError::UnsupportedVersion { .. })) => { + node.payload_status = DumpPayloadStatus::UnsupportedVersion; + } + Err(error) => return Err(error), + } + } + + if is_full { + let capacity = match usize::try_from(info.payload_size()?) { + Ok(capacity) => capacity, + Err(_) => { + *dump_error = Some(DumpError::NumericOverflow); + return Err(io::Error::other("dump payload too large").into()); + } + }; + let mut bytes = Vec::with_capacity(capacity); + handle.read_data(&mut bytes)?; + if !matches!(node.payload_status, DumpPayloadStatus::UnsupportedVersion) { + node.payload_status = DumpPayloadStatus::Bytes; + } + node.payload_bytes = Some(bytes); + } else if !matches!(node.payload_status, DumpPayloadStatus::UnsupportedVersion) { + node.payload_status = DumpPayloadStatus::Omitted; + } + + Ok((node, WalkControl::Continue)) +} + +fn finalize_completed_boxes( + depth: usize, + stack: &mut Vec, + roots: &mut Vec, +) { + while stack.len() > depth { + let node = stack.pop().expect("stack length checked before pop"); + if let Some(parent) = stack.last_mut() { + parent.children.push(node); + } else { + roots.push(node); + } + } +} + +fn finalize_completed_field_boxes( + depth: usize, + stack: &mut Vec, + roots: &mut Vec, +) { + while stack.len() > depth { + let node = stack.pop().expect("stack length checked before pop"); + if let Some(parent) = stack.last_mut() { + parent.children.push(node); + } else { + roots.push(node); + } + } } fn parse_full_box_types(value: &str, dst: &mut BTreeSet) -> Result<(), DumpError> { @@ -316,6 +990,668 @@ fn render_hex_bytes(bytes: &[u8]) -> String { .join(" ") } +fn write_json_structured_report(writer: &mut W, report: &StructuredDumpReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "{{")?; + writeln!(writer, " \"Boxes\": [")?; + for (index, entry) in report.boxes.iter().enumerate() { + write_json_structured_box(writer, entry, 2)?; + let trailing = if index + 1 == report.boxes.len() { + "" + } else { + "," + }; + writeln!(writer, "{trailing}")?; + } + writeln!(writer, " ]")?; + writeln!(writer, "}}") +} + +fn write_json_structured_box( + writer: &mut W, + entry: &StructuredDumpBoxReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + writeln!(writer, "{indent}{{")?; + write_json_field( + writer, + indent_level + 1, + "BoxType", + &json_string(&entry.box_type), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Path", + &json_string(&entry.path), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Offset", + &entry.offset.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Size", + &entry.size.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Supported", + if entry.supported { "true" } else { "false" }, + true, + )?; + write_json_field( + writer, + indent_level + 1, + "PayloadStatus", + &json_string(payload_status_name(entry.payload_status)), + true, + )?; + if let Some(summary) = entry.payload_summary.as_ref() { + write_json_field( + writer, + indent_level + 1, + "PayloadSummary", + &json_string(summary), + true, + )?; + } + if let Some(bytes) = entry.payload_bytes.as_ref() { + write_json_u8_array_field(writer, indent_level + 1, "PayloadBytes", bytes, true)?; + } + + writeln!(writer, "{}\"Children\": [", " ".repeat(indent_level + 1))?; + for (index, child) in entry.children.iter().enumerate() { + write_json_structured_box(writer, child, indent_level + 2)?; + let trailing = if index + 1 == entry.children.len() { + "" + } else { + "," + }; + writeln!(writer, "{trailing}")?; + } + writeln!(writer, "{}]", " ".repeat(indent_level + 1))?; + write!(writer, "{indent}}}") +} + +fn write_json_u8_array_field( + writer: &mut W, + indent_level: usize, + name: &str, + values: &[u8], + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + write_json_array_field( + writer, + indent_level, + name, + &values.iter().map(u8::to_string).collect::>(), + trailing_comma, + ) +} + +fn write_json_array_field( + writer: &mut W, + indent_level: usize, + name: &str, + values: &[String], + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + let trailing = if trailing_comma { "," } else { "" }; + writeln!(writer, "{}\"{name}\": [", " ".repeat(indent_level))?; + for (index, value) in values.iter().enumerate() { + let trailing_value = if index + 1 == values.len() { "" } else { "," }; + writeln!( + writer, + "{}{value}{trailing_value}", + " ".repeat(indent_level + 1) + )?; + } + writeln!(writer, "{}]{trailing}", " ".repeat(indent_level)) +} + +fn write_json_field( + writer: &mut W, + indent_level: usize, + name: &str, + value: &str, + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + let trailing = if trailing_comma { "," } else { "" }; + writeln!( + writer, + "{}\"{name}\": {value}{trailing}", + " ".repeat(indent_level) + ) +} + +fn write_yaml_structured_report(writer: &mut W, report: &StructuredDumpReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "boxes:")?; + for entry in &report.boxes { + write_yaml_structured_box(writer, entry, 0)?; + } + Ok(()) +} + +fn write_yaml_structured_box( + writer: &mut W, + entry: &StructuredDumpBoxReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + let child_indent = " ".repeat(indent_level + 1); + + writeln!( + writer, + "{indent}- box_type: {}", + yaml_string(&entry.box_type) + )?; + writeln!(writer, "{child_indent}path: {}", yaml_string(&entry.path))?; + writeln!(writer, "{child_indent}offset: {}", entry.offset)?; + writeln!(writer, "{child_indent}size: {}", entry.size)?; + writeln!(writer, "{child_indent}supported: {}", entry.supported)?; + writeln!( + writer, + "{child_indent}payload_status: {}", + yaml_string(payload_status_name(entry.payload_status)) + )?; + if let Some(summary) = entry.payload_summary.as_ref() { + writeln!( + writer, + "{child_indent}payload_summary: {}", + yaml_string(summary) + )?; + } + if let Some(bytes) = entry.payload_bytes.as_ref() { + writeln!(writer, "{child_indent}payload_bytes:")?; + for value in bytes { + writeln!(writer, "{}- {value}", " ".repeat(indent_level + 2))?; + } + } + if entry.children.is_empty() { + writeln!(writer, "{child_indent}children: []")?; + } else { + writeln!(writer, "{child_indent}children:")?; + for child in &entry.children { + write_yaml_structured_box(writer, child, indent_level + 1)?; + } + } + Ok(()) +} + +fn write_json_field_structured_report( + writer: &mut W, + report: &FieldStructuredDumpReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "{{")?; + writeln!(writer, " \"Boxes\": [")?; + for (index, entry) in report.boxes.iter().enumerate() { + write_json_field_structured_box(writer, entry, 2)?; + let trailing = if index + 1 == report.boxes.len() { + "" + } else { + "," + }; + writeln!(writer, "{trailing}")?; + } + writeln!(writer, " ]")?; + writeln!(writer, "}}") +} + +fn write_json_field_structured_box( + writer: &mut W, + entry: &FieldStructuredDumpBoxReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + writeln!(writer, "{indent}{{")?; + write_json_field( + writer, + indent_level + 1, + "BoxType", + &json_string(&entry.box_type), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Path", + &json_string(&entry.path), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Offset", + &entry.offset.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Size", + &entry.size.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Supported", + if entry.supported { "true" } else { "false" }, + true, + )?; + write_json_field( + writer, + indent_level + 1, + "PayloadStatus", + &json_string(payload_status_name(entry.payload_status)), + true, + )?; + write_json_payload_fields(writer, indent_level + 1, &entry.payload_fields, true)?; + if let Some(summary) = entry.payload_summary.as_ref() { + write_json_field( + writer, + indent_level + 1, + "PayloadSummary", + &json_string(summary), + true, + )?; + } + if let Some(bytes) = entry.payload_bytes.as_ref() { + write_json_u8_array_field(writer, indent_level + 1, "PayloadBytes", bytes, true)?; + } + + writeln!(writer, "{}\"Children\": [", " ".repeat(indent_level + 1))?; + for (index, child) in entry.children.iter().enumerate() { + write_json_field_structured_box(writer, child, indent_level + 2)?; + let trailing = if index + 1 == entry.children.len() { + "" + } else { + "," + }; + writeln!(writer, "{trailing}")?; + } + writeln!(writer, "{}]", " ".repeat(indent_level + 1))?; + write!(writer, "{indent}}}") +} + +fn write_json_payload_fields( + writer: &mut W, + indent_level: usize, + fields: &[StructuredDumpFieldReport], + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + let trailing = if trailing_comma { "," } else { "" }; + writeln!(writer, "{}\"PayloadFields\": [", " ".repeat(indent_level))?; + for (index, field) in fields.iter().enumerate() { + write_json_payload_field(writer, field, indent_level + 1)?; + let trailing_field = if index + 1 == fields.len() { "" } else { "," }; + writeln!(writer, "{trailing_field}")?; + } + writeln!(writer, "{}]{trailing}", " ".repeat(indent_level)) +} + +fn write_json_payload_field( + writer: &mut W, + field: &StructuredDumpFieldReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + writeln!(writer, "{indent}{{")?; + write_json_field( + writer, + indent_level + 1, + "Name", + &json_string(&field.name), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "ValueKind", + &json_string(structured_field_value_kind_name(&field.value)), + true, + )?; + write_json_dump_field_value( + writer, + indent_level + 1, + "Value", + &field.value, + field.display_value.is_some(), + )?; + if let Some(display_value) = field.display_value.as_ref() { + write_json_field( + writer, + indent_level + 1, + "DisplayValue", + &json_string(display_value), + false, + )?; + } + write!(writer, "{indent}}}") +} + +fn write_json_dump_field_value( + writer: &mut W, + indent_level: usize, + name: &str, + value: &FieldValue, + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + match value { + FieldValue::Unsigned(value) => write_json_field( + writer, + indent_level, + name, + &value.to_string(), + trailing_comma, + ), + FieldValue::Signed(value) => write_json_field( + writer, + indent_level, + name, + &value.to_string(), + trailing_comma, + ), + FieldValue::Boolean(value) => write_json_field( + writer, + indent_level, + name, + if *value { "true" } else { "false" }, + trailing_comma, + ), + FieldValue::String(value) => write_json_field( + writer, + indent_level, + name, + &json_string(value), + trailing_comma, + ), + FieldValue::Bytes(values) => { + write_json_u8_array_field(writer, indent_level, name, values, trailing_comma) + } + FieldValue::UnsignedArray(values) => write_json_array_field( + writer, + indent_level, + name, + &values.iter().map(u64::to_string).collect::>(), + trailing_comma, + ), + FieldValue::SignedArray(values) => write_json_array_field( + writer, + indent_level, + name, + &values.iter().map(i64::to_string).collect::>(), + trailing_comma, + ), + FieldValue::BooleanArray(values) => write_json_array_field( + writer, + indent_level, + name, + &values.iter().map(bool::to_string).collect::>(), + trailing_comma, + ), + } +} + +fn write_yaml_field_structured_report( + writer: &mut W, + report: &FieldStructuredDumpReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "boxes:")?; + for entry in &report.boxes { + write_yaml_field_structured_box(writer, entry, 0)?; + } + Ok(()) +} + +fn write_yaml_field_structured_box( + writer: &mut W, + entry: &FieldStructuredDumpBoxReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + let child_indent = " ".repeat(indent_level + 1); + + writeln!( + writer, + "{indent}- box_type: {}", + yaml_string(&entry.box_type) + )?; + writeln!(writer, "{child_indent}path: {}", yaml_string(&entry.path))?; + writeln!(writer, "{child_indent}offset: {}", entry.offset)?; + writeln!(writer, "{child_indent}size: {}", entry.size)?; + writeln!(writer, "{child_indent}supported: {}", entry.supported)?; + writeln!( + writer, + "{child_indent}payload_status: {}", + yaml_string(payload_status_name(entry.payload_status)) + )?; + if entry.payload_fields.is_empty() { + writeln!(writer, "{child_indent}payload_fields: []")?; + } else { + writeln!(writer, "{child_indent}payload_fields:")?; + for field in &entry.payload_fields { + write_yaml_payload_field(writer, field, indent_level + 1)?; + } + } + if let Some(summary) = entry.payload_summary.as_ref() { + writeln!( + writer, + "{child_indent}payload_summary: {}", + yaml_string(summary) + )?; + } + if let Some(bytes) = entry.payload_bytes.as_ref() { + writeln!(writer, "{child_indent}payload_bytes:")?; + for value in bytes { + writeln!(writer, "{}- {value}", " ".repeat(indent_level + 2))?; + } + } + if entry.children.is_empty() { + writeln!(writer, "{child_indent}children: []")?; + } else { + writeln!(writer, "{child_indent}children:")?; + for child in &entry.children { + write_yaml_field_structured_box(writer, child, indent_level + 1)?; + } + } + Ok(()) +} + +fn write_yaml_payload_field( + writer: &mut W, + field: &StructuredDumpFieldReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level + 1); + let child_indent = " ".repeat(indent_level + 2); + writeln!(writer, "{indent}- name: {}", yaml_string(&field.name))?; + writeln!( + writer, + "{child_indent}value_kind: {}", + yaml_string(structured_field_value_kind_name(&field.value)) + )?; + write_yaml_dump_field_value(writer, indent_level + 2, "value", &field.value)?; + if let Some(display_value) = field.display_value.as_ref() { + writeln!( + writer, + "{child_indent}display_value: {}", + yaml_string(display_value) + )?; + } + Ok(()) +} + +fn write_yaml_dump_field_value( + writer: &mut W, + indent_level: usize, + name: &str, + value: &FieldValue, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + let child_indent = " ".repeat(indent_level + 1); + match value { + FieldValue::Unsigned(value) => writeln!(writer, "{indent}{name}: {value}"), + FieldValue::Signed(value) => writeln!(writer, "{indent}{name}: {value}"), + FieldValue::Boolean(value) => writeln!(writer, "{indent}{name}: {value}"), + FieldValue::String(value) => writeln!(writer, "{indent}{name}: {}", yaml_string(value)), + FieldValue::Bytes(values) => { + if values.is_empty() { + writeln!(writer, "{indent}{name}: []") + } else { + writeln!(writer, "{indent}{name}:")?; + for value in values { + writeln!(writer, "{child_indent}- {value}")?; + } + Ok(()) + } + } + FieldValue::UnsignedArray(values) => { + if values.is_empty() { + writeln!(writer, "{indent}{name}: []") + } else { + writeln!(writer, "{indent}{name}:")?; + for value in values { + writeln!(writer, "{child_indent}- {value}")?; + } + Ok(()) + } + } + FieldValue::SignedArray(values) => { + if values.is_empty() { + writeln!(writer, "{indent}{name}: []") + } else { + writeln!(writer, "{indent}{name}:")?; + for value in values { + writeln!(writer, "{child_indent}- {value}")?; + } + Ok(()) + } + } + FieldValue::BooleanArray(values) => { + if values.is_empty() { + writeln!(writer, "{indent}{name}: []") + } else { + writeln!(writer, "{indent}{name}:")?; + for value in values { + writeln!(writer, "{child_indent}- {value}")?; + } + Ok(()) + } + } + } +} + +fn payload_status_name(status: DumpPayloadStatus) -> &'static str { + match status { + DumpPayloadStatus::Summary => "summary", + DumpPayloadStatus::Empty => "empty", + DumpPayloadStatus::Bytes => "bytes", + DumpPayloadStatus::Omitted => "omitted", + DumpPayloadStatus::UnsupportedVersion => "unsupported_version", + } +} + +fn structured_field_value_kind_name(value: &FieldValue) -> &'static str { + match value { + FieldValue::Unsigned(_) => "unsigned", + FieldValue::Signed(_) => "signed", + FieldValue::Boolean(_) => "boolean", + FieldValue::Bytes(_) => "bytes", + FieldValue::String(_) => "string", + FieldValue::UnsignedArray(_) => "unsigned_array", + FieldValue::SignedArray(_) => "signed_array", + FieldValue::BooleanArray(_) => "boolean_array", + } +} + +fn json_string(value: &str) -> String { + let mut escaped = String::from("\""); + for ch in value.chars() { + match ch { + '"' => escaped.push_str("\\\""), + '\\' => escaped.push_str("\\\\"), + '\n' => escaped.push_str("\\n"), + '\r' => escaped.push_str("\\r"), + '\t' => escaped.push_str("\\t"), + ch if ch.is_control() => escaped.push_str(&format!("\\u{:04x}", ch as u32)), + ch => escaped.push(ch), + } + } + escaped.push('"'); + escaped +} + +fn yaml_string(value: &str) -> String { + if !value.is_empty() + && value.trim() == value + && value + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '.' | '-' | '_' | '/' | ' ')) + { + value.to_string() + } else { + format!("'{}'", value.replace('\'', "''")) + } +} + /// Errors raised while parsing dump arguments or rendering dump output. #[derive(Debug)] pub enum DumpError { diff --git a/src/cli/edit.rs b/src/cli/edit.rs index d1d3cbe..a522fa0 100644 --- a/src/cli/edit.rs +++ b/src/cli/edit.rs @@ -4,7 +4,7 @@ use std::collections::BTreeSet; use std::error::Error; use std::fmt; use std::fs::File; -use std::io::{self, Read, Seek, SeekFrom, Write}; +use std::io::{self, Cursor, Read, Seek, SeekFrom, Write}; use crate::FourCc; use crate::boxes::iso14496_12::{Ftyp, Tfdt}; @@ -13,7 +13,10 @@ use crate::boxes::{BoxLookupContext, BoxRegistry, default_registry}; use crate::codec::{ CodecError, DynCodecBox, ImmutableBox, marshal_dyn, unmarshal, unmarshal_any_with_context, }; +use crate::extract::{ExtractError, extract_boxes_as}; use crate::header::{BoxInfo, HeaderError, SMALL_HEADER_SIZE}; +use crate::rewrite::{RewriteError, rewrite_boxes_as}; +use crate::walk::{BoxPath, WalkError}; use crate::writer::{Writer, WriterError}; const FTYP: FourCc = FourCc::from_bytes(*b"ftyp"); @@ -37,7 +40,7 @@ where { match run_inner(args) { Ok(()) => 0, - Err(EditError::UsageRequested) => { + Err(EditCliError::UsageRequested) => { let _ = write_usage(stderr); 1 } @@ -63,6 +66,10 @@ where writer, " -base_media_decode_time Replace tfdt base media decode times" )?; + writeln!( + writer, + " -path Limit supported typed rewrites to parsed slash-delimited box paths" + )?; writeln!( writer, " -drop Drop boxes by fourcc" @@ -95,34 +102,61 @@ where Ok(()) } -fn run_inner(args: &[String]) -> Result<(), EditError> { - let (options, input_path, output_path) = parse_args(args)?; - let mut input = File::open(input_path)?; - let output = File::create(output_path)?; - edit_reader(&mut input, output, &options) +fn run_inner(args: &[String]) -> Result<(), EditCliError> { + let parsed = parse_args(args)?; + let mut input = File::open(parsed.input_path)?; + let output = File::create(parsed.output_path)?; + if parsed.paths.is_empty() { + return edit_reader(&mut input, output, &parsed.options).map_err(EditCliError::Edit); + } + + edit_reader_scoped_paths(&mut input, output, &parsed.options, &parsed.paths) +} + +#[derive(Debug)] +struct ParsedEditArgs<'a> { + options: EditOptions, + paths: Vec, + input_path: &'a str, + output_path: &'a str, } -fn parse_args(args: &[String]) -> Result<(EditOptions, &str, &str), EditError> { +fn parse_args(args: &[String]) -> Result, EditCliError> { let mut options = EditOptions::default(); + let mut paths = Vec::new(); let mut positional = Vec::new(); let mut index = 0usize; while index < args.len() { match args[index].as_str() { "-base_media_decode_time" | "--base_media_decode_time" => { let Some(value) = args.get(index + 1) else { - return Err(EditError::InvalidArgument( + return Err(EditCliError::InvalidArgument( "missing value for -base_media_decode_time".to_string(), )); }; let value = value.parse::().map_err(|_| { - EditError::InvalidArgument(format!("invalid base media decode time: {value}")) + EditCliError::InvalidArgument(format!( + "invalid base media decode time: {value}" + )) })?; options.base_media_decode_time = Some(value); index += 2; } + "-path" | "--path" => { + let Some(value) = args.get(index + 1) else { + return Err(EditCliError::InvalidArgument( + "missing value for -path".to_string(), + )); + }; + let path = BoxPath::parse(value).map_err(|error| { + EditCliError::InvalidArgument(format!("invalid box path: {error}")) + })?; + paths.push(path); + index += 2; + } "-drop" | "--drop" => { let Some(value) = args.get(index + 1) else { - return Err(EditError::InvalidArgument( + return Err(EditCliError::InvalidArgument( "missing value for -drop".to_string(), )); }; @@ -130,16 +164,16 @@ fn parse_args(args: &[String]) -> Result<(EditOptions, &str, &str), EditError> { options .drop_boxes .insert(FourCc::try_from(name).map_err(|_| { - EditError::InvalidArgument(format!( + EditCliError::InvalidArgument(format!( "box types passed to -drop must be 4 bytes: {name}" )) })?); } index += 2; } - "-h" | "--help" => return Err(EditError::UsageRequested), + "-h" | "--help" => return Err(EditCliError::UsageRequested), value if value.starts_with('-') => { - return Err(EditError::InvalidArgument(format!( + return Err(EditCliError::InvalidArgument(format!( "unknown edit option: {value}" ))); } @@ -151,10 +185,177 @@ fn parse_args(args: &[String]) -> Result<(EditOptions, &str, &str), EditError> { } if positional.len() != 2 { - return Err(EditError::UsageRequested); + return Err(EditCliError::UsageRequested); + } + + if !paths.is_empty() && options.base_media_decode_time.is_none() { + return Err(EditCliError::InvalidArgument( + "edit -path currently supports only -base_media_decode_time rewrites".to_string(), + )); + } + + Ok(ParsedEditArgs { + options, + paths, + input_path: positional[0], + output_path: positional[1], + }) +} + +fn edit_reader_scoped_paths( + reader: &mut R, + writer: W, + options: &EditOptions, + paths: &[BoxPath], +) -> Result<(), EditCliError> +where + R: Read + Seek, + W: Write + Seek, +{ + let Some(base_media_decode_time) = options.base_media_decode_time else { + return Err(EditCliError::InvalidArgument( + "edit -path currently supports only -base_media_decode_time rewrites".to_string(), + )); + }; + + let matched_tfdt = + extract_boxes_as::<_, Tfdt>(reader, None, paths).map_err(map_scoped_extract_error)?; + if base_media_decode_time > u64::from(u32::MAX) + && matched_tfdt.iter().any(|tfdt| tfdt.version() == 0) + { + return Err(EditCliError::Edit(EditError::NumericOverflow { + field_name: "base media decode time", + })); + } + + reader.seek(SeekFrom::Start(0))?; + let mut scoped_output = Cursor::new(Vec::new()); + rewrite_boxes_as::<_, _, Tfdt, _>(reader, &mut scoped_output, paths, |tfdt| { + if tfdt.version() == 0 { + tfdt.base_media_decode_time_v0 = base_media_decode_time as u32; + } else { + tfdt.base_media_decode_time_v1 = base_media_decode_time; + } + }) + .map_err(map_scoped_rewrite_error)?; + + let scoped_bytes = scoped_output.into_inner(); + let follow_up_options = EditOptions { + base_media_decode_time: None, + drop_boxes: options.drop_boxes.clone(), + }; + if follow_up_options.is_noop() { + let mut writer = writer; + writer.write_all(&scoped_bytes)?; + return Ok(()); + } + + let mut scoped_reader = Cursor::new(scoped_bytes); + edit_reader(&mut scoped_reader, writer, &follow_up_options).map_err(EditCliError::Edit) +} + +fn map_scoped_extract_error(error: ExtractError) -> EditCliError { + match error { + ExtractError::Io(error) => EditCliError::Edit(EditError::Io(error)), + ExtractError::Header(error) => EditCliError::Edit(EditError::Header(error)), + ExtractError::Codec(error) => EditCliError::Edit(EditError::Codec(error)), + ExtractError::Walk(error) => EditCliError::Edit(map_walk_error(error)), + ExtractError::EmptyPath => { + EditCliError::InvalidArgument("box path must not be empty".to_string()) + } + ExtractError::PayloadDecode { source, .. } => EditCliError::Edit(EditError::Codec(source)), + ExtractError::UnexpectedPayloadType { + path, + box_type, + offset, + .. + } => EditCliError::InvalidArgument(format!( + "path-based -base_media_decode_time rewrites require tfdt boxes: matched {path} (type={box_type}, offset={offset})" + )), + } +} + +fn map_scoped_rewrite_error(error: RewriteError) -> EditCliError { + match error { + RewriteError::Io(error) => EditCliError::Edit(EditError::Io(error)), + RewriteError::Header(error) => EditCliError::Edit(EditError::Header(error)), + RewriteError::Codec(error) => EditCliError::Edit(EditError::Codec(error)), + RewriteError::Writer(error) => EditCliError::Edit(EditError::Writer(error)), + RewriteError::EmptyPath => { + EditCliError::InvalidArgument("box path must not be empty".to_string()) + } + RewriteError::PayloadDecode { source, .. } | RewriteError::PayloadEncode { source, .. } => { + EditCliError::Edit(EditError::Codec(source)) + } + RewriteError::UnexpectedPayloadType { + path, + box_type, + offset, + .. + } => EditCliError::InvalidArgument(format!( + "path-based -base_media_decode_time rewrites require tfdt boxes: matched {path} (type={box_type}, offset={offset})" + )), + RewriteError::TooLargeBoxSize { + box_type, + size, + available_size, + } => EditCliError::Edit(EditError::TooLargeBoxSize { + box_type, + size, + available_size, + }), + RewriteError::UnexpectedEof => EditCliError::Edit(EditError::UnexpectedEof), + } +} + +fn map_walk_error(error: WalkError) -> EditError { + match error { + WalkError::Io(error) => EditError::Io(error), + WalkError::Header(error) => EditError::Header(error), + WalkError::Codec(error) => EditError::Codec(error), + WalkError::TooLargeBoxSize { + box_type, + size, + available_size, + } => EditError::TooLargeBoxSize { + box_type, + size, + available_size, + }, + WalkError::UnexpectedEof => EditError::UnexpectedEof, + } +} + +#[derive(Debug)] +enum EditCliError { + Edit(EditError), + InvalidArgument(String), + UsageRequested, +} + +impl fmt::Display for EditCliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Edit(error) => error.fmt(f), + Self::InvalidArgument(message) => f.write_str(message), + Self::UsageRequested => f.write_str("usage requested"), + } } +} - Ok((options, positional[0], positional[1])) +impl Error for EditCliError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Self::Edit(error) => Some(error), + Self::InvalidArgument(..) | Self::UsageRequested => None, + } + } +} + +impl From for EditCliError { + fn from(value: io::Error) -> Self { + Self::Edit(EditError::Io(value)) + } } #[derive(Clone, Copy)] diff --git a/src/cli/extract.rs b/src/cli/extract.rs index 9880cd1..2dc6d70 100644 --- a/src/cli/extract.rs +++ b/src/cli/extract.rs @@ -7,8 +7,9 @@ use std::io::{self, Read, Seek, Write}; use crate::FourCc; use crate::codec::CodecError; +use crate::extract::{ExtractError, extract_boxes_bytes}; use crate::header::HeaderError; -use crate::walk::{WalkControl, WalkError, walk_structure}; +use crate::walk::{BoxPath, WalkControl, WalkError, walk_structure}; use super::util::should_have_no_children; @@ -37,7 +38,16 @@ where W: Write, { writeln!(writer, "USAGE: mp4forge extract BOX_TYPE INPUT.mp4")?; - Ok(()) + writeln!( + writer, + " mp4forge extract -path [-path ...] INPUT.mp4" + )?; + writeln!(writer)?; + writeln!(writer, "OPTIONS:")?; + writeln!( + writer, + " -path Extract raw boxes that match the parsed slash-delimited box path" + ) } /// Extracts every box of type `box_type` from `reader`, preserving raw bytes. @@ -77,18 +87,97 @@ where Ok(()) } +/// Extracts every box that matches any path in `paths` from `reader`, preserving raw bytes. +/// +/// Paths use the existing [`BoxPath`] parser, including slash-delimited segments and `*` +/// wildcards. Each match is copied with its original box header and payload bytes intact. +pub fn extract_reader_paths( + reader: &mut R, + paths: &[BoxPath], + writer: &mut W, +) -> Result<(), ExtractCliError> +where + R: Read + Seek, + W: Write, +{ + for bytes in extract_boxes_bytes(reader, None, paths).map_err(map_extract_error)? { + writer.write_all(&bytes)?; + } + Ok(()) +} + fn run_inner(args: &[String], stdout: &mut W) -> Result<(), ExtractCliError> where W: Write, { - if args.len() != 2 { - return Err(ExtractCliError::UsageRequested); + let mut paths = Vec::new(); + let mut positional = Vec::new(); + let mut index = 0usize; + while index < args.len() { + match args[index].as_str() { + "-path" | "--path" => { + let Some(value) = args.get(index + 1) else { + return Err(ExtractCliError::InvalidArgument( + "missing value for -path".to_string(), + )); + }; + let path = BoxPath::parse(value).map_err(|error| { + ExtractCliError::InvalidArgument(format!("invalid box path: {error}")) + })?; + paths.push(path); + index += 2; + } + "-h" | "--help" => return Err(ExtractCliError::UsageRequested), + value if value.starts_with('-') => { + return Err(ExtractCliError::InvalidArgument(format!( + "unknown extract option: {value}" + ))); + } + value => { + positional.push(value); + index += 1; + } + } + } + + if paths.is_empty() { + if positional.len() != 2 { + return Err(ExtractCliError::UsageRequested); + } + + let box_type = FourCc::try_from(positional[0]).map_err(|_| { + ExtractCliError::InvalidArgument(format!("invalid box type: {}", positional[0])) + })?; + let mut file = File::open(positional[1])?; + return extract_reader(&mut file, box_type, stdout); + } + + if positional.len() != 1 { + return Err(ExtractCliError::InvalidArgument( + "extract with -path accepts exactly one input path".to_string(), + )); } - let box_type = FourCc::try_from(args[0].as_str()) - .map_err(|_| ExtractCliError::InvalidArgument(format!("invalid box type: {}", args[0])))?; - let mut file = File::open(&args[1])?; - extract_reader(&mut file, box_type, stdout) + let mut file = File::open(positional[0])?; + extract_reader_paths(&mut file, &paths, stdout) +} + +fn map_extract_error(error: ExtractError) -> ExtractCliError { + match error { + ExtractError::Io(error) => ExtractCliError::Io(error), + ExtractError::Header(error) => ExtractCliError::Header(error), + ExtractError::Codec(error) => ExtractCliError::Walk(WalkError::Codec(error)), + ExtractError::Walk(error) => ExtractCliError::Walk(error), + ExtractError::EmptyPath => { + ExtractCliError::InvalidArgument("box path must not be empty".to_string()) + } + ExtractError::PayloadDecode { source, .. } => { + ExtractCliError::Walk(WalkError::Codec(source)) + } + unexpected @ ExtractError::UnexpectedPayloadType { .. } => { + ExtractCliError::InvalidArgument(unexpected.to_string()) + } + } } /// Errors raised while parsing extract arguments or copying raw boxes. diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c996db4..85781f1 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -26,7 +26,7 @@ where let _ = write_usage(stderr); 0 } - "divide" => divide::run(&args[1..], stderr), + "divide" => divide::run_with_output(&args[1..], stdout, stderr), "dump" => dump::run(&args[1..], stdout, stderr), "edit" => edit::run(&args[1..], stderr), "extract" => extract::run(&args[1..], stdout, stderr), @@ -53,7 +53,7 @@ where )?; writeln!(writer, " dump display the MP4 box tree")?; writeln!(writer, " edit rewrite selected boxes")?; - writeln!(writer, " extract extract raw boxes by type")?; + writeln!(writer, " extract extract raw boxes by type or path")?; writeln!(writer, " psshdump summarize pssh boxes")?; writeln!(writer, " probe summarize an MP4 file")?; Ok(()) diff --git a/src/cli/probe.rs b/src/cli/probe.rs index 5103a05..98467a5 100644 --- a/src/cli/probe.rs +++ b/src/cli/probe.rs @@ -6,8 +6,10 @@ use std::fs::File; use std::io::{self, Read, Seek, Write}; use crate::probe::{ - ProbeError, TrackCodec, average_sample_bitrate, average_segment_bitrate, find_idr_frames, - max_sample_bitrate, max_segment_bitrate, probe, + DetailedTrackInfo, ProbeError, ProbeOptions, TrackCodec, TrackCodecDetails, TrackCodecFamily, + TrackMediaCharacteristics, average_sample_bitrate, average_segment_bitrate, find_idr_frames, + max_sample_bitrate, max_segment_bitrate, probe_codec_detailed_with_options, + probe_detailed_with_options, probe_media_characteristics_with_options, probe_with_options, }; /// Structured output format supported by the probe command. @@ -31,7 +33,70 @@ impl ProbeFormat { } } +/// Additive controls for expensive probe-report rendering work. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ProbeReportOptions { + /// Library-side probe expansion controls. + pub probe: ProbeOptions, + /// Whether to aggregate bitrate summaries for each track. + pub include_bitrate: bool, + /// Whether to scan AVC samples for IDR frame counts. + pub include_idr_frame_count: bool, +} + +impl ProbeReportOptions { + /// Returns the existing eager probe-report behavior. + pub const fn full() -> Self { + Self { + probe: ProbeOptions::full(), + include_bitrate: true, + include_idr_frame_count: true, + } + } + + /// Returns a lighter-weight probe-report behavior for large-file inspection. + pub const fn lightweight() -> Self { + Self { + probe: ProbeOptions::lightweight(), + include_bitrate: false, + include_idr_frame_count: false, + } + } +} + +impl Default for ProbeReportOptions { + fn default() -> Self { + Self::full() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ProbeDetailLevel { + Full, + Light, +} + +impl ProbeDetailLevel { + fn parse(value: &str) -> Result { + match value { + "full" => Ok(Self::Full), + "light" => Ok(Self::Light), + other => Err(ProbeCliError::InvalidArgument(format!( + "unsupported probe detail level: {other}" + ))), + } + } + + const fn report_options(self) -> ProbeReportOptions { + match self { + Self::Full => ProbeReportOptions::full(), + Self::Light => ProbeReportOptions::lightweight(), + } + } +} + /// Top-level probe report used by the CLI layer. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq)] pub struct ProbeReport { /// Root `ftyp` major brand. @@ -53,6 +118,7 @@ pub struct ProbeReport { } /// One track entry in the CLI probe report. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone, Debug, Default, PartialEq)] pub struct ProbeTrackReport { /// Track identifier from `tkhd`. @@ -83,6 +149,228 @@ pub struct ProbeTrackReport { pub max_bitrate: Option, } +/// Top-level detailed probe report used by the CLI command surface. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct DetailedProbeReport { + /// Root `ftyp` major brand. + pub major_brand: String, + /// Root `ftyp` minor version. + pub minor_version: u32, + /// Root `ftyp` compatible brands. + pub compatible_brands: Vec, + /// Whether the file places `moov` before the first `mdat`. + pub fast_start: bool, + /// Movie timescale from `mvhd`. + pub timescale: u32, + /// Movie duration from `mvhd`. + pub duration: u64, + /// Movie duration expressed in seconds. + pub duration_seconds: f32, + /// Per-track detailed probe summaries. + pub tracks: Vec, +} + +/// One track entry in the detailed CLI probe report. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct DetailedProbeTrackReport { + /// Track identifier from `tkhd`. + pub track_id: u32, + /// Track timescale from `mdhd`. + pub timescale: u32, + /// Track duration from `mdhd`. + pub duration: u64, + /// Track duration expressed in seconds. + pub duration_seconds: f32, + /// Human-readable codec identifier. + pub codec: String, + /// Normalized codec-family label. + pub codec_family: String, + /// Whether the track uses an encrypted sample entry. + pub encrypted: bool, + /// Handler type when present. + pub handler_type: Option, + /// ISO-639-2 language code when present. + pub language: Option, + /// Sample-entry box type when present. + pub sample_entry_type: Option, + /// Protected original-format sample-entry type when present. + pub original_format: Option, + /// Protection-scheme type when present. + pub protection_scheme_type: Option, + /// Protection-scheme version when present. + pub protection_scheme_version: Option, + /// Display width for visual tracks. + pub width: Option, + /// Display height for visual tracks. + pub height: Option, + /// Channel count for audio tracks. + pub channel_count: Option, + /// Integer sample rate for audio tracks. + pub sample_rate: Option, + /// Expanded sample count when present. + pub sample_num: Option, + /// Expanded chunk count when present. + pub chunk_num: Option, + /// Count of samples carrying IDR NAL units. + pub idr_frame_num: Option, + /// Average bitrate in bits per second. + pub bitrate: Option, + /// Maximum bitrate in bits per second. + pub max_bitrate: Option, +} + +/// Top-level codec-detailed probe report used by the CLI command surface. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CodecDetailedProbeReport { + /// Root `ftyp` major brand. + pub major_brand: String, + /// Root `ftyp` minor version. + pub minor_version: u32, + /// Root `ftyp` compatible brands. + pub compatible_brands: Vec, + /// Whether the file places `moov` before the first `mdat`. + pub fast_start: bool, + /// Movie timescale from `mvhd`. + pub timescale: u32, + /// Movie duration from `mvhd`. + pub duration: u64, + /// Movie duration expressed in seconds. + pub duration_seconds: f32, + /// Per-track codec-detailed probe summaries. + pub tracks: Vec, +} + +/// One track entry in the codec-detailed CLI probe report. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct CodecDetailedProbeTrackReport { + /// Track identifier from `tkhd`. + pub track_id: u32, + /// Track timescale from `mdhd`. + pub timescale: u32, + /// Track duration from `mdhd`. + pub duration: u64, + /// Track duration expressed in seconds. + pub duration_seconds: f32, + /// Human-readable codec identifier. + pub codec: String, + /// Normalized codec-family label. + pub codec_family: String, + /// Parsed codec-specific configuration details. + pub codec_details: TrackCodecDetails, + /// Whether the track uses an encrypted sample entry. + pub encrypted: bool, + /// Handler type when present. + pub handler_type: Option, + /// ISO-639-2 language code when present. + pub language: Option, + /// Sample-entry box type when present. + pub sample_entry_type: Option, + /// Protected original-format sample-entry type when present. + pub original_format: Option, + /// Protection-scheme type when present. + pub protection_scheme_type: Option, + /// Protection-scheme version when present. + pub protection_scheme_version: Option, + /// Display width for visual tracks. + pub width: Option, + /// Display height for visual tracks. + pub height: Option, + /// Channel count for audio tracks. + pub channel_count: Option, + /// Integer sample rate for audio tracks. + pub sample_rate: Option, + /// Expanded sample count when present. + pub sample_num: Option, + /// Expanded chunk count when present. + pub chunk_num: Option, + /// Count of samples carrying IDR NAL units. + pub idr_frame_num: Option, + /// Average bitrate in bits per second. + pub bitrate: Option, + /// Maximum bitrate in bits per second. + pub max_bitrate: Option, +} + +/// Top-level media-characteristics probe report used by the CLI command surface. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MediaCharacteristicsProbeReport { + /// Root `ftyp` major brand. + pub major_brand: String, + /// Root `ftyp` minor version. + pub minor_version: u32, + /// Root `ftyp` compatible brands. + pub compatible_brands: Vec, + /// Whether the file places `moov` before the first `mdat`. + pub fast_start: bool, + /// Movie timescale from `mvhd`. + pub timescale: u32, + /// Movie duration from `mvhd`. + pub duration: u64, + /// Movie duration expressed in seconds. + pub duration_seconds: f32, + /// Per-track media-characteristics probe summaries. + pub tracks: Vec, +} + +/// One track entry in the media-characteristics CLI probe report. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq)] +pub struct MediaCharacteristicsProbeTrackReport { + /// Track identifier from `tkhd`. + pub track_id: u32, + /// Track timescale from `mdhd`. + pub timescale: u32, + /// Track duration from `mdhd`. + pub duration: u64, + /// Track duration expressed in seconds. + pub duration_seconds: f32, + /// Human-readable codec identifier. + pub codec: String, + /// Normalized codec-family label. + pub codec_family: String, + /// Parsed codec-specific configuration details. + pub codec_details: TrackCodecDetails, + /// Sample-entry media characteristics already parsed by the crate. + pub media_characteristics: TrackMediaCharacteristics, + /// Whether the track uses an encrypted sample entry. + pub encrypted: bool, + /// Handler type when present. + pub handler_type: Option, + /// ISO-639-2 language code when present. + pub language: Option, + /// Sample-entry box type when present. + pub sample_entry_type: Option, + /// Protected original-format sample-entry type when present. + pub original_format: Option, + /// Protection-scheme type when present. + pub protection_scheme_type: Option, + /// Protection-scheme version when present. + pub protection_scheme_version: Option, + /// Display width for visual tracks. + pub width: Option, + /// Display height for visual tracks. + pub height: Option, + /// Channel count for audio tracks. + pub channel_count: Option, + /// Integer sample rate for audio tracks. + pub sample_rate: Option, + /// Expanded sample count when present. + pub sample_num: Option, + /// Expanded chunk count when present. + pub chunk_num: Option, + /// Count of samples carrying IDR NAL units. + pub idr_frame_num: Option, + /// Average bitrate in bits per second. + pub bitrate: Option, + /// Maximum bitrate in bits per second. + pub max_bitrate: Option, +} + /// Runs the probe subcommand with `args`, writing output to `stdout`. pub fn run(args: &[String], stdout: &mut W, stderr: &mut E) -> i32 where @@ -114,6 +402,10 @@ where writer, " -format Output format (default: json)" )?; + writeln!( + writer, + " -detail Probe detail level (default: full)" + )?; Ok(()) } @@ -122,7 +414,18 @@ pub fn build_report(reader: &mut R) -> Result where R: Read + Seek, { - let summary = probe(reader)?; + build_report_with_options(reader, ProbeReportOptions::default()) +} + +/// Builds a CLI probe report from an MP4 reader with additive report controls. +pub fn build_report_with_options( + reader: &mut R, + options: ProbeReportOptions, +) -> Result +where + R: Read + Seek, +{ + let summary = probe_with_options(reader, options.probe)?; let mut report = ProbeReport { major_brand: summary.major_brand.to_string(), @@ -140,13 +443,13 @@ where }; for track in &summary.tracks { - let mut bitrate = average_sample_bitrate(&track.samples, track.timescale); - let mut max_bitrate = - max_sample_bitrate(&track.samples, track.timescale, track.timescale.into()); - if bitrate == 0 || max_bitrate == 0 { - bitrate = average_segment_bitrate(&summary.segments, track.track_id, track.timescale); - max_bitrate = max_segment_bitrate(&summary.segments, track.track_id, track.timescale); - } + let (bitrate, max_bitrate) = summarize_bitrate( + &track.samples, + track.timescale, + track.track_id, + &summary.segments, + options.include_bitrate, + ); let mut row = ProbeTrackReport { track_id: track.track_id, @@ -167,7 +470,9 @@ where if let Some(avc) = track.avc.as_ref() { row.width = Some(avc.width); row.height = Some(avc.height); - row.idr_frame_num = idr_frame_count(reader, track)?; + if options.include_idr_frame_count && !track.samples.is_empty() { + row.idr_frame_num = idr_frame_count(reader, track)?; + } } report.tracks.push(row); @@ -176,72 +481,406 @@ where Ok(report) } -/// Writes `report` in the selected `format`. -pub fn write_report( - writer: &mut W, - report: &ProbeReport, - format: ProbeFormat, -) -> Result<(), ProbeCliError> +/// Builds a detailed CLI probe report from an MP4 reader. +pub fn build_detailed_report(reader: &mut R) -> Result where - W: Write, + R: Read + Seek, { - match format { - ProbeFormat::Json => write_json_report(writer, report).map_err(ProbeCliError::Io), - ProbeFormat::Yaml => write_yaml_report(writer, report).map_err(ProbeCliError::Io), - } + build_detailed_report_with_options(reader, ProbeReportOptions::default()) } -fn run_inner(args: &[String], stdout: &mut W) -> Result<(), ProbeCliError> +/// Builds a detailed CLI probe report from an MP4 reader with additive report controls. +pub fn build_detailed_report_with_options( + reader: &mut R, + options: ProbeReportOptions, +) -> Result where - W: Write, + R: Read + Seek, { - let mut format = ProbeFormat::Json; - let mut input_path = None; - let mut index = 0usize; - while index < args.len() { - match args[index].as_str() { - "-format" | "--format" => { - let Some(value) = args.get(index + 1) else { - return Err(ProbeCliError::InvalidArgument( - "missing value for -format".to_string(), - )); - }; - format = ProbeFormat::parse(value)?; - index += 2; - } - "-h" | "--help" => return Err(ProbeCliError::UsageRequested), - value if value.starts_with('-') => { - return Err(ProbeCliError::InvalidArgument(format!( - "unknown probe option: {value}" - ))); - } - value => { - if input_path.is_some() { - return Err(ProbeCliError::InvalidArgument( - "probe accepts exactly one input path".to_string(), - )); - } - input_path = Some(value); - index += 1; - } + let summary = probe_detailed_with_options(reader, options.probe)?; + + let mut report = DetailedProbeReport { + major_brand: summary.major_brand.to_string(), + minor_version: summary.minor_version, + compatible_brands: summary + .compatible_brands + .iter() + .map(ToString::to_string) + .collect(), + fast_start: summary.fast_start, + timescale: summary.timescale, + duration: summary.duration, + duration_seconds: seconds(summary.duration, summary.timescale), + tracks: Vec::with_capacity(summary.tracks.len()), + }; + + for track in &summary.tracks { + let basic = &track.summary; + let (bitrate, max_bitrate) = summarize_bitrate( + &basic.samples, + basic.timescale, + basic.track_id, + &summary.segments, + options.include_bitrate, + ); + + let mut row = DetailedProbeTrackReport { + track_id: basic.track_id, + timescale: basic.timescale, + duration: basic.duration, + duration_seconds: seconds(basic.duration, basic.timescale), + codec: detailed_track_codec_string(track), + codec_family: track_codec_family_string(track.codec_family).to_string(), + encrypted: basic.encrypted, + handler_type: track.handler_type.map(|value| value.to_string()), + language: track.language.clone(), + sample_entry_type: track.sample_entry_type.map(|value| value.to_string()), + original_format: track.original_format.map(|value| value.to_string()), + protection_scheme_type: track + .protection_scheme + .as_ref() + .map(|value| value.scheme_type.to_string()), + protection_scheme_version: track + .protection_scheme + .as_ref() + .map(|value| value.scheme_version), + width: track.display_width, + height: track.display_height, + channel_count: track.channel_count, + sample_rate: track.sample_rate, + sample_num: some_if_nonzero(basic.samples.len()), + chunk_num: some_if_nonzero(basic.chunks.len()), + idr_frame_num: None, + bitrate: some_if_nonzero(bitrate as usize).map(|_| bitrate), + max_bitrate: some_if_nonzero(max_bitrate as usize).map(|_| max_bitrate), + }; + + if options.include_idr_frame_count && basic.avc.is_some() && !basic.samples.is_empty() { + row.idr_frame_num = idr_frame_count(reader, basic)?; } + + report.tracks.push(row); } - let Some(input_path) = input_path else { - return Err(ProbeCliError::UsageRequested); - }; + Ok(report) +} - let mut file = File::open(input_path)?; - let report = build_report(&mut file)?; - write_report(stdout, &report, format) +/// Builds a codec-detailed CLI probe report from an MP4 reader. +pub fn build_codec_detailed_report( + reader: &mut R, +) -> Result +where + R: Read + Seek, +{ + build_codec_detailed_report_with_options(reader, ProbeReportOptions::default()) } -fn track_codec_string(track: &crate::probe::TrackInfo) -> String { - match track.codec { - TrackCodec::Avc1 => track - .avc - .as_ref() - .map(|avc| { +/// Builds a codec-detailed CLI probe report from an MP4 reader with additive report controls. +pub fn build_codec_detailed_report_with_options( + reader: &mut R, + options: ProbeReportOptions, +) -> Result +where + R: Read + Seek, +{ + let summary = probe_codec_detailed_with_options(reader, options.probe)?; + + let mut report = CodecDetailedProbeReport { + major_brand: summary.major_brand.to_string(), + minor_version: summary.minor_version, + compatible_brands: summary + .compatible_brands + .iter() + .map(ToString::to_string) + .collect(), + fast_start: summary.fast_start, + timescale: summary.timescale, + duration: summary.duration, + duration_seconds: seconds(summary.duration, summary.timescale), + tracks: Vec::with_capacity(summary.tracks.len()), + }; + + for track in &summary.tracks { + let basic = &track.summary.summary; + let (bitrate, max_bitrate) = summarize_bitrate( + &basic.samples, + basic.timescale, + basic.track_id, + &summary.segments, + options.include_bitrate, + ); + + let mut row = CodecDetailedProbeTrackReport { + track_id: basic.track_id, + timescale: basic.timescale, + duration: basic.duration, + duration_seconds: seconds(basic.duration, basic.timescale), + codec: detailed_track_codec_string(&track.summary), + codec_family: track_codec_family_string(track.summary.codec_family).to_string(), + codec_details: track.codec_details.clone(), + encrypted: basic.encrypted, + handler_type: track.summary.handler_type.map(|value| value.to_string()), + language: track.summary.language.clone(), + sample_entry_type: track + .summary + .sample_entry_type + .map(|value| value.to_string()), + original_format: track.summary.original_format.map(|value| value.to_string()), + protection_scheme_type: track + .summary + .protection_scheme + .as_ref() + .map(|value| value.scheme_type.to_string()), + protection_scheme_version: track + .summary + .protection_scheme + .as_ref() + .map(|value| value.scheme_version), + width: track.summary.display_width, + height: track.summary.display_height, + channel_count: track.summary.channel_count, + sample_rate: track.summary.sample_rate, + sample_num: some_if_nonzero(basic.samples.len()), + chunk_num: some_if_nonzero(basic.chunks.len()), + idr_frame_num: None, + bitrate: some_if_nonzero(bitrate as usize).map(|_| bitrate), + max_bitrate: some_if_nonzero(max_bitrate as usize).map(|_| max_bitrate), + }; + + if options.include_idr_frame_count && basic.avc.is_some() && !basic.samples.is_empty() { + row.idr_frame_num = idr_frame_count(reader, basic)?; + } + + report.tracks.push(row); + } + + Ok(report) +} + +/// Builds a media-characteristics CLI probe report from an MP4 reader. +pub fn build_media_characteristics_report( + reader: &mut R, +) -> Result +where + R: Read + Seek, +{ + build_media_characteristics_report_with_options(reader, ProbeReportOptions::default()) +} + +/// Builds a media-characteristics CLI probe report from an MP4 reader with additive report +/// controls. +pub fn build_media_characteristics_report_with_options( + reader: &mut R, + options: ProbeReportOptions, +) -> Result +where + R: Read + Seek, +{ + let summary = probe_media_characteristics_with_options(reader, options.probe)?; + + let mut report = MediaCharacteristicsProbeReport { + major_brand: summary.major_brand.to_string(), + minor_version: summary.minor_version, + compatible_brands: summary + .compatible_brands + .iter() + .map(ToString::to_string) + .collect(), + fast_start: summary.fast_start, + timescale: summary.timescale, + duration: summary.duration, + duration_seconds: seconds(summary.duration, summary.timescale), + tracks: Vec::with_capacity(summary.tracks.len()), + }; + + for track in &summary.tracks { + let basic = &track.summary.summary; + let (bitrate, max_bitrate) = summarize_bitrate( + &basic.samples, + basic.timescale, + basic.track_id, + &summary.segments, + options.include_bitrate, + ); + + let mut row = MediaCharacteristicsProbeTrackReport { + track_id: basic.track_id, + timescale: basic.timescale, + duration: basic.duration, + duration_seconds: seconds(basic.duration, basic.timescale), + codec: detailed_track_codec_string(&track.summary), + codec_family: track_codec_family_string(track.summary.codec_family).to_string(), + codec_details: track.codec_details.clone(), + media_characteristics: track.media_characteristics.clone(), + encrypted: basic.encrypted, + handler_type: track.summary.handler_type.map(|value| value.to_string()), + language: track.summary.language.clone(), + sample_entry_type: track + .summary + .sample_entry_type + .map(|value| value.to_string()), + original_format: track.summary.original_format.map(|value| value.to_string()), + protection_scheme_type: track + .summary + .protection_scheme + .as_ref() + .map(|value| value.scheme_type.to_string()), + protection_scheme_version: track + .summary + .protection_scheme + .as_ref() + .map(|value| value.scheme_version), + width: track.summary.display_width, + height: track.summary.display_height, + channel_count: track.summary.channel_count, + sample_rate: track.summary.sample_rate, + sample_num: some_if_nonzero(basic.samples.len()), + chunk_num: some_if_nonzero(basic.chunks.len()), + idr_frame_num: None, + bitrate: some_if_nonzero(bitrate as usize).map(|_| bitrate), + max_bitrate: some_if_nonzero(max_bitrate as usize).map(|_| max_bitrate), + }; + + if options.include_idr_frame_count && basic.avc.is_some() && !basic.samples.is_empty() { + row.idr_frame_num = idr_frame_count(reader, basic)?; + } + + report.tracks.push(row); + } + + Ok(report) +} + +/// Writes `report` in the selected `format`. +pub fn write_report( + writer: &mut W, + report: &ProbeReport, + format: ProbeFormat, +) -> Result<(), ProbeCliError> +where + W: Write, +{ + match format { + ProbeFormat::Json => write_json_report(writer, report).map_err(ProbeCliError::Io), + ProbeFormat::Yaml => write_yaml_report(writer, report).map_err(ProbeCliError::Io), + } +} + +/// Writes `report` in the selected `format`. +pub fn write_detailed_report( + writer: &mut W, + report: &DetailedProbeReport, + format: ProbeFormat, +) -> Result<(), ProbeCliError> +where + W: Write, +{ + match format { + ProbeFormat::Json => write_json_detailed_report(writer, report).map_err(ProbeCliError::Io), + ProbeFormat::Yaml => write_yaml_detailed_report(writer, report).map_err(ProbeCliError::Io), + } +} + +/// Writes `report` in the selected `format`. +pub fn write_codec_detailed_report( + writer: &mut W, + report: &CodecDetailedProbeReport, + format: ProbeFormat, +) -> Result<(), ProbeCliError> +where + W: Write, +{ + match format { + ProbeFormat::Json => { + write_json_codec_detailed_report(writer, report).map_err(ProbeCliError::Io) + } + ProbeFormat::Yaml => { + write_yaml_codec_detailed_report(writer, report).map_err(ProbeCliError::Io) + } + } +} + +/// Writes `report` in the selected `format`. +pub fn write_media_characteristics_report( + writer: &mut W, + report: &MediaCharacteristicsProbeReport, + format: ProbeFormat, +) -> Result<(), ProbeCliError> +where + W: Write, +{ + match format { + ProbeFormat::Json => { + write_json_media_characteristics_report(writer, report).map_err(ProbeCliError::Io) + } + ProbeFormat::Yaml => { + write_yaml_media_characteristics_report(writer, report).map_err(ProbeCliError::Io) + } + } +} + +fn run_inner(args: &[String], stdout: &mut W) -> Result<(), ProbeCliError> +where + W: Write, +{ + let mut format = ProbeFormat::Json; + let mut detail = ProbeDetailLevel::Full; + let mut input_path = None; + let mut index = 0usize; + while index < args.len() { + match args[index].as_str() { + "-format" | "--format" => { + let Some(value) = args.get(index + 1) else { + return Err(ProbeCliError::InvalidArgument( + "missing value for -format".to_string(), + )); + }; + format = ProbeFormat::parse(value)?; + index += 2; + } + "-detail" | "--detail" => { + let Some(value) = args.get(index + 1) else { + return Err(ProbeCliError::InvalidArgument( + "missing value for -detail".to_string(), + )); + }; + detail = ProbeDetailLevel::parse(value)?; + index += 2; + } + "-h" | "--help" => return Err(ProbeCliError::UsageRequested), + value if value.starts_with('-') => { + return Err(ProbeCliError::InvalidArgument(format!( + "unknown probe option: {value}" + ))); + } + value => { + if input_path.is_some() { + return Err(ProbeCliError::InvalidArgument( + "probe accepts exactly one input path".to_string(), + )); + } + input_path = Some(value); + index += 1; + } + } + } + + let Some(input_path) = input_path else { + return Err(ProbeCliError::UsageRequested); + }; + + let mut file = File::open(input_path)?; + let report = + build_media_characteristics_report_with_options(&mut file, detail.report_options())?; + write_media_characteristics_report(stdout, &report, format) +} + +fn track_codec_string(track: &crate::probe::TrackInfo) -> String { + match track.codec { + TrackCodec::Avc1 => track + .avc + .as_ref() + .map(|avc| { format!( "avc1.{:02X}{:02X}{:02X}", avc.profile, avc.profile_compatibility, avc.level @@ -268,6 +907,56 @@ fn track_codec_string(track: &crate::probe::TrackInfo) -> String { } } +fn detailed_track_codec_string(track: &DetailedTrackInfo) -> String { + let codec_box_type = track.original_format.or(track.sample_entry_type); + match track.codec_family { + TrackCodecFamily::Avc | TrackCodecFamily::Mp4Audio => track_codec_string(&track.summary), + TrackCodecFamily::Unknown => codec_box_type + .map(|value| value.to_string()) + .unwrap_or_else(|| track_codec_string(&track.summary)), + _ => codec_box_type + .map(|value| value.to_string()) + .unwrap_or_else(|| "unknown".to_string()), + } +} + +fn track_codec_family_string(family: TrackCodecFamily) -> &'static str { + match family { + TrackCodecFamily::Unknown => "unknown", + TrackCodecFamily::Avc => "avc", + TrackCodecFamily::Hevc => "hevc", + TrackCodecFamily::Av1 => "av1", + TrackCodecFamily::Vp8 => "vp8", + TrackCodecFamily::Vp9 => "vp9", + TrackCodecFamily::Mp4Audio => "mp4_audio", + TrackCodecFamily::Opus => "opus", + TrackCodecFamily::Ac3 => "ac3", + TrackCodecFamily::Pcm => "pcm", + TrackCodecFamily::XmlSubtitle => "xml_subtitle", + TrackCodecFamily::TextSubtitle => "text_subtitle", + TrackCodecFamily::WebVtt => "webvtt", + } +} + +fn summarize_bitrate( + samples: &[crate::probe::SampleInfo], + timescale: u32, + track_id: u32, + segments: &[crate::probe::SegmentInfo], + include_bitrate: bool, +) -> (u64, u64) { + if !include_bitrate { + return (0, 0); + } + let mut bitrate = average_sample_bitrate(samples, timescale); + let mut max_bitrate = max_sample_bitrate(samples, timescale, timescale.into()); + if bitrate == 0 || max_bitrate == 0 { + bitrate = average_segment_bitrate(segments, track_id, timescale); + max_bitrate = max_segment_bitrate(segments, track_id, timescale); + } + (bitrate, max_bitrate) +} + fn idr_frame_count( reader: &mut R, track: &crate::probe::TrackInfo, @@ -405,29 +1094,877 @@ where Ok(()) } -fn write_json_field( - writer: &mut W, - indent_level: usize, - name: &str, - value: &str, - trailing_comma: bool, -) -> io::Result<()> +fn write_json_detailed_report(writer: &mut W, report: &DetailedProbeReport) -> io::Result<()> where W: Write, { - let trailing = if trailing_comma { "," } else { "" }; - writeln!( + writeln!(writer, "{{")?; + write_json_field( writer, - "{}\"{name}\": {value}{trailing}", - " ".repeat(indent_level) - ) + 1, + "MajorBrand", + &json_string(&report.major_brand), + true, + )?; + write_json_field( + writer, + 1, + "MinorVersion", + &report.minor_version.to_string(), + true, + )?; + writeln!(writer, " \"CompatibleBrands\": [")?; + for (index, brand) in report.compatible_brands.iter().enumerate() { + let trailing = if index + 1 == report.compatible_brands.len() { + "" + } else { + "," + }; + writeln!(writer, " {}{trailing}", json_string(brand))?; + } + writeln!(writer, " ],")?; + write_json_field( + writer, + 1, + "FastStart", + if report.fast_start { "true" } else { "false" }, + true, + )?; + write_json_field(writer, 1, "Timescale", &report.timescale.to_string(), true)?; + write_json_field(writer, 1, "Duration", &report.duration.to_string(), true)?; + write_json_field( + writer, + 1, + "DurationSeconds", + &format_seconds(report.duration_seconds), + true, + )?; + writeln!(writer, " \"Tracks\": [")?; + for (index, track) in report.tracks.iter().enumerate() { + let trailing = if index + 1 == report.tracks.len() { + "" + } else { + "," + }; + write_json_detailed_track(writer, track)?; + writeln!(writer, " }}{trailing}")?; + } + writeln!(writer, " ]")?; + writeln!(writer, "}}") } -fn write_yaml_report(writer: &mut W, report: &ProbeReport) -> io::Result<()> +fn write_json_detailed_track(writer: &mut W, track: &DetailedProbeTrackReport) -> io::Result<()> where W: Write, { - writeln!(writer, "major_brand: {}", yaml_string(&report.major_brand))?; + let mut fields = vec![ + ("TrackID", track.track_id.to_string()), + ("Timescale", track.timescale.to_string()), + ("Duration", track.duration.to_string()), + ("DurationSeconds", format_seconds(track.duration_seconds)), + ("Codec", json_string(&track.codec)), + ("CodecFamily", json_string(&track.codec_family)), + ( + "Encrypted", + if track.encrypted { "true" } else { "false" }.to_string(), + ), + ]; + + if let Some(handler_type) = track.handler_type.as_ref() { + fields.push(("HandlerType", json_string(handler_type))); + } + if let Some(language) = track.language.as_ref() { + fields.push(("Language", json_string(language))); + } + if let Some(sample_entry_type) = track.sample_entry_type.as_ref() { + fields.push(("SampleEntryType", json_string(sample_entry_type))); + } + if let Some(original_format) = track.original_format.as_ref() { + fields.push(("OriginalFormat", json_string(original_format))); + } + if let Some(protection_scheme_type) = track.protection_scheme_type.as_ref() { + fields.push(("ProtectionSchemeType", json_string(protection_scheme_type))); + } + if let Some(protection_scheme_version) = track.protection_scheme_version { + fields.push(( + "ProtectionSchemeVersion", + protection_scheme_version.to_string(), + )); + } + if let Some(width) = track.width { + fields.push(("Width", width.to_string())); + } + if let Some(height) = track.height { + fields.push(("Height", height.to_string())); + } + if let Some(channel_count) = track.channel_count { + fields.push(("ChannelCount", channel_count.to_string())); + } + if let Some(sample_rate) = track.sample_rate { + fields.push(("SampleRate", sample_rate.to_string())); + } + if let Some(sample_num) = track.sample_num { + fields.push(("SampleNum", sample_num.to_string())); + } + if let Some(chunk_num) = track.chunk_num { + fields.push(("ChunkNum", chunk_num.to_string())); + } + if let Some(idr_frame_num) = track.idr_frame_num { + fields.push(("IDRFrameNum", idr_frame_num.to_string())); + } + if let Some(bitrate) = track.bitrate { + fields.push(("Bitrate", bitrate.to_string())); + } + if let Some(max_bitrate) = track.max_bitrate { + fields.push(("MaxBitrate", max_bitrate.to_string())); + } + + writeln!(writer, " {{")?; + for (index, (name, value)) in fields.iter().enumerate() { + write_json_field(writer, 3, name, value, index + 1 != fields.len())?; + } + Ok(()) +} + +fn write_json_codec_detailed_report( + writer: &mut W, + report: &CodecDetailedProbeReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "{{")?; + write_json_field( + writer, + 1, + "MajorBrand", + &json_string(&report.major_brand), + true, + )?; + write_json_field( + writer, + 1, + "MinorVersion", + &report.minor_version.to_string(), + true, + )?; + writeln!(writer, " \"CompatibleBrands\": [")?; + for (index, brand) in report.compatible_brands.iter().enumerate() { + let trailing = if index + 1 == report.compatible_brands.len() { + "" + } else { + "," + }; + writeln!(writer, " {}{trailing}", json_string(brand))?; + } + writeln!(writer, " ],")?; + write_json_field( + writer, + 1, + "FastStart", + if report.fast_start { "true" } else { "false" }, + true, + )?; + write_json_field(writer, 1, "Timescale", &report.timescale.to_string(), true)?; + write_json_field(writer, 1, "Duration", &report.duration.to_string(), true)?; + write_json_field( + writer, + 1, + "DurationSeconds", + &format_seconds(report.duration_seconds), + true, + )?; + writeln!(writer, " \"Tracks\": [")?; + for (index, track) in report.tracks.iter().enumerate() { + let trailing = if index + 1 == report.tracks.len() { + "" + } else { + "," + }; + write_json_codec_detailed_track(writer, track)?; + writeln!(writer, " }}{trailing}")?; + } + writeln!(writer, " ]")?; + writeln!(writer, "}}") +} + +fn write_json_codec_detailed_track( + writer: &mut W, + track: &CodecDetailedProbeTrackReport, +) -> io::Result<()> +where + W: Write, +{ + let mut fields = vec![ + ("TrackID", track.track_id.to_string()), + ("Timescale", track.timescale.to_string()), + ("Duration", track.duration.to_string()), + ("DurationSeconds", format_seconds(track.duration_seconds)), + ("Codec", json_string(&track.codec)), + ("CodecFamily", json_string(&track.codec_family)), + ( + "Encrypted", + if track.encrypted { "true" } else { "false" }.to_string(), + ), + ]; + + if let Some(handler_type) = track.handler_type.as_ref() { + fields.push(("HandlerType", json_string(handler_type))); + } + if let Some(language) = track.language.as_ref() { + fields.push(("Language", json_string(language))); + } + if let Some(sample_entry_type) = track.sample_entry_type.as_ref() { + fields.push(("SampleEntryType", json_string(sample_entry_type))); + } + if let Some(original_format) = track.original_format.as_ref() { + fields.push(("OriginalFormat", json_string(original_format))); + } + if let Some(protection_scheme_type) = track.protection_scheme_type.as_ref() { + fields.push(("ProtectionSchemeType", json_string(protection_scheme_type))); + } + if let Some(protection_scheme_version) = track.protection_scheme_version { + fields.push(( + "ProtectionSchemeVersion", + protection_scheme_version.to_string(), + )); + } + if let Some(width) = track.width { + fields.push(("Width", width.to_string())); + } + if let Some(height) = track.height { + fields.push(("Height", height.to_string())); + } + if let Some(channel_count) = track.channel_count { + fields.push(("ChannelCount", channel_count.to_string())); + } + if let Some(sample_rate) = track.sample_rate { + fields.push(("SampleRate", sample_rate.to_string())); + } + if let Some(sample_num) = track.sample_num { + fields.push(("SampleNum", sample_num.to_string())); + } + if let Some(chunk_num) = track.chunk_num { + fields.push(("ChunkNum", chunk_num.to_string())); + } + if let Some(idr_frame_num) = track.idr_frame_num { + fields.push(("IDRFrameNum", idr_frame_num.to_string())); + } + if let Some(bitrate) = track.bitrate { + fields.push(("Bitrate", bitrate.to_string())); + } + if let Some(max_bitrate) = track.max_bitrate { + fields.push(("MaxBitrate", max_bitrate.to_string())); + } + + writeln!(writer, " {{")?; + for (name, value) in &fields { + write_json_field(writer, 3, name, value, true)?; + } + write_json_codec_details(writer, 3, &track.codec_family, &track.codec_details, false)?; + Ok(()) +} + +fn write_json_media_characteristics_report( + writer: &mut W, + report: &MediaCharacteristicsProbeReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "{{")?; + write_json_field( + writer, + 1, + "MajorBrand", + &json_string(&report.major_brand), + true, + )?; + write_json_field( + writer, + 1, + "MinorVersion", + &report.minor_version.to_string(), + true, + )?; + writeln!(writer, " \"CompatibleBrands\": [")?; + for (index, brand) in report.compatible_brands.iter().enumerate() { + let trailing = if index + 1 == report.compatible_brands.len() { + "" + } else { + "," + }; + writeln!(writer, " {}{trailing}", json_string(brand))?; + } + writeln!(writer, " ],")?; + write_json_field( + writer, + 1, + "FastStart", + if report.fast_start { "true" } else { "false" }, + true, + )?; + write_json_field(writer, 1, "Timescale", &report.timescale.to_string(), true)?; + write_json_field(writer, 1, "Duration", &report.duration.to_string(), true)?; + write_json_field( + writer, + 1, + "DurationSeconds", + &format_seconds(report.duration_seconds), + true, + )?; + writeln!(writer, " \"Tracks\": [")?; + for (index, track) in report.tracks.iter().enumerate() { + let trailing = if index + 1 == report.tracks.len() { + "" + } else { + "," + }; + write_json_media_characteristics_track(writer, track)?; + writeln!(writer, " }}{trailing}")?; + } + writeln!(writer, " ]")?; + writeln!(writer, "}}") +} + +fn write_json_media_characteristics_track( + writer: &mut W, + track: &MediaCharacteristicsProbeTrackReport, +) -> io::Result<()> +where + W: Write, +{ + let mut fields = vec![ + ("TrackID", track.track_id.to_string()), + ("Timescale", track.timescale.to_string()), + ("Duration", track.duration.to_string()), + ("DurationSeconds", format_seconds(track.duration_seconds)), + ("Codec", json_string(&track.codec)), + ("CodecFamily", json_string(&track.codec_family)), + ( + "Encrypted", + if track.encrypted { "true" } else { "false" }.to_string(), + ), + ]; + + if let Some(handler_type) = track.handler_type.as_ref() { + fields.push(("HandlerType", json_string(handler_type))); + } + if let Some(language) = track.language.as_ref() { + fields.push(("Language", json_string(language))); + } + if let Some(sample_entry_type) = track.sample_entry_type.as_ref() { + fields.push(("SampleEntryType", json_string(sample_entry_type))); + } + if let Some(original_format) = track.original_format.as_ref() { + fields.push(("OriginalFormat", json_string(original_format))); + } + if let Some(protection_scheme_type) = track.protection_scheme_type.as_ref() { + fields.push(("ProtectionSchemeType", json_string(protection_scheme_type))); + } + if let Some(protection_scheme_version) = track.protection_scheme_version { + fields.push(( + "ProtectionSchemeVersion", + protection_scheme_version.to_string(), + )); + } + if let Some(width) = track.width { + fields.push(("Width", width.to_string())); + } + if let Some(height) = track.height { + fields.push(("Height", height.to_string())); + } + if let Some(channel_count) = track.channel_count { + fields.push(("ChannelCount", channel_count.to_string())); + } + if let Some(sample_rate) = track.sample_rate { + fields.push(("SampleRate", sample_rate.to_string())); + } + if let Some(sample_num) = track.sample_num { + fields.push(("SampleNum", sample_num.to_string())); + } + if let Some(chunk_num) = track.chunk_num { + fields.push(("ChunkNum", chunk_num.to_string())); + } + if let Some(idr_frame_num) = track.idr_frame_num { + fields.push(("IDRFrameNum", idr_frame_num.to_string())); + } + if let Some(bitrate) = track.bitrate { + fields.push(("Bitrate", bitrate.to_string())); + } + if let Some(max_bitrate) = track.max_bitrate { + fields.push(("MaxBitrate", max_bitrate.to_string())); + } + + writeln!(writer, " {{")?; + for (name, value) in &fields { + write_json_field(writer, 3, name, value, true)?; + } + let include_media = has_media_characteristics(&track.media_characteristics); + write_json_codec_details( + writer, + 3, + &track.codec_family, + &track.codec_details, + include_media, + )?; + if include_media { + write_json_media_characteristics(writer, 3, &track.media_characteristics)?; + } + Ok(()) +} + +fn write_json_media_characteristics( + writer: &mut W, + indent_level: usize, + characteristics: &TrackMediaCharacteristics, +) -> io::Result<()> +where + W: Write, +{ + let section_count = usize::from(characteristics.declared_bitrate.is_some()) + + usize::from(characteristics.color.is_some()) + + usize::from(characteristics.pixel_aspect_ratio.is_some()) + + usize::from(characteristics.field_order.is_some()); + if section_count == 0 { + return Ok(()); + } + + let indent = " ".repeat(indent_level); + writeln!(writer, "{indent}\"MediaCharacteristics\": {{")?; + let mut written = 0usize; + + if let Some(value) = characteristics.declared_bitrate.as_ref() { + written += 1; + writeln!( + writer, + "{}\"DeclaredBitrate\": {{", + " ".repeat(indent_level + 1) + )?; + write_json_field( + writer, + indent_level + 2, + "BufferSizeDB", + &value.buffer_size_db.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 2, + "MaxBitrate", + &value.max_bitrate.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 2, + "AvgBitrate", + &value.avg_bitrate.to_string(), + false, + )?; + let trailing = if written == section_count { "" } else { "," }; + writeln!(writer, "{}}}{trailing}", " ".repeat(indent_level + 1))?; + } + + if let Some(value) = characteristics.color.as_ref() { + written += 1; + writeln!(writer, "{}\"Color\": {{", " ".repeat(indent_level + 1))?; + let mut fields = vec![("ColourType", json_string(&value.colour_type.to_string()))]; + if let Some(colour_primaries) = value.colour_primaries { + fields.push(("ColourPrimaries", colour_primaries.to_string())); + } + if let Some(transfer_characteristics) = value.transfer_characteristics { + fields.push(( + "TransferCharacteristics", + transfer_characteristics.to_string(), + )); + } + if let Some(matrix_coefficients) = value.matrix_coefficients { + fields.push(("MatrixCoefficients", matrix_coefficients.to_string())); + } + if let Some(full_range) = value.full_range { + fields.push(( + "FullRange", + if full_range { "true" } else { "false" }.to_string(), + )); + } + if let Some(profile_size) = value.profile_size { + fields.push(("ProfileSize", profile_size.to_string())); + } + if let Some(unknown_size) = value.unknown_size { + fields.push(("UnknownSize", unknown_size.to_string())); + } + for (index, (name, field_value)) in fields.iter().enumerate() { + write_json_field( + writer, + indent_level + 2, + name, + field_value, + index + 1 != fields.len(), + )?; + } + let trailing = if written == section_count { "" } else { "," }; + writeln!(writer, "{}}}{trailing}", " ".repeat(indent_level + 1))?; + } + + if let Some(value) = characteristics.pixel_aspect_ratio.as_ref() { + written += 1; + writeln!( + writer, + "{}\"PixelAspectRatio\": {{", + " ".repeat(indent_level + 1) + )?; + write_json_field( + writer, + indent_level + 2, + "HSpacing", + &value.h_spacing.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 2, + "VSpacing", + &value.v_spacing.to_string(), + false, + )?; + let trailing = if written == section_count { "" } else { "," }; + writeln!(writer, "{}}}{trailing}", " ".repeat(indent_level + 1))?; + } + + if let Some(value) = characteristics.field_order.as_ref() { + written += 1; + writeln!( + writer, + "{}\"FieldOrder\": {{", + " ".repeat(indent_level + 1) + )?; + write_json_field( + writer, + indent_level + 2, + "FieldCount", + &value.field_count.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 2, + "FieldOrdering", + &value.field_ordering.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 2, + "Interlaced", + if value.interlaced { "true" } else { "false" }, + false, + )?; + let trailing = if written == section_count { "" } else { "," }; + writeln!(writer, "{}}}{trailing}", " ".repeat(indent_level + 1))?; + } + + writeln!(writer, "{}}}", indent) +} + +fn write_json_codec_details( + writer: &mut W, + indent_level: usize, + codec_family: &str, + details: &TrackCodecDetails, + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "{}\"CodecDetails\": {{", " ".repeat(indent_level))?; + let fields = codec_detail_json_fields(codec_family, details); + for (index, (name, value)) in fields.iter().enumerate() { + write_json_field( + writer, + indent_level + 1, + name, + value, + index + 1 != fields.len(), + )?; + } + let trailing = if trailing_comma { "," } else { "" }; + writeln!(writer, "{}}}{trailing}", " ".repeat(indent_level)) +} + +fn codec_detail_json_fields( + codec_family: &str, + details: &TrackCodecDetails, +) -> Vec<(&'static str, String)> { + let mut fields = vec![("Kind", json_string(codec_family))]; + match details { + TrackCodecDetails::Unknown => {} + TrackCodecDetails::Avc(details) => { + fields.push(( + "ConfigurationVersion", + details.configuration_version.to_string(), + )); + fields.push(("Profile", details.profile.to_string())); + fields.push(( + "ProfileCompatibility", + details.profile_compatibility.to_string(), + )); + fields.push(("Level", details.level.to_string())); + fields.push(("LengthSize", details.length_size.to_string())); + if let Some(chroma_format) = details.chroma_format { + fields.push(("ChromaFormat", chroma_format.to_string())); + } + if let Some(bit_depth_luma) = details.bit_depth_luma { + fields.push(("BitDepthLuma", bit_depth_luma.to_string())); + } + if let Some(bit_depth_chroma) = details.bit_depth_chroma { + fields.push(("BitDepthChroma", bit_depth_chroma.to_string())); + } + } + TrackCodecDetails::Hevc(details) => { + fields.push(( + "ConfigurationVersion", + details.configuration_version.to_string(), + )); + fields.push(("ProfileSpace", details.profile_space.to_string())); + fields.push(( + "TierFlag", + if details.tier_flag { "true" } else { "false" }.to_string(), + )); + fields.push(("ProfileIDC", details.profile_idc.to_string())); + fields.push(( + "ProfileCompatibilityMask", + details.profile_compatibility_mask.to_string(), + )); + fields.push(( + "ConstraintIndicator", + json_u8_array(&details.constraint_indicator), + )); + fields.push(("LevelIDC", details.level_idc.to_string())); + fields.push(( + "MinSpatialSegmentationIDC", + details.min_spatial_segmentation_idc.to_string(), + )); + fields.push(("ParallelismType", details.parallelism_type.to_string())); + fields.push(("ChromaFormatIDC", details.chroma_format_idc.to_string())); + fields.push(("BitDepthLuma", details.bit_depth_luma.to_string())); + fields.push(("BitDepthChroma", details.bit_depth_chroma.to_string())); + fields.push(("AvgFrameRate", details.avg_frame_rate.to_string())); + fields.push(("ConstantFrameRate", details.constant_frame_rate.to_string())); + fields.push(("NumTemporalLayers", details.num_temporal_layers.to_string())); + fields.push(("TemporalIDNested", details.temporal_id_nested.to_string())); + fields.push(("LengthSize", details.length_size.to_string())); + } + TrackCodecDetails::Av1(details) => { + fields.push(("SeqProfile", details.seq_profile.to_string())); + fields.push(("SeqLevelIdx0", details.seq_level_idx_0.to_string())); + fields.push(("SeqTier0", details.seq_tier_0.to_string())); + fields.push(("BitDepth", details.bit_depth.to_string())); + fields.push(( + "Monochrome", + if details.monochrome { "true" } else { "false" }.to_string(), + )); + fields.push(( + "ChromaSubsamplingX", + details.chroma_subsampling_x.to_string(), + )); + fields.push(( + "ChromaSubsamplingY", + details.chroma_subsampling_y.to_string(), + )); + fields.push(( + "ChromaSamplePosition", + details.chroma_sample_position.to_string(), + )); + if let Some(delay) = details.initial_presentation_delay_minus_one { + fields.push(("InitialPresentationDelayMinusOne", delay.to_string())); + } + } + TrackCodecDetails::Vp8(details) | TrackCodecDetails::Vp9(details) => { + fields.push(("Profile", details.profile.to_string())); + fields.push(("Level", details.level.to_string())); + fields.push(("BitDepth", details.bit_depth.to_string())); + fields.push(("ChromaSubsampling", details.chroma_subsampling.to_string())); + fields.push(( + "FullRange", + if details.full_range { "true" } else { "false" }.to_string(), + )); + fields.push(("ColourPrimaries", details.colour_primaries.to_string())); + fields.push(( + "TransferCharacteristics", + details.transfer_characteristics.to_string(), + )); + fields.push(( + "MatrixCoefficients", + details.matrix_coefficients.to_string(), + )); + fields.push(( + "CodecInitializationDataSize", + details.codec_initialization_data_size.to_string(), + )); + } + TrackCodecDetails::Mp4Audio(details) => { + fields.push(( + "ObjectTypeIndication", + details.object_type_indication.to_string(), + )); + fields.push(("AudioObjectType", details.audio_object_type.to_string())); + fields.push(("ChannelCount", details.channel_count.to_string())); + if let Some(sample_rate) = details.sample_rate { + fields.push(("SampleRate", sample_rate.to_string())); + } + } + TrackCodecDetails::Opus(details) => { + fields.push(( + "OutputChannelCount", + details.output_channel_count.to_string(), + )); + fields.push(("PreSkip", details.pre_skip.to_string())); + fields.push(("InputSampleRate", details.input_sample_rate.to_string())); + fields.push(("OutputGain", details.output_gain.to_string())); + fields.push(( + "ChannelMappingFamily", + details.channel_mapping_family.to_string(), + )); + if let Some(stream_count) = details.stream_count { + fields.push(("StreamCount", stream_count.to_string())); + } + if let Some(coupled_count) = details.coupled_count { + fields.push(("CoupledCount", coupled_count.to_string())); + } + if !details.channel_mapping.is_empty() { + fields.push(("ChannelMapping", json_u8_array(&details.channel_mapping))); + } + } + TrackCodecDetails::Ac3(details) => { + fields.push(("SampleRateCode", details.sample_rate_code.to_string())); + fields.push(( + "BitStreamIdentification", + details.bit_stream_identification.to_string(), + )); + fields.push(("BitStreamMode", details.bit_stream_mode.to_string())); + fields.push(("AudioCodingMode", details.audio_coding_mode.to_string())); + fields.push(( + "LfeOn", + if details.lfe_on { "true" } else { "false" }.to_string(), + )); + fields.push(("BitRateCode", details.bit_rate_code.to_string())); + } + TrackCodecDetails::Pcm(details) => { + fields.push(("FormatFlags", details.format_flags.to_string())); + fields.push(("SampleSize", details.sample_size.to_string())); + } + TrackCodecDetails::XmlSubtitle(details) => { + fields.push(("Namespace", json_string(&details.namespace))); + fields.push(("SchemaLocation", json_string(&details.schema_location))); + fields.push(( + "AuxiliaryMimeTypes", + json_string(&details.auxiliary_mime_types), + )); + } + TrackCodecDetails::TextSubtitle(details) => { + fields.push(("ContentEncoding", json_string(&details.content_encoding))); + fields.push(("MimeFormat", json_string(&details.mime_format))); + } + TrackCodecDetails::WebVtt(details) => { + if let Some(config) = details.config.as_ref() { + fields.push(("Config", json_string(config))); + } + if let Some(source_label) = details.source_label.as_ref() { + fields.push(("SourceLabel", json_string(source_label))); + } + } + } + + fields +} + +fn write_json_field( + writer: &mut W, + indent_level: usize, + name: &str, + value: &str, + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + let trailing = if trailing_comma { "," } else { "" }; + writeln!( + writer, + "{}\"{name}\": {value}{trailing}", + " ".repeat(indent_level) + ) +} + +fn write_yaml_report(writer: &mut W, report: &ProbeReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "major_brand: {}", yaml_string(&report.major_brand))?; + writeln!(writer, "minor_version: {}", report.minor_version)?; + writeln!(writer, "compatible_brands:")?; + for brand in &report.compatible_brands { + writeln!(writer, "- {}", yaml_string(brand))?; + } + writeln!(writer, "fast_start: {}", report.fast_start)?; + writeln!(writer, "timescale: {}", report.timescale)?; + writeln!(writer, "duration: {}", report.duration)?; + writeln!( + writer, + "duration_seconds: {}", + format_seconds(report.duration_seconds) + )?; + writeln!(writer, "tracks:")?; + for track in &report.tracks { + write_yaml_track(writer, track)?; + } + Ok(()) +} + +fn write_yaml_track(writer: &mut W, track: &ProbeTrackReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "- track_id: {}", track.track_id)?; + writeln!(writer, " timescale: {}", track.timescale)?; + writeln!(writer, " duration: {}", track.duration)?; + writeln!( + writer, + " duration_seconds: {}", + format_seconds(track.duration_seconds) + )?; + writeln!(writer, " codec: {}", yaml_string(&track.codec))?; + writeln!(writer, " encrypted: {}", track.encrypted)?; + if let Some(width) = track.width { + writeln!(writer, " width: {width}")?; + } + if let Some(height) = track.height { + writeln!(writer, " height: {height}")?; + } + if let Some(sample_num) = track.sample_num { + writeln!(writer, " sample_num: {sample_num}")?; + } + if let Some(chunk_num) = track.chunk_num { + writeln!(writer, " chunk_num: {chunk_num}")?; + } + if let Some(idr_frame_num) = track.idr_frame_num { + writeln!(writer, " idr_frame_num: {idr_frame_num}")?; + } + if let Some(bitrate) = track.bitrate { + writeln!(writer, " bitrate: {bitrate}")?; + } + if let Some(max_bitrate) = track.max_bitrate { + writeln!(writer, " max_bitrate: {max_bitrate}")?; + } + Ok(()) +} + +fn write_yaml_detailed_report(writer: &mut W, report: &DetailedProbeReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "major_brand: {}", yaml_string(&report.major_brand))?; writeln!(writer, "minor_version: {}", report.minor_version)?; writeln!(writer, "compatible_brands:")?; for brand in &report.compatible_brands { @@ -443,12 +1980,242 @@ where )?; writeln!(writer, "tracks:")?; for track in &report.tracks { - write_yaml_track(writer, track)?; + write_yaml_detailed_track(writer, track)?; } Ok(()) } -fn write_yaml_track(writer: &mut W, track: &ProbeTrackReport) -> io::Result<()> +fn write_yaml_detailed_track(writer: &mut W, track: &DetailedProbeTrackReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "- track_id: {}", track.track_id)?; + writeln!(writer, " timescale: {}", track.timescale)?; + writeln!(writer, " duration: {}", track.duration)?; + writeln!( + writer, + " duration_seconds: {}", + format_seconds(track.duration_seconds) + )?; + writeln!(writer, " codec: {}", yaml_string(&track.codec))?; + writeln!( + writer, + " codec_family: {}", + yaml_string(&track.codec_family) + )?; + writeln!(writer, " encrypted: {}", track.encrypted)?; + if let Some(handler_type) = track.handler_type.as_ref() { + writeln!(writer, " handler_type: {}", yaml_string(handler_type))?; + } + if let Some(language) = track.language.as_ref() { + writeln!(writer, " language: {}", yaml_string(language))?; + } + if let Some(sample_entry_type) = track.sample_entry_type.as_ref() { + writeln!( + writer, + " sample_entry_type: {}", + yaml_string(sample_entry_type) + )?; + } + if let Some(original_format) = track.original_format.as_ref() { + writeln!( + writer, + " original_format: {}", + yaml_string(original_format) + )?; + } + if let Some(protection_scheme_type) = track.protection_scheme_type.as_ref() { + writeln!( + writer, + " protection_scheme_type: {}", + yaml_string(protection_scheme_type) + )?; + } + if let Some(protection_scheme_version) = track.protection_scheme_version { + writeln!( + writer, + " protection_scheme_version: {protection_scheme_version}" + )?; + } + if let Some(width) = track.width { + writeln!(writer, " width: {width}")?; + } + if let Some(height) = track.height { + writeln!(writer, " height: {height}")?; + } + if let Some(channel_count) = track.channel_count { + writeln!(writer, " channel_count: {channel_count}")?; + } + if let Some(sample_rate) = track.sample_rate { + writeln!(writer, " sample_rate: {sample_rate}")?; + } + if let Some(sample_num) = track.sample_num { + writeln!(writer, " sample_num: {sample_num}")?; + } + if let Some(chunk_num) = track.chunk_num { + writeln!(writer, " chunk_num: {chunk_num}")?; + } + if let Some(idr_frame_num) = track.idr_frame_num { + writeln!(writer, " idr_frame_num: {idr_frame_num}")?; + } + if let Some(bitrate) = track.bitrate { + writeln!(writer, " bitrate: {bitrate}")?; + } + if let Some(max_bitrate) = track.max_bitrate { + writeln!(writer, " max_bitrate: {max_bitrate}")?; + } + Ok(()) +} + +fn write_yaml_codec_detailed_report( + writer: &mut W, + report: &CodecDetailedProbeReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "major_brand: {}", yaml_string(&report.major_brand))?; + writeln!(writer, "minor_version: {}", report.minor_version)?; + writeln!(writer, "compatible_brands:")?; + for brand in &report.compatible_brands { + writeln!(writer, "- {}", yaml_string(brand))?; + } + writeln!(writer, "fast_start: {}", report.fast_start)?; + writeln!(writer, "timescale: {}", report.timescale)?; + writeln!(writer, "duration: {}", report.duration)?; + writeln!( + writer, + "duration_seconds: {}", + format_seconds(report.duration_seconds) + )?; + writeln!(writer, "tracks:")?; + for track in &report.tracks { + write_yaml_codec_detailed_track(writer, track)?; + } + Ok(()) +} + +fn write_yaml_codec_detailed_track( + writer: &mut W, + track: &CodecDetailedProbeTrackReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "- track_id: {}", track.track_id)?; + writeln!(writer, " timescale: {}", track.timescale)?; + writeln!(writer, " duration: {}", track.duration)?; + writeln!( + writer, + " duration_seconds: {}", + format_seconds(track.duration_seconds) + )?; + writeln!(writer, " codec: {}", yaml_string(&track.codec))?; + writeln!( + writer, + " codec_family: {}", + yaml_string(&track.codec_family) + )?; + writeln!(writer, " encrypted: {}", track.encrypted)?; + if let Some(handler_type) = track.handler_type.as_ref() { + writeln!(writer, " handler_type: {}", yaml_string(handler_type))?; + } + if let Some(language) = track.language.as_ref() { + writeln!(writer, " language: {}", yaml_string(language))?; + } + if let Some(sample_entry_type) = track.sample_entry_type.as_ref() { + writeln!( + writer, + " sample_entry_type: {}", + yaml_string(sample_entry_type) + )?; + } + if let Some(original_format) = track.original_format.as_ref() { + writeln!( + writer, + " original_format: {}", + yaml_string(original_format) + )?; + } + if let Some(protection_scheme_type) = track.protection_scheme_type.as_ref() { + writeln!( + writer, + " protection_scheme_type: {}", + yaml_string(protection_scheme_type) + )?; + } + if let Some(protection_scheme_version) = track.protection_scheme_version { + writeln!( + writer, + " protection_scheme_version: {protection_scheme_version}" + )?; + } + if let Some(width) = track.width { + writeln!(writer, " width: {width}")?; + } + if let Some(height) = track.height { + writeln!(writer, " height: {height}")?; + } + if let Some(channel_count) = track.channel_count { + writeln!(writer, " channel_count: {channel_count}")?; + } + if let Some(sample_rate) = track.sample_rate { + writeln!(writer, " sample_rate: {sample_rate}")?; + } + if let Some(sample_num) = track.sample_num { + writeln!(writer, " sample_num: {sample_num}")?; + } + if let Some(chunk_num) = track.chunk_num { + writeln!(writer, " chunk_num: {chunk_num}")?; + } + if let Some(idr_frame_num) = track.idr_frame_num { + writeln!(writer, " idr_frame_num: {idr_frame_num}")?; + } + if let Some(bitrate) = track.bitrate { + writeln!(writer, " bitrate: {bitrate}")?; + } + if let Some(max_bitrate) = track.max_bitrate { + writeln!(writer, " max_bitrate: {max_bitrate}")?; + } + writeln!(writer, " codec_details:")?; + for (name, value) in codec_detail_yaml_fields(&track.codec_family, &track.codec_details) { + writeln!(writer, " {name}: {value}")?; + } + Ok(()) +} + +fn write_yaml_media_characteristics_report( + writer: &mut W, + report: &MediaCharacteristicsProbeReport, +) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "major_brand: {}", yaml_string(&report.major_brand))?; + writeln!(writer, "minor_version: {}", report.minor_version)?; + writeln!(writer, "compatible_brands:")?; + for brand in &report.compatible_brands { + writeln!(writer, "- {}", yaml_string(brand))?; + } + writeln!(writer, "fast_start: {}", report.fast_start)?; + writeln!(writer, "timescale: {}", report.timescale)?; + writeln!(writer, "duration: {}", report.duration)?; + writeln!( + writer, + "duration_seconds: {}", + format_seconds(report.duration_seconds) + )?; + writeln!(writer, "tracks:")?; + for track in &report.tracks { + write_yaml_media_characteristics_track(writer, track)?; + } + Ok(()) +} + +fn write_yaml_media_characteristics_track( + writer: &mut W, + track: &MediaCharacteristicsProbeTrackReport, +) -> io::Result<()> where W: Write, { @@ -461,13 +2228,57 @@ where format_seconds(track.duration_seconds) )?; writeln!(writer, " codec: {}", yaml_string(&track.codec))?; + writeln!( + writer, + " codec_family: {}", + yaml_string(&track.codec_family) + )?; writeln!(writer, " encrypted: {}", track.encrypted)?; + if let Some(handler_type) = track.handler_type.as_ref() { + writeln!(writer, " handler_type: {}", yaml_string(handler_type))?; + } + if let Some(language) = track.language.as_ref() { + writeln!(writer, " language: {}", yaml_string(language))?; + } + if let Some(sample_entry_type) = track.sample_entry_type.as_ref() { + writeln!( + writer, + " sample_entry_type: {}", + yaml_string(sample_entry_type) + )?; + } + if let Some(original_format) = track.original_format.as_ref() { + writeln!( + writer, + " original_format: {}", + yaml_string(original_format) + )?; + } + if let Some(protection_scheme_type) = track.protection_scheme_type.as_ref() { + writeln!( + writer, + " protection_scheme_type: {}", + yaml_string(protection_scheme_type) + )?; + } + if let Some(protection_scheme_version) = track.protection_scheme_version { + writeln!( + writer, + " protection_scheme_version: {protection_scheme_version}" + )?; + } if let Some(width) = track.width { writeln!(writer, " width: {width}")?; } if let Some(height) = track.height { writeln!(writer, " height: {height}")?; } + if let Some(channel_count) = track.channel_count { + writeln!(writer, " channel_count: {channel_count}")?; + } + if let Some(sample_rate) = track.sample_rate { + writeln!(writer, " sample_rate: {sample_rate}")?; + } if let Some(sample_num) = track.sample_num { writeln!(writer, " sample_num: {sample_num}")?; } @@ -483,9 +2294,266 @@ where if let Some(max_bitrate) = track.max_bitrate { writeln!(writer, " max_bitrate: {max_bitrate}")?; } + writeln!(writer, " codec_details:")?; + for (name, value) in codec_detail_yaml_fields(&track.codec_family, &track.codec_details) { + writeln!(writer, " {name}: {value}")?; + } + if has_media_characteristics(&track.media_characteristics) { + writeln!(writer, " media_characteristics:")?; + if let Some(value) = track.media_characteristics.declared_bitrate.as_ref() { + writeln!(writer, " declared_bitrate:")?; + writeln!(writer, " buffer_size_db: {}", value.buffer_size_db)?; + writeln!(writer, " max_bitrate: {}", value.max_bitrate)?; + writeln!(writer, " avg_bitrate: {}", value.avg_bitrate)?; + } + if let Some(value) = track.media_characteristics.color.as_ref() { + writeln!(writer, " color:")?; + writeln!( + writer, + " colour_type: {}", + yaml_string(&value.colour_type.to_string()) + )?; + if let Some(colour_primaries) = value.colour_primaries { + writeln!(writer, " colour_primaries: {colour_primaries}")?; + } + if let Some(transfer_characteristics) = value.transfer_characteristics { + writeln!( + writer, + " transfer_characteristics: {transfer_characteristics}" + )?; + } + if let Some(matrix_coefficients) = value.matrix_coefficients { + writeln!(writer, " matrix_coefficients: {matrix_coefficients}")?; + } + if let Some(full_range) = value.full_range { + writeln!(writer, " full_range: {full_range}")?; + } + if let Some(profile_size) = value.profile_size { + writeln!(writer, " profile_size: {profile_size}")?; + } + if let Some(unknown_size) = value.unknown_size { + writeln!(writer, " unknown_size: {unknown_size}")?; + } + } + if let Some(value) = track.media_characteristics.pixel_aspect_ratio.as_ref() { + writeln!(writer, " pixel_aspect_ratio:")?; + writeln!(writer, " h_spacing: {}", value.h_spacing)?; + writeln!(writer, " v_spacing: {}", value.v_spacing)?; + } + if let Some(value) = track.media_characteristics.field_order.as_ref() { + writeln!(writer, " field_order:")?; + writeln!(writer, " field_count: {}", value.field_count)?; + writeln!(writer, " field_ordering: {}", value.field_ordering)?; + writeln!(writer, " interlaced: {}", value.interlaced)?; + } + } Ok(()) } +fn codec_detail_yaml_fields( + codec_family: &str, + details: &TrackCodecDetails, +) -> Vec<(&'static str, String)> { + let mut fields = vec![("kind", yaml_string(codec_family))]; + match details { + TrackCodecDetails::Unknown => {} + TrackCodecDetails::Avc(details) => { + fields.push(( + "configuration_version", + details.configuration_version.to_string(), + )); + fields.push(("profile", details.profile.to_string())); + fields.push(( + "profile_compatibility", + details.profile_compatibility.to_string(), + )); + fields.push(("level", details.level.to_string())); + fields.push(("length_size", details.length_size.to_string())); + if let Some(chroma_format) = details.chroma_format { + fields.push(("chroma_format", chroma_format.to_string())); + } + if let Some(bit_depth_luma) = details.bit_depth_luma { + fields.push(("bit_depth_luma", bit_depth_luma.to_string())); + } + if let Some(bit_depth_chroma) = details.bit_depth_chroma { + fields.push(("bit_depth_chroma", bit_depth_chroma.to_string())); + } + } + TrackCodecDetails::Hevc(details) => { + fields.push(( + "configuration_version", + details.configuration_version.to_string(), + )); + fields.push(("profile_space", details.profile_space.to_string())); + fields.push(("tier_flag", details.tier_flag.to_string())); + fields.push(("profile_idc", details.profile_idc.to_string())); + fields.push(( + "profile_compatibility_mask", + details.profile_compatibility_mask.to_string(), + )); + fields.push(( + "constraint_indicator", + yaml_u8_array(&details.constraint_indicator), + )); + fields.push(("level_idc", details.level_idc.to_string())); + fields.push(( + "min_spatial_segmentation_idc", + details.min_spatial_segmentation_idc.to_string(), + )); + fields.push(("parallelism_type", details.parallelism_type.to_string())); + fields.push(("chroma_format_idc", details.chroma_format_idc.to_string())); + fields.push(("bit_depth_luma", details.bit_depth_luma.to_string())); + fields.push(("bit_depth_chroma", details.bit_depth_chroma.to_string())); + fields.push(("avg_frame_rate", details.avg_frame_rate.to_string())); + fields.push(( + "constant_frame_rate", + details.constant_frame_rate.to_string(), + )); + fields.push(( + "num_temporal_layers", + details.num_temporal_layers.to_string(), + )); + fields.push(("temporal_id_nested", details.temporal_id_nested.to_string())); + fields.push(("length_size", details.length_size.to_string())); + } + TrackCodecDetails::Av1(details) => { + fields.push(("seq_profile", details.seq_profile.to_string())); + fields.push(("seq_level_idx_0", details.seq_level_idx_0.to_string())); + fields.push(("seq_tier_0", details.seq_tier_0.to_string())); + fields.push(("bit_depth", details.bit_depth.to_string())); + fields.push(("monochrome", details.monochrome.to_string())); + fields.push(( + "chroma_subsampling_x", + details.chroma_subsampling_x.to_string(), + )); + fields.push(( + "chroma_subsampling_y", + details.chroma_subsampling_y.to_string(), + )); + fields.push(( + "chroma_sample_position", + details.chroma_sample_position.to_string(), + )); + if let Some(delay) = details.initial_presentation_delay_minus_one { + fields.push(("initial_presentation_delay_minus_one", delay.to_string())); + } + } + TrackCodecDetails::Vp8(details) | TrackCodecDetails::Vp9(details) => { + fields.push(("profile", details.profile.to_string())); + fields.push(("level", details.level.to_string())); + fields.push(("bit_depth", details.bit_depth.to_string())); + fields.push(("chroma_subsampling", details.chroma_subsampling.to_string())); + fields.push(("full_range", details.full_range.to_string())); + fields.push(("colour_primaries", details.colour_primaries.to_string())); + fields.push(( + "transfer_characteristics", + details.transfer_characteristics.to_string(), + )); + fields.push(( + "matrix_coefficients", + details.matrix_coefficients.to_string(), + )); + fields.push(( + "codec_initialization_data_size", + details.codec_initialization_data_size.to_string(), + )); + } + TrackCodecDetails::Mp4Audio(details) => { + fields.push(( + "object_type_indication", + details.object_type_indication.to_string(), + )); + fields.push(("audio_object_type", details.audio_object_type.to_string())); + fields.push(("channel_count", details.channel_count.to_string())); + if let Some(sample_rate) = details.sample_rate { + fields.push(("sample_rate", sample_rate.to_string())); + } + } + TrackCodecDetails::Opus(details) => { + fields.push(( + "output_channel_count", + details.output_channel_count.to_string(), + )); + fields.push(("pre_skip", details.pre_skip.to_string())); + fields.push(("input_sample_rate", details.input_sample_rate.to_string())); + fields.push(("output_gain", details.output_gain.to_string())); + fields.push(( + "channel_mapping_family", + details.channel_mapping_family.to_string(), + )); + if let Some(stream_count) = details.stream_count { + fields.push(("stream_count", stream_count.to_string())); + } + if let Some(coupled_count) = details.coupled_count { + fields.push(("coupled_count", coupled_count.to_string())); + } + if !details.channel_mapping.is_empty() { + fields.push(("channel_mapping", yaml_u8_array(&details.channel_mapping))); + } + } + TrackCodecDetails::Ac3(details) => { + fields.push(("sample_rate_code", details.sample_rate_code.to_string())); + fields.push(( + "bit_stream_identification", + details.bit_stream_identification.to_string(), + )); + fields.push(("bit_stream_mode", details.bit_stream_mode.to_string())); + fields.push(("audio_coding_mode", details.audio_coding_mode.to_string())); + fields.push(("lfe_on", details.lfe_on.to_string())); + fields.push(("bit_rate_code", details.bit_rate_code.to_string())); + } + TrackCodecDetails::Pcm(details) => { + fields.push(("format_flags", details.format_flags.to_string())); + fields.push(("sample_size", details.sample_size.to_string())); + } + TrackCodecDetails::XmlSubtitle(details) => { + fields.push(("namespace", yaml_string(&details.namespace))); + fields.push(("schema_location", yaml_string(&details.schema_location))); + fields.push(( + "auxiliary_mime_types", + yaml_string(&details.auxiliary_mime_types), + )); + } + TrackCodecDetails::TextSubtitle(details) => { + fields.push(("content_encoding", yaml_string(&details.content_encoding))); + fields.push(("mime_format", yaml_string(&details.mime_format))); + } + TrackCodecDetails::WebVtt(details) => { + if let Some(config) = details.config.as_ref() { + fields.push(("config", yaml_string(config))); + } + if let Some(source_label) = details.source_label.as_ref() { + fields.push(("source_label", yaml_string(source_label))); + } + } + } + + fields +} + +fn has_media_characteristics(characteristics: &TrackMediaCharacteristics) -> bool { + characteristics.declared_bitrate.is_some() + || characteristics.color.is_some() + || characteristics.pixel_aspect_ratio.is_some() + || characteristics.field_order.is_some() +} + +fn json_u8_array(values: &[u8]) -> String { + let mut rendered = String::from("["); + for (index, value) in values.iter().enumerate() { + if index != 0 { + rendered.push_str(", "); + } + rendered.push_str(&value.to_string()); + } + rendered.push(']'); + rendered +} + +fn yaml_u8_array(values: &[u8]) -> String { + json_u8_array(values) +} + fn json_string(value: &str) -> String { let mut escaped = String::from("\""); for ch in value.chars() { diff --git a/src/cli/pssh.rs b/src/cli/pssh.rs index 4b45b1f..4b0880a 100644 --- a/src/cli/pssh.rs +++ b/src/cli/pssh.rs @@ -8,13 +8,96 @@ use std::io::{self, Read, Seek, Write}; use crate::FourCc; use crate::boxes::iso23001_7::Pssh; use crate::codec::ImmutableBox; -use crate::extract::{ExtractError, extract_boxes_with_payload}; -use crate::walk::BoxPath; +use crate::extract::ExtractError; +use crate::walk::{BoxPath, WalkControl, WalkError, WalkHandle, walk_structure}; const MOOV: FourCc = FourCc::from_bytes(*b"moov"); const MOOF: FourCc = FourCc::from_bytes(*b"moof"); const PSSH: FourCc = FourCc::from_bytes(*b"pssh"); +/// Structured output format supported by the pssh-dump command. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PsshDumpFormat { + /// Pretty-printed JSON output. + Json, + /// Simple YAML output with stable field order. + Yaml, +} + +impl PsshDumpFormat { + fn parse(value: &str) -> Result, PsshDumpError> { + match value { + "text" => Ok(None), + "json" => Ok(Some(Self::Json)), + "yaml" => Ok(Some(Self::Yaml)), + other => Err(invalid_argument(format!( + "unsupported psshdump format: {other}" + ))), + } + } +} + +/// Additive selection controls for reusable `pssh` reports. +/// +/// Filters inside one category are combined with OR semantics, while different categories are +/// combined with AND semantics. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PsshReportFilter { + /// Parsed subtree selectors that scope which `pssh` paths are eligible for inclusion. + /// + /// These reuse the existing [`BoxPath`] parser, including `*` wildcard segments and the + /// special `` marker. The filter behaves like subtree selection, so `moov` matches + /// `moov/pssh` and `` matches every discovered `pssh` box. + pub paths: Vec, + /// Protection-system UUIDs that are allowed to match. + pub system_ids: Vec<[u8; 16]>, + /// Key IDs that are allowed to match. + pub kids: Vec<[u8; 16]>, +} + +impl PsshReportFilter { + /// Returns `true` when the filter leaves the report unscoped. + pub fn is_unfiltered(&self) -> bool { + self.paths.is_empty() && self.system_ids.is_empty() && self.kids.is_empty() + } +} + +/// Top-level structured `pssh` summary report used by JSON and YAML output. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PsshReport { + /// Parsed `pssh` entries in file order. + pub entries: Vec, +} + +/// One parsed `pssh` summary entry. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PsshEntryReport { + /// Zero-based file-order index used by the text formatter. + pub index: usize, + /// Slash-delimited path from the file root to the matched `pssh` box. + pub path: String, + /// Absolute file offset of the `pssh` header. + pub offset: u64, + /// Total box size including the header. + pub size: u64, + /// Parsed full-box version. + pub version: u8, + /// Parsed full-box flags. + pub flags: u32, + /// Formatted protection-system UUID. + pub system_id: String, + /// Parsed KID count field. + pub kid_count: u32, + /// Formatted key IDs carried by version `1` entries. + pub kids: Vec, + /// Parsed data-size field. + pub data_size: u32, + /// Raw `Data` field bytes. + pub data_bytes: Vec, + /// Base64 encoding of the exact serialized box bytes, including the header. + pub raw_box_base64: String, +} + /// Runs the pssh-dump subcommand with `args`, writing output to `stdout`. pub fn run(args: &[String], stdout: &mut W, stderr: &mut E) -> i32 where @@ -39,59 +122,637 @@ pub fn write_usage(writer: &mut W) -> io::Result<()> where W: Write, { - writeln!(writer, "USAGE: mp4forge psshdump INPUT.mp4") + writeln!(writer, "USAGE: mp4forge psshdump [OPTIONS] INPUT.mp4")?; + writeln!(writer)?; + writeln!(writer, "OPTIONS:")?; + writeln!( + writer, + " -format Output format (default: text)" + )?; + writeln!( + writer, + " -path Limit results to matching parsed subtrees (repeatable)" + )?; + writeln!( + writer, + " -system-id Limit results to matching protection-system IDs (repeatable)" + )?; + writeln!( + writer, + " -kid Limit results to matching key IDs (repeatable)" + )?; + Ok(()) } -/// Writes formatted `pssh` summaries discovered in `reader`. +/// Writes the existing human-readable `pssh` summaries discovered in `reader`. pub fn dump_pssh(reader: &mut R, writer: &mut W) -> Result<(), PsshDumpError> where R: Read + Seek, W: Write, { - let extracted = extract_boxes_with_payload( - reader, - None, - &[BoxPath::from([MOOV, PSSH]), BoxPath::from([MOOF, PSSH])], - )?; - - for (index, entry) in extracted.iter().enumerate() { - let pssh = entry - .payload - .as_ref() - .as_any() - .downcast_ref::() - .ok_or(PsshDumpError::UnexpectedPayloadType)?; - - entry.info.seek_to_start(reader)?; - let raw_len = - usize::try_from(entry.info.size()).map_err(|_| PsshDumpError::NumericOverflow)?; - let mut raw = vec![0_u8; raw_len]; - reader.read_exact(&mut raw)?; - - writeln!(writer, "{index}:")?; - writeln!(writer, " offset: {}", entry.info.offset())?; - writeln!(writer, " size: {}", entry.info.size())?; - writeln!(writer, " version: {}", pssh.version())?; - writeln!(writer, " flags: 0x{:06x}", pssh.flags())?; - writeln!(writer, " systemId: {}", format_uuid(&pssh.system_id))?; - writeln!(writer, " dataSize: {}", pssh.data_size)?; - writeln!(writer, " base64: \"{}\"", encode_base64(&raw))?; - writeln!(writer)?; + let report = build_pssh_report(reader)?; + write_text_report(writer, &report) +} + +/// Builds a reusable structured `pssh` summary report from one MP4 reader. +pub fn build_pssh_report(reader: &mut R) -> Result +where + R: Read + Seek, +{ + build_pssh_report_with_filters(reader, &PsshReportFilter::default()) +} + +/// Writes the existing human-readable `pssh` summaries discovered in `reader`, limited by +/// `filters`. +pub fn dump_pssh_with_filters( + reader: &mut R, + filters: &PsshReportFilter, + writer: &mut W, +) -> Result<(), PsshDumpError> +where + R: Read + Seek, + W: Write, +{ + let report = build_pssh_report_with_filters(reader, filters)?; + write_text_report(writer, &report) +} + +/// Builds a reusable structured `pssh` summary report from one MP4 reader, limited by `filters`. +pub fn build_pssh_report_with_filters( + reader: &mut R, + filters: &PsshReportFilter, +) -> Result +where + R: Read + Seek, +{ + let mut collector = PsshReportCollector { + filters, + next_index: 0, + entries: Vec::new(), + build_error: None, + }; + let result = walk_structure(reader, |handle| { + collect_pssh_report_entry(handle, &mut collector) + }); + if let Some(error) = collector.build_error { + return Err(error); } + result.map_err(walk_error_as_extract)?; + Ok(PsshReport { + entries: collector.entries, + }) +} - Ok(()) +/// Writes a structured `pssh` `report` in the selected `format`. +pub fn write_pssh_report( + writer: &mut W, + report: &PsshReport, + format: PsshDumpFormat, +) -> Result<(), PsshDumpError> +where + W: Write, +{ + match format { + PsshDumpFormat::Json => write_json_pssh_report(writer, report).map_err(PsshDumpError::Io), + PsshDumpFormat::Yaml => write_yaml_pssh_report(writer, report).map_err(PsshDumpError::Io), + } +} + +/// Writes one MP4 reader as a structured `pssh` JSON or YAML report. +pub fn dump_pssh_structured( + reader: &mut R, + format: PsshDumpFormat, + writer: &mut W, +) -> Result<(), PsshDumpError> +where + R: Read + Seek, + W: Write, +{ + dump_pssh_structured_with_filters(reader, &PsshReportFilter::default(), format, writer) +} + +/// Writes one MP4 reader as a structured `pssh` JSON or YAML report, limited by `filters`. +pub fn dump_pssh_structured_with_filters( + reader: &mut R, + filters: &PsshReportFilter, + format: PsshDumpFormat, + writer: &mut W, +) -> Result<(), PsshDumpError> +where + R: Read + Seek, + W: Write, +{ + let report = build_pssh_report_with_filters(reader, filters)?; + write_pssh_report(writer, &report, format) } fn run_inner(args: &[String], stdout: &mut W) -> Result<(), PsshDumpError> where W: Write, { - if args.len() != 1 { + let mut format = None; + let mut filters = PsshReportFilter::default(); + let mut input_path = None; + let mut index = 0usize; + while index < args.len() { + match args[index].as_str() { + "-format" | "--format" => { + let Some(value) = args.get(index + 1) else { + return Err(invalid_argument("missing value for -format")); + }; + format = PsshDumpFormat::parse(value)?; + index += 2; + } + "-path" | "--path" => { + let Some(value) = args.get(index + 1) else { + return Err(invalid_argument("missing value for -path")); + }; + let path = + BoxPath::parse(value).map_err(|error| invalid_argument(error.to_string()))?; + filters.paths.push(path); + index += 2; + } + "-system-id" | "--system-id" => { + let Some(value) = args.get(index + 1) else { + return Err(invalid_argument("missing value for -system-id")); + }; + let system_id = parse_uuid_filter(value, "system ID")?; + filters.system_ids.push(system_id); + index += 2; + } + "-kid" | "--kid" => { + let Some(value) = args.get(index + 1) else { + return Err(invalid_argument("missing value for -kid")); + }; + let kid = parse_uuid_filter(value, "KID")?; + filters.kids.push(kid); + index += 2; + } + "-h" | "--help" => return Err(PsshDumpError::UsageRequested), + value if value.starts_with('-') => { + return Err(invalid_argument(format!( + "unknown psshdump option: {value}" + ))); + } + value => { + if input_path.is_some() { + return Err(invalid_argument("psshdump accepts exactly one input path")); + } + input_path = Some(value); + index += 1; + } + } + } + + let Some(input_path) = input_path else { return Err(PsshDumpError::UsageRequested); + }; + + let mut file = File::open(input_path)?; + match format { + Some(format) => dump_pssh_structured_with_filters(&mut file, &filters, format, stdout), + None => dump_pssh_with_filters(&mut file, &filters, stdout), } +} - let mut file = File::open(&args[0])?; - dump_pssh(&mut file, stdout) +struct PsshReportCollector<'a> { + filters: &'a PsshReportFilter, + next_index: usize, + entries: Vec, + build_error: Option, +} + +fn collect_pssh_report_entry( + handle: &mut WalkHandle<'_, R>, + collector: &mut PsshReportCollector<'_>, +) -> Result +where + R: Read + Seek, +{ + if should_descend_pssh_path(handle.path().as_slice()) { + return Ok(WalkControl::Descend); + } + + if !is_pssh_path(handle.path().as_slice()) { + return Ok(WalkControl::Continue); + } + + let entry_index = collector.next_index; + collector.next_index += 1; + if !matches_path_filters(collector.filters, handle.path()) { + return Ok(WalkControl::Continue); + } + + let (payload, _) = handle.read_payload()?; + let Some(pssh) = payload.as_ref().as_any().downcast_ref::() else { + collector.build_error = Some(PsshDumpError::UnexpectedPayloadType); + return Err(io::Error::other("unexpected pssh payload type").into()); + }; + if !matches_system_id_filters(collector.filters, &pssh.system_id) + || !matches_kid_filters(collector.filters, &pssh.kids) + { + return Ok(WalkControl::Continue); + } + + let payload_bytes = read_payload_bytes(handle, &mut collector.build_error)?; + let mut raw_box = handle.info().encode(); + raw_box.extend_from_slice(&payload_bytes); + + collector.entries.push(PsshEntryReport { + index: entry_index, + path: handle.path().to_string(), + offset: handle.info().offset(), + size: handle.info().size(), + version: pssh.version(), + flags: pssh.flags(), + system_id: format_uuid(&pssh.system_id), + kid_count: pssh.kid_count, + kids: pssh.kids.iter().map(|kid| format_uuid(&kid.kid)).collect(), + data_size: pssh.data_size, + data_bytes: pssh.data.clone(), + raw_box_base64: encode_base64(&raw_box), + }); + + Ok(WalkControl::Continue) +} + +fn should_descend_pssh_path(path: &[FourCc]) -> bool { + matches!(path, [MOOV] | [MOOF]) +} + +fn is_pssh_path(path: &[FourCc]) -> bool { + matches!(path, [MOOV, PSSH] | [MOOF, PSSH]) +} + +fn matches_path_filters(filters: &PsshReportFilter, entry_path: &BoxPath) -> bool { + filters.paths.is_empty() + || filters.paths.iter().any(|path| { + let selected_vs_entry = path.compare_with(entry_path); + selected_vs_entry.exact_match || selected_vs_entry.forward_match + }) +} + +fn matches_system_id_filters(filters: &PsshReportFilter, system_id: &[u8; 16]) -> bool { + filters.system_ids.is_empty() + || filters + .system_ids + .iter() + .any(|candidate| candidate == system_id) +} + +fn matches_kid_filters( + filters: &PsshReportFilter, + kids: &[crate::boxes::iso23001_7::PsshKid], +) -> bool { + filters.kids.is_empty() + || kids + .iter() + .any(|kid| filters.kids.iter().any(|candidate| candidate == &kid.kid)) +} + +fn parse_uuid_filter(value: &str, label: &str) -> Result<[u8; 16], PsshDumpError> { + let mut digits = String::with_capacity(32); + for ch in value.chars() { + if ch == '-' { + continue; + } + digits.push(ch); + } + + if digits.len() != 32 { + return Err(invalid_argument(format!( + "invalid {label}: expected 32 hexadecimal digits with optional hyphens" + ))); + } + + let mut parsed = [0u8; 16]; + let bytes = digits.as_bytes(); + for (index, slot) in parsed.iter_mut().enumerate() { + let high = decode_hex_nibble(bytes[index * 2]).ok_or_else(|| { + invalid_argument(format!( + "invalid {label}: expected 32 hexadecimal digits with optional hyphens" + )) + })?; + let low = decode_hex_nibble(bytes[index * 2 + 1]).ok_or_else(|| { + invalid_argument(format!( + "invalid {label}: expected 32 hexadecimal digits with optional hyphens" + )) + })?; + *slot = (high << 4) | low; + } + + Ok(parsed) +} + +fn decode_hex_nibble(value: u8) -> Option { + match value { + b'0'..=b'9' => Some(value - b'0'), + b'a'..=b'f' => Some(value - b'a' + 10), + b'A'..=b'F' => Some(value - b'A' + 10), + _ => None, + } +} + +fn read_payload_bytes( + handle: &mut WalkHandle<'_, R>, + build_error: &mut Option, +) -> Result, WalkError> +where + R: Read + Seek, +{ + let payload_size = handle.info().payload_size().map_err(WalkError::Header)?; + let capacity = match usize::try_from(payload_size) { + Ok(capacity) => capacity, + Err(_) => { + *build_error = Some(PsshDumpError::NumericOverflow); + return Err(io::Error::other("payload too large").into()); + } + }; + let mut payload = Vec::with_capacity(capacity); + handle.read_data(&mut payload)?; + Ok(payload) +} + +fn write_text_report(writer: &mut W, report: &PsshReport) -> Result<(), PsshDumpError> +where + W: Write, +{ + for entry in &report.entries { + writeln!(writer, "{}:", entry.index)?; + writeln!(writer, " offset: {}", entry.offset)?; + writeln!(writer, " size: {}", entry.size)?; + writeln!(writer, " version: {}", entry.version)?; + writeln!(writer, " flags: 0x{:06x}", entry.flags)?; + writeln!(writer, " systemId: {}", entry.system_id)?; + writeln!(writer, " dataSize: {}", entry.data_size)?; + writeln!(writer, " base64: \"{}\"", entry.raw_box_base64)?; + writeln!(writer)?; + } + + Ok(()) +} + +fn write_json_pssh_report(writer: &mut W, report: &PsshReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "{{")?; + writeln!(writer, " \"Entries\": [")?; + for (index, entry) in report.entries.iter().enumerate() { + write_json_pssh_entry(writer, entry, 2)?; + let trailing = if index + 1 == report.entries.len() { + "" + } else { + "," + }; + writeln!(writer, "{trailing}")?; + } + writeln!(writer, " ]")?; + writeln!(writer, "}}") +} + +fn write_json_pssh_entry( + writer: &mut W, + entry: &PsshEntryReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + writeln!(writer, "{indent}{{")?; + write_json_field( + writer, + indent_level + 1, + "Index", + &entry.index.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Path", + &json_string(&entry.path), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Offset", + &entry.offset.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Size", + &entry.size.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Version", + &entry.version.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "Flags", + &entry.flags.to_string(), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "SystemId", + &json_string(&entry.system_id), + true, + )?; + write_json_field( + writer, + indent_level + 1, + "KidCount", + &entry.kid_count.to_string(), + true, + )?; + write_json_string_array_field(writer, indent_level + 1, "Kids", &entry.kids, true)?; + write_json_field( + writer, + indent_level + 1, + "DataSize", + &entry.data_size.to_string(), + true, + )?; + write_json_u8_array_field( + writer, + indent_level + 1, + "DataBytes", + &entry.data_bytes, + true, + )?; + write_json_field( + writer, + indent_level + 1, + "RawBoxBase64", + &json_string(&entry.raw_box_base64), + false, + )?; + write!(writer, "{indent}}}") +} + +fn write_json_u8_array_field( + writer: &mut W, + indent_level: usize, + name: &str, + values: &[u8], + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + write_json_array_field( + writer, + indent_level, + name, + &values.iter().map(u8::to_string).collect::>(), + trailing_comma, + ) +} + +fn write_json_string_array_field( + writer: &mut W, + indent_level: usize, + name: &str, + values: &[String], + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + write_json_array_field( + writer, + indent_level, + name, + &values + .iter() + .map(|value| json_string(value)) + .collect::>(), + trailing_comma, + ) +} + +fn write_json_array_field( + writer: &mut W, + indent_level: usize, + name: &str, + values: &[String], + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + let trailing = if trailing_comma { "," } else { "" }; + writeln!(writer, "{}\"{name}\": [", " ".repeat(indent_level))?; + for (index, value) in values.iter().enumerate() { + let trailing_value = if index + 1 == values.len() { "" } else { "," }; + writeln!( + writer, + "{}{value}{trailing_value}", + " ".repeat(indent_level + 1) + )?; + } + writeln!(writer, "{}]{trailing}", " ".repeat(indent_level)) +} + +fn write_json_field( + writer: &mut W, + indent_level: usize, + name: &str, + value: &str, + trailing_comma: bool, +) -> io::Result<()> +where + W: Write, +{ + let trailing = if trailing_comma { "," } else { "" }; + writeln!( + writer, + "{}\"{name}\": {value}{trailing}", + " ".repeat(indent_level) + ) +} + +fn write_yaml_pssh_report(writer: &mut W, report: &PsshReport) -> io::Result<()> +where + W: Write, +{ + writeln!(writer, "entries:")?; + for entry in &report.entries { + write_yaml_pssh_entry(writer, entry, 0)?; + } + Ok(()) +} + +fn write_yaml_pssh_entry( + writer: &mut W, + entry: &PsshEntryReport, + indent_level: usize, +) -> io::Result<()> +where + W: Write, +{ + let indent = " ".repeat(indent_level); + let child_indent = " ".repeat(indent_level + 1); + writeln!(writer, "{indent}- index: {}", entry.index)?; + writeln!(writer, "{child_indent}path: {}", yaml_string(&entry.path))?; + writeln!(writer, "{child_indent}offset: {}", entry.offset)?; + writeln!(writer, "{child_indent}size: {}", entry.size)?; + writeln!(writer, "{child_indent}version: {}", entry.version)?; + writeln!(writer, "{child_indent}flags: {}", entry.flags)?; + writeln!( + writer, + "{child_indent}system_id: {}", + yaml_string(&entry.system_id) + )?; + writeln!(writer, "{child_indent}kid_count: {}", entry.kid_count)?; + if entry.kids.is_empty() { + writeln!(writer, "{child_indent}kids: []")?; + } else { + writeln!(writer, "{child_indent}kids:")?; + for kid in &entry.kids { + writeln!( + writer, + "{}- {}", + " ".repeat(indent_level + 2), + yaml_string(kid) + )?; + } + } + writeln!(writer, "{child_indent}data_size: {}", entry.data_size)?; + if entry.data_bytes.is_empty() { + writeln!(writer, "{child_indent}data_bytes: []")?; + } else { + writeln!(writer, "{child_indent}data_bytes:")?; + for value in &entry.data_bytes { + writeln!(writer, "{}- {value}", " ".repeat(indent_level + 2))?; + } + } + writeln!( + writer, + "{child_indent}raw_box_base64: {}", + yaml_string(&entry.raw_box_base64) + )?; + Ok(()) +} + +fn walk_error_as_extract(error: WalkError) -> PsshDumpError { + PsshDumpError::Extract(ExtractError::from(error)) +} + +fn invalid_argument(message: impl Into) -> PsshDumpError { + PsshDumpError::Io(io::Error::new(io::ErrorKind::InvalidInput, message.into())) } fn format_uuid(value: &[u8; 16]) -> String { @@ -143,6 +804,36 @@ fn encode_base64(data: &[u8]) -> String { encoded } +fn json_string(value: &str) -> String { + let mut escaped = String::from("\""); + for ch in value.chars() { + match ch { + '"' => escaped.push_str("\\\""), + '\\' => escaped.push_str("\\\\"), + '\n' => escaped.push_str("\\n"), + '\r' => escaped.push_str("\\r"), + '\t' => escaped.push_str("\\t"), + ch if ch.is_control() => escaped.push_str(&format!("\\u{:04x}", ch as u32)), + ch => escaped.push(ch), + } + } + escaped.push('"'); + escaped +} + +fn yaml_string(value: &str) -> String { + if !value.is_empty() + && value.trim() == value + && value + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '.' | '-' | '_' | '/' | ' ')) + { + value.to_string() + } else { + format!("'{}'", value.replace('\'', "''")) + } +} + /// Errors raised while parsing `psshdump` arguments or formatting summaries. #[derive(Debug)] pub enum PsshDumpError { diff --git a/src/codec.rs b/src/codec.rs index 41a3dc8..96e81e4 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -626,6 +626,11 @@ fn select_hooks<'a>( } /// Owned field value transferred between descriptor code and concrete boxes. +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "kind", content = "value", rename_all = "snake_case") +)] #[derive(Clone, Debug, PartialEq, Eq)] pub enum FieldValue { Unsigned(u64), diff --git a/src/fourcc.rs b/src/fourcc.rs index 019fe17..30f2a60 100644 --- a/src/fourcc.rs +++ b/src/fourcc.rs @@ -103,6 +103,61 @@ impl fmt::Debug for FourCc { } } +#[cfg(feature = "serde")] +impl serde::Serialize for FourCc { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.serde_string()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for FourCc { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + Self::from_serde_str(&value).map_err(serde::de::Error::custom) + } +} + +#[cfg(feature = "serde")] +impl FourCc { + // Keep the serde form lossless instead of reusing the display formatter's `(c)` expansion. + fn serde_string(self) -> String { + if self.0.iter().all(|byte| matches!(byte, 0x20..=0x7e)) { + self.0.iter().map(|byte| char::from(*byte)).collect() + } else { + format!( + "0x{:02x}{:02x}{:02x}{:02x}", + self.0[0], self.0[1], self.0[2], self.0[3] + ) + } + } + + fn from_serde_str(value: &str) -> Result { + if let Some(hex) = value + .strip_prefix("0x") + .or_else(|| value.strip_prefix("0X")) + { + if hex.len() != 8 { + return Err(format!( + "hex fourcc values must contain exactly 8 digits, got {}", + hex.len() + )); + } + let parsed = u32::from_str_radix(hex, 16) + .map_err(|error| format!("invalid hex fourcc value: {error}"))?; + return Ok(Self::from_u32(parsed)); + } + + Self::try_from(value).map_err(|error| error.to_string()) + } +} + /// Error returned when a string does not contain exactly four bytes. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ParseFourCcError { diff --git a/src/probe.rs b/src/probe.rs index 01cdcef..505d2f8 100644 --- a/src/probe.rs +++ b/src/probe.rs @@ -8,11 +8,19 @@ use std::io::{self, Cursor, Read, Seek, SeekFrom}; use crate::BoxInfo; use crate::FourCc; use crate::bitio::BitReader; +use crate::boxes::av1::AV1CodecConfiguration; +use crate::boxes::etsi_ts_102_366::Dac3; use crate::boxes::iso14496_12::{ - AVCDecoderConfiguration, AudioSampleEntry, Co64, Ctts, Mvhd, Stco, Stsc, Stsz, Stts, Tfdt, - Tfhd, Tkhd, Trun, VisualSampleEntry, + AVCDecoderConfiguration, AudioSampleEntry, Btrt, Co64, Colr, Ctts, Fiel, + HEVCDecoderConfiguration, Mvhd, Pasp, Stco, Stsc, Stsz, Stts, TextSubtitleSampleEntry, Tfdt, + Tfhd, Tkhd, Trun, VisualSampleEntry, XMLSubtitleSampleEntry, }; +use crate::boxes::iso14496_12::{Frma, Hdlr, Schm}; use crate::boxes::iso14496_14::Esds; +use crate::boxes::iso14496_30::{WebVTTConfigurationBox, WebVTTSourceLabelBox}; +use crate::boxes::iso23001_5::PcmC; +use crate::boxes::opus::DOps; +use crate::boxes::vp::VpCodecConfiguration; use crate::codec::{CodecBox, CodecError, ImmutableBox, unmarshal}; use crate::extract::{ExtractError, ExtractedBox, extract_boxes, extract_boxes_with_payload}; use crate::header::HeaderError; @@ -28,17 +36,48 @@ const TKHD: FourCc = FourCc::from_bytes(*b"tkhd"); const EDTS: FourCc = FourCc::from_bytes(*b"edts"); const ELST: FourCc = FourCc::from_bytes(*b"elst"); const MDIA: FourCc = FourCc::from_bytes(*b"mdia"); +const HDLR: FourCc = FourCc::from_bytes(*b"hdlr"); const MDHD: FourCc = FourCc::from_bytes(*b"mdhd"); const MINF: FourCc = FourCc::from_bytes(*b"minf"); const STBL: FourCc = FourCc::from_bytes(*b"stbl"); const STSD: FourCc = FourCc::from_bytes(*b"stsd"); const AVC1: FourCc = FourCc::from_bytes(*b"avc1"); const AVCC: FourCc = FourCc::from_bytes(*b"avcC"); +const HEV1: FourCc = FourCc::from_bytes(*b"hev1"); +const HVC1: FourCc = FourCc::from_bytes(*b"hvc1"); +const HVCC: FourCc = FourCc::from_bytes(*b"hvcC"); +const AV01: FourCc = FourCc::from_bytes(*b"av01"); +const AV1C: FourCc = FourCc::from_bytes(*b"av1C"); +const VP08: FourCc = FourCc::from_bytes(*b"vp08"); +const VP09: FourCc = FourCc::from_bytes(*b"vp09"); +const VPCC: FourCc = FourCc::from_bytes(*b"vpcC"); const ENCV: FourCc = FourCc::from_bytes(*b"encv"); +const BTRT: FourCc = FourCc::from_bytes(*b"btrt"); +const COLR: FourCc = FourCc::from_bytes(*b"colr"); +const FIEL: FourCc = FourCc::from_bytes(*b"fiel"); +const PASP: FourCc = FourCc::from_bytes(*b"pasp"); const MP4A: FourCc = FourCc::from_bytes(*b"mp4a"); +const OPUS: FourCc = FourCc::from_bytes(*b"Opus"); +const DOPS: FourCc = FourCc::from_bytes(*b"dOps"); +const AC_3: FourCc = FourCc::from_bytes(*b"ac-3"); +const DAC3: FourCc = FourCc::from_bytes(*b"dac3"); +const IPCM: FourCc = FourCc::from_bytes(*b"ipcm"); +const FPCM: FourCc = FourCc::from_bytes(*b"fpcm"); +const PCMC: FourCc = FourCc::from_bytes(*b"pcmC"); const WAVE: FourCc = FourCc::from_bytes(*b"wave"); const ESDS: FourCc = FourCc::from_bytes(*b"esds"); const ENCA: FourCc = FourCc::from_bytes(*b"enca"); +const STPP: FourCc = FourCc::from_bytes(*b"stpp"); +const SBTT: FourCc = FourCc::from_bytes(*b"sbtt"); +const WVTT: FourCc = FourCc::from_bytes(*b"wvtt"); +const VTTC_CONFIG: FourCc = FourCc::from_bytes(*b"vttC"); +const VLAB: FourCc = FourCc::from_bytes(*b"vlab"); +const COLR_NCLX: FourCc = FourCc::from_bytes(*b"nclx"); +const COLR_RICC: FourCc = FourCc::from_bytes(*b"rICC"); +const COLR_PROF: FourCc = FourCc::from_bytes(*b"prof"); +const SINF: FourCc = FourCc::from_bytes(*b"sinf"); +const FRMA: FourCc = FourCc::from_bytes(*b"frma"); +const SCHM: FourCc = FourCc::from_bytes(*b"schm"); const STCO: FourCc = FourCc::from_bytes(*b"stco"); const CO64: FourCc = FourCc::from_bytes(*b"co64"); const STTS: FourCc = FourCc::from_bytes(*b"stts"); @@ -86,6 +125,48 @@ impl Default for ProbeInfo { } } +/// Additive controls for eager probe expansion. +/// +/// The existing [`probe`], [`probe_detailed`], and [`probe_codec_detailed`] entry points continue +/// to use the full eager behavior. Callers that need a lighter-weight summary can opt into the +/// companion `*_with_options` entry points and disable the expensive expansions they do not need. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ProbeOptions { + /// Whether to expand per-sample timing, composition-offset, and size data from `stts`, `ctts`, + /// and `stsz`. + pub expand_samples: bool, + /// Whether to expand per-chunk offsets and sample counts from `stco`/`co64` and `stsc`. + pub expand_chunks: bool, + /// Whether to aggregate fragmented segment summaries from `moof` boxes. + pub include_segments: bool, +} + +impl ProbeOptions { + /// Returns the existing eager probe behavior. + pub const fn full() -> Self { + Self { + expand_samples: true, + expand_chunks: true, + include_segments: true, + } + } + + /// Returns a lighter-weight probe behavior for large-file inspection. + pub const fn lightweight() -> Self { + Self { + expand_samples: false, + expand_chunks: false, + include_segments: false, + } + } +} + +impl Default for ProbeOptions { + fn default() -> Self { + Self::full() + } +} + /// Summary of one logical media track. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct TrackInfo { @@ -111,6 +192,520 @@ pub struct TrackInfo { pub mp4a: Option, } +/// Additive detailed probe summary that extends [`ProbeInfo`] without changing its public shape. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct DetailedProbeInfo { + /// Major brand from the root `ftyp` box. + pub major_brand: FourCc, + /// Minor version from the root `ftyp` box. + pub minor_version: u32, + /// Compatible brands listed by the root `ftyp` box. + pub compatible_brands: Vec, + /// Whether the `moov` box appears before the first `mdat`. + pub fast_start: bool, + /// Movie timescale from `mvhd`. + pub timescale: u32, + /// Movie duration from `mvhd`. + pub duration: u64, + /// Per-track detailed summaries extracted from `trak` boxes. + pub tracks: Vec, + /// Fragment summaries extracted from `moof` boxes. + pub segments: Vec, +} + +impl Default for DetailedProbeInfo { + fn default() -> Self { + Self { + major_brand: FourCc::ANY, + minor_version: 0, + compatible_brands: Vec::new(), + fast_start: false, + timescale: 0, + duration: 0, + tracks: Vec::new(), + segments: Vec::new(), + } + } +} + +/// Additive per-track summary that extends [`TrackInfo`] with richer sample-entry details. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct DetailedTrackInfo { + /// Backwards-compatible coarse summary preserved from [`TrackInfo`]. + pub summary: TrackInfo, + /// Normalized codec family derived from the sample entry or protected original format. + pub codec_family: TrackCodecFamily, + /// Handler type from `hdlr` when present. + pub handler_type: Option, + /// ISO-639-2 language code derived from `mdhd` when present. + pub language: Option, + /// Sample-entry box type found under `stsd`, including encrypted wrappers such as `encv`. + pub sample_entry_type: Option, + /// Original-format sample-entry type from `frma` when the track is protected. + pub original_format: Option, + /// Protection-scheme summary from `schm` when the track is protected. + pub protection_scheme: Option, + /// Display width from the visual sample entry when present. + pub display_width: Option, + /// Display height from the visual sample entry when present. + pub display_height: Option, + /// Channel count from the audio sample entry when present. + pub channel_count: Option, + /// Integer sample rate from the audio sample entry when present. + pub sample_rate: Option, +} + +/// Additive detailed probe summary that extends [`DetailedProbeInfo`] with codec-specific +/// configuration details. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CodecDetailedProbeInfo { + /// Major brand from the root `ftyp` box. + pub major_brand: FourCc, + /// Minor version from the root `ftyp` box. + pub minor_version: u32, + /// Compatible brands listed by the root `ftyp` box. + pub compatible_brands: Vec, + /// Whether the `moov` box appears before the first `mdat`. + pub fast_start: bool, + /// Movie timescale from `mvhd`. + pub timescale: u32, + /// Movie duration from `mvhd`. + pub duration: u64, + /// Per-track detailed summaries extracted from `trak` boxes. + pub tracks: Vec, + /// Fragment summaries extracted from `moof` boxes. + pub segments: Vec, +} + +impl Default for CodecDetailedProbeInfo { + fn default() -> Self { + Self { + major_brand: FourCc::ANY, + minor_version: 0, + compatible_brands: Vec::new(), + fast_start: false, + timescale: 0, + duration: 0, + tracks: Vec::new(), + segments: Vec::new(), + } + } +} + +/// Additive per-track summary that extends [`DetailedTrackInfo`] with parsed codec-specific +/// configuration. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CodecDetailedTrackInfo { + /// Backwards-compatible detailed track summary preserved from [`DetailedTrackInfo`]. + pub summary: DetailedTrackInfo, + /// Parsed codec-specific configuration when it is available for the track family. + pub codec_details: TrackCodecDetails, +} + +/// Additive detailed probe summary that extends [`CodecDetailedProbeInfo`] with media +/// characteristics already parsed by the crate. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MediaCharacteristicsProbeInfo { + /// Major brand from the root `ftyp` box. + pub major_brand: FourCc, + /// Minor version from the root `ftyp` box. + pub minor_version: u32, + /// Compatible brands listed by the root `ftyp` box. + pub compatible_brands: Vec, + /// Whether the `moov` box appears before the first `mdat`. + pub fast_start: bool, + /// Movie timescale from `mvhd`. + pub timescale: u32, + /// Movie duration from `mvhd`. + pub duration: u64, + /// Per-track detailed summaries extracted from `trak` boxes. + pub tracks: Vec, + /// Fragment summaries extracted from `moof` boxes. + pub segments: Vec, +} + +impl Default for MediaCharacteristicsProbeInfo { + fn default() -> Self { + Self { + major_brand: FourCc::ANY, + minor_version: 0, + compatible_brands: Vec::new(), + fast_start: false, + timescale: 0, + duration: 0, + tracks: Vec::new(), + segments: Vec::new(), + } + } +} + +/// Additive per-track summary that extends [`DetailedTrackInfo`] with parsed codec and media +/// characteristics. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct MediaCharacteristicsTrackInfo { + /// Backwards-compatible detailed track summary preserved from [`DetailedTrackInfo`]. + pub summary: DetailedTrackInfo, + /// Parsed codec-specific configuration when it is available for the track family. + pub codec_details: TrackCodecDetails, + /// Sample-entry media characteristics already parsed by the crate. + pub media_characteristics: TrackMediaCharacteristics, +} + +/// Media characteristics derived from sample-entry side boxes such as `btrt`, `colr`, `pasp`, +/// and `fiel`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TrackMediaCharacteristics { + /// Declared buffering and bitrate data from `btrt` when present. + pub declared_bitrate: Option, + /// Declared colorimetry data from `colr` when present. + pub color: Option, + /// Declared pixel aspect ratio from `pasp` when present. + pub pixel_aspect_ratio: Option, + /// Declared field-order hint from `fiel` when present. + pub field_order: Option, +} + +/// Declared buffering and bitrate values parsed from `btrt`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct DeclaredBitrateInfo { + /// Decoder buffer size from `BufferSizeDB`. + pub buffer_size_db: u32, + /// Peak bitrate from `MaxBitrate`. + pub max_bitrate: u32, + /// Average bitrate from `AvgBitrate`. + pub avg_bitrate: u32, +} + +/// Declared color information parsed from `colr`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ColorInfo { + /// Active colour-type discriminator such as `nclx`, `rICC`, or `prof`. + pub colour_type: FourCc, + /// Colour-primaries code when `ColourType` is `nclx`. + pub colour_primaries: Option, + /// Transfer-characteristics code when `ColourType` is `nclx`. + pub transfer_characteristics: Option, + /// Matrix-coefficients code when `ColourType` is `nclx`. + pub matrix_coefficients: Option, + /// Full-range flag when `ColourType` is `nclx`. + pub full_range: Option, + /// Embedded ICC profile size when `ColourType` stores profile bytes. + pub profile_size: Option, + /// Opaque payload size for unrecognized colour types. + pub unknown_size: Option, +} + +impl Default for ColorInfo { + fn default() -> Self { + Self { + colour_type: FourCc::ANY, + colour_primaries: None, + transfer_characteristics: None, + matrix_coefficients: None, + full_range: None, + profile_size: None, + unknown_size: None, + } + } +} + +/// Declared pixel aspect ratio parsed from `pasp`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PixelAspectRatioInfo { + /// Horizontal spacing numerator from `HSpacing`. + pub h_spacing: u32, + /// Vertical spacing denominator from `VSpacing`. + pub v_spacing: u32, +} + +/// Declared field-order hint parsed from `fiel`. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct FieldOrderInfo { + /// Stored field count from `FieldCount`. + pub field_count: u8, + /// Stored field-ordering code from `FieldOrdering`. + pub field_ordering: u8, + /// Whether the hint indicates multiple interlaced fields. + pub interlaced: bool, +} + +/// Parsed codec-specific configuration for one recognized track family. +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "kind", content = "value", rename_all = "snake_case") +)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub enum TrackCodecDetails { + /// No codec-specific configuration was parsed for the track. + #[default] + Unknown, + /// AVC decoder configuration parsed from `avcC`. + Avc(AvcCodecDetails), + /// HEVC decoder configuration parsed from `hvcC`. + Hevc(HevcCodecDetails), + /// AV1 decoder configuration parsed from `av1C`. + Av1(Av1CodecDetails), + /// VP8 decoder configuration parsed from `vpcC`. + Vp8(VpCodecDetails), + /// VP9 decoder configuration parsed from `vpcC`. + Vp9(VpCodecDetails), + /// MPEG-4 audio configuration parsed from `esds`. + Mp4Audio(Mp4AudioCodecDetails), + /// Opus decoder configuration parsed from `dOps`. + Opus(OpusCodecDetails), + /// AC-3 decoder configuration parsed from `dac3`. + Ac3(Ac3CodecDetails), + /// PCM configuration parsed from `pcmC`. + Pcm(PcmCodecDetails), + /// XML subtitle metadata parsed from `stpp`. + XmlSubtitle(XmlSubtitleCodecDetails), + /// Text subtitle metadata parsed from `sbtt`. + TextSubtitle(TextSubtitleCodecDetails), + /// WebVTT metadata parsed from `vttC` and `vlab`. + WebVtt(WebVttCodecDetails), +} + +/// Parsed AVC decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct AvcCodecDetails { + /// AVC decoder configuration version. + pub configuration_version: u8, + /// AVC profile indication. + pub profile: u8, + /// AVC profile-compatibility byte. + pub profile_compatibility: u8, + /// AVC level indication. + pub level: u8, + /// Length-prefix width used for NAL units. + pub length_size: u16, + /// Chroma-format identifier when the high-profile extension fields are present. + pub chroma_format: Option, + /// Bit depth for luma samples when the high-profile extension fields are present. + pub bit_depth_luma: Option, + /// Bit depth for chroma samples when the high-profile extension fields are present. + pub bit_depth_chroma: Option, +} + +/// Parsed HEVC decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct HevcCodecDetails { + /// HEVC decoder configuration version. + pub configuration_version: u8, + /// General profile space value. + pub profile_space: u8, + /// General tier flag. + pub tier_flag: bool, + /// General profile identifier. + pub profile_idc: u8, + /// Packed 32-bit compatibility mask derived from `general_profile_compatibility`. + pub profile_compatibility_mask: u32, + /// General constraint-indicator bytes. + pub constraint_indicator: [u8; 6], + /// General level identifier. + pub level_idc: u8, + /// Minimum spatial segmentation identifier. + pub min_spatial_segmentation_idc: u16, + /// Parallelism type. + pub parallelism_type: u8, + /// Chroma format identifier. + pub chroma_format_idc: u8, + /// Luma bit depth in bits. + pub bit_depth_luma: u8, + /// Chroma bit depth in bits. + pub bit_depth_chroma: u8, + /// Average frame rate from `hvcC`. + pub avg_frame_rate: u16, + /// Constant-frame-rate indicator. + pub constant_frame_rate: u8, + /// Number of temporal layers. + pub num_temporal_layers: u8, + /// Temporal-ID-nested indicator. + pub temporal_id_nested: u8, + /// Length-prefix width used for NAL units. + pub length_size: u16, +} + +/// Parsed AV1 decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Av1CodecDetails { + /// Sequence profile identifier. + pub seq_profile: u8, + /// Sequence level identifier. + pub seq_level_idx_0: u8, + /// Sequence tier identifier. + pub seq_tier_0: u8, + /// Decoded bit depth in bits. + pub bit_depth: u8, + /// Whether the sequence is monochrome. + pub monochrome: bool, + /// Horizontal chroma-subsampling flag. + pub chroma_subsampling_x: u8, + /// Vertical chroma-subsampling flag. + pub chroma_subsampling_y: u8, + /// Chroma sample-position code. + pub chroma_sample_position: u8, + /// Initial presentation-delay offset when the field is present. + pub initial_presentation_delay_minus_one: Option, +} + +/// Parsed VP8 or VP9 decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct VpCodecDetails { + /// VP profile identifier. + pub profile: u8, + /// VP level identifier. + pub level: u8, + /// Decoded bit depth in bits. + pub bit_depth: u8, + /// Chroma-subsampling code. + pub chroma_subsampling: u8, + /// Whether the stream uses full-range luma values. + pub full_range: bool, + /// Color-primaries code. + pub colour_primaries: u8, + /// Transfer-characteristics code. + pub transfer_characteristics: u8, + /// Matrix-coefficients code. + pub matrix_coefficients: u8, + /// Codec-initialization-data size from `vpcC`. + pub codec_initialization_data_size: u16, +} + +/// Parsed MPEG-4 audio decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Mp4AudioCodecDetails { + /// MPEG object-type indication from the decoder-config descriptor. + pub object_type_indication: u8, + /// AAC audio object type derived from the decoder-specific info payload. + pub audio_object_type: u8, + /// Channel count from the audio sample entry. + pub channel_count: u16, + /// Integer sample rate from the audio sample entry when present. + pub sample_rate: Option, +} + +/// Parsed Opus decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct OpusCodecDetails { + /// Output channel count from `dOps`. + pub output_channel_count: u8, + /// Decoder pre-skip from `dOps`. + pub pre_skip: u16, + /// Input sample rate from `dOps`. + pub input_sample_rate: u32, + /// Output gain from `dOps`. + pub output_gain: i16, + /// Channel-mapping-family identifier from `dOps`. + pub channel_mapping_family: u8, + /// Stream count when explicit channel mapping is present. + pub stream_count: Option, + /// Coupled-stream count when explicit channel mapping is present. + pub coupled_count: Option, + /// Channel-mapping table when explicit channel mapping is present. + pub channel_mapping: Vec, +} + +/// Parsed AC-3 decoder configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct Ac3CodecDetails { + /// Sample-rate code from `dac3`. + pub sample_rate_code: u8, + /// Bit-stream identification from `dac3`. + pub bit_stream_identification: u8, + /// Bit-stream mode from `dac3`. + pub bit_stream_mode: u8, + /// Audio-coding-mode from `dac3`. + pub audio_coding_mode: u8, + /// Whether the bitstream carries an LFE channel. + pub lfe_on: bool, + /// Bit-rate code from `dac3`. + pub bit_rate_code: u8, +} + +/// Parsed PCM configuration details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PcmCodecDetails { + /// PCM format flags from `pcmC`. + pub format_flags: u8, + /// PCM sample size from `pcmC`. + pub sample_size: u8, +} + +/// Parsed XML subtitle sample-entry details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct XmlSubtitleCodecDetails { + /// XML namespace string from `stpp`. + pub namespace: String, + /// XML schema-location string from `stpp`. + pub schema_location: String, + /// Auxiliary MIME types from `stpp`. + pub auxiliary_mime_types: String, +} + +/// Parsed text subtitle sample-entry details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct TextSubtitleCodecDetails { + /// Content-encoding label from `sbtt`. + pub content_encoding: String, + /// MIME format label from `sbtt`. + pub mime_format: String, +} + +/// Parsed WebVTT sample-entry details. +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct WebVttCodecDetails { + /// Configuration payload from `vttC` when present. + pub config: Option, + /// Source-label payload from `vlab` when present. + pub source_label: Option, +} + +#[derive(Default)] +struct TrackCodecConfigRefs<'a> { + avcc: Option<&'a AVCDecoderConfiguration>, + hvcc: Option<&'a HEVCDecoderConfiguration>, + av1c: Option<&'a AV1CodecConfiguration>, + vpcc: Option<&'a VpCodecConfiguration>, + dops: Option<&'a DOps>, + dac3: Option<&'a Dac3>, + pcmc: Option<&'a PcmC>, + xml_subtitle_sample_entry: Option<&'a XMLSubtitleSampleEntry>, + text_subtitle_sample_entry: Option<&'a TextSubtitleSampleEntry>, + webvtt_configuration: Option<&'a WebVTTConfigurationBox>, + webvtt_source_label: Option<&'a WebVTTSourceLabelBox>, +} + +#[derive(Default)] +struct TrackMediaCharacteristicRefs<'a> { + btrt: Option<&'a Btrt>, + colr: Option<&'a Colr>, + pasp: Option<&'a Pasp>, + fiel: Option<&'a Fiel>, +} + +struct ParsedRichTrackInfo { + summary: DetailedTrackInfo, + codec_details: TrackCodecDetails, + media_characteristics: TrackMediaCharacteristics, +} + /// Coarse codec classification used by the probe surface. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum TrackCodec { @@ -123,6 +718,56 @@ pub enum TrackCodec { Mp4a, } +/// Normalized codec family derived from the sample entry or protected original format. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum TrackCodecFamily { + /// No recognized codec family was derived. + #[default] + Unknown, + /// AVC/H.264 video. + Avc, + /// HEVC/H.265 video. + Hevc, + /// AV1 video. + Av1, + /// VP8 video. + Vp8, + /// VP9 video. + Vp9, + /// MPEG-4 audio carried by `mp4a`. + Mp4Audio, + /// Opus audio. + Opus, + /// AC-3 audio. + Ac3, + /// PCM audio carried by `ipcm` or `fpcm`. + Pcm, + /// XML subtitle text carried by `stpp`. + XmlSubtitle, + /// Plain-text subtitle data carried by `sbtt`. + TextSubtitle, + /// WebVTT text carried by `wvtt`. + WebVtt, +} + +/// Protection-scheme summary derived from `schm`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtectionSchemeInfo { + /// Protection scheme type from `schm`. + pub scheme_type: FourCc, + /// Protection scheme version from `schm`. + pub scheme_version: u32, +} + +impl Default for ProtectionSchemeInfo { + fn default() -> Self { + Self { + scheme_type: FourCc::ANY, + scheme_version: 0, + } + } +} + /// One edit-list entry from `elst`. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct EditListEntry { @@ -212,25 +857,130 @@ pub struct SegmentInfo { pub size: u32, } -/// Probes a file and returns high-level movie, track, and fragment summaries. +/// Probes a file and returns the backwards-compatible coarse movie, track, and fragment summary. +/// +/// For richer sample-entry, handler, language, and protection metadata, use [`probe_detailed`]. pub fn probe(reader: &mut R) -> Result where R: Read + Seek, { - let infos = extract_boxes( - reader, - None, - &[ - BoxPath::from([FTYP]), - BoxPath::from([MOOV]), - BoxPath::from([MOOV, MVHD]), - BoxPath::from([MOOV, TRAK]), - BoxPath::from([MOOF]), - BoxPath::from([MDAT]), - ], - )?; + probe_with_options(reader, ProbeOptions::default()) +} + +/// Probes a file with additive expansion controls and returns the backwards-compatible coarse +/// movie, track, and fragment summary. +pub fn probe_with_options(reader: &mut R, options: ProbeOptions) -> Result +where + R: Read + Seek, +{ + Ok(strip_probe_details(probe_detailed_with_options( + reader, options, + )?)) +} + +/// Probes a file and returns an additive detailed movie, track, and fragment summary. +pub fn probe_detailed(reader: &mut R) -> Result +where + R: Read + Seek, +{ + probe_detailed_with_options(reader, ProbeOptions::default()) +} + +/// Probes a file with additive expansion controls and returns the detailed movie, track, and +/// fragment summary. +pub fn probe_detailed_with_options( + reader: &mut R, + options: ProbeOptions, +) -> Result +where + R: Read + Seek, +{ + Ok(strip_codec_details(probe_codec_detailed_with_options( + reader, options, + )?)) +} + +/// Probes a file and returns an additive detailed summary with parsed codec-specific +/// configuration when it is available. +pub fn probe_codec_detailed(reader: &mut R) -> Result +where + R: Read + Seek, +{ + probe_codec_detailed_with_options(reader, ProbeOptions::default()) +} + +/// Probes a file with additive expansion controls and returns the codec-detailed summary. +pub fn probe_codec_detailed_with_options( + reader: &mut R, + options: ProbeOptions, +) -> Result +where + R: Read + Seek, +{ + let paths = root_probe_box_paths(options); + let infos = extract_boxes(reader, None, &paths)?; + + let mut summary = CodecDetailedProbeInfo::default(); + let mut mdat_appeared = false; + + for info in infos { + match info.box_type() { + FTYP => { + let ftyp = read_payload_as::<_, crate::boxes::iso14496_12::Ftyp>(reader, &info)?; + summary.major_brand = ftyp.major_brand; + summary.minor_version = ftyp.minor_version; + summary.compatible_brands = ftyp.compatible_brands; + } + MOOV => { + summary.fast_start = !mdat_appeared; + } + MVHD => { + let mvhd = read_payload_as::<_, Mvhd>(reader, &info)?; + summary.timescale = mvhd.timescale; + summary.duration = mvhd.duration(); + } + TRAK => { + summary + .tracks + .push(probe_trak_codec_detailed(reader, &info, options)?); + } + MOOF => { + if options.include_segments { + summary.segments.push(probe_moof(reader, &info)?); + } + } + MDAT => { + mdat_appeared = true; + } + _ => {} + } + } + + Ok(summary) +} + +/// Probes a file and returns an additive summary with parsed codec and media characteristics. +pub fn probe_media_characteristics( + reader: &mut R, +) -> Result +where + R: Read + Seek, +{ + probe_media_characteristics_with_options(reader, ProbeOptions::default()) +} + +/// Probes a file with additive expansion controls and returns the media-characteristics summary. +pub fn probe_media_characteristics_with_options( + reader: &mut R, + options: ProbeOptions, +) -> Result +where + R: Read + Seek, +{ + let paths = root_probe_box_paths(options); + let infos = extract_boxes(reader, None, &paths)?; - let mut summary = ProbeInfo::default(); + let mut summary = MediaCharacteristicsProbeInfo::default(); let mut mdat_appeared = false; for info in infos { @@ -250,10 +1000,14 @@ where summary.duration = mvhd.duration(); } TRAK => { - summary.tracks.push(probe_trak(reader, &info)?); + summary + .tracks + .push(probe_trak_media_characteristics(reader, &info, options)?); } MOOF => { - summary.segments.push(probe_moof(reader, &info)?); + if options.include_segments { + summary.segments.push(probe_moof(reader, &info)?); + } } MDAT => { mdat_appeared = true; @@ -265,8 +1019,8 @@ where Ok(summary) } -/// Probes an in-memory MP4 byte slice and returns high-level movie, track, and fragment -/// summaries. +/// Probes an in-memory MP4 byte slice and returns the coarse movie, track, and fragment +/// summary. /// /// This is equivalent to calling [`probe`] with `Cursor<&[u8]>`. pub fn probe_bytes(input: &[u8]) -> Result { @@ -274,6 +1028,72 @@ pub fn probe_bytes(input: &[u8]) -> Result { probe(&mut reader) } +/// Probes an in-memory MP4 byte slice with additive expansion controls and returns the coarse +/// movie, track, and fragment summary. +pub fn probe_bytes_with_options( + input: &[u8], + options: ProbeOptions, +) -> Result { + let mut reader = Cursor::new(input); + probe_with_options(&mut reader, options) +} + +/// Probes an in-memory MP4 byte slice and returns the additive detailed summary. +/// +/// This is equivalent to calling [`probe_detailed`] with `Cursor<&[u8]>`. +pub fn probe_detailed_bytes(input: &[u8]) -> Result { + let mut reader = Cursor::new(input); + probe_detailed(&mut reader) +} + +/// Probes an in-memory MP4 byte slice with additive expansion controls and returns the detailed +/// summary. +pub fn probe_detailed_bytes_with_options( + input: &[u8], + options: ProbeOptions, +) -> Result { + let mut reader = Cursor::new(input); + probe_detailed_with_options(&mut reader, options) +} + +/// Probes an in-memory MP4 byte slice and returns the additive codec-detailed summary. +/// +/// This is equivalent to calling [`probe_codec_detailed`] with `Cursor<&[u8]>`. +pub fn probe_codec_detailed_bytes(input: &[u8]) -> Result { + let mut reader = Cursor::new(input); + probe_codec_detailed(&mut reader) +} + +/// Probes an in-memory MP4 byte slice with additive expansion controls and returns the +/// codec-detailed summary. +pub fn probe_codec_detailed_bytes_with_options( + input: &[u8], + options: ProbeOptions, +) -> Result { + let mut reader = Cursor::new(input); + probe_codec_detailed_with_options(&mut reader, options) +} + +/// Probes an in-memory MP4 byte slice and returns the additive media-characteristics summary. +/// +/// This is equivalent to calling [`probe_media_characteristics`] with `Cursor<&[u8]>`. +pub fn probe_media_characteristics_bytes( + input: &[u8], +) -> Result { + let mut reader = Cursor::new(input); + probe_media_characteristics(&mut reader) +} + +/// Probes an in-memory MP4 byte slice with additive expansion controls and returns the +/// media-characteristics summary. +pub fn probe_media_characteristics_bytes_with_options( + input: &[u8], + options: ProbeOptions, +) -> Result { + let mut reader = Cursor::new(input); + probe_media_characteristics_with_options(&mut reader, options) +} + /// Legacy fragmented-file probe entry point that currently aliases [`probe`]. pub fn probe_fra(reader: &mut R) -> Result where @@ -282,6 +1102,34 @@ where probe(reader) } +/// Legacy fragmented-file detailed probe entry point that currently aliases [`probe_detailed`]. +pub fn probe_fra_detailed(reader: &mut R) -> Result +where + R: Read + Seek, +{ + probe_detailed(reader) +} + +/// Legacy fragmented-file codec-detailed probe entry point that currently aliases +/// [`probe_codec_detailed`]. +pub fn probe_fra_codec_detailed(reader: &mut R) -> Result +where + R: Read + Seek, +{ + probe_codec_detailed(reader) +} + +/// Legacy fragmented-file media-characteristics probe entry point that currently aliases +/// [`probe_media_characteristics`]. +pub fn probe_fra_media_characteristics( + reader: &mut R, +) -> Result +where + R: Read + Seek, +{ + probe_media_characteristics(reader) +} + /// Legacy fragmented-file probe entry point for in-memory MP4 bytes. /// /// This currently aliases [`probe_bytes`] for callers that already use the `probe_fra` naming. @@ -290,6 +1138,33 @@ pub fn probe_fra_bytes(input: &[u8]) -> Result { probe_fra(&mut reader) } +/// Legacy fragmented-file detailed probe entry point for in-memory MP4 bytes. +/// +/// This currently aliases [`probe_detailed_bytes`] for callers that already use the +/// `probe_fra` naming. +pub fn probe_fra_detailed_bytes(input: &[u8]) -> Result { + let mut reader = Cursor::new(input); + probe_fra_detailed(&mut reader) +} + +/// Legacy fragmented-file codec-detailed probe entry point for in-memory MP4 bytes. +/// +/// This currently aliases [`probe_codec_detailed_bytes`] for callers that already use the +/// `probe_fra` naming. +pub fn probe_fra_codec_detailed_bytes(input: &[u8]) -> Result { + let mut reader = Cursor::new(input); + probe_fra_codec_detailed(&mut reader) +} + +/// This currently aliases [`probe_media_characteristics_bytes`] for callers that already use the +/// fragmented-file helper naming. +pub fn probe_fra_media_characteristics_bytes( + input: &[u8], +) -> Result { + let mut reader = Cursor::new(input); + probe_fra_media_characteristics(&mut reader) +} + /// Detects the AAC object profile exposed by an `esds` descriptor stream. pub fn detect_aac_profile(esds: &Esds) -> Result, ProbeError> { let Some(decoder_config) = esds.decoder_config_descriptor() else { @@ -502,42 +1377,203 @@ pub fn max_segment_bitrate(segments: &[SegmentInfo], track_id: u32, timescale: u max_bitrate } -fn probe_trak(reader: &mut R, parent: &BoxInfo) -> Result -where - R: Read + Seek, -{ - let boxes = extract_boxes_with_payload( - reader, - Some(parent), - &[ - BoxPath::from([TKHD]), - BoxPath::from([EDTS, ELST]), - BoxPath::from([MDIA, MDHD]), - BoxPath::from([MDIA, MINF, STBL, STSD, AVC1]), - BoxPath::from([MDIA, MINF, STBL, STSD, AVC1, AVCC]), - BoxPath::from([MDIA, MINF, STBL, STSD, ENCV]), - BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, AVCC]), - BoxPath::from([MDIA, MINF, STBL, STSD, MP4A]), - BoxPath::from([MDIA, MINF, STBL, STSD, MP4A, ESDS]), - BoxPath::from([MDIA, MINF, STBL, STSD, MP4A, WAVE, ESDS]), - BoxPath::from([MDIA, MINF, STBL, STSD, ENCA]), - BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, ESDS]), +fn strip_probe_details(details: DetailedProbeInfo) -> ProbeInfo { + ProbeInfo { + major_brand: details.major_brand, + minor_version: details.minor_version, + compatible_brands: details.compatible_brands, + fast_start: details.fast_start, + timescale: details.timescale, + duration: details.duration, + tracks: details + .tracks + .into_iter() + .map(|track| track.summary) + .collect(), + segments: details.segments, + } +} + +fn strip_codec_details(details: CodecDetailedProbeInfo) -> DetailedProbeInfo { + DetailedProbeInfo { + major_brand: details.major_brand, + minor_version: details.minor_version, + compatible_brands: details.compatible_brands, + fast_start: details.fast_start, + timescale: details.timescale, + duration: details.duration, + tracks: details + .tracks + .into_iter() + .map(|track| track.summary) + .collect(), + segments: details.segments, + } +} + +fn root_probe_box_paths(options: ProbeOptions) -> Vec { + let mut paths = vec![ + BoxPath::from([FTYP]), + BoxPath::from([MOOV]), + BoxPath::from([MOOV, MVHD]), + BoxPath::from([MOOV, TRAK]), + BoxPath::from([MDAT]), + ]; + if options.include_segments { + paths.push(BoxPath::from([MOOF])); + } + paths +} + +fn track_probe_box_paths(options: ProbeOptions) -> Vec { + let visual_sample_entries = [AVC1, HEV1, HVC1, AV01, VP08, VP09, ENCV]; + let audio_sample_entries = [MP4A, OPUS, AC_3, IPCM, FPCM, ENCA]; + let mut paths = vec![ + BoxPath::from([TKHD]), + BoxPath::from([EDTS, ELST]), + BoxPath::from([MDIA, MDHD]), + BoxPath::from([MDIA, HDLR]), + BoxPath::from([MDIA, MINF, STBL, STSD, AVC1]), + BoxPath::from([MDIA, MINF, STBL, STSD, AVC1, AVCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, HEV1]), + BoxPath::from([MDIA, MINF, STBL, STSD, HEV1, HVCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, HVC1]), + BoxPath::from([MDIA, MINF, STBL, STSD, HVC1, HVCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, AV01]), + BoxPath::from([MDIA, MINF, STBL, STSD, AV01, AV1C]), + BoxPath::from([MDIA, MINF, STBL, STSD, VP08]), + BoxPath::from([MDIA, MINF, STBL, STSD, VP08, VPCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, VP09]), + BoxPath::from([MDIA, MINF, STBL, STSD, VP09, VPCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, AVCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, HVCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, AV1C]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, VPCC]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, SINF, FRMA]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, SINF, SCHM]), + BoxPath::from([MDIA, MINF, STBL, STSD, MP4A]), + BoxPath::from([MDIA, MINF, STBL, STSD, MP4A, ESDS]), + BoxPath::from([MDIA, MINF, STBL, STSD, MP4A, WAVE, ESDS]), + BoxPath::from([MDIA, MINF, STBL, STSD, OPUS]), + BoxPath::from([MDIA, MINF, STBL, STSD, OPUS, DOPS]), + BoxPath::from([MDIA, MINF, STBL, STSD, AC_3]), + BoxPath::from([MDIA, MINF, STBL, STSD, AC_3, DAC3]), + BoxPath::from([MDIA, MINF, STBL, STSD, IPCM]), + BoxPath::from([MDIA, MINF, STBL, STSD, IPCM, PCMC]), + BoxPath::from([MDIA, MINF, STBL, STSD, FPCM]), + BoxPath::from([MDIA, MINF, STBL, STSD, FPCM, PCMC]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, ESDS]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, WAVE, ESDS]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, DOPS]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, DAC3]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, PCMC]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, SINF, FRMA]), + BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, SINF, SCHM]), + BoxPath::from([MDIA, MINF, STBL, STSD, STPP]), + BoxPath::from([MDIA, MINF, STBL, STSD, SBTT]), + BoxPath::from([MDIA, MINF, STBL, STSD, WVTT]), + BoxPath::from([MDIA, MINF, STBL, STSD, WVTT, VTTC_CONFIG]), + BoxPath::from([MDIA, MINF, STBL, STSD, WVTT, VLAB]), + ]; + + for sample_entry in visual_sample_entries { + paths.extend([ + BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, BTRT]), + BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, COLR]), + BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, PASP]), + BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, FIEL]), + ]); + } + + for sample_entry in audio_sample_entries { + paths.push(BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, BTRT])); + } + + if options.expand_chunks { + paths.extend([ BoxPath::from([MDIA, MINF, STBL, STCO]), BoxPath::from([MDIA, MINF, STBL, CO64]), + BoxPath::from([MDIA, MINF, STBL, STSC]), + ]); + } + + if options.expand_samples { + paths.extend([ BoxPath::from([MDIA, MINF, STBL, STTS]), BoxPath::from([MDIA, MINF, STBL, CTTS]), - BoxPath::from([MDIA, MINF, STBL, STSC]), BoxPath::from([MDIA, MINF, STBL, STSZ]), - ], - )?; + ]); + } + + paths +} + +fn probe_trak_codec_detailed( + reader: &mut R, + parent: &BoxInfo, + options: ProbeOptions, +) -> Result +where + R: Read + Seek, +{ + let track = probe_trak_rich_details(reader, parent, options)?; + Ok(CodecDetailedTrackInfo { + summary: track.summary, + codec_details: track.codec_details, + }) +} + +fn probe_trak_media_characteristics( + reader: &mut R, + parent: &BoxInfo, + options: ProbeOptions, +) -> Result +where + R: Read + Seek, +{ + let track = probe_trak_rich_details(reader, parent, options)?; + Ok(MediaCharacteristicsTrackInfo { + summary: track.summary, + codec_details: track.codec_details, + media_characteristics: track.media_characteristics, + }) +} + +fn probe_trak_rich_details( + reader: &mut R, + parent: &BoxInfo, + options: ProbeOptions, +) -> Result +where + R: Read + Seek, +{ + let paths = track_probe_box_paths(options); + let boxes = extract_boxes_with_payload(reader, Some(parent), &paths)?; - let mut track = TrackInfo::default(); + let mut track = DetailedTrackInfo::default(); let mut tkhd = None; let mut mdhd = None; let mut visual_sample_entry = None; let mut avcc = None; + let mut hvcc = None; + let mut av1c = None; + let mut vpcc = None; let mut audio_sample_entry = None; let mut esds = None; + let mut dops = None; + let mut dac3 = None; + let mut pcmc = None; + let mut xml_subtitle_sample_entry = None; + let mut text_subtitle_sample_entry = None; + let mut webvtt_configuration = None; + let mut webvtt_source_label = None; + let mut btrt = None; + let mut colr = None; + let mut pasp = None; + let mut fiel = None; + let mut original_format = None; let mut stco = None; let mut co64 = None; let mut stts = None; @@ -549,12 +1585,12 @@ where match extracted.info.box_type() { TKHD => { let payload = downcast_clone::(&extracted)?; - track.track_id = payload.track_id; + track.summary.track_id = payload.track_id; tkhd = Some(payload); } ELST => { let elst = downcast_clone::(&extracted)?; - track.edit_list = elst + track.summary.edit_list = elst .entries .iter() .enumerate() @@ -566,34 +1602,154 @@ where } MDHD => { let payload = downcast_clone::(&extracted)?; - track.timescale = payload.timescale; - track.duration = payload.duration(); + track.summary.timescale = payload.timescale; + track.summary.duration = payload.duration(); + track.language = Some(decode_language(payload.language)); mdhd = Some(payload); } + HDLR => { + let payload = downcast_clone::(&extracted)?; + track.handler_type = Some(payload.handler_type); + } AVC1 => { - track.codec = TrackCodec::Avc1; + track.summary.codec = TrackCodec::Avc1; + track.codec_family = TrackCodecFamily::Avc; + track.sample_entry_type = Some(AVC1); visual_sample_entry = Some(downcast_clone::(&extracted)?); } AVCC => { avcc = Some(downcast_clone::(&extracted)?); } + HVCC => { + hvcc = Some(downcast_clone::(&extracted)?); + } + HEV1 => { + track.codec_family = TrackCodecFamily::Hevc; + track.sample_entry_type = Some(HEV1); + visual_sample_entry = Some(downcast_clone::(&extracted)?); + } + HVC1 => { + track.codec_family = TrackCodecFamily::Hevc; + track.sample_entry_type = Some(HVC1); + visual_sample_entry = Some(downcast_clone::(&extracted)?); + } + AV01 => { + track.codec_family = TrackCodecFamily::Av1; + track.sample_entry_type = Some(AV01); + visual_sample_entry = Some(downcast_clone::(&extracted)?); + } + AV1C => { + av1c = Some(downcast_clone::(&extracted)?); + } + VP08 => { + track.codec_family = TrackCodecFamily::Vp8; + track.sample_entry_type = Some(VP08); + visual_sample_entry = Some(downcast_clone::(&extracted)?); + } + VP09 => { + track.codec_family = TrackCodecFamily::Vp9; + track.sample_entry_type = Some(VP09); + visual_sample_entry = Some(downcast_clone::(&extracted)?); + } + VPCC => { + vpcc = Some(downcast_clone::(&extracted)?); + } ENCV => { - track.codec = TrackCodec::Avc1; - track.encrypted = true; + track.summary.codec = TrackCodec::Avc1; + track.summary.encrypted = true; + track.sample_entry_type = Some(ENCV); visual_sample_entry = Some(downcast_clone::(&extracted)?); } MP4A => { - track.codec = TrackCodec::Mp4a; + track.summary.codec = TrackCodec::Mp4a; + track.codec_family = TrackCodecFamily::Mp4Audio; + track.sample_entry_type = Some(MP4A); audio_sample_entry = Some(downcast_clone::(&extracted)?); } ENCA => { - track.codec = TrackCodec::Mp4a; - track.encrypted = true; + track.summary.codec = TrackCodec::Mp4a; + track.summary.encrypted = true; + track.sample_entry_type = Some(ENCA); + audio_sample_entry = Some(downcast_clone::(&extracted)?); + } + OPUS => { + track.codec_family = TrackCodecFamily::Opus; + track.sample_entry_type = Some(OPUS); + audio_sample_entry = Some(downcast_clone::(&extracted)?); + } + DOPS => { + dops = Some(downcast_clone::(&extracted)?); + } + AC_3 => { + track.codec_family = TrackCodecFamily::Ac3; + track.sample_entry_type = Some(AC_3); + audio_sample_entry = Some(downcast_clone::(&extracted)?); + } + DAC3 => { + dac3 = Some(downcast_clone::(&extracted)?); + } + IPCM => { + track.codec_family = TrackCodecFamily::Pcm; + track.sample_entry_type = Some(IPCM); audio_sample_entry = Some(downcast_clone::(&extracted)?); } + FPCM => { + track.codec_family = TrackCodecFamily::Pcm; + track.sample_entry_type = Some(FPCM); + audio_sample_entry = Some(downcast_clone::(&extracted)?); + } + PCMC => { + pcmc = Some(downcast_clone::(&extracted)?); + } + STPP => { + track.codec_family = TrackCodecFamily::XmlSubtitle; + track.sample_entry_type = Some(STPP); + xml_subtitle_sample_entry = + Some(downcast_clone::(&extracted)?); + } + SBTT => { + track.codec_family = TrackCodecFamily::TextSubtitle; + track.sample_entry_type = Some(SBTT); + text_subtitle_sample_entry = + Some(downcast_clone::(&extracted)?); + } + WVTT => { + track.codec_family = TrackCodecFamily::WebVtt; + track.sample_entry_type = Some(WVTT); + } + VTTC_CONFIG => { + webvtt_configuration = Some(downcast_clone::(&extracted)?); + } + VLAB => { + webvtt_source_label = Some(downcast_clone::(&extracted)?); + } + BTRT => { + btrt = Some(downcast_clone::(&extracted)?); + } + COLR => { + colr = Some(downcast_clone::(&extracted)?); + } + PASP => { + pasp = Some(downcast_clone::(&extracted)?); + } + FIEL => { + fiel = Some(downcast_clone::(&extracted)?); + } ESDS => { esds = Some(downcast_clone::(&extracted)?); } + FRMA => { + let payload = downcast_clone::(&extracted)?; + original_format = Some(payload.data_format); + track.original_format = Some(payload.data_format); + } + SCHM => { + let payload = downcast_clone::(&extracted)?; + track.protection_scheme = Some(ProtectionSchemeInfo { + scheme_type: payload.scheme_type, + scheme_version: payload.scheme_version, + }); + } STCO => { stco = Some(downcast_clone::(&extracted)?); } @@ -623,8 +1779,24 @@ where return Err(ProbeError::MissingRequiredBox("mdhd")); } + if let Some(entry) = visual_sample_entry.as_ref() { + track.display_width = Some(entry.width); + track.display_height = Some(entry.height); + } + + if let Some(entry) = audio_sample_entry.as_ref() { + track.channel_count = Some(entry.channel_count); + track.sample_rate = Some(entry.sample_rate_int()); + } + + if let Some(original_format) = original_format { + track.codec_family = codec_family_from_sample_entry(original_format); + } else if let Some(sample_entry_type) = track.sample_entry_type { + track.codec_family = codec_family_from_sample_entry(sample_entry_type); + } + if let (Some(entry), Some(avcc)) = (visual_sample_entry.as_ref(), avcc.as_ref()) { - track.avc = Some(AvcDecoderConfigInfo { + track.summary.avc = Some(AvcDecoderConfigInfo { configuration_version: avcc.configuration_version, profile: avcc.profile, profile_compatibility: avcc.profile_compatibility, @@ -638,85 +1810,395 @@ where if let (Some(entry), Some(esds)) = (audio_sample_entry.as_ref(), esds.as_ref()) && let Some(profile) = detect_aac_profile(esds)? { - track.mp4a = Some(Mp4aInfo { + track.summary.mp4a = Some(Mp4aInfo { object_type_indication: profile.object_type_indication, audio_object_type: profile.audio_object_type, channel_count: entry.channel_count, }); } - let mut chunks = Vec::new(); - if let Some(stco) = stco.as_ref() { - chunks.extend(stco.chunk_offset.iter().map(|offset| ChunkInfo { - data_offset: *offset, - samples_per_chunk: 0, - })); - } else if let Some(co64) = co64.as_ref() { - chunks.extend(co64.chunk_offset.iter().map(|offset| ChunkInfo { - data_offset: *offset, - samples_per_chunk: 0, - })); - } else { - return Err(ProbeError::MissingRequiredBox("stco/co64")); - } + if options.expand_chunks { + if let Some(stco) = stco.as_ref() { + track + .summary + .chunks + .extend(stco.chunk_offset.iter().map(|offset| ChunkInfo { + data_offset: *offset, + samples_per_chunk: 0, + })); + } else if let Some(co64) = co64.as_ref() { + track + .summary + .chunks + .extend(co64.chunk_offset.iter().map(|offset| ChunkInfo { + data_offset: *offset, + samples_per_chunk: 0, + })); + } else { + return Err(ProbeError::MissingRequiredBox("stco/co64")); + } - let stts = stts.ok_or(ProbeError::MissingRequiredBox("stts"))?; - let mut samples = Vec::new(); - for entry in &stts.entries { - for _ in 0..entry.sample_count { - samples.push(SampleInfo { - time_delta: entry.sample_delta, - ..SampleInfo::default() - }); + let stsc = stsc.ok_or(ProbeError::MissingRequiredBox("stsc"))?; + for (index, entry) in stsc.entries.iter().enumerate() { + let mut end = track.summary.chunks.len() as u32; + if index + 1 != stsc.entries.len() { + end = end.min(stsc.entries[index + 1].first_chunk.saturating_sub(1)); + } + for chunk_index in entry.first_chunk.saturating_sub(1)..end { + if let Some(chunk) = track.summary.chunks.get_mut(chunk_index as usize) { + chunk.samples_per_chunk = entry.samples_per_chunk; + } + } } } - let stsc = stsc.ok_or(ProbeError::MissingRequiredBox("stsc"))?; - for (index, entry) in stsc.entries.iter().enumerate() { - let mut end = chunks.len() as u32; - if index + 1 != stsc.entries.len() { - end = end.min(stsc.entries[index + 1].first_chunk.saturating_sub(1)); + if options.expand_samples { + let stts = stts.ok_or(ProbeError::MissingRequiredBox("stts"))?; + for entry in &stts.entries { + for _ in 0..entry.sample_count { + track.summary.samples.push(SampleInfo { + time_delta: entry.sample_delta, + ..SampleInfo::default() + }); + } } - for chunk_index in entry.first_chunk.saturating_sub(1)..end { - if let Some(chunk) = chunks.get_mut(chunk_index as usize) { - chunk.samples_per_chunk = entry.samples_per_chunk; + + if let Some(ctts) = ctts.as_ref() { + let mut sample_index = 0usize; + for (entry_index, entry) in ctts.entries.iter().enumerate() { + for _ in 0..entry.sample_count { + if sample_index >= track.summary.samples.len() { + break; + } + track.summary.samples[sample_index].composition_time_offset = + ctts.sample_offset(entry_index); + sample_index += 1; + } } } - } - if let Some(ctts) = ctts.as_ref() { - let mut sample_index = 0usize; - for (entry_index, entry) in ctts.entries.iter().enumerate() { - for _ in 0..entry.sample_count { - if sample_index >= samples.len() { - break; + if let Some(stsz) = stsz.as_ref() { + if stsz.sample_size != 0 { + for sample in &mut track.summary.samples { + sample.size = stsz.sample_size; + } + } else { + for (sample, entry_size) in + track.summary.samples.iter_mut().zip(stsz.entry_size.iter()) + { + sample.size = + (*entry_size) + .try_into() + .map_err(|_| ProbeError::NumericOverflow { + field_name: "stsz entry size", + })?; } - samples[sample_index].composition_time_offset = ctts.sample_offset(entry_index); - sample_index += 1; } } } + let codec_details = build_track_codec_details( + &track, + &TrackCodecConfigRefs { + avcc: avcc.as_ref(), + hvcc: hvcc.as_ref(), + av1c: av1c.as_ref(), + vpcc: vpcc.as_ref(), + dops: dops.as_ref(), + dac3: dac3.as_ref(), + pcmc: pcmc.as_ref(), + xml_subtitle_sample_entry: xml_subtitle_sample_entry.as_ref(), + text_subtitle_sample_entry: text_subtitle_sample_entry.as_ref(), + webvtt_configuration: webvtt_configuration.as_ref(), + webvtt_source_label: webvtt_source_label.as_ref(), + }, + ); + let media_characteristics = build_track_media_characteristics(&TrackMediaCharacteristicRefs { + btrt: btrt.as_ref(), + colr: colr.as_ref(), + pasp: pasp.as_ref(), + fiel: fiel.as_ref(), + }); + + Ok(ParsedRichTrackInfo { + summary: track, + codec_details, + media_characteristics, + }) +} + +fn codec_family_from_sample_entry(sample_entry_type: FourCc) -> TrackCodecFamily { + match sample_entry_type { + AVC1 => TrackCodecFamily::Avc, + HEV1 | HVC1 => TrackCodecFamily::Hevc, + AV01 => TrackCodecFamily::Av1, + VP08 => TrackCodecFamily::Vp8, + VP09 => TrackCodecFamily::Vp9, + MP4A => TrackCodecFamily::Mp4Audio, + OPUS => TrackCodecFamily::Opus, + AC_3 => TrackCodecFamily::Ac3, + IPCM | FPCM => TrackCodecFamily::Pcm, + STPP => TrackCodecFamily::XmlSubtitle, + SBTT => TrackCodecFamily::TextSubtitle, + WVTT => TrackCodecFamily::WebVtt, + _ => TrackCodecFamily::Unknown, + } +} - if let Some(stsz) = stsz.as_ref() { - if stsz.sample_size != 0 { - for sample in &mut samples { - sample.size = stsz.sample_size; +fn build_track_codec_details( + track: &DetailedTrackInfo, + config_refs: &TrackCodecConfigRefs<'_>, +) -> TrackCodecDetails { + if let Some(avc) = track.summary.avc.as_ref() { + return TrackCodecDetails::Avc(AvcCodecDetails { + configuration_version: avc.configuration_version, + profile: avc.profile, + profile_compatibility: avc.profile_compatibility, + level: avc.level, + length_size: avc.length_size, + chroma_format: config_refs + .avcc + .filter(|config| config.high_profile_fields_enabled) + .map(|config| config.chroma_format), + bit_depth_luma: config_refs + .avcc + .filter(|config| config.high_profile_fields_enabled) + .map(|config| config.bit_depth_luma_minus8.saturating_add(8)), + bit_depth_chroma: config_refs + .avcc + .filter(|config| config.high_profile_fields_enabled) + .map(|config| config.bit_depth_chroma_minus8.saturating_add(8)), + }); + } + + if let Some(mp4a) = track.summary.mp4a.as_ref() { + return TrackCodecDetails::Mp4Audio(Mp4AudioCodecDetails { + object_type_indication: mp4a.object_type_indication, + audio_object_type: mp4a.audio_object_type, + channel_count: mp4a.channel_count, + sample_rate: track.sample_rate, + }); + } + + match track.codec_family { + TrackCodecFamily::Hevc => { + if let Some(hvcc) = config_refs.hvcc { + return TrackCodecDetails::Hevc(HevcCodecDetails { + configuration_version: hvcc.configuration_version, + profile_space: hvcc.general_profile_space, + tier_flag: hvcc.general_tier_flag, + profile_idc: hvcc.general_profile_idc, + profile_compatibility_mask: hevc_profile_compatibility_mask( + &hvcc.general_profile_compatibility, + ), + constraint_indicator: hvcc.general_constraint_indicator, + level_idc: hvcc.general_level_idc, + min_spatial_segmentation_idc: hvcc.min_spatial_segmentation_idc, + parallelism_type: hvcc.parallelism_type, + chroma_format_idc: hvcc.chroma_format_idc, + bit_depth_luma: hvcc.bit_depth_luma_minus8.saturating_add(8), + bit_depth_chroma: hvcc.bit_depth_chroma_minus8.saturating_add(8), + avg_frame_rate: hvcc.avg_frame_rate, + constant_frame_rate: hvcc.constant_frame_rate, + num_temporal_layers: hvcc.num_temporal_layers, + temporal_id_nested: hvcc.temporal_id_nested, + length_size: u16::from(hvcc.length_size_minus_one) + 1, + }); } - } else { - for (sample, entry_size) in samples.iter_mut().zip(stsz.entry_size.iter()) { - sample.size = - (*entry_size) - .try_into() - .map_err(|_| ProbeError::NumericOverflow { - field_name: "stsz entry size", - })?; + } + TrackCodecFamily::Av1 => { + if let Some(av1c) = config_refs.av1c { + return TrackCodecDetails::Av1(Av1CodecDetails { + seq_profile: av1c.seq_profile, + seq_level_idx_0: av1c.seq_level_idx_0, + seq_tier_0: av1c.seq_tier_0, + bit_depth: av1_bit_depth(av1c), + monochrome: av1c.monochrome != 0, + chroma_subsampling_x: av1c.chroma_subsampling_x, + chroma_subsampling_y: av1c.chroma_subsampling_y, + chroma_sample_position: av1c.chroma_sample_position, + initial_presentation_delay_minus_one: if av1c.initial_presentation_delay_present + != 0 + { + Some(av1c.initial_presentation_delay_minus_one) + } else { + None + }, + }); + } + } + TrackCodecFamily::Vp8 => { + if let Some(vpcc) = config_refs.vpcc { + return TrackCodecDetails::Vp8(vp_codec_details(vpcc)); + } + } + TrackCodecFamily::Vp9 => { + if let Some(vpcc) = config_refs.vpcc { + return TrackCodecDetails::Vp9(vp_codec_details(vpcc)); + } + } + TrackCodecFamily::Opus => { + if let Some(dops) = config_refs.dops { + return TrackCodecDetails::Opus(OpusCodecDetails { + output_channel_count: dops.output_channel_count, + pre_skip: dops.pre_skip, + input_sample_rate: dops.input_sample_rate, + output_gain: dops.output_gain, + channel_mapping_family: dops.channel_mapping_family, + stream_count: if dops.channel_mapping_family != 0 { + Some(dops.stream_count) + } else { + None + }, + coupled_count: if dops.channel_mapping_family != 0 { + Some(dops.coupled_count) + } else { + None + }, + channel_mapping: if dops.channel_mapping_family != 0 { + dops.channel_mapping.clone() + } else { + Vec::new() + }, + }); + } + } + TrackCodecFamily::Ac3 => { + if let Some(dac3) = config_refs.dac3 { + return TrackCodecDetails::Ac3(Ac3CodecDetails { + sample_rate_code: dac3.fscod, + bit_stream_identification: dac3.bsid, + bit_stream_mode: dac3.bsmod, + audio_coding_mode: dac3.acmod, + lfe_on: dac3.lfe_on != 0, + bit_rate_code: dac3.bit_rate_code, + }); + } + } + TrackCodecFamily::Pcm => { + if let Some(pcmc) = config_refs.pcmc { + return TrackCodecDetails::Pcm(PcmCodecDetails { + format_flags: pcmc.format_flags, + sample_size: pcmc.pcm_sample_size, + }); + } + } + TrackCodecFamily::XmlSubtitle => { + if let Some(entry) = config_refs.xml_subtitle_sample_entry { + return TrackCodecDetails::XmlSubtitle(XmlSubtitleCodecDetails { + namespace: entry.namespace.clone(), + schema_location: entry.schema_location.clone(), + auxiliary_mime_types: entry.auxiliary_mime_types.clone(), + }); + } + } + TrackCodecFamily::TextSubtitle => { + if let Some(entry) = config_refs.text_subtitle_sample_entry { + return TrackCodecDetails::TextSubtitle(TextSubtitleCodecDetails { + content_encoding: entry.content_encoding.clone(), + mime_format: entry.mime_format.clone(), + }); + } + } + TrackCodecFamily::WebVtt => { + if config_refs.webvtt_configuration.is_some() + || config_refs.webvtt_source_label.is_some() + { + return TrackCodecDetails::WebVtt(WebVttCodecDetails { + config: config_refs + .webvtt_configuration + .map(|value| value.config.clone()), + source_label: config_refs + .webvtt_source_label + .map(|value| value.source_label.clone()), + }); } } + TrackCodecFamily::Unknown | TrackCodecFamily::Avc | TrackCodecFamily::Mp4Audio => {} + } + + TrackCodecDetails::Unknown +} + +fn build_track_media_characteristics( + refs: &TrackMediaCharacteristicRefs<'_>, +) -> TrackMediaCharacteristics { + TrackMediaCharacteristics { + declared_bitrate: refs.btrt.map(|value| DeclaredBitrateInfo { + buffer_size_db: value.buffer_size_db, + max_bitrate: value.max_bitrate, + avg_bitrate: value.avg_bitrate, + }), + color: refs.colr.map(track_color_info), + pixel_aspect_ratio: refs.pasp.map(|value| PixelAspectRatioInfo { + h_spacing: value.h_spacing, + v_spacing: value.v_spacing, + }), + field_order: refs.fiel.map(track_field_order_info), + } +} + +fn track_color_info(value: &Colr) -> ColorInfo { + let is_nclx = value.colour_type == COLR_NCLX; + let stores_profile = matches!(value.colour_type, COLR_RICC | COLR_PROF); + ColorInfo { + colour_type: value.colour_type, + colour_primaries: is_nclx.then_some(value.colour_primaries), + transfer_characteristics: is_nclx.then_some(value.transfer_characteristics), + matrix_coefficients: is_nclx.then_some(value.matrix_coefficients), + full_range: is_nclx.then_some(value.full_range_flag), + profile_size: stores_profile.then_some(value.profile.len()), + unknown_size: (!is_nclx && !stores_profile).then_some(value.unknown.len()), } +} + +fn track_field_order_info(value: &Fiel) -> FieldOrderInfo { + FieldOrderInfo { + field_count: value.field_count, + field_ordering: value.field_ordering, + // `fiel` uses `1` for progressive content and multiple fields for interlaced layouts. + interlaced: value.field_count > 1, + } +} + +fn hevc_profile_compatibility_mask(flags: &[bool; 32]) -> u32 { + let mut mask = 0_u32; + for (index, value) in flags.iter().copied().enumerate() { + if value { + mask |= 1_u32 << (31 - index); + } + } + mask +} + +fn av1_bit_depth(config: &AV1CodecConfiguration) -> u8 { + if config.high_bitdepth == 0 { + 8 + } else if config.twelve_bit != 0 { + 12 + } else { + 10 + } +} + +fn vp_codec_details(config: &VpCodecConfiguration) -> VpCodecDetails { + VpCodecDetails { + profile: config.profile, + level: config.level, + bit_depth: config.bit_depth, + chroma_subsampling: config.chroma_subsampling, + full_range: config.video_full_range_flag != 0, + colour_primaries: config.colour_primaries, + transfer_characteristics: config.transfer_characteristics, + matrix_coefficients: config.matrix_coefficients, + codec_initialization_data_size: config.codec_initialization_data_size, + } +} - track.chunks = chunks; - track.samples = samples; - Ok(track) +fn decode_language(language: [u8; 3]) -> String { + language + .into_iter() + .map(|value| char::from(value.saturating_add(0x60))) + .collect() } fn probe_moof(reader: &mut R, parent: &BoxInfo) -> Result diff --git a/src/stringify.rs b/src/stringify.rs index 1f13c94..248ce69 100644 --- a/src/stringify.rs +++ b/src/stringify.rs @@ -8,20 +8,18 @@ use crate::codec::{ ResolvedField, }; -/// Renders a descriptor-backed box into the compact single-line form used by tests and CLI output. -pub fn stringify( - src: &dyn CodecDescription, - hooks: Option<&dyn FieldHooks>, -) -> Result { - stringify_with_indent(src, "", hooks) +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct StructuredStringifyField { + pub name: &'static str, + pub value: FieldValue, + pub rendered_value: String, + pub include_display_value: bool, } -/// Renders a descriptor-backed box with one field per line using the supplied indentation prefix. -pub fn stringify_with_indent( +pub(crate) fn collect_structured_fields( src: &dyn CodecDescription, - indent: &str, hooks: Option<&dyn FieldHooks>, -) -> Result { +) -> Result, StringifyError> { let mut resolved = src.field_table().resolve_active(src, hooks)?; resolved.sort_by_key(ResolvedField::display_order); let mut rendered_fields = Vec::new(); @@ -31,9 +29,37 @@ pub fn stringify_with_indent( continue; } - rendered_fields.push(render_field(src, field)?); + let (value, rendered_value, include_display_value) = collect_field(src, field)?; + rendered_fields.push(StructuredStringifyField { + name: field.name(), + value, + rendered_value, + include_display_value, + }); } + Ok(rendered_fields) +} + +/// Renders a descriptor-backed box into the compact single-line form used by tests and CLI output. +pub fn stringify( + src: &dyn CodecDescription, + hooks: Option<&dyn FieldHooks>, +) -> Result { + stringify_with_indent(src, "", hooks) +} + +/// Renders a descriptor-backed box with one field per line using the supplied indentation prefix. +pub fn stringify_with_indent( + src: &dyn CodecDescription, + indent: &str, + hooks: Option<&dyn FieldHooks>, +) -> Result { + let rendered_fields = collect_structured_fields(src, hooks)? + .into_iter() + .map(|field| format!("{}={}", field.name, field.rendered_value)) + .collect::>(); + if indent.is_empty() { return Ok(rendered_fields.join(" ")); } @@ -47,24 +73,50 @@ pub fn stringify_with_indent( Ok(rendered) } -fn render_field( +fn collect_field( src: &dyn CodecDescription, field: ResolvedField<'_>, +) -> Result<(FieldValue, String, bool), StringifyError> { + match field.descriptor.role { + crate::codec::FieldRole::Version => { + let value = FieldValue::Unsigned(u64::from(src.version())); + let rendered = value_string(field, src, &value)?; + Ok((value, rendered, false)) + } + crate::codec::FieldRole::Flags => { + let value = FieldValue::Unsigned(u64::from(src.flags())); + let rendered = render_flags(src.flags(), field); + Ok((value, rendered, true)) + } + crate::codec::FieldRole::Data => { + let value = src.field_value(field.name())?; + let rendered = value_string(field, src, &value)?; + let include_display_value = src.display_field(field.name()).is_some() + || !matches!( + field.descriptor.display.format, + FieldFormat::Default | FieldFormat::Decimal + ); + Ok((value, rendered, include_display_value)) + } + } +} + +fn value_string( + field: ResolvedField<'_>, + src: &dyn CodecDescription, + value: &FieldValue, ) -> Result { - let value = match field.descriptor.role { - crate::codec::FieldRole::Version => src.version().to_string(), - crate::codec::FieldRole::Flags => render_flags(src.flags(), field), + match field.descriptor.role { + crate::codec::FieldRole::Version => render_default_value(value), + crate::codec::FieldRole::Flags => Ok(render_flags(src.flags(), field)), crate::codec::FieldRole::Data => { if let Some(rendered) = src.display_field(field.name()) { - rendered + Ok(rendered) } else { - let value = src.field_value(field.name())?; - render_value(field, &value)? + render_value(field, value) } } - }; - - Ok(format!("{}={value}", field.name())) + } } fn render_flags(value: u32, field: ResolvedField<'_>) -> String { diff --git a/tests/cli_dispatch.rs b/tests/cli_dispatch.rs index b0c4eef..ecc1caf 100644 --- a/tests/cli_dispatch.rs +++ b/tests/cli_dispatch.rs @@ -15,7 +15,7 @@ fn dispatch_prints_usage_for_empty_or_unknown_commands() { " divide split a fragmented MP4 into track playlists\n", " dump display the MP4 box tree\n", " edit rewrite selected boxes\n", - " extract extract raw boxes by type\n", + " extract extract raw boxes by type or path\n", " psshdump summarize pssh boxes\n", " probe summarize an MP4 file\n" ) @@ -37,7 +37,7 @@ fn dispatch_prints_usage_for_empty_or_unknown_commands() { " divide split a fragmented MP4 into track playlists\n", " dump display the MP4 box tree\n", " edit rewrite selected boxes\n", - " extract extract raw boxes by type\n", + " extract extract raw boxes by type or path\n", " psshdump summarize pssh boxes\n", " probe summarize an MP4 file\n" ) @@ -62,7 +62,7 @@ fn dispatch_handles_help() { " divide split a fragmented MP4 into track playlists\n", " dump display the MP4 box tree\n", " edit rewrite selected boxes\n", - " extract extract raw boxes by type\n", + " extract extract raw boxes by type or path\n", " psshdump summarize pssh boxes\n", " probe summarize an MP4 file\n" ) diff --git a/tests/cli_divide.rs b/tests/cli_divide.rs index 038d357..5a425c7 100644 --- a/tests/cli_divide.rs +++ b/tests/cli_divide.rs @@ -7,9 +7,13 @@ use std::path::Path; use mp4forge::boxes::AnyTypeBox; use mp4forge::boxes::iso14496_12::{ - AVCDecoderConfiguration, Mdhd, Stco, Stsc, StscEntry, Stsd, Stsz, Stts, SttsEntry, - TFHD_DEFAULT_SAMPLE_DURATION_PRESENT, TFHD_DEFAULT_SAMPLE_SIZE_PRESENT, Tfdt, Tfhd, Tkhd, Trun, - VisualSampleEntry, + AVCDecoderConfiguration, AudioSampleEntry, Ftyp, HEVCDecoderConfiguration, Mdhd, SampleEntry, + Stco, Stsc, StscEntry, Stsd, Stsz, Stts, SttsEntry, TFHD_DEFAULT_SAMPLE_DURATION_PRESENT, + TFHD_DEFAULT_SAMPLE_SIZE_PRESENT, Tfdt, Tfhd, Tkhd, Trun, VisualSampleEntry, +}; +use mp4forge::boxes::iso14496_14::{ + DECODER_CONFIG_DESCRIPTOR_TAG, DECODER_SPECIFIC_INFO_TAG, DecoderConfigDescriptor, Descriptor, + Esds, }; use mp4forge::cli::divide; use mp4forge::codec::MutableBox; @@ -49,7 +53,7 @@ fn divide_command_writes_playlists_and_segments() { master_playlist, concat!( "#EXTM3U\n", - "#EXT-X-STREAM-INF:BANDWIDTH=128,CODECS=\"avc1.64001f,mp4a.40.2\",RESOLUTION=1920x1080\n", + "#EXT-X-STREAM-INF:BANDWIDTH=128,CODECS=\"avc1.64001f\",RESOLUTION=1920x1080\n", "video/playlist.m3u8\n" ) ); @@ -82,8 +86,72 @@ fn divide_command_validates_argument_shape() { assert_eq!(divide::run(&[], &mut stderr), 1); assert_eq!( String::from_utf8(stderr).unwrap(), - "USAGE: mp4forge divide INPUT.mp4 OUTPUT_DIR\n" + concat!( + "USAGE: mp4forge divide INPUT.mp4 OUTPUT_DIR\n", + " mp4forge divide -validate INPUT.mp4\n", + "\n", + "OPTIONS:\n", + " -validate Validate the fragmented divide layout without writing output files\n", + "\n", + "Currently supports fragmented inputs with up to one AVC video track and one MP4A audio track,\n", + "including encrypted wrappers that preserve those original sample-entry formats.\n", + ) + ); +} + +#[test] +fn divide_command_derives_master_playlist_signaling_from_probe_metadata() { + let input = build_video_and_audio_divide_input_file(); + let input_path = write_temp_file("divide-signaling-input", &input); + let output_dir = temp_output_dir("divide-signaling-output"); + let args = vec![ + input_path.to_string_lossy().into_owned(), + output_dir.to_string_lossy().into_owned(), + ]; + + let mut stderr = Vec::new(); + let exit_code = divide::run(&args, &mut stderr); + + assert_eq!(exit_code, 0, "{}", String::from_utf8_lossy(&stderr)); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!( + read_text(&output_dir.join("playlist.m3u8")), + concat!( + "#EXTM3U\n", + "#EXT-X-MEDIA:TYPE=AUDIO,URI=\"audio/playlist.m3u8\",GROUP-ID=\"audio\",NAME=\"audio\",AUTOSELECT=YES,CHANNELS=\"6\"\n", + "#EXT-X-STREAM-INF:BANDWIDTH=128,CODECS=\"avc1.4d401f,mp4a.40.5\",RESOLUTION=640x360,AUDIO=\"audio\"\n", + "video/playlist.m3u8\n" + ) + ); + + let _ = fs::remove_file(&input_path); + let _ = fs::remove_dir_all(&output_dir); +} + +#[test] +fn divide_command_rejects_multiple_video_tracks_with_clear_message() { + let input = build_two_video_track_divide_input_file(); + let input_path = write_temp_file("divide-multi-video-input", &input); + let output_dir = temp_output_dir("divide-multi-video-output"); + let args = vec![ + input_path.to_string_lossy().into_owned(), + output_dir.to_string_lossy().into_owned(), + ]; + + let mut stderr = Vec::new(); + let exit_code = divide::run(&args, &mut stderr); + + assert_eq!(exit_code, 1); + assert_eq!( + String::from_utf8(stderr).unwrap(), + concat!( + "Error: divide currently supports fragmented inputs with at most one AVC video track and one MP4A audio track; ", + "found multiple fragmented video tracks (1 and 2).\n" + ) ); + + let _ = fs::remove_file(&input_path); + let _ = fs::remove_dir_all(&output_dir); } #[test] @@ -180,37 +248,187 @@ fn divide_command_matches_shared_fragmented_fixture_outputs() { let _ = fs::remove_dir_all(&output_dir); } +#[test] +fn divide_validate_reports_supported_layout_without_writing_files() { + let input = build_video_and_audio_divide_input_file(); + let input_path = write_temp_file("divide-validate-supported-input", &input); + let args = vec![ + "-validate".to_string(), + input_path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = divide::run_with_output(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&input_path); + + assert_eq!(exit_code, 0, "{}", String::from_utf8_lossy(&stderr)); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!( + String::from_utf8(stdout).unwrap(), + concat!( + "supported fragmented divide layout\n", + "track 1: role=video codec=avc1 segments=1\n", + "track 2: role=audio codec=mp4a segments=1\n", + ) + ); +} + +#[test] +fn divide_validate_rejects_duplicate_video_layouts_before_writing_output() { + let input = build_two_video_track_divide_input_file(); + let input_path = write_temp_file("divide-validate-duplicate-video-input", &input); + let args = vec![ + "--validate".to_string(), + input_path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = divide::run_with_output(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&input_path); + + assert_eq!(exit_code, 1); + assert_eq!(String::from_utf8(stdout).unwrap(), ""); + assert_eq!( + String::from_utf8(stderr).unwrap(), + concat!( + "Error: divide currently supports fragmented inputs with at most one AVC video track and one MP4A audio track; ", + "found multiple fragmented video tracks (1 and 2).\n" + ) + ); +} + +#[test] +fn divide_validate_rejects_unsupported_hevc_layout_with_clear_message() { + let input = build_hevc_divide_input_file(); + let input_path = write_temp_file("divide-validate-hevc-input", &input); + let args = vec![ + "-validate".to_string(), + input_path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = divide::run_with_output(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&input_path); + + assert_eq!(exit_code, 1); + assert_eq!(String::from_utf8(stdout).unwrap(), ""); + assert_eq!( + String::from_utf8(stderr).unwrap(), + concat!( + "Error: track 1 uses unsupported codec `hvc1`; ", + "divide currently supports fragmented inputs with at most one AVC video track and one MP4A audio track\n" + ) + ); +} + +#[test] +fn validate_divide_reader_reports_supported_tracks() { + let input = build_video_and_audio_divide_input_file(); + let report = divide::validate_divide_reader(&mut std::io::Cursor::new(input)).unwrap(); + + assert_eq!(report.tracks.len(), 2); + assert_eq!(report.tracks[0].track_id, 1); + assert_eq!(report.tracks[0].role, divide::DivideTrackRole::Video); + assert_eq!(report.tracks[0].sample_entry_type, Some(fourcc("avc1"))); + assert_eq!(report.tracks[0].segment_count, 1); + assert_eq!(report.tracks[1].track_id, 2); + assert_eq!(report.tracks[1].role, divide::DivideTrackRole::Audio); + assert_eq!(report.tracks[1].sample_entry_type, Some(fourcc("mp4a"))); + assert_eq!(report.tracks[1].segment_count, 1); +} + fn build_divide_input_file() -> Vec { + build_fragmented_input_file( + vec![build_video_trak_with_profile( + 1, 1_920, 1_080, 0x64, 0x00, 0x1f, + )], + vec![ + build_track_segment(1, 0, 1_000, 8), + build_track_segment(1, 1_000, 1_000, 8), + ], + ) +} + +fn build_video_and_audio_divide_input_file() -> Vec { + build_fragmented_input_file( + vec![ + build_video_trak_with_profile(1, 640, 360, 0x4d, 0x40, 0x1f), + build_audio_trak(2, 6, 0x40, &[0x10, 0x02, 0xb7, 0x2c, 0x00]), + ], + vec![ + build_track_segment(1, 0, 1_000, 8), + build_track_segment(2, 0, 1_000, 6), + ], + ) +} + +fn build_two_video_track_divide_input_file() -> Vec { + build_fragmented_input_file( + vec![ + build_video_trak_with_profile(1, 640, 360, 0x64, 0x00, 0x1f), + build_video_trak_with_profile(2, 320, 180, 0x42, 0x00, 0x1e), + ], + vec![ + build_track_segment(1, 0, 1_000, 8), + build_track_segment(2, 0, 1_000, 8), + ], + ) +} + +fn build_hevc_divide_input_file() -> Vec { + build_fragmented_input_file( + vec![build_hevc_trak(1, 640, 360)], + vec![build_track_segment(1, 0, 1_000, 8)], + ) +} + +fn build_fragmented_input_file(traks: Vec>, segments: Vec>) -> Vec { let ftyp = encode_supported_box( - &mp4forge::boxes::iso14496_12::Ftyp { + &Ftyp { major_brand: fourcc("iso6"), minor_version: 1, compatible_brands: vec![fourcc("iso6"), fourcc("dash")], }, &[], ); - let moov = encode_raw_box(fourcc("moov"), &build_video_trak()); - let segment0 = build_video_segment(0); - let segment1 = build_video_segment(1_000); - [ftyp, moov, segment0, segment1].concat() + let moov = encode_raw_box(fourcc("moov"), &traks.concat()); + + let mut file = [ftyp, moov].concat(); + for segment in segments { + file.extend_from_slice(&segment); + } + file } -fn build_video_trak() -> Vec { +fn build_video_trak_with_profile( + track_id: u32, + width: u16, + height: u16, + profile: u8, + profile_compatibility: u8, + level: u8, +) -> Vec { let mut tkhd = Tkhd::default(); - tkhd.track_id = 1; - tkhd.width = 1_920 << 16; - tkhd.height = 1_080 << 16; + tkhd.track_id = track_id; + tkhd.width = u32::from(width) << 16; + tkhd.height = u32::from(height) << 16; let mut mdhd = Mdhd::default(); mdhd.timescale = 1_000; - mdhd.duration_v0 = 2_000; + mdhd.duration_v0 = 1_000; let avcc = encode_supported_box( &AVCDecoderConfiguration { configuration_version: 1, - profile: 0x64, - profile_compatibility: 0, - level: 0x1f, + profile, + profile_compatibility, + level, length_size_minus_one: 3, ..AVCDecoderConfiguration::default() }, @@ -220,8 +438,8 @@ fn build_video_trak() -> Vec { let mut avc1 = VisualSampleEntry::default(); avc1.set_box_type(fourcc("avc1")); avc1.sample_entry.data_reference_index = 1; - avc1.width = 1_920; - avc1.height = 1_080; + avc1.width = width; + avc1.height = height; avc1.horizresolution = 0x0048_0000; avc1.vertresolution = 0x0048_0000; avc1.frame_count = 1; @@ -270,11 +488,161 @@ fn build_video_trak() -> Vec { ) } -fn build_video_segment(base_media_decode_time: u32) -> Vec { +fn build_audio_trak( + track_id: u32, + channel_count: u16, + object_type_indication: u8, + decoder_specific_info: &[u8], +) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = track_id; + + let mut mdhd = Mdhd::default(); + mdhd.timescale = 1_000; + mdhd.duration_v0 = 1_000; + + let mut mp4a = AudioSampleEntry::default(); + mp4a.set_box_type(fourcc("mp4a")); + mp4a.sample_entry = SampleEntry { + box_type: fourcc("mp4a"), + data_reference_index: 1, + }; + mp4a.channel_count = channel_count; + mp4a.sample_size = 16; + mp4a.sample_rate = 48_000_u32 << 16; + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let mp4a = encode_supported_box( + &mp4a, + &encode_supported_box( + &aac_profile_esds(object_type_indication, decoder_specific_info), + &[], + ), + ); + let stsd = encode_supported_box(&stsd, &mp4a); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: 1_000, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_size = 6; + stsz.sample_count = 1; + let stsz = encode_supported_box(&stsz, &[]); + + let mut stco = Stco::default(); + stco.entry_count = 0; + let stco = encode_supported_box(&stco, &[]); + + let stbl = encode_raw_box(fourcc("stbl"), &[stsd, stts, stsc, stsz, stco].concat()); + let minf = encode_raw_box(fourcc("minf"), &stbl); + let mdia = encode_raw_box( + fourcc("mdia"), + &[encode_supported_box(&mdhd, &[]), minf].concat(), + ); + encode_raw_box( + fourcc("trak"), + &[encode_supported_box(&tkhd, &[]), mdia].concat(), + ) +} + +fn build_hevc_trak(track_id: u32, width: u16, height: u16) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = track_id; + tkhd.width = u32::from(width) << 16; + tkhd.height = u32::from(height) << 16; + + let mut mdhd = Mdhd::default(); + mdhd.timescale = 1_000; + mdhd.duration_v0 = 1_000; + + let hvcc = encode_supported_box( + &HEVCDecoderConfiguration { + configuration_version: 1, + general_profile_idc: 1, + length_size_minus_one: 3, + ..HEVCDecoderConfiguration::default() + }, + &[], + ); + + let mut hvc1 = VisualSampleEntry::default(); + hvc1.set_box_type(fourcc("hvc1")); + hvc1.sample_entry.data_reference_index = 1; + hvc1.width = width; + hvc1.height = height; + hvc1.horizresolution = 0x0048_0000; + hvc1.vertresolution = 0x0048_0000; + hvc1.frame_count = 1; + hvc1.depth = 0x0018; + hvc1.pre_defined3 = -1; + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let stsd = encode_supported_box(&stsd, &encode_supported_box(&hvc1, &hvcc)); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: 1_000, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_size = 8; + stsz.sample_count = 1; + let stsz = encode_supported_box(&stsz, &[]); + + let mut stco = Stco::default(); + stco.entry_count = 0; + let stco = encode_supported_box(&stco, &[]); + + let stbl = encode_raw_box(fourcc("stbl"), &[stsd, stts, stsc, stsz, stco].concat()); + let minf = encode_raw_box(fourcc("minf"), &stbl); + let mdia = encode_raw_box( + fourcc("mdia"), + &[encode_supported_box(&mdhd, &[]), minf].concat(), + ); + encode_raw_box( + fourcc("trak"), + &[encode_supported_box(&tkhd, &[]), mdia].concat(), + ) +} + +fn build_track_segment( + track_id: u32, + base_media_decode_time: u32, + sample_duration: u32, + sample_size: u32, +) -> Vec { let mut tfhd = Tfhd::default(); - tfhd.track_id = 1; - tfhd.default_sample_duration = 1_000; - tfhd.default_sample_size = 8; + tfhd.track_id = track_id; + tfhd.default_sample_duration = sample_duration; + tfhd.default_sample_size = sample_size; tfhd.set_flags(TFHD_DEFAULT_SAMPLE_DURATION_PRESENT | TFHD_DEFAULT_SAMPLE_SIZE_PRESENT); let mut tfdt = Tfdt::default(); @@ -293,10 +661,34 @@ fn build_video_segment(base_media_decode_time: u32) -> Vec { .concat(), ); let moof = encode_raw_box(fourcc("moof"), &traf); - let mdat = encode_raw_box(fourcc("mdat"), &[0, 1, 2, 3, 4, 5, 6, 7]); + let mdat = encode_raw_box(fourcc("mdat"), &vec![0_u8; sample_size as usize]); [moof, mdat].concat() } +fn aac_profile_esds(object_type_indication: u8, decoder_specific_info: &[u8]) -> Esds { + let mut esds = Esds::default(); + esds.descriptors = vec![ + Descriptor { + tag: DECODER_CONFIG_DESCRIPTOR_TAG, + size: 13, + decoder_config_descriptor: Some(DecoderConfigDescriptor { + object_type_indication, + stream_type: 5, + reserved: true, + ..DecoderConfigDescriptor::default() + }), + ..Descriptor::default() + }, + Descriptor { + tag: DECODER_SPECIFIC_INFO_TAG, + size: decoder_specific_info.len() as u32, + data: decoder_specific_info.to_vec(), + ..Descriptor::default() + }, + ]; + esds +} + fn sorted_file_names(path: &Path) -> Vec { let mut names = fs::read_dir(path) .unwrap() diff --git a/tests/cli_dump.rs b/tests/cli_dump.rs index c89f12b..172c7aa 100644 --- a/tests/cli_dump.rs +++ b/tests/cli_dump.rs @@ -3,15 +3,276 @@ mod support; use std::fs; +use std::io::Cursor; use mp4forge::boxes::iso14496_12::{Ftyp, Moov, Mvhd}; -use mp4forge::cli::dump; +use mp4forge::cli::dump::{ + self, DumpPayloadStatus, FieldStructuredDumpBoxReport, FieldStructuredDumpReport, + StructuredDumpBoxReport, StructuredDumpFieldReport, StructuredDumpFormat, StructuredDumpReport, +}; +use mp4forge::codec::FieldValue; +use mp4forge::walk::BoxPath; use support::{ encode_raw_box, encode_supported_box, fixture_path, fourcc, normalize_text, read_golden, write_temp_file, }; +#[test] +fn structured_dump_report_renders_json_and_yaml_with_stable_field_order() { + let report = StructuredDumpReport { + boxes: vec![ + StructuredDumpBoxReport { + box_type: "ftyp".to_string(), + path: "ftyp".to_string(), + offset: 0, + size: 20, + supported: true, + payload_status: DumpPayloadStatus::Summary, + payload_summary: Some( + "MajorBrand=\"isom\" MinorVersion=512 CompatibleBrands=[{CompatibleBrand=\"isom\"}]" + .to_string(), + ), + payload_bytes: None, + children: Vec::new(), + }, + StructuredDumpBoxReport { + box_type: "moov".to_string(), + path: "moov".to_string(), + offset: 20, + size: 116, + supported: true, + payload_status: DumpPayloadStatus::Empty, + payload_summary: None, + payload_bytes: None, + children: vec![StructuredDumpBoxReport { + box_type: "mvhd".to_string(), + path: "moov/mvhd".to_string(), + offset: 28, + size: 108, + supported: true, + payload_status: DumpPayloadStatus::Omitted, + payload_summary: None, + payload_bytes: None, + children: Vec::new(), + }], + }, + StructuredDumpBoxReport { + box_type: "zzzz".to_string(), + path: "zzzz".to_string(), + offset: 136, + size: 11, + supported: false, + payload_status: DumpPayloadStatus::Bytes, + payload_summary: None, + payload_bytes: Some(vec![1, 2, 3]), + children: Vec::new(), + }, + ], + }; + + let mut json = Vec::new(); + dump::write_structured_report(&mut json, &report, StructuredDumpFormat::Json).unwrap(); + assert_eq!( + String::from_utf8(json).unwrap(), + concat!( + "{\n", + " \"Boxes\": [\n", + " {\n", + " \"BoxType\": \"ftyp\",\n", + " \"Path\": \"ftyp\",\n", + " \"Offset\": 0,\n", + " \"Size\": 20,\n", + " \"Supported\": true,\n", + " \"PayloadStatus\": \"summary\",\n", + " \"PayloadSummary\": \"MajorBrand=\\\"isom\\\" MinorVersion=512 CompatibleBrands=[{CompatibleBrand=\\\"isom\\\"}]\",\n", + " \"Children\": [\n", + " ]\n", + " },\n", + " {\n", + " \"BoxType\": \"moov\",\n", + " \"Path\": \"moov\",\n", + " \"Offset\": 20,\n", + " \"Size\": 116,\n", + " \"Supported\": true,\n", + " \"PayloadStatus\": \"empty\",\n", + " \"Children\": [\n", + " {\n", + " \"BoxType\": \"mvhd\",\n", + " \"Path\": \"moov/mvhd\",\n", + " \"Offset\": 28,\n", + " \"Size\": 108,\n", + " \"Supported\": true,\n", + " \"PayloadStatus\": \"omitted\",\n", + " \"Children\": [\n", + " ]\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \"BoxType\": \"zzzz\",\n", + " \"Path\": \"zzzz\",\n", + " \"Offset\": 136,\n", + " \"Size\": 11,\n", + " \"Supported\": false,\n", + " \"PayloadStatus\": \"bytes\",\n", + " \"PayloadBytes\": [\n", + " 1,\n", + " 2,\n", + " 3\n", + " ],\n", + " \"Children\": [\n", + " ]\n", + " }\n", + " ]\n", + "}\n" + ) + ); + + let mut yaml = Vec::new(); + dump::write_structured_report(&mut yaml, &report, StructuredDumpFormat::Yaml).unwrap(); + assert_eq!( + String::from_utf8(yaml).unwrap(), + concat!( + "boxes:\n", + "- box_type: ftyp\n", + " path: ftyp\n", + " offset: 0\n", + " size: 20\n", + " supported: true\n", + " payload_status: summary\n", + " payload_summary: 'MajorBrand=\"isom\" MinorVersion=512 CompatibleBrands=[{CompatibleBrand=\"isom\"}]'\n", + " children: []\n", + "- box_type: moov\n", + " path: moov\n", + " offset: 20\n", + " size: 116\n", + " supported: true\n", + " payload_status: empty\n", + " children:\n", + " - box_type: mvhd\n", + " path: moov/mvhd\n", + " offset: 28\n", + " size: 108\n", + " supported: true\n", + " payload_status: omitted\n", + " children: []\n", + "- box_type: zzzz\n", + " path: zzzz\n", + " offset: 136\n", + " size: 11\n", + " supported: false\n", + " payload_status: bytes\n", + " payload_bytes:\n", + " - 1\n", + " - 2\n", + " - 3\n", + " children: []\n" + ) + ); +} + +#[test] +fn field_structured_dump_report_renders_json_and_yaml_with_stable_field_order() { + let report = FieldStructuredDumpReport { + boxes: vec![FieldStructuredDumpBoxReport { + box_type: "ftyp".to_string(), + path: "ftyp".to_string(), + offset: 0, + size: 20, + supported: true, + payload_status: DumpPayloadStatus::Summary, + payload_fields: vec![ + StructuredDumpFieldReport { + name: "MajorBrand".to_string(), + value: FieldValue::String("isom".to_string()), + display_value: None, + }, + StructuredDumpFieldReport { + name: "CompatibleBrands".to_string(), + value: FieldValue::Bytes(vec![105, 115, 111, 109]), + display_value: Some("[{CompatibleBrand=\"isom\"}]".to_string()), + }, + ], + payload_summary: Some( + "MajorBrand=\"isom\" CompatibleBrands=[{CompatibleBrand=\"isom\"}]".to_string(), + ), + payload_bytes: None, + children: Vec::new(), + }], + }; + + let mut json = Vec::new(); + dump::write_field_structured_report(&mut json, &report, StructuredDumpFormat::Json).unwrap(); + assert_eq!( + String::from_utf8(json).unwrap(), + concat!( + "{\n", + " \"Boxes\": [\n", + " {\n", + " \"BoxType\": \"ftyp\",\n", + " \"Path\": \"ftyp\",\n", + " \"Offset\": 0,\n", + " \"Size\": 20,\n", + " \"Supported\": true,\n", + " \"PayloadStatus\": \"summary\",\n", + " \"PayloadFields\": [\n", + " {\n", + " \"Name\": \"MajorBrand\",\n", + " \"ValueKind\": \"string\",\n", + " \"Value\": \"isom\"\n", + " },\n", + " {\n", + " \"Name\": \"CompatibleBrands\",\n", + " \"ValueKind\": \"bytes\",\n", + " \"Value\": [\n", + " 105,\n", + " 115,\n", + " 111,\n", + " 109\n", + " ],\n", + " \"DisplayValue\": \"[{CompatibleBrand=\\\"isom\\\"}]\"\n", + " }\n", + " ],\n", + " \"PayloadSummary\": \"MajorBrand=\\\"isom\\\" CompatibleBrands=[{CompatibleBrand=\\\"isom\\\"}]\",\n", + " \"Children\": [\n", + " ]\n", + " }\n", + " ]\n", + "}\n" + ) + ); + + let mut yaml = Vec::new(); + dump::write_field_structured_report(&mut yaml, &report, StructuredDumpFormat::Yaml).unwrap(); + assert_eq!( + String::from_utf8(yaml).unwrap(), + concat!( + "boxes:\n", + "- box_type: ftyp\n", + " path: ftyp\n", + " offset: 0\n", + " size: 20\n", + " supported: true\n", + " payload_status: summary\n", + " payload_fields:\n", + " - name: MajorBrand\n", + " value_kind: string\n", + " value: isom\n", + " - name: CompatibleBrands\n", + " value_kind: bytes\n", + " value:\n", + " - 105\n", + " - 115\n", + " - 111\n", + " - 109\n", + " display_value: '[{CompatibleBrand=\"isom\"}]'\n", + " payload_summary: 'MajorBrand=\"isom\" CompatibleBrands=[{CompatibleBrand=\"isom\"}]'\n", + " children: []\n" + ) + ); +} + #[test] fn dump_command_renders_supported_and_unsupported_boxes() { let path = write_temp_file("dump-cli", &build_dump_input_file()); @@ -42,6 +303,8 @@ fn dump_command_matches_shared_fixture_goldens() { let fixture = fixture_path("sample.mp4"); let cases: &[(&[&str], &str)] = &[ (&[], "cli_dump/sample.txt"), + (&["-format", "json"], "cli_dump/sample.json"), + (&["-format", "yaml"], "cli_dump/sample.yaml"), ( &["-full", "mvhd,loci"], "cli_dump/sample-full-mvhd-loci.txt", @@ -75,6 +338,106 @@ fn dump_command_matches_shared_fixture_goldens() { } } +#[test] +fn structured_dump_report_respects_full_payload_controls() { + let mut default_reader = Cursor::new(build_dump_input_file()); + let default_report = + dump::build_structured_report(&mut default_reader, &dump::DumpOptions::default()).unwrap(); + + assert_eq!(default_report.boxes.len(), 4); + assert_eq!( + default_report.boxes[0].payload_status, + DumpPayloadStatus::Summary + ); + assert_eq!( + default_report.boxes[1].payload_status, + DumpPayloadStatus::Omitted + ); + assert_eq!( + default_report.boxes[2].payload_status, + DumpPayloadStatus::Omitted + ); + assert_eq!( + default_report.boxes[3].payload_status, + DumpPayloadStatus::Empty + ); + assert_eq!( + default_report.boxes[3].children[0].payload_status, + DumpPayloadStatus::Summary + ); + + let mut full_options = dump::DumpOptions::default(); + full_options.full_box_types.insert(fourcc("free")); + full_options.full_box_types.insert(fourcc("zzzz")); + full_options.full_box_types.insert(fourcc("mvhd")); + + let mut full_reader = Cursor::new(build_dump_input_file()); + let full_report = dump::build_structured_report(&mut full_reader, &full_options).unwrap(); + + assert_eq!( + full_report.boxes[1].payload_status, + DumpPayloadStatus::Summary + ); + assert!( + full_report.boxes[1] + .payload_summary + .as_ref() + .unwrap() + .contains("Data=[0xaa, 0xbb, 0xcc, 0xdd]") + ); + assert_eq!( + full_report.boxes[2].payload_status, + DumpPayloadStatus::Bytes + ); + assert_eq!( + full_report.boxes[2].payload_bytes.as_deref(), + Some(&[1, 2, 3][..]) + ); + assert_eq!( + full_report.boxes[3].children[0].payload_status, + DumpPayloadStatus::Summary + ); + assert!( + full_report.boxes[3].children[0] + .payload_summary + .as_ref() + .unwrap() + .contains("Version=0") + ); +} + +#[test] +fn field_structured_dump_report_prefers_supported_fields_over_legacy_leaf_omission() { + let fixture = fixture_path("sample.mp4"); + let mut reader = fs::File::open(&fixture).unwrap(); + let report = + dump::build_field_structured_report(&mut reader, &dump::DumpOptions::default()).unwrap(); + + let mdat = find_field_box(&report.boxes, "mdat").unwrap(); + assert_eq!(mdat.payload_status, DumpPayloadStatus::Omitted); + assert!(mdat.payload_fields.is_empty()); + + let ctts = find_field_box(&report.boxes, "moov/trak/mdia/minf/stbl/ctts").unwrap(); + assert_eq!(ctts.payload_status, DumpPayloadStatus::Summary); + assert!(ctts.payload_summary.is_some()); + assert_eq!( + ctts.payload_fields + .iter() + .map(|field| field.name.as_str()) + .collect::>(), + vec!["Version", "Flags", "EntryCount", "Entries"] + ); + let entries = &ctts.payload_fields[3]; + assert!(matches!(entries.value, FieldValue::Bytes(_))); + assert!( + entries + .display_value + .as_ref() + .unwrap() + .contains("SampleCount=1") + ); +} + #[test] fn dump_command_accepts_go_style_long_options() { let fixture = fixture_path("sample.mp4"); @@ -119,6 +482,165 @@ fn dump_command_reads_quicktime_wave_audio_children() { )); } +#[test] +fn dump_command_scopes_text_output_to_selected_subtrees() { + let path = write_temp_file("dump-cli-path", &build_dump_input_file()); + let args = vec![ + "--path".to_string(), + "moov".to_string(), + path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = dump::run(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&path); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!( + String::from_utf8(stdout).unwrap(), + concat!( + "[moov] Size=116\n", + " [mvhd] Size=108 ... (use \"-full mvhd\" to show all)\n" + ) + ); +} + +#[test] +fn dump_command_scopes_structured_output_to_selected_subtrees() { + let path = write_temp_file("dump-cli-path-json", &build_dump_input_file()); + let args = vec![ + "--format".to_string(), + "json".to_string(), + "--path".to_string(), + "moov".to_string(), + path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = dump::run(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&path); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + + let output = String::from_utf8(stdout).unwrap(); + assert!(output.contains("\"Path\": \"moov\"")); + assert!(output.contains("\"Path\": \"moov/mvhd\"")); + assert!(!output.contains("\"Path\": \"ftyp\"")); + assert!(!output.contains("\"Path\": \"zzzz\"")); +} + +#[test] +fn structured_dump_report_paths_support_wildcards_and_exact_roots() { + let fixture = fixture_path("sample.mp4"); + let mut reader = fs::File::open(&fixture).unwrap(); + let paths = vec![BoxPath::parse("moov/*/mdia/mdhd").unwrap()]; + let report = + dump::build_structured_report_paths(&mut reader, &dump::DumpOptions::default(), &paths) + .unwrap(); + + assert_eq!(report.boxes.len(), 2); + assert!(report.boxes.iter().all(|entry| entry.box_type == "mdhd")); + assert!( + report + .boxes + .iter() + .all(|entry| entry.path == "moov/trak/mdia/mdhd") + ); + assert!(report.boxes.iter().all(|entry| entry.children.is_empty())); +} + +#[test] +fn dump_command_treats_root_path_like_full_dump() { + let fixture = fixture_path("sample.mp4"); + let args = vec![ + "--path".to_string(), + "".to_string(), + fixture.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = dump::run(&args, &mut stdout, &mut stderr); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!( + normalize_text(&String::from_utf8(stdout).unwrap()), + read_golden("cli_dump/sample.txt") + ); +} + +#[test] +fn dump_command_returns_empty_output_for_unmatched_paths() { + let fixture = fixture_path("sample.mp4"); + + let mut text_stdout = Vec::new(); + let mut text_stderr = Vec::new(); + let text_exit_code = dump::run( + &[ + "--path".to_string(), + "moov/zzzz".to_string(), + fixture.to_string_lossy().into_owned(), + ], + &mut text_stdout, + &mut text_stderr, + ); + + assert_eq!(text_exit_code, 0); + assert_eq!(String::from_utf8(text_stderr).unwrap(), ""); + assert_eq!(String::from_utf8(text_stdout).unwrap(), ""); + + let mut json_stdout = Vec::new(); + let mut json_stderr = Vec::new(); + let json_exit_code = dump::run( + &[ + "--format".to_string(), + "json".to_string(), + "--path".to_string(), + "moov/zzzz".to_string(), + fixture.to_string_lossy().into_owned(), + ], + &mut json_stdout, + &mut json_stderr, + ); + + assert_eq!(json_exit_code, 0); + assert_eq!(String::from_utf8(json_stderr).unwrap(), ""); + assert_eq!( + String::from_utf8(json_stdout).unwrap(), + concat!("{\n", " \"Boxes\": [\n", " ]\n", "}\n") + ); +} + +#[test] +fn dump_command_rejects_invalid_path_arguments() { + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + assert_eq!( + dump::run( + &[ + "--path".to_string(), + "moov/trakk".to_string(), + fixture_path("sample.mp4").to_string_lossy().into_owned(), + ], + &mut stdout, + &mut stderr, + ), + 1 + ); + assert_eq!(String::from_utf8(stdout).unwrap(), ""); + assert_eq!( + String::from_utf8(stderr).unwrap(), + "Error: invalid box path: invalid box path segment 2 (\"trakk\"): fourcc values must be exactly 4 bytes, got 5\n" + ); +} + fn build_dump_input_file() -> Vec { let ftyp = encode_supported_box( &Ftyp { @@ -140,3 +662,18 @@ fn build_dump_input_file() -> Vec { [ftyp, free, unknown, moov].concat() } + +fn find_field_box<'a>( + boxes: &'a [FieldStructuredDumpBoxReport], + path: &str, +) -> Option<&'a FieldStructuredDumpBoxReport> { + for entry in boxes { + if entry.path == path { + return Some(entry); + } + if let Some(found) = find_field_box(&entry.children, path) { + return Some(found); + } + } + None +} diff --git a/tests/cli_edit.rs b/tests/cli_edit.rs index 8b9867c..83405fd 100644 --- a/tests/cli_edit.rs +++ b/tests/cli_edit.rs @@ -6,12 +6,12 @@ use std::fs; use std::io::Cursor; use mp4forge::boxes::iso14496_12::{ - Ftyp, Moof, TFHD_DEFAULT_SAMPLE_DURATION_PRESENT, TFHD_DEFAULT_SAMPLE_SIZE_PRESENT, Tfdt, Tfhd, - Traf, + Ftyp, Meta, Moof, TFHD_DEFAULT_SAMPLE_DURATION_PRESENT, TFHD_DEFAULT_SAMPLE_SIZE_PRESENT, Tfdt, + Tfhd, Traf, }; use mp4forge::cli::edit; use mp4forge::codec::MutableBox; -use mp4forge::extract::extract_box; +use mp4forge::extract::{extract_box, extract_box_as}; use mp4forge::probe::probe; use mp4forge::walk::BoxPath; @@ -59,6 +59,7 @@ fn edit_command_validates_argument_shape() { "\n", "OPTIONS:\n", " -base_media_decode_time Replace tfdt base media decode times\n", + " -path Limit supported typed rewrites to parsed slash-delimited box paths\n", " -drop Drop boxes by fourcc\n" ) ); @@ -165,6 +166,153 @@ fn edit_command_matches_shared_fragmented_fixture_behavior() { assert!(mfra.is_empty()); } +#[test] +fn edit_command_scopes_tfdt_rewrites_to_matching_paths() { + let input = build_edit_scoped_input_file(); + let input_path = write_temp_file("edit-scoped-input", &input); + let output_path = write_temp_file("edit-scoped-output", &[]); + let args = vec![ + "-path".to_string(), + "moof/traf/tfdt".to_string(), + "-base_media_decode_time".to_string(), + "12345".to_string(), + input_path.to_string_lossy().into_owned(), + output_path.to_string_lossy().into_owned(), + ]; + + let mut stderr = Vec::new(); + let exit_code = edit::run(&args, &mut stderr); + + let output = fs::read(&output_path).unwrap(); + let scoped_tfdt = extract_box_as::<_, Tfdt>( + &mut Cursor::new(output.clone()), + None, + BoxPath::from([fourcc("moof"), fourcc("traf"), fourcc("tfdt")]), + ) + .unwrap(); + let untouched_tfdt = extract_box_as::<_, Tfdt>( + &mut Cursor::new(output), + None, + BoxPath::from([fourcc("meta"), fourcc("tfdt")]), + ) + .unwrap(); + + let _ = fs::remove_file(&input_path); + let _ = fs::remove_file(&output_path); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!(scoped_tfdt.len(), 1); + assert_eq!(untouched_tfdt.len(), 1); + assert_eq!(scoped_tfdt[0].base_media_decode_time_v0, 12_345); + assert_eq!(untouched_tfdt[0].base_media_decode_time_v0, 54_321); +} + +#[test] +fn edit_command_rejects_path_scoped_type_mismatches() { + let input = build_edit_input_file(); + let input_path = write_temp_file("edit-path-type-mismatch-input", &input); + let output_path = write_temp_file("edit-path-type-mismatch-output", &[]); + let args = vec![ + "-path".to_string(), + "moof/traf/tfhd".to_string(), + "-base_media_decode_time".to_string(), + "12345".to_string(), + input_path.to_string_lossy().into_owned(), + output_path.to_string_lossy().into_owned(), + ]; + + let mut stderr = Vec::new(); + let exit_code = edit::run(&args, &mut stderr); + let stderr = String::from_utf8(stderr).unwrap(); + + let _ = fs::remove_file(&input_path); + let _ = fs::remove_file(&output_path); + + assert_eq!(exit_code, 1); + assert!( + stderr.contains( + "Error: path-based -base_media_decode_time rewrites require tfdt boxes: matched moof/traf/tfhd (type=tfhd" + ), + "{stderr}" + ); +} + +#[test] +fn edit_command_reports_invalid_path_arguments() { + let mut stderr = Vec::new(); + let exit_code = edit::run( + &[ + "-path".to_string(), + "moof//tfdt".to_string(), + "-base_media_decode_time".to_string(), + "12345".to_string(), + "input.mp4".to_string(), + "output.mp4".to_string(), + ], + &mut stderr, + ); + + assert_eq!(exit_code, 1); + assert_eq!( + String::from_utf8(stderr).unwrap(), + "Error: invalid box path: box path segment 2 must not be empty\n" + ); +} + +#[test] +fn edit_command_rejects_unsupported_path_only_rewrites() { + let input = build_edit_input_file(); + let input_path = write_temp_file("edit-path-unsupported-input", &input); + let output_path = write_temp_file("edit-path-unsupported-output", &[]); + let args = vec![ + "-path".to_string(), + "moof/traf/tfdt".to_string(), + "-drop".to_string(), + "free".to_string(), + input_path.to_string_lossy().into_owned(), + output_path.to_string_lossy().into_owned(), + ]; + + let mut stderr = Vec::new(); + let exit_code = edit::run(&args, &mut stderr); + + let _ = fs::remove_file(&input_path); + let _ = fs::remove_file(&output_path); + + assert_eq!(exit_code, 1); + assert_eq!( + String::from_utf8(stderr).unwrap(), + "Error: edit -path currently supports only -base_media_decode_time rewrites\n" + ); +} + +#[test] +fn edit_command_preserves_bytes_when_scoped_path_matches_nothing() { + let input = build_edit_input_file(); + let input_path = write_temp_file("edit-path-noop-input", &input); + let output_path = write_temp_file("edit-path-noop-output", &[]); + let args = vec![ + "-path".to_string(), + "moov/trak/tfdt".to_string(), + "-base_media_decode_time".to_string(), + "12345".to_string(), + input_path.to_string_lossy().into_owned(), + output_path.to_string_lossy().into_owned(), + ]; + + let mut stderr = Vec::new(); + let exit_code = edit::run(&args, &mut stderr); + let output = fs::read(&output_path).unwrap(); + + let _ = fs::remove_file(&input_path); + let _ = fs::remove_file(&output_path); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!(output, input); +} + fn build_edit_input_file() -> Vec { let ftyp = encode_supported_box( &Ftyp { @@ -197,3 +345,32 @@ fn build_edit_input_file() -> Vec { [ftyp, free, moof, mdat].concat() } + +fn build_edit_scoped_input_file() -> Vec { + let ftyp = encode_supported_box( + &Ftyp { + major_brand: fourcc("iso6"), + minor_version: 1, + compatible_brands: vec![fourcc("iso6"), fourcc("dash")], + }, + &[], + ); + + let tfdt_in_fragment = { + let mut tfdt = Tfdt::default(); + tfdt.base_media_decode_time_v0 = 9_000; + encode_supported_box(&tfdt, &[]) + }; + let tfdt_in_meta = { + let mut tfdt = Tfdt::default(); + tfdt.base_media_decode_time_v0 = 54_321; + encode_supported_box(&tfdt, &[]) + }; + + let traf = encode_supported_box(&Traf, &tfdt_in_fragment); + let moof = encode_supported_box(&Moof, &traf); + let meta = encode_supported_box(&Meta::default(), &tfdt_in_meta); + let mdat = encode_raw_box(fourcc("mdat"), &[0, 1, 2, 3]); + + [ftyp, moof, meta, mdat].concat() +} diff --git a/tests/cli_extract.rs b/tests/cli_extract.rs index b3c6793..5b3bc23 100644 --- a/tests/cli_extract.rs +++ b/tests/cli_extract.rs @@ -29,6 +29,28 @@ fn extract_command_writes_matching_raw_boxes() { assert_eq!(&stdout[4..8], b"mvhd"); } +#[test] +fn extract_command_writes_matching_raw_boxes_by_path() { + let file = build_extract_input_file(); + let path = write_temp_file("extract-cli-path", &file); + let args = vec![ + "-path".to_string(), + "moov/mvhd".to_string(), + path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = extract::run(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&path); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!(stdout.len(), 108); + assert_eq!(&stdout[4..8], b"mvhd"); +} + #[test] fn extract_command_rejects_invalid_arguments() { let mut stdout = Vec::new(); @@ -36,7 +58,13 @@ fn extract_command_rejects_invalid_arguments() { assert_eq!(extract::run(&[], &mut stdout, &mut stderr), 1); assert_eq!( String::from_utf8(stderr).unwrap(), - "USAGE: mp4forge extract BOX_TYPE INPUT.mp4\n" + concat!( + "USAGE: mp4forge extract BOX_TYPE INPUT.mp4\n", + " mp4forge extract -path [-path ...] INPUT.mp4\n", + "\n", + "OPTIONS:\n", + " -path Extract raw boxes that match the parsed slash-delimited box path\n" + ) ); let mut stdout = Vec::new(); @@ -56,6 +84,29 @@ fn extract_command_rejects_invalid_arguments() { ); } +#[test] +fn extract_command_rejects_invalid_path_arguments() { + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + assert_eq!( + extract::run( + &[ + "-path".to_string(), + "moov/trakk".to_string(), + fixture_path("sample.mp4").to_string_lossy().into_owned(), + ], + &mut stdout, + &mut stderr, + ), + 1 + ); + assert_eq!(String::from_utf8(stdout).unwrap(), ""); + assert_eq!( + String::from_utf8(stderr).unwrap(), + "Error: invalid box path: invalid box path segment 2 (\"trakk\"): fourcc values must be exactly 4 bytes, got 5\n" + ); +} + #[test] fn extract_command_matches_shared_fixture_reference_sizes() { let cases = [ @@ -96,6 +147,28 @@ fn extract_command_matches_shared_fixture_reference_sizes() { } } +#[test] +fn extract_command_matches_shared_fixture_reference_paths() { + let args = vec![ + "--path".to_string(), + "moov/*/mdia/mdhd".to_string(), + fixture_path("sample.mp4").to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = extract::run(&args, &mut stdout, &mut stderr); + + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!(stdout.len(), 64); + + let infos = parse_box_stream(&stdout); + assert_eq!(infos.len(), 2); + assert!(infos.iter().all(|info| info.box_type() == fourcc("mdhd"))); + assert_eq!(infos.iter().map(BoxInfo::size).sum::(), 64); +} + fn build_extract_input_file() -> Vec { let ftyp = encode_supported_box( &Ftyp { diff --git a/tests/cli_probe.rs b/tests/cli_probe.rs index f406b46..8e2b0fe 100644 --- a/tests/cli_probe.rs +++ b/tests/cli_probe.rs @@ -5,7 +5,16 @@ mod support; use std::fs; use mp4forge::boxes::iso14496_12::{Ftyp, Moov, Mvhd}; -use mp4forge::cli::probe::{self, ProbeFormat, ProbeReport, ProbeTrackReport}; +use mp4forge::cli::probe::{ + self, CodecDetailedProbeReport, CodecDetailedProbeTrackReport, DetailedProbeReport, + DetailedProbeTrackReport, MediaCharacteristicsProbeReport, + MediaCharacteristicsProbeTrackReport, ProbeFormat, ProbeReport, ProbeReportOptions, + ProbeTrackReport, +}; +use mp4forge::probe::{ + Av1CodecDetails, ColorInfo, DeclaredBitrateInfo, FieldOrderInfo, PixelAspectRatioInfo, + TrackCodecDetails, TrackMediaCharacteristics, +}; use support::{ encode_supported_box, fixture_path, fourcc, normalize_text, read_golden, write_temp_file, @@ -148,6 +157,451 @@ fn probe_report_renders_json_and_yaml_with_stable_field_order() { ); } +#[test] +fn detailed_probe_report_renders_json_and_yaml_with_stable_field_order() { + let report = DetailedProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso8".to_string(), "av01".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![DetailedProbeTrackReport { + track_id: 1, + timescale: 1_000, + duration: 1_000, + duration_seconds: 1.0, + codec: "av01".to_string(), + codec_family: "av1".to_string(), + encrypted: false, + handler_type: Some("vide".to_string()), + language: Some("eng".to_string()), + sample_entry_type: Some("av01".to_string()), + original_format: None, + protection_scheme_type: None, + protection_scheme_version: None, + width: Some(640), + height: Some(360), + channel_count: None, + sample_rate: None, + sample_num: Some(1), + chunk_num: Some(1), + idr_frame_num: None, + bitrate: Some(32_000), + max_bitrate: Some(32_000), + }], + }; + + let mut json = Vec::new(); + probe::write_detailed_report(&mut json, &report, ProbeFormat::Json).unwrap(); + assert_eq!( + String::from_utf8(json).unwrap(), + concat!( + "{\n", + " \"MajorBrand\": \"isom\",\n", + " \"MinorVersion\": 512,\n", + " \"CompatibleBrands\": [\n", + " \"isom\",\n", + " \"iso8\",\n", + " \"av01\"\n", + " ],\n", + " \"FastStart\": true,\n", + " \"Timescale\": 1000,\n", + " \"Duration\": 2000,\n", + " \"DurationSeconds\": 2,\n", + " \"Tracks\": [\n", + " {\n", + " \"TrackID\": 1,\n", + " \"Timescale\": 1000,\n", + " \"Duration\": 1000,\n", + " \"DurationSeconds\": 1,\n", + " \"Codec\": \"av01\",\n", + " \"CodecFamily\": \"av1\",\n", + " \"Encrypted\": false,\n", + " \"HandlerType\": \"vide\",\n", + " \"Language\": \"eng\",\n", + " \"SampleEntryType\": \"av01\",\n", + " \"Width\": 640,\n", + " \"Height\": 360,\n", + " \"SampleNum\": 1,\n", + " \"ChunkNum\": 1,\n", + " \"Bitrate\": 32000,\n", + " \"MaxBitrate\": 32000\n", + " }\n", + " ]\n", + "}\n" + ) + ); + + let mut yaml = Vec::new(); + probe::write_detailed_report(&mut yaml, &report, ProbeFormat::Yaml).unwrap(); + assert_eq!( + String::from_utf8(yaml).unwrap(), + concat!( + "major_brand: isom\n", + "minor_version: 512\n", + "compatible_brands:\n", + "- isom\n", + "- iso8\n", + "- av01\n", + "fast_start: true\n", + "timescale: 1000\n", + "duration: 2000\n", + "duration_seconds: 2\n", + "tracks:\n", + "- track_id: 1\n", + " timescale: 1000\n", + " duration: 1000\n", + " duration_seconds: 1\n", + " codec: av01\n", + " codec_family: av1\n", + " encrypted: false\n", + " handler_type: vide\n", + " language: eng\n", + " sample_entry_type: av01\n", + " width: 640\n", + " height: 360\n", + " sample_num: 1\n", + " chunk_num: 1\n", + " bitrate: 32000\n", + " max_bitrate: 32000\n" + ) + ); +} + +#[test] +fn codec_detailed_probe_report_renders_json_and_yaml_with_stable_field_order() { + let report = CodecDetailedProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso8".to_string(), "av01".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![CodecDetailedProbeTrackReport { + track_id: 1, + timescale: 1_000, + duration: 1_000, + duration_seconds: 1.0, + codec: "av01".to_string(), + codec_family: "av1".to_string(), + codec_details: TrackCodecDetails::Av1(Av1CodecDetails { + seq_profile: 0, + seq_level_idx_0: 13, + seq_tier_0: 1, + bit_depth: 10, + monochrome: false, + chroma_subsampling_x: 1, + chroma_subsampling_y: 0, + chroma_sample_position: 2, + initial_presentation_delay_minus_one: Some(3), + }), + encrypted: false, + handler_type: Some("vide".to_string()), + language: Some("eng".to_string()), + sample_entry_type: Some("av01".to_string()), + original_format: None, + protection_scheme_type: None, + protection_scheme_version: None, + width: Some(640), + height: Some(360), + channel_count: None, + sample_rate: None, + sample_num: Some(1), + chunk_num: Some(1), + idr_frame_num: None, + bitrate: Some(32_000), + max_bitrate: Some(32_000), + }], + }; + + let mut json = Vec::new(); + probe::write_codec_detailed_report(&mut json, &report, ProbeFormat::Json).unwrap(); + assert_eq!( + String::from_utf8(json).unwrap(), + concat!( + "{\n", + " \"MajorBrand\": \"isom\",\n", + " \"MinorVersion\": 512,\n", + " \"CompatibleBrands\": [\n", + " \"isom\",\n", + " \"iso8\",\n", + " \"av01\"\n", + " ],\n", + " \"FastStart\": true,\n", + " \"Timescale\": 1000,\n", + " \"Duration\": 2000,\n", + " \"DurationSeconds\": 2,\n", + " \"Tracks\": [\n", + " {\n", + " \"TrackID\": 1,\n", + " \"Timescale\": 1000,\n", + " \"Duration\": 1000,\n", + " \"DurationSeconds\": 1,\n", + " \"Codec\": \"av01\",\n", + " \"CodecFamily\": \"av1\",\n", + " \"Encrypted\": false,\n", + " \"HandlerType\": \"vide\",\n", + " \"Language\": \"eng\",\n", + " \"SampleEntryType\": \"av01\",\n", + " \"Width\": 640,\n", + " \"Height\": 360,\n", + " \"SampleNum\": 1,\n", + " \"ChunkNum\": 1,\n", + " \"Bitrate\": 32000,\n", + " \"MaxBitrate\": 32000,\n", + " \"CodecDetails\": {\n", + " \"Kind\": \"av1\",\n", + " \"SeqProfile\": 0,\n", + " \"SeqLevelIdx0\": 13,\n", + " \"SeqTier0\": 1,\n", + " \"BitDepth\": 10,\n", + " \"Monochrome\": false,\n", + " \"ChromaSubsamplingX\": 1,\n", + " \"ChromaSubsamplingY\": 0,\n", + " \"ChromaSamplePosition\": 2,\n", + " \"InitialPresentationDelayMinusOne\": 3\n", + " }\n", + " }\n", + " ]\n", + "}\n" + ) + ); + + let mut yaml = Vec::new(); + probe::write_codec_detailed_report(&mut yaml, &report, ProbeFormat::Yaml).unwrap(); + assert_eq!( + String::from_utf8(yaml).unwrap(), + concat!( + "major_brand: isom\n", + "minor_version: 512\n", + "compatible_brands:\n", + "- isom\n", + "- iso8\n", + "- av01\n", + "fast_start: true\n", + "timescale: 1000\n", + "duration: 2000\n", + "duration_seconds: 2\n", + "tracks:\n", + "- track_id: 1\n", + " timescale: 1000\n", + " duration: 1000\n", + " duration_seconds: 1\n", + " codec: av01\n", + " codec_family: av1\n", + " encrypted: false\n", + " handler_type: vide\n", + " language: eng\n", + " sample_entry_type: av01\n", + " width: 640\n", + " height: 360\n", + " sample_num: 1\n", + " chunk_num: 1\n", + " bitrate: 32000\n", + " max_bitrate: 32000\n", + " codec_details:\n", + " kind: av1\n", + " seq_profile: 0\n", + " seq_level_idx_0: 13\n", + " seq_tier_0: 1\n", + " bit_depth: 10\n", + " monochrome: false\n", + " chroma_subsampling_x: 1\n", + " chroma_subsampling_y: 0\n", + " chroma_sample_position: 2\n", + " initial_presentation_delay_minus_one: 3\n" + ) + ); +} + +#[test] +fn media_characteristics_probe_report_renders_json_and_yaml_with_stable_field_order() { + let report = MediaCharacteristicsProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso8".to_string(), "avc1".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![MediaCharacteristicsProbeTrackReport { + track_id: 1, + timescale: 1_000, + duration: 1_000, + duration_seconds: 1.0, + codec: "avc1.64001F".to_string(), + codec_family: "avc".to_string(), + codec_details: TrackCodecDetails::Unknown, + media_characteristics: TrackMediaCharacteristics { + declared_bitrate: Some(DeclaredBitrateInfo { + buffer_size_db: 32_768, + max_bitrate: 4_000_000, + avg_bitrate: 2_500_000, + }), + color: Some(ColorInfo { + colour_type: fourcc("nclx"), + colour_primaries: Some(9), + transfer_characteristics: Some(16), + matrix_coefficients: Some(9), + full_range: Some(true), + profile_size: None, + unknown_size: None, + }), + pixel_aspect_ratio: Some(PixelAspectRatioInfo { + h_spacing: 4, + v_spacing: 3, + }), + field_order: Some(FieldOrderInfo { + field_count: 2, + field_ordering: 6, + interlaced: true, + }), + }, + encrypted: false, + handler_type: Some("vide".to_string()), + language: Some("eng".to_string()), + sample_entry_type: Some("avc1".to_string()), + original_format: None, + protection_scheme_type: None, + protection_scheme_version: None, + width: Some(640), + height: Some(360), + channel_count: None, + sample_rate: None, + sample_num: Some(1), + chunk_num: Some(1), + idr_frame_num: None, + bitrate: Some(32_000), + max_bitrate: Some(32_000), + }], + }; + + let mut json = Vec::new(); + probe::write_media_characteristics_report(&mut json, &report, ProbeFormat::Json).unwrap(); + assert_eq!( + String::from_utf8(json).unwrap(), + concat!( + "{\n", + " \"MajorBrand\": \"isom\",\n", + " \"MinorVersion\": 512,\n", + " \"CompatibleBrands\": [\n", + " \"isom\",\n", + " \"iso8\",\n", + " \"avc1\"\n", + " ],\n", + " \"FastStart\": true,\n", + " \"Timescale\": 1000,\n", + " \"Duration\": 2000,\n", + " \"DurationSeconds\": 2,\n", + " \"Tracks\": [\n", + " {\n", + " \"TrackID\": 1,\n", + " \"Timescale\": 1000,\n", + " \"Duration\": 1000,\n", + " \"DurationSeconds\": 1,\n", + " \"Codec\": \"avc1.64001F\",\n", + " \"CodecFamily\": \"avc\",\n", + " \"Encrypted\": false,\n", + " \"HandlerType\": \"vide\",\n", + " \"Language\": \"eng\",\n", + " \"SampleEntryType\": \"avc1\",\n", + " \"Width\": 640,\n", + " \"Height\": 360,\n", + " \"SampleNum\": 1,\n", + " \"ChunkNum\": 1,\n", + " \"Bitrate\": 32000,\n", + " \"MaxBitrate\": 32000,\n", + " \"CodecDetails\": {\n", + " \"Kind\": \"avc\"\n", + " },\n", + " \"MediaCharacteristics\": {\n", + " \"DeclaredBitrate\": {\n", + " \"BufferSizeDB\": 32768,\n", + " \"MaxBitrate\": 4000000,\n", + " \"AvgBitrate\": 2500000\n", + " },\n", + " \"Color\": {\n", + " \"ColourType\": \"nclx\",\n", + " \"ColourPrimaries\": 9,\n", + " \"TransferCharacteristics\": 16,\n", + " \"MatrixCoefficients\": 9,\n", + " \"FullRange\": true\n", + " },\n", + " \"PixelAspectRatio\": {\n", + " \"HSpacing\": 4,\n", + " \"VSpacing\": 3\n", + " },\n", + " \"FieldOrder\": {\n", + " \"FieldCount\": 2,\n", + " \"FieldOrdering\": 6,\n", + " \"Interlaced\": true\n", + " }\n", + " }\n", + " }\n", + " ]\n", + "}\n" + ) + ); + + let mut yaml = Vec::new(); + probe::write_media_characteristics_report(&mut yaml, &report, ProbeFormat::Yaml).unwrap(); + assert_eq!( + String::from_utf8(yaml).unwrap(), + concat!( + "major_brand: isom\n", + "minor_version: 512\n", + "compatible_brands:\n", + "- isom\n", + "- iso8\n", + "- avc1\n", + "fast_start: true\n", + "timescale: 1000\n", + "duration: 2000\n", + "duration_seconds: 2\n", + "tracks:\n", + "- track_id: 1\n", + " timescale: 1000\n", + " duration: 1000\n", + " duration_seconds: 1\n", + " codec: avc1.64001F\n", + " codec_family: avc\n", + " encrypted: false\n", + " handler_type: vide\n", + " language: eng\n", + " sample_entry_type: avc1\n", + " width: 640\n", + " height: 360\n", + " sample_num: 1\n", + " chunk_num: 1\n", + " bitrate: 32000\n", + " max_bitrate: 32000\n", + " codec_details:\n", + " kind: avc\n", + " media_characteristics:\n", + " declared_bitrate:\n", + " buffer_size_db: 32768\n", + " max_bitrate: 4000000\n", + " avg_bitrate: 2500000\n", + " color:\n", + " colour_type: nclx\n", + " colour_primaries: 9\n", + " transfer_characteristics: 16\n", + " matrix_coefficients: 9\n", + " full_range: true\n", + " pixel_aspect_ratio:\n", + " h_spacing: 4\n", + " v_spacing: 3\n", + " field_order:\n", + " field_count: 2\n", + " field_ordering: 6\n", + " interlaced: true\n" + ) + ); +} + #[test] fn probe_command_reads_a_file_and_honors_the_yaml_flag() { let path = write_temp_file("probe-cli", &build_probe_input_file()); @@ -189,6 +643,11 @@ fn probe_command_matches_shared_fixture_goldens() { (&[], "cli_probe/sample.json"), (&["-format", "json"], "cli_probe/sample.json"), (&["-format", "yaml"], "cli_probe/sample.yaml"), + (&["-detail", "light"], "cli_probe/sample_light.json"), + ( + &["-detail", "light", "-format", "yaml"], + "cli_probe/sample_light.yaml", + ), ]; for (options, golden) in cases { @@ -216,6 +675,44 @@ fn probe_command_matches_shared_fixture_goldens() { } } +#[test] +fn probe_report_with_lightweight_options_omits_expensive_fields() { + let mut file = fs::File::open(fixture_path("sample.mp4")).unwrap(); + let report = probe::build_codec_detailed_report_with_options( + &mut file, + ProbeReportOptions::lightweight(), + ) + .unwrap(); + + assert_eq!(report.major_brand, "isom"); + assert!(!report.fast_start); + assert_eq!(report.tracks.len(), 2); + + let video = &report.tracks[0]; + assert_eq!(video.track_id, 1); + assert_eq!(video.codec, "avc1.64000C"); + assert_eq!(video.codec_family, "avc"); + assert_eq!(video.width, Some(320)); + assert_eq!(video.height, Some(180)); + assert_eq!(video.sample_num, None); + assert_eq!(video.chunk_num, None); + assert_eq!(video.idr_frame_num, None); + assert_eq!(video.bitrate, None); + assert_eq!(video.max_bitrate, None); + + let audio = &report.tracks[1]; + assert_eq!(audio.track_id, 2); + assert_eq!(audio.codec, "mp4a.40.2"); + assert_eq!(audio.codec_family, "mp4_audio"); + assert_eq!(audio.channel_count, Some(2)); + assert_eq!(audio.sample_rate, Some(44100)); + assert_eq!(audio.sample_num, None); + assert_eq!(audio.chunk_num, None); + assert_eq!(audio.idr_frame_num, None); + assert_eq!(audio.bitrate, None); + assert_eq!(audio.max_bitrate, None); +} + #[test] fn probe_library_handles_quicktime_fixture() { let mut file = fs::File::open(fixture_path("sample_qt.mp4")).unwrap(); diff --git a/tests/cli_psshdump.rs b/tests/cli_psshdump.rs index 17fa688..9c87a67 100644 --- a/tests/cli_psshdump.rs +++ b/tests/cli_psshdump.rs @@ -3,19 +3,186 @@ mod support; use std::fs; +use std::io::Cursor; -use mp4forge::boxes::iso14496_12::{Ftyp, Moov}; +use mp4forge::boxes::iso14496_12::{Ftyp, Moof, Moov}; use mp4forge::boxes::iso23001_7::{Pssh, PsshKid}; -use mp4forge::cli::pssh; +use mp4forge::cli::pssh::{self, PsshDumpFormat, PsshEntryReport, PsshReport, PsshReportFilter}; use mp4forge::codec::MutableBox; +use mp4forge::walk::BoxPath; use support::{ encode_supported_box, fixture_path, fourcc, normalize_text, read_golden, write_temp_file, }; +const PRIMARY_SYSTEM_ID: [u8; 16] = [ + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, +]; +const PRIMARY_KID: [u8; 16] = [ + 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, +]; +const SECONDARY_SYSTEM_ID: [u8; 16] = [ + 0xed, 0xef, 0x8b, 0xa9, 0x79, 0xd6, 0x4a, 0xce, 0xa3, 0xc8, 0x27, 0xdc, 0xd5, 0x1d, 0x21, 0xed, +]; +const SECONDARY_KID: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, 0xfe, 0xdc, 0xba, 0x98, 0x76, 0x54, 0x32, 0x10, +]; + +#[test] +fn pssh_report_renders_json_and_yaml_with_stable_field_order() { + let report = PsshReport { + entries: vec![PsshEntryReport { + index: 0, + path: "moov/pssh".to_string(), + offset: 28, + size: 54, + version: 1, + flags: 0, + system_id: "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b".to_string(), + kid_count: 1, + kids: vec!["01234567-89ab-cdef-0123-456789abcdef".to_string()], + data_size: 2, + data_bytes: vec![170, 187], + raw_box_base64: "AAA=".to_string(), + }], + }; + + let mut json = Vec::new(); + pssh::write_pssh_report(&mut json, &report, PsshDumpFormat::Json).unwrap(); + assert_eq!( + String::from_utf8(json).unwrap(), + concat!( + "{\n", + " \"Entries\": [\n", + " {\n", + " \"Index\": 0,\n", + " \"Path\": \"moov/pssh\",\n", + " \"Offset\": 28,\n", + " \"Size\": 54,\n", + " \"Version\": 1,\n", + " \"Flags\": 0,\n", + " \"SystemId\": \"1077efec-c0b2-4d02-ace3-3c1e52e2fb4b\",\n", + " \"KidCount\": 1,\n", + " \"Kids\": [\n", + " \"01234567-89ab-cdef-0123-456789abcdef\"\n", + " ],\n", + " \"DataSize\": 2,\n", + " \"DataBytes\": [\n", + " 170,\n", + " 187\n", + " ],\n", + " \"RawBoxBase64\": \"AAA=\"\n", + " }\n", + " ]\n", + "}\n" + ) + ); + + let mut yaml = Vec::new(); + pssh::write_pssh_report(&mut yaml, &report, PsshDumpFormat::Yaml).unwrap(); + assert_eq!( + String::from_utf8(yaml).unwrap(), + concat!( + "entries:\n", + "- index: 0\n", + " path: moov/pssh\n", + " offset: 28\n", + " size: 54\n", + " version: 1\n", + " flags: 0\n", + " system_id: 1077efec-c0b2-4d02-ace3-3c1e52e2fb4b\n", + " kid_count: 1\n", + " kids:\n", + " - 01234567-89ab-cdef-0123-456789abcdef\n", + " data_size: 2\n", + " data_bytes:\n", + " - 170\n", + " - 187\n", + " raw_box_base64: 'AAA='\n" + ) + ); +} + +#[test] +fn build_pssh_report_extracts_kids_data_and_raw_box_base64() { + let file = build_pssh_input_file(&[0xaa, 0xbb]); + let report = pssh::build_pssh_report(&mut Cursor::new(file)).unwrap(); + + assert_eq!( + report, + PsshReport { + entries: vec![PsshEntryReport { + index: 0, + path: "moov/pssh".to_string(), + offset: 28, + size: 54, + version: 1, + flags: 0, + system_id: "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b".to_string(), + kid_count: 1, + kids: vec!["01234567-89ab-cdef-0123-456789abcdef".to_string()], + data_size: 2, + data_bytes: vec![0xaa, 0xbb], + raw_box_base64: + "AAAANnBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniavN7wEjRWeJq83vAAAAAqq7" + .to_string(), + }], + } + ); +} + +#[test] +fn build_pssh_report_with_filters_scopes_by_path_system_id_and_kid() { + let file = build_filtered_pssh_input_file(); + + let moof_only = pssh::build_pssh_report_with_filters( + &mut Cursor::new(&file), + &PsshReportFilter { + paths: vec![BoxPath::parse("moof").unwrap()], + ..PsshReportFilter::default() + }, + ) + .unwrap(); + assert_eq!(moof_only.entries.len(), 1); + assert_eq!(moof_only.entries[0].index, 1); + assert_eq!(moof_only.entries[0].path, "moof/pssh"); + assert_eq!( + moof_only.entries[0].system_id, + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" + ); + + let system_filtered = pssh::build_pssh_report_with_filters( + &mut Cursor::new(&file), + &PsshReportFilter { + system_ids: vec![SECONDARY_SYSTEM_ID], + ..PsshReportFilter::default() + }, + ) + .unwrap(); + assert_eq!(system_filtered.entries.len(), 1); + assert_eq!(system_filtered.entries[0].index, 1); + assert_eq!(system_filtered.entries[0].path, "moof/pssh"); + + let kid_filtered = pssh::build_pssh_report_with_filters( + &mut Cursor::new(&file), + &PsshReportFilter { + kids: vec![PRIMARY_KID], + ..PsshReportFilter::default() + }, + ) + .unwrap(); + assert_eq!(kid_filtered.entries.len(), 1); + assert_eq!(kid_filtered.entries[0].index, 0); + assert_eq!(kid_filtered.entries[0].path, "moov/pssh"); + assert_eq!( + kid_filtered.entries[0].kids, + vec!["01234567-89ab-cdef-0123-456789abcdef".to_string()] + ); +} + #[test] fn psshdump_command_renders_offsets_flags_and_base64() { - let file = build_pssh_input_file(); + let file = build_pssh_input_file(&[]); let path = write_temp_file("psshdump-cli", &file); let args = vec![path.to_string_lossy().into_owned()]; @@ -44,54 +211,248 @@ fn psshdump_command_renders_offsets_flags_and_base64() { } #[test] -fn psshdump_command_matches_shared_encrypted_init_fixtures() { - for fixture_name in ["sample_init.encv.mp4", "sample_init.enca.mp4"] { - let args = vec![fixture_path(fixture_name).to_string_lossy().into_owned()]; +fn psshdump_command_filters_text_output_by_path() { + let file = build_filtered_pssh_input_file(); + let path = write_temp_file("psshdump-cli-filter-path", &file); + let args = vec![ + "-path".to_string(), + "moof".to_string(), + path.to_string_lossy().into_owned(), + ]; + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = pssh::run(&args, &mut stdout, &mut stderr); + + let _ = fs::remove_file(&path); + + let stdout = String::from_utf8(stdout).unwrap(); + assert_eq!(exit_code, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert!(stdout.contains("1:\n")); + assert!(stdout.contains("systemId: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed")); + assert!(!stdout.contains("1077efec-c0b2-4d02-ace3-3c1e52e2fb4b")); +} + +#[test] +fn psshdump_command_filters_structured_output_with_stable_goldens() { + let file = build_filtered_pssh_input_file(); + let path = write_temp_file("psshdump-cli-filtered", &file); + let cases: &[(&[&str], &str)] = &[ + ( + &["-path", "moof", "-format", "json"], + "cli_psshdump/filtered_path.json", + ), + ( + &[ + "-system-id", + "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + "-format", + "json", + ], + "cli_psshdump/filtered_system_id.json", + ), + ( + &[ + "-kid", + "fedcba98-7654-3210-fedc-ba9876543210", + "-format", + "yaml", + ], + "cli_psshdump/filtered_kid.yaml", + ), + ]; + + for (options, golden) in cases { + let mut args = options + .iter() + .map(|value| value.to_string()) + .collect::>(); + args.push(path.to_string_lossy().into_owned()); let mut stdout = Vec::new(); let mut stderr = Vec::new(); let exit_code = pssh::run(&args, &mut stdout, &mut stderr); - assert_eq!(exit_code, 0, "fixture psshdump failed for {fixture_name}"); + assert_eq!(exit_code, 0, "filtered psshdump failed for {golden}"); assert_eq!( String::from_utf8(stderr).unwrap(), "", - "stderr for {fixture_name}" + "stderr for {golden}" ); assert_eq!( normalize_text(&String::from_utf8(stdout).unwrap()), - read_golden("cli_psshdump/sample_init.txt"), - "golden mismatch for {fixture_name}" + read_golden(golden), + "golden mismatch for {golden}" ); } + + let _ = fs::remove_file(&path); +} + +#[test] +fn psshdump_command_rejects_invalid_filter_values() { + let file = build_filtered_pssh_input_file(); + let path = write_temp_file("psshdump-cli-invalid-filter", &file); + let cases: &[(&[&str], &str)] = &[ + ( + &["-path", "moov//pssh"], + "Error: box path segment 2 must not be empty\n", + ), + ( + &["-system-id", "not-a-uuid"], + "Error: invalid system ID: expected 32 hexadecimal digits with optional hyphens\n", + ), + ( + &["-kid", "xyz"], + "Error: invalid KID: expected 32 hexadecimal digits with optional hyphens\n", + ), + ]; + + for (options, expected_stderr) in cases { + let mut args = options + .iter() + .map(|value| value.to_string()) + .collect::>(); + args.push(path.to_string_lossy().into_owned()); + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = pssh::run(&args, &mut stdout, &mut stderr); + + assert_eq!(exit_code, 1, "invalid filter unexpectedly succeeded"); + assert_eq!(String::from_utf8(stdout).unwrap(), ""); + assert_eq!(String::from_utf8(stderr).unwrap(), *expected_stderr); + } + + let _ = fs::remove_file(&path); +} + +#[test] +fn psshdump_command_returns_empty_reports_for_empty_matches() { + let file = build_filtered_pssh_input_file(); + let path = write_temp_file("psshdump-cli-empty-filter", &file); + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let text_exit = pssh::run( + &[ + "-system-id".to_string(), + "ffffffff-ffff-ffff-ffff-ffffffffffff".to_string(), + path.to_string_lossy().into_owned(), + ], + &mut stdout, + &mut stderr, + ); + assert_eq!(text_exit, 0); + assert_eq!(String::from_utf8(stderr).unwrap(), ""); + assert_eq!(String::from_utf8(stdout).unwrap(), ""); + + let mut json_stdout = Vec::new(); + let mut json_stderr = Vec::new(); + let json_exit = pssh::run( + &[ + "-system-id".to_string(), + "ffffffff-ffff-ffff-ffff-ffffffffffff".to_string(), + "-format".to_string(), + "json".to_string(), + path.to_string_lossy().into_owned(), + ], + &mut json_stdout, + &mut json_stderr, + ); + assert_eq!(json_exit, 0); + assert_eq!(String::from_utf8(json_stderr).unwrap(), ""); + assert_eq!( + String::from_utf8(json_stdout).unwrap(), + "{\n \"Entries\": [\n ]\n}\n" + ); + + let _ = fs::remove_file(&path); +} + +#[test] +fn psshdump_command_matches_shared_encrypted_init_fixtures() { + let cases: &[(&[&str], &str)] = &[ + (&[], "cli_psshdump/sample_init.txt"), + (&["-format", "json"], "cli_psshdump/sample_init.json"), + (&["-format", "yaml"], "cli_psshdump/sample_init.yaml"), + ]; + + for fixture_name in ["sample_init.encv.mp4", "sample_init.enca.mp4"] { + for (options, golden) in cases { + let mut args = options + .iter() + .map(|value| value.to_string()) + .collect::>(); + args.push(fixture_path(fixture_name).to_string_lossy().into_owned()); + + let mut stdout = Vec::new(); + let mut stderr = Vec::new(); + let exit_code = pssh::run(&args, &mut stdout, &mut stderr); + + assert_eq!( + exit_code, 0, + "fixture psshdump failed for {fixture_name} {golden}" + ); + assert_eq!( + String::from_utf8(stderr).unwrap(), + "", + "stderr for {fixture_name} {golden}" + ); + assert_eq!( + normalize_text(&String::from_utf8(stdout).unwrap()), + read_golden(golden), + "golden mismatch for {fixture_name} {golden}" + ); + } + } +} + +fn build_pssh_input_file(data: &[u8]) -> Vec { + let moov = encode_supported_box( + &Moov, + &encode_supported_box(&build_pssh_box(PRIMARY_SYSTEM_ID, PRIMARY_KID, data), &[]), + ); + [build_ftyp_box(), moov].concat() } -fn build_pssh_input_file() -> Vec { - let ftyp = encode_supported_box( +fn build_filtered_pssh_input_file() -> Vec { + let moov = encode_supported_box( + &Moov, + &encode_supported_box( + &build_pssh_box(PRIMARY_SYSTEM_ID, PRIMARY_KID, &[0xaa]), + &[], + ), + ); + let moof = encode_supported_box( + &Moof, + &encode_supported_box( + &build_pssh_box(SECONDARY_SYSTEM_ID, SECONDARY_KID, &[0xbb, 0xcc]), + &[], + ), + ); + [build_ftyp_box(), moov, moof].concat() +} + +fn build_ftyp_box() -> Vec { + encode_supported_box( &Ftyp { major_brand: fourcc("isom"), minor_version: 1, compatible_brands: vec![fourcc("isom")], }, &[], - ); + ) +} +fn build_pssh_box(system_id: [u8; 16], kid: [u8; 16], data: &[u8]) -> Pssh { let mut pssh = Pssh::default(); - pssh.system_id = [ - 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, - 0x4b, - ]; + pssh.system_id = system_id; pssh.kid_count = 1; - pssh.kids = vec![PsshKid { - kid: [ - 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, - 0xcd, 0xef, - ], - }]; - pssh.data_size = 0; - pssh.data = Vec::new(); + pssh.kids = vec![PsshKid { kid }]; + pssh.data_size = u32::try_from(data.len()).unwrap(); + pssh.data = data.to_vec(); pssh.set_version(1); - - let moov = encode_supported_box(&Moov, &encode_supported_box(&pssh, &[])); - [ftyp, moov].concat() + pssh } diff --git a/tests/fixture_probe_coverage.rs b/tests/fixture_probe_coverage.rs new file mode 100644 index 0000000..b4cabd1 --- /dev/null +++ b/tests/fixture_probe_coverage.rs @@ -0,0 +1,251 @@ +mod support; + +use std::fs; + +use mp4forge::cli::probe as cli_probe; +use mp4forge::probe::{TrackCodec, TrackCodecFamily, probe, probe_media_characteristics}; + +use support::fixture_path; + +struct FixtureExpectation { + file_name: &'static str, + major_brand: &'static str, + tracks: &'static [TrackExpectation], +} + +struct TrackExpectation { + coarse_codec: TrackCodec, + report_codec: &'static str, + codec_family: TrackCodecFamily, + sample_entry_type: &'static str, + width: Option, + height: Option, + channel_count: Option, + sample_rate: Option, +} + +#[test] +fn fixture_probe_surfaces_cover_added_codec_families() { + let cases = [ + FixtureExpectation { + file_name: "vp9_opus.mp4", + major_brand: "isom", + tracks: &[ + TrackExpectation { + coarse_codec: TrackCodec::Unknown, + report_codec: "vp09", + codec_family: TrackCodecFamily::Vp9, + sample_entry_type: "vp09", + width: Some(1920), + height: Some(1080), + channel_count: None, + sample_rate: None, + }, + TrackExpectation { + coarse_codec: TrackCodec::Unknown, + report_codec: "Opus", + codec_family: TrackCodecFamily::Opus, + sample_entry_type: "Opus", + width: None, + height: None, + channel_count: Some(2), + sample_rate: Some(48_000), + }, + ], + }, + FixtureExpectation { + file_name: "av1_opus.mp4", + major_brand: "isom", + tracks: &[ + TrackExpectation { + coarse_codec: TrackCodec::Unknown, + report_codec: "av01", + codec_family: TrackCodecFamily::Av1, + sample_entry_type: "av01", + width: Some(1280), + height: Some(720), + channel_count: None, + sample_rate: None, + }, + TrackExpectation { + coarse_codec: TrackCodec::Unknown, + report_codec: "Opus", + codec_family: TrackCodecFamily::Opus, + sample_entry_type: "Opus", + width: None, + height: None, + channel_count: Some(2), + sample_rate: Some(48_000), + }, + ], + }, + FixtureExpectation { + file_name: "aac_audio.mp4", + major_brand: "isom", + tracks: &[TrackExpectation { + coarse_codec: TrackCodec::Mp4a, + report_codec: "mp4a.40.2", + codec_family: TrackCodecFamily::Mp4Audio, + sample_entry_type: "mp4a", + width: None, + height: None, + channel_count: Some(2), + sample_rate: Some(48_000), + }], + }, + FixtureExpectation { + file_name: "opus_audio.mp4", + major_brand: "isom", + tracks: &[TrackExpectation { + coarse_codec: TrackCodec::Unknown, + report_codec: "Opus", + codec_family: TrackCodecFamily::Opus, + sample_entry_type: "Opus", + width: None, + height: None, + channel_count: Some(2), + sample_rate: Some(48_000), + }], + }, + FixtureExpectation { + file_name: "pcm_audio.mp4", + major_brand: "isom", + tracks: &[TrackExpectation { + coarse_codec: TrackCodec::Unknown, + report_codec: "ipcm", + codec_family: TrackCodecFamily::Pcm, + sample_entry_type: "ipcm", + width: None, + height: None, + channel_count: Some(2), + sample_rate: Some(48_000), + }], + }, + ]; + + for case in cases { + let path = fixture_path(case.file_name); + + let mut summary_file = fs::File::open(&path).unwrap(); + let summary = probe(&mut summary_file).unwrap(); + assert_eq!(summary.major_brand.to_string(), case.major_brand); + assert_eq!( + summary.tracks.len(), + case.tracks.len(), + "fixture={}", + case.file_name + ); + + let mut rich_file = fs::File::open(&path).unwrap(); + let rich = probe_media_characteristics(&mut rich_file).unwrap(); + assert_eq!( + rich.major_brand.to_string(), + case.major_brand, + "fixture={}", + case.file_name + ); + assert_eq!( + rich.tracks.len(), + case.tracks.len(), + "fixture={}", + case.file_name + ); + + let mut report_file = fs::File::open(&path).unwrap(); + let report = cli_probe::build_media_characteristics_report(&mut report_file).unwrap(); + assert_eq!( + report.major_brand, case.major_brand, + "fixture={}", + case.file_name + ); + assert_eq!( + report.tracks.len(), + case.tracks.len(), + "fixture={}", + case.file_name + ); + + for (((summary_track, rich_track), report_track), expected_track) in summary + .tracks + .iter() + .zip(rich.tracks.iter()) + .zip(report.tracks.iter()) + .zip(case.tracks.iter()) + { + assert_eq!( + summary_track.codec, expected_track.coarse_codec, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + assert_eq!( + rich_track.summary.codec_family, expected_track.codec_family, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + assert_eq!( + rich_track.summary.sample_entry_type, + Some(mp4forge::FourCc::try_from(expected_track.sample_entry_type).unwrap()), + "fixture={} track={}", + case.file_name, + summary_track.track_id + ); + assert_eq!( + report_track.codec, expected_track.report_codec, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + assert_eq!( + report_track.codec_family, + codec_family_name(expected_track.codec_family), + "fixture={} track={}", + case.file_name, + summary_track.track_id + ); + assert_eq!( + report_track.sample_entry_type.as_deref(), + Some(expected_track.sample_entry_type), + "fixture={} track={}", + case.file_name, + summary_track.track_id + ); + assert_eq!( + report_track.width, expected_track.width, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + assert_eq!( + report_track.height, expected_track.height, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + assert_eq!( + report_track.channel_count, expected_track.channel_count, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + assert_eq!( + report_track.sample_rate, expected_track.sample_rate, + "fixture={} track={}", + case.file_name, summary_track.track_id + ); + } + } +} + +fn codec_family_name(value: TrackCodecFamily) -> &'static str { + match value { + TrackCodecFamily::Unknown => "unknown", + TrackCodecFamily::Avc => "avc", + TrackCodecFamily::Hevc => "hevc", + TrackCodecFamily::Av1 => "av1", + TrackCodecFamily::Vp8 => "vp8", + TrackCodecFamily::Vp9 => "vp9", + TrackCodecFamily::Mp4Audio => "mp4_audio", + TrackCodecFamily::Opus => "opus", + TrackCodecFamily::Ac3 => "ac3", + TrackCodecFamily::Pcm => "pcm", + TrackCodecFamily::XmlSubtitle => "xml_subtitle", + TrackCodecFamily::TextSubtitle => "text_subtitle", + TrackCodecFamily::WebVtt => "webvtt", + } +} diff --git a/tests/fixtures/aac_audio.mp4 b/tests/fixtures/aac_audio.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..5f49cf0569bd61bfb28a09ad3a163b4ae1d890a6 GIT binary patch literal 12918 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/av1_opus.mp4 b/tests/fixtures/av1_opus.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..d89eec06678777a0dcb6e3bec4ebe38e75092113 GIT binary patch literal 308559 zcmcG#b9A1~*EW2`XlymMZKrW#+h$|iwrw;v+SqLDq_K@g&3EPQ{d=Fj&wBOGx6`?f zy^qH4Fw8Bs5hYeY5V?uK=~&wM3#-Q zg#pkav@!ngv54}4B=**3+9sbdPKQ0!=CjZgjQ59(0Nf_7~TbsQ5K#1E| z*qQcXQ0Kftc2>^fx zq(uCo;Q_D+Kufs9cX(-o?bowb9lk+mDJ=l4ILAlVx@+5_?3 zcrR#%P7Z&@oFw%U@Lm8Qf{srA%m=Uo)M1nFOV=YVtbf3$&X-k;&9cK_YAZPT40-*mF|4%)z-TRpSqXYZ;Pa6Q#Up(|* zJltPA{9iosUp&fRJjP!<=3hMSUp(GlJmFtF(O*2-U;Mx8;-6=w{)?yi%b(#dp7FnU z5OCmFcmVDH2rmGHg98A#{|zL7?+;7z2NB=n0&Q^7KS+f3ubut}G4=l-EWsZXd+!_Q z4~_ZIeW0iw1z^BLhIcDg!{PJOQA$qX5vmdjRMg zD*z063IGOteK0H_Vm|=Dm@fff9H0O&PD%inQV0M{xf%ebqYD6sMgoAtZ~?&KjR4>X z;Q(;_JODTeP$vxofb;PK!1;{<;37a?2FS@q0KgSt0pLmm0Pw(g0C*TY06d%#0G`wZ z08c&vfT!;O!0UJb;Ppztj!XUz=f(RP2MXBHAN=PR%5rzr^!EY!BGD9=S7Df0MOF?zdA1*?VN4@?p6tC8~@jN0s8IV^8(57&w245-~WfR z0@VNCvqI~?ofXn@_Rfy~2Kuid0R_BozJT{f1q!OMoIP+6fN=f~=C3^*r2jkMy#SAi zzxMF=P{6eZ)(u3wzxJVb{_g@@lmAm*0^5K9_XQAOeu3Zv3-Q1BZoik0{~~Z+L4f-T z2obQ5{#B@eI_1AM{l7YJ4*?_wvrY`~Am<0;mJ` zxqqJJy`B(Q-k^4=6afCw7XVHS)JcFkFCH-0gaF{e z1_1ESe85}-a`M1jQUvB#fCB(L1lTqVn5T*Pz+CGEfTw*2fY+h{bB!PPPB@Nc_Qvm% z=U`|3e(eCx0RZIfePsk?>12AfaSfBfwQrb0T4B8O#VTj3LyIXwgk)= z2LpTi_sIW4Xf6I+&q&Li?B2I7JY$1@8ovhw0Iw~k>`e5Gz*`F=(1bR1FfjpuS9sX~ zo&Lui)=OF62h zk@Pc9m0Go#ND}ZEq^QIoxbfz* z%``DGo!!8a@2bs5R!&?d7RQtsztG?VithO0#L-CwGP^-Ku%LovT7ZB9~#+H*{| zvi@ABps$5SWv)hre~Y$;D;!`h74dHxse4RkCO=gCG1~-Tc2nbzt(CTAa6~JBKl-t$ zOz(nexl! z)aH>dOevdVRehHjw!glNMqJP$hIQX^TFdE~FWqr3@h*R)(*~CC~n4j)0Is8SwIucI>FT<*a(#EMLEz?#SLLqi!8B ze;Qfh2xMBNJ$IB28IxD4u}8l0pYd$91PZjaz4a4y=J?aEyDBypp_nx0w#&>Al)}BW zmZwvTJBOVM%@-A&Jt6P@_!t6%cxU3+?R>-}j*OhD5L|1jU2I zzW(fBt4i@1G%)GQR7vd}klPk2C(1sU4d-C?Y|7}keUaF+4@_Aa`hZs)yld}@~#7k4J)-z7rY2& zt^7$2e^&79+pfZh@=47n!^G=nYE|&>Pwh;w*Fi!Qmay1^CK1ubJbu2!dsy_j%!eE_ z-%S_4C}4gv6U9if(bi#wcRh46?``Gqg?xsba`|Ocy4*^_85#Mxmkdq%iQB~Z!j)*t zLrO$1Z6L79IQnsQlf9DWkjMe)Yljf?&yx69Pkgp=%+}e50Nu85iGg0^@O{C#$vtQM zF2rk6u7aivih_m)TG|q5&;~PKM!H!e3#}vkpsF>xV)m4bWinx12fC>B@PGorC+GQE z^QU+)z7~KeYN;_!Emhe)BsOC(s#-{d$OA8%#Va(P6o{;+-tdEA5$ z_QLk~WbDCT`r3J6^-QN-p-#I({>3*w!W58@eWfTVI0PEgj519Y&HV6|w7)6sqhPl1 z;;$}k`@%vpFGUY?^G7=FMd-AACQ3Aw=L&k<1$gKvg*V8uj>3vMe@RWJY?X}(v@9y` zE@d?SoSai6(Q5>CY#UL>XE1!4f#l8Vaw2lq?@W<;(@z^EAidffOFnz}aQDofxs<7X zy^TjIa5o)lQ8am*OyC;Ym74>xe}TAaR~R^;vij{jl_>%2YX~Y?I3rcf1(mmGy{>S( z%V8)4fzdFB5eBUr>h3ihB$D%>Nb;g+YSddBqG{?A{|OIf6urZUGTXxQ&~W|M?6=VD z_`N*TMy}VNXlSoEW%Xi-k)`-~;eabObe4_jyij8f4C_YC65=?l_LE}Uu!3DTRhj96 zE~In2-G?}}XDPZ7b}y-uuvlpJcqoEc5AZ)OzkcQT zRz4BvlJE8D6n@fO)Y>0Bwy-QB;PI6dyUnv1xdvX$(z?DlUT-YNoNKf2R&uY3R^GN= zH#6b_Mj7e(WAaB6?oA3E00N!YfiRB{(S$8&cs1w2m0kL0ralE_1g5!y_JiGJkaIM_ zoZ0OI-pXB#y(yP$&tn|ri3jn1tVrH0zs6O$jykD zC#3SgJhl>-5`q%1tDk@A&_4HaT@pu-JC~Z|C(3!oOq||(>d1|9TzH2%ae9V8_x0dv ziT)mIfr*!J7PU4r>TD;1;5?sYh#1dElFp+EV1&!Xr=%1c?yjfI;jx{?EgiDZtJdy4 z0&Scu{JiApNBXKi4_4MDJ3*OLRoYv7@FlfA1z-!k=v*6Ez7b;o%6}g+F;Cok>&tIC z@ZCm(sWC~IKOnK`Nd8eHBU{rz_Aa5Hc&q41pm8F~bXnypEyL|&-(ek@Lei*e9Yn-H zbp$@)N7CJH%*(e82$$q2P>%%a$i$1IV)GYacE(s%=fbJzTcMU=71&G7ei~nov5pL| z{3+KS1p^uE)f>m8s$g&wQnIlr@;)82DCfHQ)}7kK48Jr#(Pk+aUs+apu!4322I5)Q z#z__W7}3BGggWljQ_QF%sU(&k>7}#@L~ex{4Z$IU#sy!#HptBfaZXV}6BA4uba3Vq z%U8HY){I;@KEg%YZZe76Qg8&wTkqpkuKQBvHRu|NOZAU(a}|Yz9>*IqKiOCijogeK zXgxPpmUkXnIiz&tPy`0RGA}fCIa|$WEAwJFM_TgZN%S-3-B(^e^23@)IshX%0PTFyTc{zZbUWny67Igg;Z_=+53M5me$_ zx9z~_)TtgN#D|8-YY~mbN0 z4wyh3ahq2Q+pWWlYJ&Hf`QElQAgR-PIB)b@uq2$?ev|ick{NuEm}>M!)2dk6RDo#` zsfsCx$qSKR(j^!+#y#)mOf>78Aa}uQy(Z7Y)rURf&7nqgi1mrO=%SgNG@Ak#>MW74 zC!u_C)eB8GHm|^@#@BOPw6SXcPhz`(Pg~Nrn|J%Sy#xGV%d15p3)CWY@s11)2AZ+~Wl1vK ziw5xpRsCI7JP#FTVlNXt`i(qP7EEW~$S4J;9Jp;syMmQ4^<}~aOuw}frZJr`I$B!s zFY#m_7|Jg)T;*Uk39_ioYc2=VRq>gPf?Yx*#VFa zP|9O+0?+vA9ZXvakG*b(wh1M(<_r1Nb54n~U)$3&)jdH(bYwnPZbLBV ziCHWKXsiHk;3nNBqZTw;4x09&?+KxFJ%b5CnBe=Ajr_4bYL$^Rl}Zm7Uc0_Ypr4!+UNI>+py^p>o7PJA}u zAt(KXou$qu^t;z`@M|1xw8yD_g~m!OLTh0x<^yD@j~!-cUXDQMYikR9g_I0yx;fF3 z@>}<2*fsdZ5cf9|*K*M))9gxZY^PT$=s9vR5{h1v%wucKfu)y{U5_dsZIUO01|dd8 z@rBLI7J?>5+b28*u0XkE(~U%ZdqvxsGX8JZLDU7Z>R z=cZ2L>7#nNA%Bs<*=XFn%P%PUij44>plk?{z~sV9>Ac$&RH}M1o!rix%@e8XlPZEX zBG-4AyScaX0jKV|whbP+y@;FGP`dm?Dbo&h9M{BJLt4x!-?$=|T*ONg-OTM*k8b65 z>eS_}iEO8#Mj1nb=cL6=1daSvQ;Tu_<=Hqn=d(TnLG^=}sT)Dm9Z$mJZo^BwK(R98H`bY~sHkEiY&a}w5vNB%rfK&U^}n}@)G zY3|~CR5&G6Dd>2C3x2Dpo3eqM2BD^7M?%-(6Y{5I+EhNuyOY~{2&0Qayy0CJ ztFga3Z&}-8Y!>A}z|ANsW3xq5T9_X66&a@~S*)sfp38YLDXo$`csD&`NkKCgd1 zjmc;AE6`y1N^b^&qvwVO{L?S+w zi&fs`gAcMz2nG04Onqda-*KRkJ2Q6B^3nIweCg-*g_ zX<?J+)yu|SQ zoeriSlgOve6GZAc_inp?v;aJ8%_x1FrI$`KXdTQhbDhdS<#~_Pt;D0@#>V=?5l9$r$@QI zdK5A0ttfq=bR2J!*Q@5>(h?=vONGopd|U7}8&z=qyzhWpeY8-hfXPvv92SiS0HO9X z^n=PJ0-(DspNv{xl%3^Ac`r8+Yi~ttZiMjIhCg0rg8Ea4XJp4RcR%PeR>Y97@d_}y zh|)03S{ibu*!sG2AeE$2GCw~!+LzeRv`!rF9^C^dRPj6mExYI93v*B; zFBdFuqfqRwx~(swJI2;-miaYeNS-7rU#YGFzAs@F=xXRoMAAKn;!Sd65!0x4PY4{- zyv!gLe2NcWWgx#VsUovu|J3Ze{-c9_1oKoOk6$icg?()l#XtYyf4 z$OY>IstgILpG%hXv9F`(r;QJf87s9G+o|v^UI73L>FhE)K{8yk&h4}z0p;d4S@p%j z4!iwYBxc(~fRxVOw+!g|1gGa#%yvIQ>$uT};QQ@n22S{6owvmA%6VxYHmg?ll-;N_wV5o!Zd$GqM?HrIJ@o+V&mL87IP&a+j= zU$@qQcYg2f9pg$5IT=jxyPSF87+MFF=NK^5_OT)^s)DFQ7ROWJ1m7nbWe^BukV+f6 zGD!(hL&NU79$ca--_2O}-^tC;wZ&R9A#bAVRjLD1*^xFw|xIYTkFv@hG|ZCXITQt`GaD4vda0|!VZLyF7!&6g+kNr=1M zr0q(y>3FC;^*oabQ0M39rcaO&DOQE{5Xoc<19~Ui;2AHL{gZILL!OlX@Tx!&jfmY5%}}*8 zoQqb8Gt*~J#o2b0(FO;#YnEHEjr#Wc7BmyX3~33_@*Sgq5Go`Vlp4n*8$`f|?x$?9vxioH2~V;j}@XOBa7 zY$YSC#4~);mKLlG=~eNhw?_GJG_K|u{76Nd<2n+(xT`*2#Q`X)?tnCe;Q@yWrbFJr zk@&T*um=`n7EUGIB(3OS`lEa!j=vq`tmEuePD~h%OQ_O)-oCe?P@t$+7$0FUas?x@ zGP^Y$?T2~{NQ(w9mLqA5-6@UkiTQ#BuCK~7DJ#O~1aF|L6=c0NdUfnJ&X>D#8s}0> zW5E@84eXEou26i`6Wf1LaEAzo3WTrky=!4Y!3xA|*MI=wu zl8hC<9B4Y#suQS+SOg(^R`!qK+TCCYdyjm*Mvu%b`Y}2?Ac0k4+Ac5bS^I5wLjzz}|3huijaIPiC6;iN9`qBr zm$e!RT{`|U>a8{H?n!36D@qjfqrgnVVu_!ocyajG;osm4qG3tnXs#xrI~BTK2tp>& zW^nndz7AfnUPvwy>^vl5&_~PH3m98otlNPSzfv;k4lz5+s`{ORaDUZ*U$6aAed^u( z68wW6p}qAO+0=JQ{%r&?mokK~pZLf+XoUcO(yAlVW;u-Se*bb%68in z51^lPrsq#@KHj`-mcfi}F#Z^aphNvBEVq6dx-!xkvuexl_7I$qgDwbU} zr=+=kU+Rv-uSeD8w5AgBTA@8o(#tDICgUX^x~Xvp;2nV^{yVO8OusCZWaa#g#N( zI;yyzA2&8H!5g^A5W?+*ATcEieh;PChUU#<_t@vQ+7W>Nel-R~qqp!iMU_E9BkdV8 zhcs{_?nWx@yQYvHH?;T^RPTwB3+Qi~uGm$Zge>v)V418$HS%L06Y35-7+p-UD2R@F zs4;F_HsTMpzizP<6%1dnBH>4N_`>GB!uz*Wa8GW_K-o_;GWX&f%1LnAm9>_NF2h16 z#E46~>Osx*g$J>ese5y??DJjoae_wmJMM*U!7qO?*^vHPMmaYT=&*I^51nBtEXp$H ziayO`TY>Y$^`DTYzsF;6@V6nkqnz&5v8n^Z{w$(uY6@W(Z)_~i?fz}JClI&K1n23~ zC~w}Lq)iA-l3IXP>GdZnS4r&4qQnE$zFUsHT16#Hj6pf{`>*DuiqF7r;$AX5N(R09 zW#*>cWBpSa)3QgO4Nw=CE2#&v>0o3@a&M`%ps4F{$_S>u1L$rA^yQkrspR`}=_z}ta z&8VM=`l1TLonY%Bo-*9_TLPJ{Q}*D_HW^;A29hr@3?DHT>_ z1tP3B*OQRc=$D$o-A-O1{iqQQT~6xUnYF=`!2-daPx`J6NUpeiW|akA~3DUwNpcPOos~*7Q7DWAafF`H4k# z*2T1+LX{xGZNt0E4<8~~#Fr53rH5I)X-FJ=&`TkS!OBt|M)%U*?7SPaDS^TWpuI!*G)t5^ zcw*H$P}4P-MD{gKSU&87&Qf9#U&xLX-yIqI;&8h?lS*!fLSm(h32vu3Zk&V}`xzh&%y&qQ^@kEm~QfJwI#A7@JIepwmJNHL>$sLbgF9 z#J4krW+E&{Url3j<*sN#oIBB43xUXMEiolG55)!aR=(UxPUhq+sq%rV`WXbtJiV?;-9B9*z3YWd;Ony#p|v0G`z6DI9M zDd~J-RlJmx8({{vHlOsm1v8el}jVmtxG2JO<2GGp~wm^?>7R;DT%EA zE%hx9hv*y#wErH?3pk+&EeQ3zDqM?$k{akljb{lz0=?P7w$;rH*eU7dEOV%o*+IeA z(5-pAkSVK=EYW2p0-Rzy^jB3JC*F*G%*#;T(dMD$F$T1sn%gL>G&~ib*&e*7NJnb) zB|hZIf74J2B0!?REEX%#E%_Dh`XEzP!%%7{)w{R8y#EUib;V$S38$xu(jj?9=|e-k zpK7W*?sLVdONnM${rz|>)dlKoth#r;p&D|F&Ou{W)vnV|R3^{IW-ZmKem0hh;HUTw z%4q_{*4Z{a$0Dn(E7dG?8*?O{Ao|_TIED=g)*h1Q+Iy}sSvBi6g|_C;gFe!X z^BuHz3JCD7IKGHJ$=NzJ;ZNZ?Lj*Cu{oQm(9qWQ0e{APQhST%ECaZ$PEOZQ|z-5ua zBP3qh%mr(lOArv+u^DbN>hu?P?0>{XlrowA-u)4!1wWzagEzf!FW(Lt9ui2p(kygc zT7Ud?#w>*s=d>Z4x+V_Xyz_mL)exf_OKEJ5uu};ai~eXQJ~XMgUc-8#)z~^X7juD2iyc-; zKa7>5p9V^sRuPgrXGgafMiO9Kb6HFlpH?GkxJ`9^zfcu7{cnJkNBCe(cK)4EzDu7rbl% z@zTH)rFwHuQ8h+xETg%`C&8VUeIVQzRRqM6q3(q1O>takuyaV;l?U@nQkUP}bf>R( z6}e=(49ul^WbkeMtVr4Uv}Qj`!84$4#Uh>#=E6Eh8R1QykXRqqC1Y>S6tsHY5vUP- z*;)8C*CtosHKeFW1UHyS$LnxaQ|Q3xS4|1Y(-1DN_@VM;=2*V0RS3h9nLcLW49lfg zp4Q2%<@`!m0s$R4f{wl*e#;LB680J-m_sCQ1oR_iX>gNs(53}reJ|r5OBvjAx|}i2 z(djB@fh%gX7^0O#EEQ_)FYWvE*xSE58dZhhHSLVxMfHBMD15dc3Pa(3C_eIy!_qsn z3t1cD5%P|~kGNIRf$zl1bAzpit<~EpV0awZRsEU9YDc&^nm(3tu{goqXPA~(D1gW= zevuDx5J{>4ouVXC)mw*8;=+bOn?k_XSB0ZJ5$h8_r9Nl&0LCXdqxfdB*FmLo9{Hnu z->_}R6Kf@zZzN*0vG#7-@+C(3?N*`yqY!PkFGeCaI;FAQl#7vE3ANc0zHI!=Jq9M3*+_#qKO=MhN9odcg+M+pY(r5)( zbQ5>E2&{>)^kJI$ka@V&2o(4)kbF}ymTJ^C+R-W+^}n(fP*3lUv3|I^2+?`PIOq#^ zRSanVX606RB{U7$9%&dgeTch`K^dS)=Hn>sNy?yoR?0ddaS|Rg!&Y{$c**O@Iyc<4 z{d~?+e(1K%#4A)C;j~}Q{w)sV%1rpB3H&fjPVNY!(;`E|6w+_shqAfnixC;honYoU z^l}yAnc?I-t9#pTok?v`Uv;D@wY%Rj_}##dorpIR;eelfA}YB-B;0Pk#TP#GnKWz@ zkg`JZk3wwfXaN-3tjE?FU4{~MO=#-qQ|{l=C&k4Eh%aGr1U`CbUHAC$sN!{KX`yti zl0xlfMSddV2`D>GMh}ONC7$AU$`r)Z$*{LHXbm00*>s!x?aq?oIV690UefLg%uQ$(jN&8Bzvi^YOTS*NcZr7j5rLU1gTg5y-6 z|G1USMBV~v2N9qlE58TbylA5W26DAl2TOc&0eyOyn9uH}E>%aDVo6)djGJ#sAJoVU z&#JbL;IK=n`l?fpTaN(%D>L-j8;@2&1$q_-(miNZ)4DTQh<>z0E%MfuE`)H=kHro4 zrYE@PDW?tZOEGSs`W4)k#>qiN1aGrzdex3`5yzA@dPV_c%p7A?@xXTyK_i+)VH1 z#ZNvow@qI{1R10^5M0u=ExqMr^cOY6l`;*}CC_t@3_J=hqrOG@@j{3^mS)l*qD4PV z*U*q*O!Q|T?%rZ!>2QRQG8>}zqXg^aSlayZAlHs%A{-OXFE{XZoMK;RCzt`ld|`Vm zM?!v?HW@7q20%ASzjC?oUlx6aPWHtsR$<^q|80M7_LC2uwwvK*|9PNoKA&{AWYTsX zExp^r(pNxGtwXhW`|L+73*!JPakC+q`Fc{55hTxxT`Ao*WhOp+G@Y=0HDYE(n7PZb z?U8uMK!!82gtNP0;j5*k!6;)(r$>DePhg;RZq>GfRcP+R3%sjh??f)kiO=%R^*JDxF&Ur61^7BMnuc{Yk!Co_ z$(ebiZ`t_DL|UkRO!T5PJyMbE1^tT1@O3str|GyLv)O|Zf{6{@UiQ}W$%pehdzRYN zRS;grKjV&z%WM4RL1+S^->mWa3`WcrzIh4zeGI>YT-AKQ`1Vd(8rg=b5BrIm``!jj zcPvdjVZ8NcG=BW2SM7Aw*TUyU!^sBG&FJka3U~wowV{xNqD4z{ZyG_(o>A9aCbv&> zsx<3O%VT8wf-kuxM6%JYJTsz+6HWbSBH*;`4WEZB?_*fH!oG}hn15=%JiI|~u{@vy z!{HkIsf}vtWL<9d5#)8M?9N$|?1t}76RBl?CyZT6!tw(bZ#-077+h_F zp|0-zPTU&0E*U1vcfs66afUre2Z$d>XB0ba2`CfPo~y^IHatm{ZHy1>pYhGZ-oDA# z(2h6v)5z^mW^AT{Q>P;)S6`=J3t4b)&``V`sYtVMM-rSP3By)v`Bo^XM1Luv8h=ao`MiRRrBb)b zP%KfUvG8r&O*#`->TWXeL8M0u5lH@9y2r<#6`!W_JwJj>$*H)ry6Ti_95^ zOjhWYaaBb>r#48kU%$2Up*V}m`Q5VQqo%+}I_Vczi8=;h@KEK}H_$yu3t{cmRO+(B z>z$EoiW1_dXKd9Cw@>^C<62)7vQ84h1du_m+kaY3GKo`~;Po4<4t9M}ErK~~DLd>o z%=8YhkBx&^T7WiK^0k5e(7F)*V-efyykSC*v)=FH0AyJv{oefzsY+K=QM1JbB@CP@ zjqn;RJ(g@w{im#1!5B9shnB=oW2--RytTAu$v{(DIqtE^q9m6MOGcnXhv*WfkTT%n_#S{rgm+Tu<-Q{8;mNdP)kq-8~X$8}1ek)PsHLSNOe)}y^d{T%7V z#1Bsm)?_2mYgnanuy&Gl20N(cmxnXbg`C+(<+CZw)GDXk*0UO@l5(mp6vokxdvbTg zllnN%9+eoGjy?XOq%JSiU2eiWjXzVOSJNk@ykg&?$q2hAt8vtE<|FJ`(%hzt4;}NL}e5frOI{b@pMBq<;;e?h`WlIO=b<7oOVb7^35WbCAyebub zKS)NsskYdBJ9=hrLM)AwXx@aTl~3pTi&o{#Xk_-PoV?@0G`gx^>Gj4-6rpA|&u<-I&$EcL}7BY$e0v7bkp2>w;Z+4ncE$`tLXC@xNw814S zKkD$7>NA5(?8{%Ep`~rcV%wU;5-c2w@`8FP!tv(^v?CARr53kMc=s>0?ZSTd z#86>^{dnR{tQ`{Ic2(1^?&H>V_JqCT(*Fv*JSLl=_<-}sGyr49K~sVS04a`s&J|6is{ZzhrUqW^LXZAL=Q7`7r~riivlw2Sg<4_zQ7R@j>B zhhdFy64dMKT6iMBU=lIEcaPnDg_Q;oC=8|Y*ddlx`c=>T?xSIeI-)%_pXg3y(J*`9 zTlzQdmoE_#<__TBVWEvSC$CWg7|s{@ew$Wj@2lyk$5p!`u;pels`yA_;f&^+3VvxHr1nmX|7#;V?PtS0KCkvcI(wMF$&=IlmhVm-*;;7c6ok zgqD}HFK@YPtJt<}<$M&$S)$bVP5~>+9(qre&?=o~QG@@Pb(TD9X zVaVUVfwUfEZf+o;8%AGP5A=U=WVGQNcPRaA=x}pkCo_TD*1-nq4+E~1<8mZS@oUOw zbx~JG0e2M5NKTuCOPdBH))?n)bHpSk*FH|*!9*+%&q!WXw(bEWAW!ytmwqj-cg&_! zl_T9vbL+!OgS#*&^|*tB2B}*0t?Z}65xp8?JaL;L-B21Ji>(URv9Pc<1t^<1Z#AM-E7TH{)Kr=5 z9dIvYSZ7@AnWbi$3R3}#xmT`i6H#gX2$1Hs)L4L5C)GFkk>6}Z!ayxcLX5o_F?iSqfyf zS+l&TjI0@5Ehjsyy*7ID|7gEXuAA`Nf7P)hLkBE{2c8gUW__Ld?3ko?SZSA zG2y_?69MJh+?&r6QSN;uWx41uRWV?;DHyinH90R?2#kUKCK9 z5Hnj#7pzz@W2NJcMHe*LtYZRip(IV9L6s~wv`)6N!HO!DouqS(xYB9vvUD{Wh|N5^ zB}?agld5BFvzSB@9^H&J3lT1Y0L`Xss#_G;cmLe^5$AV^KHQbPUZ^Ixt@PbBkDhcNbSVyz-y%H5_&(ODUY@fvQn`eX6*riTpefNLy4qI$^l#z9E6z zO!Tr=u(0O%k{wX(^`UXF;xPhZ(JPh=oK4%BDFSr-GO0Zwo{S}WEXXyyrP^5oKsxN> z6`ME36kI0i^aRx|qc1&lz_B$zghi;(0iT-Pq}M(xnzou6r%vu*_x3Z5Qu-m6HU|DO zB)h8E-!RcgFI?$6*+bPCyz7AoVNVAaVq?oVq7;qwREB|35BYc-Nl-reIIQz>x~`pgQv zA>A3FEz4tZaDJ=_sZUfu*b+sHf3N>0awUyP22l($HAl*}27fqsK3Xk}sU!zlIGDZX z&1p#xyzLhEd48;QU<)Vg^Gt0;oGxUm+o;G1S_YQ???p>?=a2S$iHb{kd5Dn*`IGBU z$QuunC=vAEo7$!}(R)Bc7gxYSNP z?<+3{aL5xk3bjy24tED{>N~JG6B*smhw5L0yt4E`uV}-YH&r}FFaqj+Inlc-vE&Z;?B(w(>3ujYRN(tA8 zx}ql}zu1L*c_t_S4@+vpbR<%r$+TAW$KE_R&iditkDph^T*anZ;~kKY*phAUHtQ)s ztx)VvySnp|reO9iK?^3nig%XL(BN}xCQ2CIrtd3X%Te4$?p)c})25#j#Wn`RG7vTR z_b2?|;~lb8HpD1d(yhc6BG+xh!y31}%6kd~SGkj*giRclawK3JKhq0_bx6@(m^f8*>VH&w9j(#b4c=QG|UJ1SX4AH+zLj% zwZ-T0IdZ(^6Sr?mb?WD~6j9{|5yN(&`h-j&WudEjA}$)YYqJe{4$NQ1^*9H4{jYhB ztsoyQNAF4CS-?lp_@E{=^%`J#ka$t^f@yaxKU5)-qRn~@*qGx|?x>)!b}^51r|U6k zU)6(1Q3Pb>qQQ7%2maz%UQm2c&ecgsv05T2B0!lemcO0Ub#qRn>?$2{MM#n?f0%pW zP1=u=nA;Be5p5H%A-2$4#g4#&Q%yt-N<4$rb^J+%F5p1__u&#vLEo&oK1J);5)wu1 ziy!j1L{S<>?^IuNvu_Fx7nKlfw2b!=q-oQ!HY39ZK+C9>tYCfa90CcX!Z&JYyAMKR zKcZi)!2k-Y$fK+P2_aO-0#gZ(hfi@)1^?uZoj_#y^rH%N_?`6V{7Ew6*X_Hd=M5w{ z_mG2-W-rO7Ub_?947qX1w@NCQn8lM?sA2WmScn5dKSh-pQPVV}Uj-VT(%j5q9Uj7t zD#{Sf=e~yNhoSuVVP!7{&_Yr;lyMW-oyA;*J}e(8%PtDSN{IPg#z&g(pd)IuXYxJS z$QJ7L6MSMF>K69#)phHW>PMU)G>rzi75t(3f-amHDb37yuud$LX&#R%O#%wh^tM7z z)>k?-xEmGhsW(LM!DBJJQ1mp}(g!6dD6@0Nz0dh<7cdJ=f2?E`hF;y$0Zy{}qMG4N zCmrCjeMqk4La+AWGFCIdht)f~!7)N}XwB9{Nj3Gp%#txg6@1wJu-I_JfatbA6q|l4 zPG9A(SyXJK$>4rMR(L|g#w;?5yqI1b$;uz*iQU5@zcw5m{I!v)UsC{uKOaL~7X-s{ z^^!3Fv5$zqDOX0h@=2M{&U!aMcQz!nIe2Tf^;^x9ZA5{>R92adg|_>)_d%lBDR_$a zD9kvF$Br)qD5cFOBJtK+aCoAUs16Rp3O#+kqL9=4bIgh z!YIXC2A!v7kDH@7krvIbxBU!Y?A7uis!l=zWW_;hax*4FCo#J z+NzQ}4-PLs{_K=dt1f+*zuS~N# zD6a&Vj*Tb@XZ4HpH)u7kf;G#ykmdT&I^sASh(D<}iAv$GphjrCPaKi?yGzpIn&x#n zqM-WY5=a?=k@+r#$I0hnvv4Wv;)ghdWsu#iqIP6SdI3a6*Vr)<;cKk}$S!696=?6K zwbLlVm3$~=u%92~RA-yU4V#0M9QD;~qSjRrVCzs)%c@Aj{oc&^sC8yOTi~`AjKEp2 zL!znB{Hoi+ZOzVSm{xXx3VH>XiM|QrBUT=r-J#uMAXfy7TZ-A+0Pzsu!qt4c$1L?E zhB^)bp}SzBwqQ9TLNRGb>D?gDY0mPjisAs@2Up0G4ecOg=R8fFD(>o}5J1skc4Lty z*Dv2eP(xDLrFZn}<~Lrz6~?-6Qx4(|`wTz4r&t}!ho#D>p~2m9ilN9BIT)DD985d< zXmot+2>cIfy2D1Z#%!1Ved`^RNX(eDx5Fu+D#sc9ER7Wj8`Kp^e>yFDV(dHe&yc%dfj;`Mx) zAYcr%iD<=kbap;}KA9w=7?7EMN*20tikRMTGR*ZcP3Q~r;GL!gb*{np0acg4Rbu8F zFjM1cG(d?uu84F!R`uF1MNHrkf~0IX}fGG1>Wa177BqADz5K5b=_-#t8i=>Otz zk5dBM=sTd|KI7sHx*Rl-L%QIuGGkWik&3~QUcMzyjA?slF#=0~#GP{n$33-IxRVkw z(JZ{{+?;^_Gznf}PKXDSCgJu9j=e8;Ib)*1YslWs$2SS2%l*zI2=5+gw~nqS5D%u)|y*10F_dXRC zjo*v`3h7SV50e0D8fowJjN_zOYa~I9TYiq06vbKB~9{C#%KEHKp z1Wl{x!7lfI5{bTHVK8a7B*Z@GNcpsB19}7~lfvcDpSC%n3fvh!h>KQ)nY_awM!~J^ zwehIJ7>tHYIK98JR9*^8-;9|4%KE^l7@ytNvgN0w$q1z(v2pnbM^_y4oZUM@`9t*h z1e3P_$NuG3XYZR;0T}gWanL_d36paBpyq@z(hn;*E!F3LOJ%FjpJgwzA#OtPTibxm zXT#xd{>v@%%MoE=nEwGxsYu{5FhW4T8)yk`hXCg-U>Z9zVaBJ$w}*WP>RU~eM5oC!i1SHfP$Bki3h|PLbpS( zIOwc2KKT5HMchRHYT-j|cZDsvpxY1I5^ZAnLuL2zg)qQHcsxLZ@OvHSf23|Z zU%BaXfu6_U(K0ZjkRTsr?3Drw3I*8p-?y4wOpcuCQItXF`P>-z%6YNVmuScg7$lDj zFL#2svV~7vr~z0_moN5KO}V5vI=l~uO`=CBkNoWLWdL0jC&J&EZ@6E0sh|qk%t4F& z5AA)s4QY>TVI&#MvbZLd5$h z4K_}_t}N1gH}ir|kK@n*kAIt7WjThPYQ%#t)Kw2Pi5&|AKOKJX28klyX0X+#^D8pg z%KJGqJ!x^2*%N&%615+Jc@Y$yo&UuQk3pedGNr)&uMEzpmq4XIBvp4N3!7jH@HPu4 zj~kBV%;3+NqKpnetNEEtM(7tIdH}GgP^8NHZIL+l{LOH_58ZnaqWB)eDZ0o~JdRrTwfTBZy z`|QH;x?^}YArU~=67ds3mGO{$0R_(EoFPH8jYq}S@}wedW4IJBlD4&>;!Ets2^Qfi zK!WyO8xSX-2Tv!TNNVAsqRm;R6FV*TIr1EY(lG<+pVF4uD;sb~wppJX6{n>l*h^(J zBw-clZdH9L=U8ycl^XgP&k4l>Ckp!j@M!ycxiG7GDc2L4+1xL*`vD{v&2SJK{JxhHB(fNP3z%oUm_vfw>!@g zJTbUIt0J5TN}H8enEWk3K_*E#wTwKgV8MjFO;h>08zy5n5KqB^TH%Ln@qO}u+vR?% zlls@ErFUGiy!=0(^-Y&_pH$0%Tep={)syn`!8S2hLaO3jqFUtMd35pp_rXw9JsAd~ z1YpGq;bu4R(tAE*lDaH(5ySdFkGQWbbA1)->@;{>na`)3xHTy-7K_5&cpX=x<9n#owsm(f+Zalh0QfGe zR6z!7P@mt)!kzg&|-hiImnyD_EhlVYf5~) zECOY2_q+PZ2++?NdLh8@yQR%b9!07{K8jekFz=;ptmy+X(GKfz}QlhFoJvK<4aA;_tD zD*p{`mS$-HKNifNn1V*!p{UItI8&2PXe73qp?I&k&h6MCVn$hhOP&2)mZ-)a)YRN! z3X+Slkm@pDRcpFKMn}2(wEd~;W1x-a^9~uKB01MOJn)cq^(|4Z&&o~;@39VH>Zm!M zjs5dy>6ARhN~$7>1E6{_+&gpQ)cvRP`{dANI`cJ-Xrxz8qpl)J1LoEQnOo=7yy$^8 zlmi>q>?JUIRNS7eS{@2Uel!h_JdV^A2TW^C8PH1ka*DCW*w}fx|3nzYcvo3%MxlGJ zw(dy+X`UWi)g{Gx8;|SgQwQqBE*G4R63aUl%AdjkWr3=&DLN(OcT?SHHR(kE5T$Ks z=a1}ep$5@0)AX#rL&_(d55Ne(H@CQhQ%bW-`dmVeRYG(#zJg&Adsc7W~y>kslQ4SyiSS_??eP4qkgQf*f}hAhqHr6DR`aIO%5@#yd%tCT?l6> z8&+NQa*3;Wg_0RehU$2fL|8&+mYWfxq=)%l7_E&l$GZtlODfAeDZPu8Kz=XK%R zYe-NqrNt{MeI1=b&KgpCt#4)OdJtR5DlV?|{xivrS?%Of~?Yg+Ya~C;{-J6oo z0h!wv;%U-eHNgjr9XUewFF2c_Si&9&H6ekg*mi()&Tfml!atQvR0!?~Lky-TaBjem>}|0b+V+1_FK}~r5LIoii{=$CG?9o= zl>Y1Qs`6@ZVK3n^~M#x}G85eRm+ zIA6cBmdM;qwRIT8R;AHHb+ZhIzAjp8?J%JQ=qe*uNA`wy3;4qT493SF%D-j`$P)jp z`NLcKvnYEXAOR%xgd`N9knD=u6REYV@dhClHm@e+fEtgG?lR&j#G4CU^~TNY25ya* zx^DPTq@2FGaUX8xzY@jxr+`#VFRe> zHQR}O+J9cV{rh%dnj>7DQJcVe`QfV`8A`Jp>U4jQSZE7#kJ&);mLGTJDrITQy=&0 zp|*k%rdQ{hVU1?D4aULa#A&{VIoTA^pFRJ|F_rxnA?Or;3K>uIfZUkeXnphJ{2S!O z{ZcPL6>p!3kMW&_j4^;NO^byG{a3L-<4YK~K}urZqt$VMt)3x^$w}MMLaq}8?b~b0 zNR)CM7GF{+R1RCjl$n~*29_wa`-G&l$*T$3tDFu$Io!%c+nu?04j>3aaucU=dlU30 zWex#pv<1zJ?-vGOMRL9BgepHo(}75-U{FgT3ov)f zOdP?%d`T7k8&Z&!THiH#;F6#?P#!KLJ9X#Q(?dUY{&44U>fNj|QxD|`wnk|s@sBB6 zou1F+?Yt||JEX_Y%Dz{RpXGN&AYyI$BE%t?)lGM4%=?-aYmQf@h1Z_f&o#iaPBergEsvsE|+aNAmn5#F~wrKgfqIPLn-LX3FmMdvc zutS)mEY)DnP8q`b44StHFn%HXb2+$&R>sfWCWFZB`B1a$Oyy1Kqdn;oc0QJu^U-{S zLdP-SvRz!H^+Zc{DKqWPfBit%=zH4QrHjqpSuNQ4P6W|ZaG~~nBI5du%x&19!!%QA z3Gctb>Tf|U+R`s4RM|?L#=aH;7&s@9!d&g~CU9R^SxRJ#imV!0gWObsJp8*tL@gf3 zq_LKRA((hROB``CIJ9)oarv5v=$wS*9kFM_D+{N}_p4iY4@5b1tTtG`NoC@|ggf8r z)}%R$q>t;m5eEa)(w6mR>_r{iK>-FJst#kyWTE-OSV#@nyde4IyL!iaAm@oB)4;b< zH(~={06yxCvryW60G}Jn{nVllkLrKHKBmV%d2om{@GLDTm6paiXRi48lA50vQpRyl zKr*H-S1}r;`ktkzFhdFIKsy8RK+(Dhn?(0E^P45I_fMuTj=7qCDh(vAYsqt;ny@&H z3}FBD0sWqe+WxEPb9|cWI06Zvr%#uQT`t#PjbZFQ69;;tHH~C7AjuZrMqxRs?LjOn zJD)@~sSMTkH`rXgVSz2BWo{y>!C^^s@x{^~vk*kJMLb1KzR)=s68LOmsBTiP$6@}a zC;t9}oIr+ISVY4rw$0BUT7G4xDT)eutja55?2 z``cF!(k+r$cWFDgh)KyNn4cY4l9{D4Q)ifo+?*B+NyVixt|MXg3pLMGMV`v4tmf4& z)ue&QWFa#*Uy`G#Qxr!|>7CmzmQPj$bec9V_)nPPhgqDUVB z9=uSaI(qp$*_eRfcR6Bw7ofBHsWS>2=FN7>SF?QMt7olPHyAvG<0CKa1tG#rM^%w6 zu~QmZz<8mxm@&h3u~0 zQP>K0kp93J#a3hg&dMj3n9NiOUCH89EE)JxBD2eY>ihb-2Y4;qAm2|V)8aG?Q4sJMK&84vc zhUF;f*unK_mjPo)jTk9W#rTE5Ey&XztriV6dw_L1>!^4fS+fKB0Ar;)iFW|c(M}(sVNSIt6~3390wLKN)M1-I z?T?tlTz971Sok@GD2Gxp1^=XSdfTiE3Xj#&cS>mN<(F;p z#*^T^%_h*-BKk#L)UA6VEPq8Sf}*O?C+Ib5oRtEam%md@=HZR z7m5qtiCC9eWTxM~OJGVKJ~(aKd78s}jBs~urMNV|eEY(5wDF+{&4^+jX8(zOTDxDR z^P0CGx$vg3(~Vpy#gKSXG5nn3P%>x9staRg@qT&0&lAO}Y=cfI*!mv|Kl_rf9b;<- z4k0f%&`x_2uSd}{*vomr)fasUuS8MxYkzyYPEdQQx-wH5#sdCm{bNb!lF$Infn%Ar zUc1rMcy{w8Cq}zpF&WmH@cjH6Dx<|WU>pF+*`CW5d#KAcJ5nNJprRS&HjOv(e$#{*jox(p?C+QOYhi2^YdF$ zfD6l%uZNDJ%Q5J!KrluQ7r{i=8DlruS|-7ZgBklA7i%g)5-Iavmze|(x_ppd1nR=R zrPB$vI8i9yRzDFKoSZlbEJ&_(!&$7Nm6K0uiW$s9$s!mZ<_AqQ_uj@3Yh|$-T!Jvg zcSsg65gnTjc`>uK`rJ$paN&%Z7@=bLU7=8!(X8lE_gpxiuplWB(U-APA8*a2*4 zDLVih_Sm3$;50tb`)OmFY&E9-`jNnT0-z(W*DAZ&2033Ma-`;P~m6OJgT?k+BNSGY>W_znjf;l zo=_AzgWW=@6tmd}@EN2D56Ck?n^r>o{k&ACunF2%u7^CP}9z41Rd#BfkX}DJZ#WNX_#aRQgS=HhOYO-%>%jEoP@f*fGjmJV^ z=av4_)4Mi6F#ix2Cu;z1H}CU$p6Bn^Dg~Psz(nRR5!lsD2YtutbqGpq58~cAzDV}c zKrqayL+0JO_jUzC-qFEgk%BjAs9{Hc z?B-wO^g9pv)XI{Uag>qWqOQhBMb@ku33q>WO{ijWIOVPoe|RMl-3&?hsfGL?os6OOvrW!ZOeqd zJ!^+JSHuerq8=hz(agX-7B%rrGI202qc5cZEor>zyF>#XQZdOachyFK<9g&S>u48E zxk8Zy@dQf&?G8VR&gk!*J~~wO6&eO6YD@ZtAXMQrE-7d|%AwIq(}I@s&Zh>1TK zfxcGK;aqU{cTD6m#4HXqPN{2F|CRrl>!Ji9J%W0W?Iq2)SClPmy77A7p4{HS1F(z} z_?(-OUWy|;P^?uDaR%Q7%W}p7i((Go_L|2{5wyi_K*0W6ge`&0AJ|x{M|G?lm5(a0 zb3HET1gP?SqXS(O_DNJ*MgnV`izk4R7qg7PDvue!`nT$#OB99o;h&ANN$pe)K?`Yw z7Acqd!k_Gi>dy@cQZC{=;zmOnez|_m6RGqVflD^|TQvc|AGIyX_(w>raQcen1sU z#L_!in9cp=R*ortzCk6Kl2q-LSj8)Sz17pc)Y{g21I1mgvM6%QeBt`cool6&lll9ZkyPE*Yar0bF!4~stP7&AX4(1DY|98F20 zFQD0(ZcYnF@oQ(^WJ`V+6!(_$38NpNQca4_!uVvFl0&cXP^IkvU$`&ZvYhXEk^ zkUtNsZ+T#4S^_kPvH35*`2~_1Y^eqexRO0co&@TC=}%(e z0$B3#!vpL;$&LPeydoT0(;9-!1w2IR*~K?Qjua$6*uv%q-|$uSEjb-j`VMd`aGzs* zaduJ?&_Cf6$*yb&Z?bz*Z#ZHs9VSmGyN2t{#i$XmP!d{IsoX6u&u(q%U%aqtWlL4VLlt+YCrY{r6Qq+GirVogtwz;327&qvII@9Yqx zGx2Gc#E#{+n@OqBd1r3X6&@f{^tm_$L0Xqg|5t$ zhJ(5AW&}hGqw3Vy+XD2vGh7+!|C0G8yuz|G84>Lqe;A-|3m;TrYQ_`!S+FCiH8EAE%C2FLBY4S0d}Bw+o> zO5l}$B!8Z^f-oHo;kbs^&T9ZvE`o6;gPEC4h8kM*qJomqfop9X!1Xn6qP2uHmryJE zuWRZ3s(Ir_ckd-hVwST-|5(?I-PBzDy3&1^56MT@qcpqgaJi!o^0@{kmDX!Z2Rmq0 zSAsH>->f7!N@$)2sY`Q^-8YL6n<5JxLs z-MAFXa|h@Y^KUfERRZ4b>B+qNPxXtJ4;x~R;+?}Yfl(Al`MJpgc815bskm`QXf?2Q z-aMb1+1=bDfJLIXQhy46rI7_TYkjbpYK4sn8o|Ds22<1jxKXDme5N=%(mWCu4s?99T?Xp%aX# z*Fg+9FQ~FhTM@jtvL7rqUjFl>)%IVudwA6DO8u`w7<9MBtwcTd3vhZ0Y3a8QO>#`7 z+-VmB;VtKok=-j63%#fEk?2M3I$&R`Pz44a51F@4Q|sF=P_KxIYFmeJO#LrkiX`NU z7VAj3BZdN7P#6*Kh$w|$_4AyY5HUQ*r3#{|it;41b^kK3GTGA_17u}0vwxf9>>PYE z9gSf)N^@XPXG@+vY8DFy1jEJt0OBd{`UHBV!ELHQwl{b5^Tei)Phh&Bt1=$GiKg*( zG2KDRDuE!O1HnG(Xz8B8R-b`xEudWaGz$vIzy5K>DbJ*zToa6?g20=>Rvw>a>1qv2 zRGGRPiQj{}B{8Qy>9a957({9K(|oeL0U{-%3$t%~ceV*K-AOW;Ev1=(xCqikyXZ(j z#{F5o+RGV}Yfjc6aeyN?gqn<|py)_ARS&bAh%*w&*ErK}#5h|`8YPLh)tqSrV$jT7svt_2nfeBlrgi0x9t>9fweoY-9Jn7p+(u@C3q3Jz_yG?mF0dO*QlL0@xZv zAo^Qx%B)`9Il7@^(V%vJ3&fms9Q-}pa6h0e;{=*M4?Exu5G#!U4&Q(M*IJTK({-T} z)=3^82(k0cLpnm81D6s}3Dvv3dc- zOv`t7UB0TOU#b6Cy2wYb+vF`dyg|(F(Cvael;9Rkeg<0yxFMnH_!XDkfk-}eWCWw8 zN{34t12l$7HcWBe)9hi?MfU`ezf{2bBK<#*r2Xu*TAvlY6*VT8@PUXCvINWe3{+X9 z3P27Z@ba6rVkhs`{hx4JwXC4w3p;_oDw75&{I7PiPtB#Xp2xU-D*h9oLB`(T!6;yW z8IIo8-`d*M1|s9pSG{pQ?G(Sc9cNeLjvjL8cpc6laynluto9fJI6R zU*^YWqYrX%#%=~Rpkbl|x8AyvbgGNnk(SIhAA8X&I-bspk0xI={boa z0r27Kxg0zb^!6LJ@OqSe%dc}}zfi>|E>>UT=62V6H?DY7)ll+%czsEMO)RcLHeqqt zw~)AW&*0V<0ev_~n!xt#9f4|89_le>JDe!(011z|7>!%-F;RfNfiNUZPJ_|OP8fy& zzNq>hKP?H(lbnu$GVK8hPK{e9kJ%&gT1b5B$+BwhcZv)LPD+7xxkJ zcBuIl#E-!q42KP07E}NA|Jht8PFQf6sv+7|pYa}NxfQC=JL%&wM9XT>f90(b3}+S# z^nEC~JwkiYg}{7J{!u*=QT8^QswiIpSERE};|PJw`OF)N_EwO2geW!Ohs4#3?_8Q0 zL56Fc-gJ?2uZd;ATRrzzk8OwXfpF9Ko!t=mN!w{f9kj`r=(1W}Z4NBaN=wyah%G|h z=XtQwM9quII`@!@6=>L}kP|Xde=3QjlUhmwixbpO-&-pwgJ0NEP-v=TuF&So2SkMtOPF9WRIIP2XNS-^?zpo8qgb6WNiW(9h!KXYjYel(R{^ zGf?wO77BL5g4D$IWT(R;lt2SxYFL9IOarH`;jbh`17$kxxOcDVDp&;xr4l976Kr5= z{8T2Ax9F6=j$o8V2gOT$3Rq-~YzyrE=r=3G$LpZ;t$(U^5YW#Ht!|<%MS3}2ESDWq?VVf()A~>p{O&KByYZO-C02Wz z|3lmXW_qZUn4KtiKZqWO$BYn2HwO(%U8(v)M?PedUkp|W*VpS^Fx2an)OPWipok_l zn&}YG98KvGu>v=J#Gz~Le>bPnkRVlA5vHot1qPVF6Lb9)2>8rDx`;w8>YV6M@8EB9 z1%uBzT~`8~J+lR_Y`)dmk`igmbm;O#zUGXUo8mA&q%Pb7($a`*9p0RkFkbyFO5t z7pg%zM>(D-u$KA+uX*?+;WB9ifQC5K`*}CQuCP>1$kI?A6^gHmdIik{Wq4}meO~09 zLh!`_axWcVl0XEsWIY%Az8RXTV%pAv&Cn&xH@3&Hj*7$FeFtr_FI1!l5%(9_;k=xP z7B+t=kI(H!qZg4+zn5F%JM)MYVSN-s5T!VL3eu9Zh=D+WqIB)4%c{W^bM{>x zfDxfiBiEDE%9+mSpwqhpZd@N$_c-RhxI&6NKvmV_l64HL(lAXW z_a1j{0rI82^81ZhtS;4QV!YlvBYp3<_*`q}*qm2sv|`Z{je~d*D)3PWSL8f-(2F1U zopy?ihdBGR)ogxjGxo{+;XT1Q)dkxBrV@V28DL2k=w;h^DLV66LWNlQHmS1Qf7Q`V zAE(Luj|zHhZ-%eT-X)o8p2qtCBg6A)emC`u))@53qQwiExf^h$yM9&cqM&Fkf;YC5 z1+r98TCQ2aOW>$8-K39dX>SKF3hj?$qf{!fNx&mc+9nqwDs|^H^(kht`u9QPh{8QOASpVSp^*PyvL^#+=8u<0Jcwb8|V{`(Yc z^%~q6(lqa0*m1^;h|rtSln)LIA*x#TMElsxGyW#<<*ub{)z0v=B-5fTGV9@qyZSr| zXd7O4o#2^<&;Y=aHYmi0iwVfc_{`v5HU8Wcz74Q_V!$Zw)?}@ot7+a-z)EM+^FU&> z|6bL`?A<8y1!qB9cS(Mq1mcE^50-pVajPdHlTLSMs7%Szm>b07mU(7 z{^V6JK#sp^p?|Qi2ub6@a)-Ur?v@uTh;-(*Q#!5uPqzJ4K5!Vf<}24hUSQE~%*Wq3 zw@FfSva8WH+_#e9c^UYV($vECqBUr0~ z^TUGql~aPMWKR|cO#rMhEuhz@bPt-`s>ruF`4#NhjobEncL~$G9U=CNA51`?X^hQyB~vuEQv?lqvzvcwUQ>rJ zGkTu!=&+R2u3N21aDW*>(FLQBS_0Fa@Cjq!Ks~t%{XtLPXodIh$URE zxYm&O@e1>VEC1iEz_e}(ZIF3a1^*@>9hkY>bw+rCXw-JquC^6HGEc&Cx8Zr35Ppdx zJ%$$OAxuiJg4Efe$S&zW)kpYCB#RXF`CRYLy&N3c7kZAU_)YY8wM8w+co**z8SVCZ zGrfrk_?Y`Qs`MP%WX$1P=MsbzbG;@0OAHYfTbH!RpWBtHA`6WW0O8Cx@@XGxxPt_H zUBon!-lMNFYEw5Bqu_$5lS`&bOTeW2MYdOMa$PJ*m}y_gcCfb;%o@l<@mT zr?4sqCsEzz;Q%5YoXirWAFhg{(YUO>St>lLcyYPjYhq1;?3t3$5#wiC9BZasWu^!= z!3-5n{$(VToQz}9Qbz!2?s5+juO#=sY^^V3BeTdIcJs@3I1V|x?$~jv#|7ky1aF;o zw?N#XJ4KV%x%50j25qy2iQ>*{UkPd98lER9dHTc22fj9yS>OqOv$43>>@2f){(3%r zi6nD})Rb{pMm1G-#VCTlmlIav->JnXHi}Wu1BzqcTET8!(!c9UQhwmGL2(|D$W{~v zf!h1{i_Mgp;LPH(@5c1Q<-~|X2CBJ@uaulIVu;}WlNXx-2{xbcb=*qk*_+nibU`Gb zU&mG^_tGKb+@M+ftleesm=QVF=rS!HKL3gzl{(WX#S@Ibs=#-4QC=rBY}12-{;%GZ zRbSY<;iiwl+zuEPQf@d+p7tlCzX(;^`)J#fRA5vqO6c#wKfm^vTP&MHJ2xpNaruj) z4`B=d!4$G&z!H7?s&jp_lNDktGE!F83IJ9aY@3@~^smA@|bL|~yt zn6oocwH@Sq`R?emxAJ>c5K*3ErD@w!ceqgi0-ut?;d7mkTSvU zpm%%IQHXdqJd` zoR;h}KVNk70g5GkL9N9So#BJOK(LfNf`B~j)Hm&w0!!#>c6lcSP?peM2v)?;06V36 zO^Si8(ivcX2yBmo#TTTAQ|_f?*17^QLRGW2@*!lfr|$5`Q+`u%cnJc>=rt-`R(Y6# zn@7~d@yxK|7ekx?P5QGd0om;Q?XH?dHtv!XX}FPpSb z{u<+;`JpyFENZ9O^SW6iJ1eTsoTq3ssrRq5JXIP`T(HNOzE^LYIvd54uW0fJE-#v1uAn z&%SC!vb)B7tv|Z!dwE6p=)$*S_VmUCjxw;m^3dAOaTp5Gps9NU@t@I2I$J<*#g>Xw zSCm?s^KiV7!0>(BAS%)*XN-6zUPFz<(M$CSj-E}D0Y4KpMDd#={WaHdaP?^%X>d+8Lqj>J6>XXzXalEc48^pWK z%yt(z<;7k*yl((2{8`#PQ{^37@r65LGJ8;wA_Bdwup}XTChhNJwz#x9wCFW_;Zd3j zPyya>E~%T(9L<^ybuH=(z#G+zJ5DLi$$7fCRg^shqr+@wFyZ4%qGkp-pHI**OUX>U zx6=W&kRE{;jzL9O%9zKwZVc;fgv9w1YxKzvT5NDLj?VA?rq4GZV@p)<9yPJ`#!V{x zVt~ucg?qs#`^{Lwk&L{lM4|Ho`m6paAFI*>nQaC^a~=_k*7ob?_BXE z@j>hWj1ULgmCAs4h;YNf`~Z5t2U#sQiV8i$IN6jLqeT$cOrIuffR4o+j2L1VkPzHb zw;Qg&$vp<6aPg=PKw*z%Sj+*}WT4XAypVa(Q6+1b9nvsRMA;*DCrdX1_I~s!H%N;! z1mFm*208qBHS$82dg%>ls93f7_ZMcoH+D1rRcTAnRHPOJ%!(jGl?NV-+T*GFkl3R_ z{Vg>>v}uAslCO3#KwRd0ES~wkh;d!XLsMoz4Kh#^r7IiJloeW2kT0=Ozeo6(uJIYw zEpD7dB)pz#`RQqz=iU+w>QH^aT71QSU z`O<{ro&Xs59@ksSnv;TgrxO#j#S!_Nxi8@gQQ4UlWcF!|skM-H~$PU3JKB{!INHh;3?H4B|_&u6&~An4o7A zOIKl6wwYPl5URl&sHt^TloY!UsaI!6Eb-+H6TpPG}-GpDxwq#T&mM^Oe^!pAjilD=4#NF1;w+FpwT@CL!Wsty-?-_?WloKdGBw0le2>@N+z4`f__Yz^cr5l4vv<$eF(?uGsEGZ}_SOd}rqr(u2A6X3kb$bbXn6Yjy z!s%*9n9;)btX#}g_+P(RQ^BZqBJvjRd_R>n+KT)ND}r%J0e$b2x^3&wP7cq9L~MfV z^v;?M6U(}^ZQVe^rZ=4=5}7S(@L;r^#D0y-<9t`c)9eo~I>H=skOe;7d8SGVN4c zk8j%buDuLqeLFFF_A|DXp^9(A-nUah9tF?<`b|(B?wxHAY#~Ni8~i+0YB8eai{8J` zZ1XurwjqiWKF?Vg82loI!OD}@bog5sV;N>)w@jkC|S3!61W@aqOQ1> z5NW2vw$ro`efL26w6+^YES6x4vl^<-AekA659$Da-%!*@=^Xzh?SV~k(U4-Ji+f%g z#6S1BeU`kZDU%N^*MsJJ_7AcFFxwD8VR6o?1it4vcmL3mjf4NqL!`RflB(SEdrZQN z_w+{qYZ+3#6~oI?b9-~K_;Io-gr97xc)r|f>YMES4m15rrt(H{v+RY+iBp{4?i)*t zzWY&Qah^d~i6f5urCIJ~-xKE7y7bIJG$E3yjRQFcvnMEQa69LAfwxE6FAr4^N02>0 z#$hr?(UEI)cgBb68kTDHXQUL_U`14bMT`DXkirlruVZrCt;aU4x0y?HSWFCIr=5eKsB%4^`DH= zFMV%qm&o_KBkVRLmzw}Vk~{oyTWIh4UYg6D0Is_InSKRUK&_qkGC(TO+tFX`%pZ+a z3o7E$xb;Pfqe0;dE-~@J_CWPO@j^kipf*D|UC{TFN)FpwA?J1N*Fs15wfT|3)xO{% z4Wuxy7}y&NU!}q~lJvmU<=J~}iz8{P$ydQm?$0{>R8g~%Lq%*(3z1ZKypmytYi3O8>H0akzh#eZDR#_M4xz|)m-v`yLyYE!P&y&FdIt+jfefgP{!4`ZF;)bShL^}7SH?H_v@mj1kR~19g-rF5zpJi3{_mx9c&sxGQJfSAZOsE7|{p^63U(sEAH%m->aekI$}mb>jlQF z6$AJBc>$BSgk|wI>*XJOKP+7+V^1U-$}cssG7d9KF3DJ)p3wTi4iu%)fvkm_4j-yD zNQx}0X=cGWd*b@NA0fobsGpQzpir@#docFfcDqeQJ-Zar?8yWtFQudKppk&}`Q&P~BwXA|_-EoyX^Iosp+`9ISg&5>RfO5ejuNu93?4@Ow~y-V4z_ zBun-7u_$jRdJDigA-P*3WNEcIDO0kKl^eNs-1R~S^ej^Xn_zpvwI-igFDc2TO0)Y5 zY5%ZUgR+6aQ*#R+&e1P|?tT&Jtb7eeDN)@vqTTjA<4~A=FliXr_CxIQvEUSN0r%XrHOp!A5O`qZx@}VHDFO?(`#miHKE?qEYC(Fwmpm+b z`K5y@;`RiNsh39xqz@Yp!l9Au3^r?WmRnzM;q|p!7jXo~=M`I!RN?g!41jg7aQV~G zh4^|Bzo-j4D~fZ&$p_i_F~xt~vMJo|6{5frI*hk=T)Z8KA1wOYshiA;VRM1X2*(9M zuiv5!w2-kj3zukg)DvRX_3?F0j2cxT>~`;_g&uPPyEdb;mmw4HYvQ*DY1J_KE$4-B zz!uR_r)u&#^hIQ#$*%Qa1H^`2Xr)agVp%7wPq>SRc$?O^mdZIkHEX^Le=EG__h}!D1E`67O1Cb9d;dRjw{JHdisJn~V~P;hnn)FV}$L!Y%v6@Wk$*FYA%rOl1&Ekpo%DULZ8EZEE-qwnnU1ZHGN%RASto&&h z#d3=?o*C2`pka1SuU~w)gnjbukf&Xk?Y>H0Li%x=H*=Xh zuSqlbY@4vt`QVJ@#$rp_%mo+4U{XIFKte}LLe6k%Tgi_)26~v#v(4<*R=m?1C-H*T zs1?Y<`0i87X}Pcv3g$q^ivT-7#J|7^n#P3ZlEdmB+yjZ}y)wr!`26om^H-S=WWU;O zYk8(&QwMwAH^U%J7YK{bQoaRY20Kr~6W1Y1IRLbMVLU+~M01Cq~0kB1m=$$;H3O;rLeTomuonN3 z8*qNx`b>S&_e~;xt`X*fSG3qhcr%!1LMaWfUoK}K@XL5 zB%-|ltW;yFOIy==;&ZN?MkHTbyI(1%NC8LMlVu9OK+A(4a9)3iYsj`HX#3kQnI^Ln zZAC1F@C4fJ?k2xpI>c#qwH8&Uk5iVbL=`pzH$^%|dYu5ekIpsEZ)G{cqM^^9x#D)b z9q_N)GWiBRy$EG+$QTgyK^W%$()p}IwCI$W92jOtZd8MK-cL`5gO1etTr@&fz7;0Mwbe$58#VQ|Ya`XT z_~8UO<0&!d-|5$dA^%H&4Y^O@B~S!AE(iR|mBz&f_%-Xc(~y0;WA)dPE0rWmd7#P` zk)Rd!kodj!kdBKz*jfh=1tUws{YD>!4!T#|ou$}qjr&u5wkw7Y4atLU_XWq?2163( zCz4d{!@GA9nQ)~WUDmRWp~?^SVP^*{~@Ic#QaF2-f_vokH@c!7V5{b0y9UYYcy<|r7?q77e zBvDdKh0h#+0JOEA7Cs zT8%F)@{*`jm2aaLEX`2E6xRT+{eV@oXKA_rVNMmN)Px+Gu4^BdTLE;3e+ zYX%Z^!2?TQxn1S@hF4b6I~^Wakc~NzEdvS-H+bw}R>1X<(zZnjK;CNAD|HI^TUFQ+ zcWirEfFTG+FEudCC3KEK24mzKSmrK2;Qk!a9ICn|Su~n8bs_pzw!`(BNE!Ii@VnZz zN6H<*!veDn>??_n&`Luy5p%17<>;3e%te|_kl&@y0}xd_F(h0aE{UluTM@SJd<~DC zSz*VusM3MO*!UVbNy-Bz21m{i$|k)!%*T5h+M5W$zp0iBx&$GT$fIAam}-Awu~)75 zQ@QcuM$UB?V^6(D#c08F6z%J-{ZGlS?f&pow`#8R{Q)EO6e_~62}#zqeO<5*xSBj+3A01ZtV;j?qD71_&)Umjv9~be||n6%A9p*1p&_73Rk$-)G4Z(&mEI zp3FtrfQO*TH66&faOpnU=Yb~PtzT~vj|mz~dk+58GK?jxW%~Cye+hfvzrH@ay@>3M zY7TAH61Az;(CA=sVbW#2B($$_0>PrAn2*uO4bm6yZQtP*w3;}Io09%2{EuBZq)iaC zKgaA7O$29OXlqmBzem8sw|RR5-Ayig)4Y-rhLshmdsNuPBD|% zW&1G58W(?9(-suz91_-87m{vKQ-CATT9pIGQ7mec1lVXnX^VAwV+NTy00#);Aa>L% z1*KXq{Qfx8)8HNMs2;PL|F~5ag~Yf94+Ot;R-u)IT(b15kKe^?m4R7T5uyIIr7(1z z{WNm+S~9|q;)#lVAe?dnSd9a~J-Y>jG<7fn*UVIPz3C0E40+cCjCKtzgpM&EX#(fu zN&K^%R*O)8-Dy8SraKi~{$Hse_mOV}ED^}cub7_mt!l=G_cps6F3&=Yhg-7z>L2f- zPSHbKeuDQNNeYke@QnfvZ^C$CI;y9>&rNfbJ7J{ko7*D9H-okR zr5sy9jr`D35mqm*#j?pc&qD<4r~(a+@7*$)DkLX#q;|?vD48UN%n({@I6X_O$ay|) z!IMS-)&>OpfR}N1_s%`0pGSJzV77f!XsJKz=KQVrPTkVb?N2sLJ)DskqOI}-z`yfN zWf8LVC(gMDj+-J;J<|YPn~am55*uo-zEo%LDmJ2Ie1{H-EXz2w)}K%1DP(#x-r%7ny~B;Dh~L{ zSZpFYVDq;c456cEfln9TZx%&b&)UzA+=oX}%7!z?TR0*+mVfCuF=v=H}zRJ<%>LvVU46;^veni6=RlL)zeQvq=%djN-u z?Xhh=ebhW`GL>?(WMw5UhdxXTB`D-h+KzxP?smzx2YioCChOI0!Cov>*7ir)T0=1m?(oOdvtgsz9f?Q zjM{u$#(<@UL=97(k@jTdTc(5Qa~16Fapia5-hr67wM@=pCXhXm7%Vy;cZ*_3&?J3l z^SOW*Tvbabwei(nz=KZ7UhD_1F_`{%J?{h-ERs5)0YjU!&7hq_6BnX*$$+EMBG73k}t_nx6^ zY4GGAUGL&|@$+`XX>&=mvsT%j466eh=Niu45CnSx4JjV8c&hKlUpNj@ zH*%qGrlGt&Y>xGQ2f2F(SG_KVk;dJl=-$C^b zYnD;CR=5J1DBweqC408F$lO?P%yPSVeed_-0Lw+8%3tEnNizOzrI$J((gk{r#zcr^V zJHiV8ZCasmW+safG<~Lu*W9MZ-Q+B$5gPQjd&#O1R36Q)eaT$7n;m!t z`=UK|*qO-#wnX;qCa50k0A56`7Xfu3$dh%q+ogvMCo+dH?DmH5o|`n$*29#}gbLRX zYvd2Bj2148?#T^PHwi$8;k*NG-)IoPNNjgRn<~W5va^kJIL=GCe#vSu@hpBn z_*mm)VGn_TNW||eZ;}oNi=kN8oj6L|LHNdQ`oz+SGFeS3Z+xk5P#=nI4K^U(P(KYH z#BF{)8{ebKtR6QCP823s5rRS9yg%QYvX(<*D-Zfu3EfS={{>mL*zTrqeh5)O%PbTu z3i`ZC`r$zajD1fTgz!EwW_-51wuc6W$0K$jM^6|^+V#X>`0Du2gCG$rF;XRv82%8h zVJ^LczW?kbz;_f=5g=+a+X*-7o7ZtJz*2iLB@QKp{Sy^fvbx$c8q0FhQ7F-x2BeneOFIY9(bk9Wss=4uNa;~)?x!0N-miZd=E!t#R`gUb1>w|UO$7qY+=~R(kEflA%0b<3sgMekC~ImV z5&Z6gvIsCZ@%wCxy=52F_euc5rh8>1P}UO?aH!d4eNMGSQ`@B8FlMpfPBH$O-Ruil z2SQl{7V@I_n@Tl^@oPqu9$Jt{K!JF3eT6CYfdqJ?Wr}``#9o*xasTi>m>`xo^tQV5 zY%L>LFa(q7a`&zKJNDF$1oB6nFaHd?pZ}FNyBS009Fp4vXbmwgnr_Cg9(nw&iB8pE z$fLvF7SXyY!w46j{-vTex;`^SBwz4HT1TA<84`;hVpP%%(T&%!;8pZ>kMs?b<^)X` zIA7JgBwS@*pvk}59+9I9WNe4yZw}tJ@`_MBXL}`j63UU3Xu3~Lcv8)B_?P?p(aq%D zYkZ6JbkQ7oC!aRqec2l+0pJv6tTv2puz9)Q!yE_1`j6L6&Wi;DbW{A zK^h^z`Wbb*CO@q)js*C35Z_h@iNs!Cg}k}$6F)bZ3#^0*{dq1fga?)%`5VbY-&{fS z)Q9=tt|2e0KTIzMmeUn8r(FM!)f(Mu&_)2ZZG%gthYz0+RPJGjOjr zAW`5Z3*ht0t+{)&GWPHr&=?8s4L%4~xHWb4*Usd;ehojZ-;K{J&HX?%buX!fow>v1 zp2?Qa;>jACN{~`D`=%J20!&ZR4$pmZC1f~od#1dw`zFIy`JGGwM8IatMefXd@f%}E zy5E|-Ju3lLxT91f4Nmsdfg8oJLJK>CaWKS9uR*VN^DOEH2SxYSh0e>>L|C`v*&PUu z233|FmcL3Ej(mBb1LcJ8Q{jEsgh?usw2PT1q`swaW3ix1RB60)r{1=}bPk#~JtKa* zM1#V_-AuJ$$HnT*)N?FwWOcgkLd=me`(g~1VipVr`FMwu=%YPUal~QE!QlhF`)lBE zGvhqtAWn5Fe4++bUqt|bX0VTi>LaDsN^#`Z)Dmt)e_V??8{vaLZ0SgDR#hh}lV>Mq z@8erdlsVj)Jtk~Me3(552P1Q`gY8r0fb1IGlI!uJ3dez~9CxA5eBhpdo#bv-phK(x zx4-3#gPv}gKJLoZQ`p`BJoOJY7OU{*1>`<4@ANE^D1l-Ru;A_6unbShmhOA4w>tOD;UymZSk!My&}>xp#yl0ekd^|!aZcz`owv@VMOp?sP&O6(A9cy&SJ>z1~KSGP=c}gyAHRRnWZ5h zFJ-`l=}&srBoc$WKdkMV^=wqk1`Y@vCISJLhB(~H+4L>ez@!q$M+Hw3*;rli6x$CX zBHw78X#dfF%>6cUH`lb)Oz=&}jncHL^fd=$K37x-KImx$`Sg2;(bQR${0eIudU<>( zbM!F{zRYpOU2l^zLNON`T5UD=sXjlU?<{<0?~bz!AnVf!Wsc5Ol-bfMc{lH;hb6KB z?3J{Y)E2Fq!_vu7XY}gaiWAJt%^?2yZ|^L8OO^uI7n(cL=8}k6s69fMic1c?FrG@x z&w^g!L6q1@GuK)!S3QDf1V$BMJ+k{B)57?O8>{Pvdvp?&e6Y5sywlnc=7NkYsP}Ir zvl979K#2t24J5yu^XTh!I`*(0c!4{r#Mp(Wi9v;@%0RG&S9bi>0LRUYgK)xYUE_2P zYUDndT8^Nurt*pT04A>e!%rHNR5vIEOrDV-0Dqbly<&d{Jqt{;h5wDnigJQ8{3JQ8 zf2e<-J3$5W+p)zXgSOqBg3Hk5s)2)C$Q(mASuBs>y!YYuT zBoMQ`=w@EhGQUv~FFtw}yPswiDoOa_;fI*2PEz<-oFjo?s{%R>`HLE- zph2;xb=6bbE4+78iXCfn>+4ADoOMi984hBsATb?5Z;j$%vvw2E(wff2c znzS>6kIY=0XCne;emow?uz938ffJ#HFPPTa0)9~36M^D@VV#LIFK)d7fR4ftM425G zzmL|LEI^~djxHI%cN+jK7XXGaVn#%|5yh5{)q96=0OYe($>_}q&qg-DI> z?WGw@WGe3Prmx;rKFMGj+Oj(6Aqp^r8^4K4y`bKXRC0=5UU(!)3%=7jf|-*9a-3@$s$beoX?;qhy`BJUM^~betJXF79Y&5o`@IQ?9rWI zTI2s}Em)|V8iqh`2xO8Hg=NMD_GYq}#C8)-__u@Rpfm>=jJjCnCHQ7GxKE-&0KbJR z1`o(XrCzwQEM0IOO+aAmep;Chi0WQUxRUw1d@eI=>@ZQ1 z0&`YKp1AO}VCXOXGGwCh4>IzZg|(ZsY(*DF z3n?*sQ&jLmBGOW7d6SAK zv(tmf41z?5T~sR;Lzq92=16ESkE>(TAK2O&^tM!2C%+Zmnnu1Pj)0eN^H7DrKU+UE z;`k9{Z`IG2!pat>T`NxYtm21stNV|q{9e9SX7v{x_)jbu1wG+inrX-FejA-e$h9_$ znsyTaxnlih4THbF&A=M#$uN#X`ho()Id*u9_9=)(Y1@C1UOMDbl_Enqs+2U1?xhW* z?6#@l^HZd*EY?IWw8p*8aZPZN4ZAMCNQC!puGQ%TA5NI&f|lTx7w653;(?vUJtV?Q zMGna_mrBOuU=;&AZdb7JxjN$x-N4j5(5v8%?3p$pDX)$3GwFuSMJ%3P`}#w3%bwmG z_QF1oa}^K~oW$GJ=pmC=5ENezk0aQmq!MU%z1R zZ8;-lD%EPK<7_Ylo=PxDTA!m^^D6{%27h~^#(5u550kWk(IorUsHqRClY=3%5Qanc z%5~J^F#FKhID`Fj5PdBA@dF@|BM@#G55xB#{_bQi=j}en!Mvi3e#qetu^c?9KgRAb zK;S4g;#IjRzMrMvynLERWF5cjQ+~7|O;Sv&_E&p|s5oXiyKj!irWk46njjgE?Ip(< zS~@yM*;-1yyb{o3x;3%S2-pi=J=$o?us%P8*aaM>1sq}}m z{6yXMVsTG`sPEuh8!qihAlCT{=(^z_rMbyrL-zBoFu5X*jYa3q7B})hAtj-$TlM8p zGOrA!(cSRFck(aFJi^x@vc#(Vti=a16c0`TxM*tqIxh1;&;(^PSU%@%3LHktTB>~Y z^3k@ik1&^Y$}*DPV)C(AyoHEqo!r{olSMWRz+t^RDV`$nWO>pI6P zv*S<;#A><@8q%Ihmz^?&;LOjz+%vEhQTM~~Ml%`E>D~6<%(M8cfZ_Y|?}|@sYkLK; z{V*`nrAL5N5r|kj3svQlJR{siidFXGNU{-4>7h5w+l%np#rJN)O@780RR5U{}MJ$P^Y4bPdj8I zBuCJHQ}yjwgX!hykAo#%q{~0_9rJ93fZUs~jH^A&R>$Sgj+iG;s!j`U7Aa0VAf5Hw zY#)2H3hkHTF@0s%=DQmZw|3iDK;!E%sXh`?QA{ zk2-$sYRo}Gp=^7c{S~jc?ll{4NPV6M?(lf5o7=bEqxRt+|0~YC7Ck;V+k8eGEI+9) zWdl3UN+&Dupq2V^q+1=?`;K#`Z}hHR&FI`Fx)@LaN_dGEj3C)^WfP_*6}db<=F#5q z8NXdTN#nxRmqtj7iti^)C)z+eRucv}9yUujd$z(hbb%MEQ!lK7`r}(JeQ5wr?y7Kc zD%L7mt#;gn`?>;KpyiX|nTDi3U;BT<0jzONw`8R?x9I*_#A>T+yqI6)+su1|TFP7* z%VSqdcWzF3pkv`JMy#r~8Xh)8H$9Q|6g&%7Bn;~&>bLmCcY59Ab=5Fvv-PV!%Rd8v zZH^~wcx%(PZhV_TIPaA1P90&a>y3<7%Q_dKgE#ic#KNcUG@aOJaPl88k2?5N2Rt`#)Jzivl!ZxQIy2lsfMJ+t96pW}p2-W{9}$STe2tPEsT z;4P@M=+N79tpPuZ9dS?R8q|dYmK87Dj?^owK)g>Hus0A}w}eBo#L3*(if_z;pp5#b{Bw5X6DsQT z$PSaqL9lMbXIXTxTOpO2Q)Br#Cb76?xu8)e*u!D^Sb>f8_p8p=D2)xrg|X))U6@Di z+|*ll7dHKgTS05=itJo<=2O{)RK_bA=rJ5CA86-D2*XFq#tvAVbo9dNxw!%aPWlDi zN5ILt%Rsq-Q)_tsBQ+v12vrvLswecf>ChqG9!EpQt#My0xKczg5PP6*hJ+RZ ziOK;cFqQfpJ>j5XQk;q>jwfAktKJ9r^;NbBysT8>bLs{Q!WWROL-7lHV->Y?bGx#1 zsa9drXSao#k>7uDi+$grpO*1wIZe6@^U&MM+_ey+0a@RNWkIhJ*?8x_?Yc8i}K~)jAJku7Me&uy3 zJ@3*#G7CPLoDW^kvy5d`Be(1j9uWcKF-|hb%ww?bW6c*Pl979nQ`bY90niz7LHrXC z#H7p5tPy}Xr|k1R#v`$jx+hlbG$od{H2v!FQ5*IfYX7u6P@aK;9w-CX`z%65DQG^I z$95>jC^f;M^1`vB(-ECDFj$j2hAC!+iO<72?`JLKw^~-pKVWKkc9DP!f&IaDcbhIq zd!b$etYZ(4~Q?24PSY3oQDX%P^A55$!K3SX8kN?2kofiOE$A`K=U_hZ9wm{ zA-^SrW&w1OQ}Zl(l((2;q;aE19X1ONA9q3A6{y)RmR%uK%bSU!T7HG8bQuH33X^<5 zK}9&}E_~sT(q5VkSlieT)y~IaK;UIZHO_ET8boYFK|w3q$`?^vz#dY1^h!3_{`LUo z69a~G7B}Wh9f2P3n5nx?A4rzP=;|?ZKp(#5ZQEnN7=S3X>u_MEDq(7t$S|oRc_40P zGE{mEAWoB@ZOIZkd{6{;7x+>!^!I$jZ1t*8EU;B0e=G59lvFzf;z0|PB}Jc^;hAx) zJ~-{sSSa{Lk4i->cI$^G@&E87M5Nb!Hx#LtPXu(fxKfbMsJmyEZ!)`A^rwtr8uZIK zLd>W#cG4p&7q#|cfZqbTnKZ)u`i4V2a2L!{JK`}Yux9u$2H1WRhH(0jcm0`d%X7v~ z$qYEg#G~K~eG2vUy~Vi~e+#98nP=i%=P+cE=_F8PIF>k@mQs|Rf*d;@8REh3IwpO% zwpPsc?CR;wW8Cwg`iBc>8p+jjP&qax4o5@TTbD{y_DXe*>EPgM1!f0^!P{UXegx2Q zC&)ZXm&;XJAm{rna_+%*33v3%erv&xJLB)&80_wa8-oI=l0Gk$>~;1$e=OKJote|q zHOkfw2IJ`~HjZg?Nw0gC-5Y?1pqn;o5#Hn3tEV<@8ooORxW97~cTCSKHx&Wv75-YD zC7p>1awS&Di^`yn#?i!m55IpTJhDKC`q*g-$T`jn_gPeFkWGSzct z;3N?;_&T48$Qnr*VCNZaKu&tje2mJjCP8J>kA9ccr|tUo7unrZ@;kXIvQ0VxwFWC46ny%oaFOJex-RGN7p?b8 zRFkG*zKd-#CbuZfZj}|M{7LQI1LVf=^OkI7j29NvPvt!4c4|INmEjxx!$eW4qLoNA zwNukVka*DiCe(5IGqmlVKC>S#fr|`Y0`<;)aR)q)GqrwXIFzGe-5O9bb>a|%3v3h0 zMG=7nfj=Ie%g$u;icKBML7LdU@Xy~%DE1ME7$D{(ZWw9&)&4@}kq{e64Nf{J0rAkx z4Co{x!kaL%z~RtpDF>u+RjQIo!4B#i-Lj;c9{`MTl06S2?{K-4%uvNC_=)Z@W)dni zM_JHutCn!?tL0Y5R;4Zr1tAA6y#fc_S3zsB>CtQ7A|Q1pXsbr;_c%_(s)?#}?0R1# zygknva5_uX`z~}nLx=w_|A?9-xFeigR|sBE9*=rB)@oQ)3TmunNgW8Rt)Me7o{O!{ zsu$ZamYTKU;e5z*b9P=ZaqeEP)XqAOeVsVVRTCL{zk_S&MGV3dlAb}weFBuI97hmM zYeLjB)Bip7r)Zb9f_FEZbOV?d{#c1vBNYG+Fhj0|aM8lQ=euNVk?>N|0j^zP*-VI# z$8>_NZR=!V*7;Zl|9XJ9&FZNRhW+G<#$`2Lvcqf^$28IB)e^VI+8DSaGpQ^9GR*qM zL`-giR%c$qjy>yE7`1L)m`+eM^`tFhl~YCcTv!&jZC{q@kWO8;*h5ztFz>)2H<6(&euw$74Wu4vi&BTB-({^OqB@z# zn*y_EwJNAIX1op=c_+0YJmWs#+Y*#YP0<{V6J?7d1)&?U$uOD}b)8i?2#f*bN&*1O zNDK@@b8qGrld-4sBc8>PG;y`_e+dZD(!mEPV6*w&rSD){WCkjuQ-6nTY{}1H2U$-1 zwDg@Wz`;5h3>5>1qBFZ3sq`J!NHK+OE?t|^R6=i1{vHBV0y1_l(FoTMe2 zkz&fDnK$_A&^@Q4e)6CvdZiiL7&dvtwwq5|+NMF@y8r~plFgK)xM6_#upGAfkJY#J zMj7&)w7nNyPiba&MIAY_bC<6V%ZE~;9 zlOA{o{$n2Ab068E|Ly7AD;k$k?7;NNFvVpq?U^{DBKESueJufADg`HeUZNxNG#n4X ztRl2P-B!2Ye&MilFo2lJjkjNI{ug{2^n)6Ny6N4D3xmEsxG~YjEyUXOh_mPBI9yWZ zZE8g3_fdu_`dk>EsZ{#T&xm#}c%w6mL(kBndHoKz-irr<$|;AQNS}}a0q4V+!@vo8 zU8>lYB}e5dWs ziJDt=4@+{Q;*rJ)aYx*UeuY`nQ86q2B`HN`Afw6=Cv=L0Y=bSnRaakYR4gZ2c?8b{g^W{|Lp-=kO4=Ct}ivp2q$MrtFE9v=Rd`NdNzr}&2nR{hB zCri-7Z!g;f=E ziB(dqG_G&*RDi8QF>XrT8;(M647q=UU)&s~(6)rTyG$aDpxh7U;OG%@I%vY`xEeUA zvDx@G0~dA!x=g$ei}&NgfrXF_ybt5-EV1M`v>(^3#hf$gVJE;H3tz|B8M5|n<2+gW zT6HAWeCaU;(ldY!|9;CW$XvzzJK^~(M7%y_4kK%GT4qq7EXCpJ#$DqbykDO10*wHH z^T)TeLI385R`LZf=7?0fceCjy1<@R|1J3=%mHe}&crOP56LF5h#l}hzL`MR8A=W=hTmZVO6?qvesu^VoFedyRa4Y zNL0_s14af$7E+v8Z1s+%N)Q=;ny4=?T6~aE%d4y9s2&4$;iZl0L?Mb~Eq&QPt&WjA z3}>$k7A>3ZO91sbgei4@dU#m?kx?Xr?a}So1)g?0&pjTU^^u$>R7p_{-dUmV9M+wz z$v*z|X7o&aQ%vEv^Km_08HMV62e4~dF-trlXx5b~u;WF5zxRQ1ArRhP=V81uRbbtd zQ;ZwZnXz!pNAXPE;6-J(aLL~~Y3o#R{CK!fqM?}6)=y3 z7q_)mDTupUYxrxcItZ+{;Mt7LegQ(x2esdwOYIvZ3^m8!v(c`e2U*dMMLwTq=W*-k z^vLqtGOh&R`HsO)L$@NO+OdwCsP`}Rban6vDqpP)LFU~=cen?!YVL#+Q2sy2kkWmx z#)Pl??wyDzub=qR?V={ZvhST4PW-vOhxh+|A!OW74o96?c zAm*ArE#rYZRz_fW;cm(V9YCcSEve(3_{U^TU}O{6jy1y+1j1MTehCZWs|)?2Cz8L% z?(|Xw2^_Fdl|J!WIhTf&&+T#OS>Ga5w-_gieB<3Pks%{bGtdxIr8j-1`PeZ1}$AylMSAot3%)$X+`^8>_O+Ie3uec zs~|zHyRH)!?@Fl?aW>{4)uVBpJxntUpN`Xbn1oUbJGnl#8Nf8Ns_tG2nLDp25!&K` zvXg;sak-1qeCG{5q~&ZglVh@G&9&uo=;1fvg)DpW8WKzVJasHC>wE6NQtrLtw9}R% zYEFXk0YK7=$?~K*A%8Sa$bBtUr)}90PGhR}*%8rp%60zo_Faiq-kyM3XJnkly)zaf zMR>lJVDXop>n3Pb$PrWw12xqBK}(hkcR$Z?C=Z8qRf}(R`*0N`#KwoTuyC* z^0e0W%1ou!?-vTJT|0@=wC@rjCbzkp1;7SPWNt~{^CT*s>C_&rL-aJ(#*-7Rn5T$U z_4<|S<-Jczx&VOPik1Z0Ah1=`zPaH&5bwfh^0{JbyD!+Ieoprs3SvmlmC&KI67|Pk zFsGqB;gRt2w2fNU7?X%3Ip)AJwRe>x8IoxuF0Ld^pqkArg@$?{GXn5_jQr^rX21LUBid9I6dXoc?#%4#{ z%6Ho~CG(CwnH0pZc1CYjuZ*bY!fTyUn|sirT>CTnr(0 zQNmI~QR1mFmMLLH7cOALiEJl=voqcuD>L{h`mct=ZTfNYbz?UUUz+GrdBBljG>N+ro)92j7(-Vx9&=agGOYT`H6%Csp?kQFoUb|v$?Rk^}!A0tNVyikcDMe zX@%&hF>d}9SQMkY-t1ju=8b(e7MsrDINdstJPZ;?0z6`;s1%SLtHPIb`sW*Z0&9W) zptJg%CRmG#d>B-yRTs8J5y!WM+t_czI44@V(H+X*60Hh(=Xq=iZ;J1AGDF1h%8oml z^umESia{APjR907Zf*9U2^p3@bT32s8va}r=zw?CL9kJ`ezw4y7yBQ{2 z@N1V3j4uj$z z=GhH!LS8VlPlP7qvtdiuOPB;OpQrMr z^SqF`Du@|p%=VgZ0t%^A!WPGzzk0gFGvbcEzTnvypilmQB%2(Si2hJxZI^1FCQE2y zK0xI`s!x6aoQmGS%B!@Im@J9WQ|)YTipmtF0qn56sI@XnsCnH$F(CE?~rb3Q`|8g`vcTRBO zMX(@&jq-xyFBH`%arw!;kH zd9+w!{LZ)RU3HE=UHhW-2|vcVBKkz3CJM-|EimPa2gI;O^Ym2aXu$Q`ju~~K%C~)G zE(3ydV|?vC+~iw6TnxysWLE9IZ( zkrt63x5D3KdAbAT-D~*Ns~xfztVcwrWl#%k8*|he*!xtN>oqSM6?&@to;BoA=ZCs} z(h(r2)Vd9iDGoToJ|;Y=|k{xrz>^Wu$03_`){6n%;DE1ahlboJsT} z77mUK%^r`SJOv@xLwYi&21&E>fS{KF7BnDg>LgDQfkZE~9BnxJb-ig*clSvU+C&-@ zKJOG<6e>_B*$SWoU#xbszKBcArTT4`i&O0k>144{wHEvZX-ijrAiab8KayY&Xo?>+ zMasp;L%{U>!CIrAJ!UTFoHng+l_AJkP9W{CO!5PqS8+}x_FpM*+f&)++;c_IQ!sAd z)>&86^kOb{46(JT=dS_ze|FjFVqc`8AsO7w6bJV|3^b~3P%hfG7{9jUee19ZUe7T{ zdL8KW`ep|%h#f|$f@^0&tU7jmH@m*3AP(0rbH^#=Dy}ZD*^6tJmlV*OzVKpt^G6O~ znX&+OUlz{~-bo9j{+cc?to{I{3#Q{+2ITV0{u79WF2o;f^7%Nr(2`)~3GTLCc$Ra$ zR)P(4>nx`5D?2f@JVY!&+13i_nofX@5S@rEm3g^asKGlBNiw*$4otQB!@nwN20(@{ zw1OB=?*}!_Bm$>$XC(Y8ok1rz#5_zy$tS0>#mX|>fonbh`{$MJ(#+uG3k{wub5 zgSfHz3JBN;pc-zg8^Xd%OnNn*)3Bl~^+S2@BvMBlTh_?32@k8szh7OZauxMe()~{A zPoh8z*fWxPHd3~?r1ejox)zv{MP4)r(SKb(+Kn(w>Yml392I&JXmCf-u@f@*(W>DMrBG@9u-F2o z!cN7T*6O2fA!#i{%uNw7&1oJ4Gh)4QcKnBHNEPlrO_ga9GpB z@q)rh4$mfG24fZXeb@-hAwa@rm^=!--z5XMrV*yy!5U5z(|nU%SlnSQ~;<{bm7` z&zG7)eDLdzwDX>FEDuNVL-LgtZ~VqU5W)PYMRPw8LJx+fgI4Vj4`^0Czx~Pz(tpYT z2wl>Ov++3CA6fO!6=Waa0iAg`SFr3i;=&*?-a)=!@0AvHH;X%NM||8=8GyY&I~Zm& zOB$H;mT3yZaB(vCP=-Q2#PbNcThY@B!8O>?(6~Aws2+L=jlB&CQ+OmTzMzru;w0~t@@$}Ux9CV~gAPq| zwv_{2+s~NIa0q~l|EM)WVi-MmKP zZO}yLGaXB`^xiPFFsr^*=u#f_CGsa8J%_qS+YeJvgJubJa~6KCW&n3D3ylDOw1^>J z8Am2U!QK0fS1v6H@h!^Iz*!H4B|+-^!QA)YqFzg5;Ml_ft4J+qBXq;c2RrLQ!UegN z4s7MgiQJuk*phh;B^=HABMmMv){$s0Jn&26N*ZHfHE#+mypLS}@dHfcEOxkl4gZzg zcCBc>`n1BAYd0S^h^S;D!43}o0EJNQv={(9@q%uN$6|kUBK8Le?fzZAIgP+kw0n@j z!>&B}12Z&P`H^p?o8XXPuiKcp!c5hv{g7{ZQRw(T-$Vnyk$pqlF{qI8?QpBAE+y}E z)JmO-fEE2UJ7FaoEZrbE33?kWD-v)_H2+{nk>djvFLU^E@F7Xy1C=9d{>fkAZ%=a2 z|9@%H*as#b6T~qfFby_e}Lqn`Vh)Ra)aE z!&@^cz~c>@`HN2R=SN3>q6eq2;YAdxy?kYyb!oF=@XLIPe`|lZS-A8SG=%!|Js9Fn z%c4t4IEXdbIjE9IzWBPKMt$XOIp``Pl8P=Y#n)1f=h)^&G$fEpW2SF6y(ck0gVx_ZUFS6LloJ&J+#uo9km(x6lJZ?*4Nd8D#J$CiXNghQ*F1Gq>1w^ zZP9;murBIxbbR2}A$=#M@Fe=viw$&4WJPp*Gev?0-R_AruRi`?wBl!drqnHGib&2Yn9n`%sR{VNi} z1$Nt7YR*6JAhpOh7z4X<#KJm zp2}HBn%f;~eX6FWzPW&N_ja>=<3Ob3BPg2q$ct!lX(jb?4Yk!=JkCWxZHwE1?yThF z*wj0lH;8i*;FWZxu!}10#{Aq+EkJrESvM|685&#fHQN8e7rJ@U+fD=B$sUT-9Oe5A zNY3JT{t7!}aOQ@&SKE%_q(LQY^~61s$>%W-+arcm`{__Fb~fbZQ(a{PC$>Cf(?UoI z=4i>la*QGuXw5eSPP@weNEw|`!HNqtK#JITXciBMI>niXiEQKqpG2t$NeJuOyu7}# zMth>S3N!?+VoW*&izJOAS?K_ACdOrkgXJ>BDo1&h_uHp>*^_&ae<9|+xp6|xh~FfW z`MCYD_8OdRa2s585+M=oSFZ^D9TNj(l^jRxy8-RFu>dDP*uVEb#umq-f%n9~*F>Xj zo&LURIRuej=Vt9C9ofJe&Ky{-6x%7Lj4zctnVXvT9XYBR@J6k|p@SUE6J?v*| zG>^})X9rMIO+u@&W<@wY#oklGH-H9HTy?|W5@GjSlQmgj?~mP)9ch9I20V3n#Xc_B z3p_@6`4$q+noA$4E6s|cw2|^xKHqyIZ;dD+tZB;bmh>QK z&ub}hFlUiZrqRe*G3z?4WHW+92UyE?C$R2~3%->&ex%L5I}IW65VBNMSyc{!8E=4c z|5B*iBz&jYgoVScFXBHe4+w3c>r`2TqpWicAMl@YS`3|eU_%F}lAy%e`3^(FH&tg+ zl?;^y{+Xi?a>Fm# z&!Q9!<=s?|M+%)i9%Pf4@Z5xQF<97$;+aBYKC3Tf-KyJkTG+>=j+c%|hA}E5W6hIhoU)utx!H6`YQ} zymau63@g}I3RQk#{KT#CzSF=n z!My(%)H=_|+Wq;-z4;GQ6ROkedqWR1VgT!YD1InpCL62iV!mlwYWAjME&!H9f!Kfx z+TgE`MR?yeSP3S)VeF3pUW*!Z)QB>aUVLP*MNBM05IKyv6^<@sq__XebRUNKvC z33FyXV|_=8VL#l9OpKtZvZFVvnk9EXD@#6=T z$4jcw+Y+WMu!vuP&(c#%w15lUPmH@rE2< zsjgFLGqt>zwpJ-=>|vJ)zQICNTmx}pQHBVjIDW%Cj(8X7@W!wj$jvagZgC+8kSVz~Nk$D$1s%I}F__@TSXktJ| z|A1`{+IfA3p3);U&{R8)a-Fizk&1PalOMbLty#&l2)qJUMK*0}LE@<}qcE${6e=}t zLxPy9yFuV$Tm2ggL&4pCaY(CxzVlqAwP>Spq-Rz&bf;mFa22Wk3%{nmfPJ{A76fmD z*j+X^D^(jEo2F}@L-^!bqvn0n`$pA__~FgvBN(;wPr8`m9xKDFo>04IGU+CH1#{Hi z`p^AMe-*l)UoSjP)Q0dpa?yCkMxPjCotH@GI?wIu(7+^n8`LIZ@tn4RAWyXnE98rL zf93)-=VT+MN6Q2MvN@Tgch@aLb-xSKzSqZ`E(yb9nu2m8ZcLg9ripHLXJW3~*0byl z9LRZ+Z!?xQ4;y}&%eGL#31z2#_gL@$n7{EUSCWT74dfJotJo5M%$|{l8D0@8R+F1D z?Xq#p`xbzomaSXjhVy?RwSpET=WRfD6sF8QHZ{U9>f)TnZZn8x9cZT9M;4PK*|%R4 zb#$cXdrl8q5o5cQ!+tGVOhbQpTE1(#)_MA%Wpaa?vmEQYR!;$ENTR)B0ln`wXQl3= z;NPD7x1F>OuYfZ@m4C`POfckS&fj;*?t9RU$&=4VLCr-~#4{hNCHqbG5F1f@Gui-f zxtt0ozbrKyhl$sc^;k8dZ)IHQt{FY2t+-AlFzLz zZtg5LCVN%y;@twQOOV<2CRGXlun4LLP~h)@RM}T4B%FlG1K~h~W>Si9hnY4KipQ+A z$o{8o9aTVT$Tm%Y>n{imR4wjy_Q@RR_YU0(?p6@ylv>S5SHSO4`<(GO6ZCQu{Ov!h zEnq`4^q9^{Dt%BnWwA&v7c!MUNWo1#)tga|7wv|&6JL|km3OP60X#k+rvpo=d{Kb*(l&hVbUIdz zl@_f8Pr1 zH>g{oKTbpCXwaDFzeUll3|D*pv+_VLkg8nf@)HzF{vR!N9m|+E9Y2id+O;gX1V{T2 zdaiUy;_ZU)Xx%a9G%jUX=+-=D(f5GgDOR~0y3RY*84hADix#1J&y{W=qD#cp#ykkG z2!fk=M5;O8_S!E0^=dn(3~KisX=uM}S~Uz-pM)f%oTrXE5c1I`*N8P#Ib|`VxmbN? z=1ZQ(IsIRN1*J;evE7ZkQcWJ7+yDdFk;;(m1#Fi*6J zgxPAZ@9Z3J4*A%)GHc3@O0VERiBCjES3E^@R6l>`EPte&i=UX_Sb0Sd#oHj2e9VBF zCfixxK2~?K_SCu!p6IS027u|*eRwhgxOBAuXHNHta!EW|{^pmqF^ux*Nmsd*ve~pF zEM3AqThxuET5S&T$U9_PQPAH-zSzaM(^ztUfs>qj`-O0ChXlO^#C$4x9zjciCYZUn zN^a$8t}QuU`tR4* z-ZF7Ww|Oz*p%hOyi1y%l&Ipj&=ZI`ffqE$=aKfwyeCbuRiT}{R3TslRSG_Pq?YNj( z_A5dRXkf>k({a#Mr^D-Jm%nOsXjT&Sq4Z#s9*g%!-Kx(#Pqgq1F|1aiV9JBWMO-3+ zWSjNCW)bCWg+=6zp=zmnZ|T`sj8xLV0(L)w(SDU;@@a(2a`Q%D@65m&EoV`bVEjfx z2pgbZBO2ZQ3P~{sj2G6+J>;ZkFea(bICNmS^3={nQq#Eos|4Hk2A#aM3gje4@&S{m$wk zAxcRXJ;z$2MADf9 z_CTfL@uZUa7pX0w@(@Jkk==nu6O17&lq1BUhx!Qk$N=T9w(r+ZMDT)LZ$iWYgkZXb zd}C(0zQF<^L%lCffI#9si{=2px3POG!gfp@y;)#j^)NTwzF8Md>xbwd%(rmRhFalR zXN%_w^Tqr~rMw3ydPNuC#@^(_8fDW{WnPJsUIa+I~!o_GEbxOjH{$>ZTnc z$KsD`y3<9DBwG(GA9Oj66v~{&MFkyqrT@?){b%Mr(xf%};Yk45#zgS{-hjSAWA> z33wS*iv-$gRPEk13lF3rxQaYH37P= zIBdue`N?@4ot9W=hg%IYfqq0rVKs5!Enz$UgqISaJCI1^XGEA^!qgqHDh|HDwX|K( zAbOj>I!jIV@AtMiw|9>7;jE*YWZ?ULTK5@M=Jfh{*eH*-Q;kb^e2&p2Qkh{Ny@-H$so)H%niOKN-Q4nwsh*f4uYB3%MFQ6Kb z@fOzxb)WG9n2q=?XkqR`cA3WsnXSuR6kX5`nh9g|ia$M;T&_tEPdKa^#(0}x2p;hy z%FaXH&kgp6#WUyV{@vrD2rb8c#Bihk*twHv>J>w8!fcZ(NZHJ|tD zTb&`SwlrRq*YKt*Sf_!~BJ;J%88Nhg>(SjaN87p}Wr6Kl^~>FepzRF|yt=Gx*drxl zX+D{W)DVw55FsD6(hUB{C;)RacBjuuU&;T3c}@^`4h6=+pS;G3yNhC?)wu6Ei2tgR zxboQoFs5;bp^#4CTq&UI?|Q&l5HWMy5OhjT_k zI+w2TziiElV$?`Q0K~GY%_|fBUW=LrE{&J)Ps|ZL(Kro+S{zwc^-XH#z;+%qdyEs} zAd+4u5bcsrahaW8b$PUnM$c8@?FtI?(8q~ubL!Y3GPNN0HctpIGt|J+PB@GCXbxz- zB(<0rEgLuT4h+@T@uWII72^qrve*o_rHHCetnxw&5-`bGB0sMEdA-c~+@9nU4C*il zEl;*w-AJB@^VtsL>bTlb*?hJ(p`^e^dr2h9!JR~RpqwfF*y+9E+ps%8YhG}QH{t;` z#-zeVEyrX2R$zf&-oLxFx7XL{3J_YZI`C^v^Z7e@&_@?D{wa+ps6r!l=LF`!E5EsIlEu z8%GO@j(-GjM4hiVLhJZCbEg73zG0%hF8HTN%CKx}v^%gfi6Adnl=!L6#l z87E2@V?;xcw#P)QD7h)`N%k^rfCQ+s5%0vis08?|HZiW@;_H6t%Ro=_B2o^acPB8d zlCedquP09zIU4P96s{FEmwkVXsh_ndmtB{fvLOGSs;$UJe(Naol|lID5J=e{;&bl4 z0?9xPq&p+)5MUuj_(O*Z#CYtW=1*#m{a)eJZAxIyj?lX`O+Yvi@*!=~i{xUWe-FQu z)}HLDGIevJw~%y+6aR60p9}yi2|)#u79^MN5&k!Y@~dq^{ItjhSb&?|$foz$gHhn4 z%O_31w!z^gzb6<4F50Vx5>XqlUSNYqZ9c3387@rA%(m=X>MZx${-6rHY=dgg8t*Wl ziat+X9E&wTC5#%CzS!yU<9*Muiv|BsoJTLdJqlnl)N@yv^bW9Cn|D$l67Y zjwnu{!mr>fx^7J5@2s8qZ5>QSd$hMFNbA*C;-6l2r5M}$rACQi3Yr$cMLqSyNx$LJ zcBHqH$s|GVZz#ZV;Oggh`E&5zB4=feG^)luVLKkb8=0gTDDn%wqn~ALGXhtwL`L3( zw)HIKJSA3KgyqDc4hfnxV6g)Xjs5Cbp_^=>8uz z&Vf~7*&h|bljTi8VVm-a6MxvS-b5zuJ-AL_cu2j)MOX2UO=%%RvO}~5WJ0*5qY;(! z%?dp)9YP%TusrzY``$)!yGhM}(_<(;q&}FFK1864_Q)mVuIdt9aaW!q)|*~?a}Ezu zQ*>d--?GXCsw_SqPRgQLs*d}LKVy|MURV>HO~x6;>HawhLJAF?_Rh2^oAj1C#k_I2 z<5?q@q;z~e&vi6`z{jO%6mP#|r*PrvMK`Qd8|vpWEm0nulli2TB@mdU<0$|Ehkw($M>q>2jw+6sSzeOS#_vR4S?IC5qSk@+XknEn=>R{8$0`#=6qghRkk_{3cV)rc8#!C2 zb`QRR{PArB%w>|G2Wlzq>)+XjyA3W;&lq^YjGTu%qE?=YFRp0|&^**C2iqze{nX{K zTzBhJux>t#x0TE41ut;E6}mNs?lLTA9LlpI=Bv|orQbA{t|xwqXb`ik{@b$TKI^(l zAt{TTwt3thHL6#&+47l)bv!MF9#r4umTRj3XRT1U8b!MKY)k;p?kNz1z*;MJZMurs zLVZ0=pPH8U+K=KP*rYCHi-K#(=D`9Kv)7oKjLt|bzv)OMMaJ+m=TWGFv$9yHZle(2 z_4cq?^XPYR8SEf0=Z-yipTZCd2LZ*pstyKt>IVCps9Uwfj>&YI*xyl87vbzoyT#Ca z30Y#jj6iiYz;tkrvDL zr&ot^m^`49Z!^^Tu6Lcv)1_mw_L$%m%_2Z`H*Jd-A8J)C1XeM#vTi~!pl}tRpOvo< z$ozvH=7DB|ekHakw|owip|Fi9+J>G1+x8_5O`^|u;=9PNIv@nG_30r^k*r|Fo&0Db zQ2%ATkdh0v`JKK^(O+$QcshZ6*iOs)v+Vs~k2glES0{SNacnN#xG5E)YOdl{|FnZO zk=RNm2V$d%#9VCDUY{CuqV6seYNXqi)qFXvPpBt6!Zoc2vOU@_C?SnxT%3)f0JXP6 zqP*;lfY)avtRMOd6cO@Iu~@yoWKO)IPXE@KqkxK+|ADq6w>AMiV%XeDh}o{+<#AAn zz*?$&jHXaJRuQa3u&)59_IwxSKEg zSj`=YV)>BoC&+c2Vz>!oG{#@<<>xl!!Y%wHPGS;4e$c7;FLV++026wNAcR{rQ-W=M z{(Uu*A@flHK@NhKEn7W$VNg>3K;Nhz0&v43e=+jQ`9Kf1Ocfmc6kLwu`VEfIjsMwD zkSl6Q$wPX}J+Dj{=N`g;5$HZx7Ei~P_A--F8{^zg$(prE_f$oLsLC2I`w$Uv?_ z6sX>fQ{`ocWD68F>MXoA3srE(P_b%xw8$i7t#5$S^ZC|XGc=DI;U{b8?LdTlQ70}Z z?K`?fvQp!eE|!$e#RPge9LPPZD<Nla zTpb*cXao0V_FM-GY8vhZAq}e-VR>&k>J5IkwvV9RN964PR^qAsqiim# zu-9E%q{3nGnN%}0J40;s(+b%o{XV-D1MMVn_uMd7a`lioIiH_fuW=*Ybo zatbN(eB_u7)jzfI;qDPv9dlLOl9q^U(#0?0u7iu7)%#Q6N~>7K|&$eaou3WE+_1d>Vmsrn30g{V{eRw_aA3Be*ul zseuz5uT!433@=?bcUxX?hv=Ne%%A zIYB>%k1^@gd=}hZ>Ny?u0ea`20auSoYE7Q=XDXU{ z@zXZV(%85-W&2H=XJW)T{$~wAIcg%nM^VEjpEc7=vW3p~Ewy$+Jjlnn=>lDp7JDrw zKuoG3Rl`2MDaC3-Ab5;~g=4S|#ITw1wWBw>1SpA>_cZU^aMtU?d+@}r&O>-qoEs(e zD!~gD5p*4s0K_9>yW#vy|LG(r*69oEpR8@qbh7+VD$2EOOF99xSY8zPDzhy>XmVab!s#T)UATT6;dO@2c^@aJm zW}*)Ox-ZluGqs*~0&;3stHo**z^o={|6UGY9AATm&o%rTW-w|AGFw^&eO@Gd`18s$lZ&V)=r|4|kMZG^|q} z8<<))1GBQNhDcj6zq?wOJg$g^kUFvj-uRC8h1UG_gQn~BDD9sv5;xIws64NVCdpJ} zw=7!5-w5}26eQ4*z7~Um!iFme5q)grdTGRz-uv77{F$F)aJ@g;pcoe| zp#5Y(&`Sgsf9l}S*756D7{b?vRODX&CyyjJ6tPw1Z7YS@>hdAXq3c|7fnw~L^))AY+bH&`2sWE~w*&G+Gv0MaRvCsE? z6~%&|84M3EAyId}mCpXVe>7bXXymWFGs``GY$;?XP+w-c6g}`(-=vb)D)6V2Yl){a z0y(XE2Gs3A3qa2kQ`-Esm4458S;;$tQ%By)ZBs?0(6yEZ0#4vp36e4~4Gd>Il<{D3 z)`5h+;aNg=a`5yhU-HpzRc##Vh6b?Px~N=3gcV(b;yvdF3>PQxYulc`-;7l@mse~c zd$Lb(BZO1w2Y^_3Z>>5i^W|o(<4F9S)8fKK35O}}>;dW20(oi;yh32r=pUx#3Qad1 zO@(-6BLgm-CK|M1AVVMtTAJT{zc4Dm-d`vFm28Ognapum$_x6r;EOTTZwl#@3ESSs z(jlw;RDf=-qfvR)tJ8gh!amV%N$=jCY31u82B+Ur)?+e*3miB)rxY zOY-MY8NDAy`m;6w5nuLWv33C2Uh%w{}XZ>H#lRxkJ;k(mw|C5b@%s zeTPI;x!L|NOd*^LbLPk&In0s0J(m)ydnh;IcX2ML_)eXFw$08Rfk`R>x9s@ukVcN_-!Z$J{A8X-*g>Ky>9VF&oC>Fc7*!EW={7y!o*Xdk;c#XM4n{@NB*M zTw{QbNcx=z%5gb`VXG>bS#-btcV=#+pJ5KSyQ8&^UlJr2K=tFMm-1Tm#fvjmSRUVkBAN2lyzc$8A0UrxoyA zE(_VrK5=?YdfenI{K?q zMymZz`tfTtxx9p5h!8Y#Xv&AeqpY^&I#M#W!o$0~0ZB%CLYM=xG-cTSVB|PYfo#qe z4p(9MMUS`-Mj1F?A^Po~UmMB_`wgu390nJ}o;i5Tmi@_SHy?!5=x8V7J_>W8vx>bx zvZvw@@2X?iS99;$!_`n#C5V2@;enx2B&Ti566lQ`uHes?N3f!Wp9x%k;`cl;h@ol>FN0GmK)8Q1h3QE&9EnZCJWnfkh>9t2SDLX9nzI{oG`mmP z?Gb7P-iD@sR8uu--|m6NzAiP8`(ImkZGc})T*E7?r8f`-`Ch@VGMKsoKeP~GVMB$E ze|X|%RRdFJ>(xd+BD$v8pNBe3X2QhT`TJv_|Jg+UE4L=%KQ`$66=oS`e+<`4jq-2! zPHaEUdV#12=EiQq(O%~qpkBU!V2%_|Q-)?)%Ptoi5k z7V|5s`^YvpA2x={lb&sD#S+jlG>Jen=QhtxnPf7PocW$YVL~>TEB*vrAsF*D;n%f= zP)s^ENX{oS&AH=Kf14+2AWcw&A2~Cjl`A11Y;~dY?7Dbz5JD^vY8x574GMWS@Gn2` zxX~FZ13N>SlhJ!szln*+5@|+Xi9K)-`oUz__Io43gH16u;~?UK!iiS_C1N4m~FA7)FlgJkf5gXVgAAocaf28n|7`5x3gOaUohFJL=|pvN9x9 zYtf}e)QGWBxL^h+dO3H|QS;1q2DK~`PFY^6oZjWcYSG*C!K~q&%cGhdXGM@NAaB93 z*!JODs*tW1?supyC8|t1%_i-V@X{DS54$N?V9kZtJBq}&4To?2PVc0FLC~^Ws`@^( z-vAeAHYm{nmH*HwJBkL5bTOu@RjY*>UcJ2>VegzAP&)ik($Luta1=+WeD58V3Agxc zJRJomJpW=S($#E=OXrm`0AeH#LB-ba=_{}ujoDfAW7%1vrti3!CWW^lf}CcK!LO^-9FmxmR0PF*A@>J#72K z8S6OPaO8SiJDn0F!~L9WklGgWYv&?CTB{Le5qIcKp!ChDC+zc5++$U_a?&n` z9lG(?)a*mY5}dAB8pKZ!?3>wuqbo{t@mh*Fn|EX8i*6~b0)S`5;rb<^#ek@H=W#*u81FBs#&4fy(NSUKvSBw z+TiLl6S~;{2iS3kHPFwM^z5)IZ3qH)F{r%rJ*H$?s&j)m zqHGe0nakpnSWiiwkEKoW$tTh;=nnxC#V)Tz(a-K~p+zPoz(pQ-R@X-af~OE!OM`r_scagw6yN|37?P2f07WvLai9i@fZiiK zN2`@VZYCA4H3Tl%3HhH62CWpQAjP#V!T8wI4KEfpOaDzU>;W%2oPf)pSiQ>3)FjexVn}!;dcQSyL7l#mi!Z-t2XML zESk%Jl!aQ_m8}|Jw+T>Xcw4tvhuP`~3s6vKb8CDoq7|)LtY1hNM|qR+B(HqbXl6_2 zT#>(!ntNM(g)#zc#SqqBt&)YCiFgX*{#Y=xEDvoNSfg|}RWsM)e#Q$3uc@WNTQ^Hz zJwwD@y5TRJ9mQ`GmjO&C z(^*Xl&~B8M|D)LI18!yJJ92q|A#V6(@y5aiGK%)7&S2+)d=9ewX!B3elYGZzQkF>N zgw@A?25MI&Hzn-bo_}Jbm9!fFMqit3;TPHS58#r?G-jlDdQ>MJqK?K;XF=RIp$7`L z8*M)8#EVb`>a}j#?NB)xM_8FOZTPQKDHe<#+nRHC+f}w#p@Vrs_EF4Tamy%k=f#SK zbj`TKf+;Wh<2N1QpP73gEx=Ghd*{)iXistTn=cR(f(&> zH(GS0#3++|CV4;}$e|E*{F*^`qe`L1^M&|=wq1{pV>B}r8$sO=NTn5rS``3nO)-qc z>`f@raP_Km&nK+Sb?=`{>UmJki!`eXK87Fi#vi}MgFI9EoC_o24{$YN-Tiu)Z*;PL zk?+23h$>bMP;ji&rki<-Ik?F9B2(2J%s_&pMjbAG!@{j^bt}2O-l4zJ&p5>c4yLNs z2y-~01QR@>()0nnc~NaDe1}F(%s};L@sEYvn_9u^<%DW^yf5p) zPy#Q%u>%X)jRK5N2MB+6Z_!2v(X3srnZK$(M zya+(HTN1U`IwhMaP5b@0F%>joRz=6=&o$hY?!k+tlWObJY~U65BY~GJJiZc%9|jQ!IUV7Nh#&*xCViGDrX*MB@WJ9N^|aVO@hGub2x(K!YcIE6pk>t z>UC{3+CWI4DH$G|-1KpSEU(K;YI8T;353;b?9RvQC!YDGL{Ks1foiP*vF zzT{ONYKyJ{aQVNHs}FQa^+;rT3s0ZAJlA2VK=c{$;Yt-2S3XEH%9pBcKtY%E$iNSz z>DDEAO*#5C?=9LF#nzHvrxJMHdYN$(#8!OW)S6ovni5wC;@=FY8(^T*K>n7)5!`Ah z4uU3PTtN7s=W{jOL0ZgBzyz9TVjlF}A^`VB!#B*?EkjD_DNssir{YX6ER}S-`HifZ z&)`&k&!6b6RG={6y5hr9O_PL3|xzDz|D$?=E6zul}~e5PB4i*geBL$7GmKL;rl z$Q#~Et!hi#WK;=*ypDVC4VM7;{a4(7DTTC43ADkDIGajRR)GXJI8%kp>cDf?%aht^ z#vfGit`a_JU#S#mUC&$ikfRvMiIsWxoYD%*8ZZ zoCu!>21}(nG}18dAz*ae9GSuq&%|3FKZPn%7=Ru(05@n~KEf(JN`*+rM?ImrSqxdO zFPifU zcw$|@OV@}dqFB>ivy>%vr`wCP)pMuY`1H5CVl4iV72nL&4bSq3@18H`SsKITxGqev zZPTVtN)cGj6Iv3mp!H_$gnI@$Awlk8PCL((Z2QQK8-Xbe1l04E2_+?beGsc5@gkM0 zDZN19pr?x6%%U#M+qxu_Z?79MHL$$7pkp;@UKl$*#x&TI+6M8>zW<(c5CPw)El~t z*RhiPt+)I<2@?upzMzmKCD5*yMcPVQN8_*jixDvG2@!JqU%PJ5aD{%cFM!Wzf`&r1 z*IqHbgxqYOCLYFvXD=@F_4v+rIs`U*w@2yQ|K74HXOq=WNWf8GEx*-OIzC;~`>0D% zlhNmyvMfMFG}^J~r5}#^X1mBaC2)RAI*cP`RG4kTHB6QNJ51VFo zJ+pq8x;*lHI;G>E9^%&r|8zT@Pk_J%R-S$3cqyzoP*XO%Qx)&)-MlV8?RhkmAVs}^ zamjKEX&McGroMa*UfqVw_fI^P^^2VdqcsBcQ@tA@r^DuuNt1K+54vlVA*$7!4#H=q zu1Hf`uk3WNwX&`}Z`CJ=&p=o;aH1}~a0eFTV49i{ERL6<%j@4m>FaxpZKmB%Y*pyg z=`Ry>`zHqGeOZb|hW!{o2Zr(Ak%{GT+dhStS6N zf<*?M&3_W6DWL16Z4Wbp}8AOdy9;{!2AGoD*4(0b(8(_;>Q6e=GgY zotOH>Y&_hu>3n^3slNhR>;B=Gh$6`b{ZseT@=5$@J}RGyOTvjTU6z>8)~2>BmjMwo zpE*xi!jsN${qi0+A4~9ZaVd`JnjZ2cv>2=e?upBD#@TmP8r`xS9>J&%h{zX3qw1>u3*k-s?elaf6NlP=}+N z8|c=e79=%V`ayOfU|jBjQoP{r8EanZ05<7ZeO_u#7lGLVtxD}rAx0Le?ud1^9Yt)`g zDaX>2+!^Vw0dMIIi0Uyx8PH#N4v2(07B(|W!w8n+Fsl{pRY*H#8`5^`Qq8)4 z!B(BW5R{fN61Eg$Msg%K(S2<#YJS+0Hm{98KI8#yc+2;yV{w>69GT7BtGLf06cef0 zc)HSq)f*l^5{gc~ki#!TUkPG9k^-a*mLccF6OTZ}-1r#rW4gI5;~(Nxy=#7~neG*QVpwgvJl5V-9B$0@}SX@Q!?c8BCjdq`)rKHIRf z>B)I7Lb45lx1nxb>WRifnO7a32njYrDKH7}q=9OcV zbZ%jC%$oMLgPUHvWq-xe2ub;>)Qg|*xvXA8! zR6R^&bGkV)`GDx~Li^3%)74ha)lAoQ^>{GjV7paZaL|S{6?0$Wv7pJjv!F~swT(~EfS|3wq(|Ex%VVi{QxtAvVLn~u$H?xd6-j(2)4 zSXve7xDyx>At^{|j9#Y))g;t>&<&?T{Hk7Hjy|mz8N%2N&^OI#VHnxIs{1+Th{y3_ zk%P!kZFDcda?%WtX#7;!cw$wnJDLB~8Py_qN>xgrr*FU4#UCo&X{C4t?6k(*yBH%! zNrgs+%YSTWWyL99$Vx2t-W$kSuP$1#kgrSY2OeuWH?--I0&!8S0VB#q;99nB6DU@` zJUEDSu#Q}XI6?dP2~j%mq`r6Wovz1CKU7>j+TCzXOC-4p6WvU*Aq!U>RGmGF3fB-8 zBUesW!;T#J1+VGY@q=63+`QoJZif`gtyj``whc+dHR?L_Us^CQK;qg6It1{r+m*Ai zXVc%de#A*m47z5K##VCa@SuO~3uMfHeI6|5X~C!8z`Bx0D7yL|zRjd8AlhHnD1{Zl zAe=FYr3y!$OLS$+w_~gQud8XLx{!+&KkBliR?Ru(_nL(Igdy%6I|?)Kn@TLH>7T!k z@OA=hSO`h*n~-ZI5(zDol7_*a?w+muKmW%uS4qtNAD>X8HF5 z0fT=FYvMLtyT3*m=bF}>a*5Zj(UWRPHb~?w10U)uvrpX1?>8jK_1VBNd8Ae3;-0sH zM<$95huws@zQ8r-k@aSt18^tlqNUbLlcFifopYl}>RBLX_9+&gV7as5be9^h-(P(|^p{b2 z5W?I+asp=UsB5dCb{)PJ*Z$k{I@3DbfI?2VK*Xhp>)D>#sH}Pw3*q!sy=?FsE>&;R zyZr|WA`C6$t|>$>)(&|SAt=+04lMd`y`~?9Rv9BD0BocB8TRvc@>3Odsh$<_UWW+w zFxOaH*P={_H9+K1h8P5#)E{2_f+v!hIRSU%e6mMp4!Y^cf`$2RX;q77l(Wt`Xy?F97mp=2)>LhPf>vFiBj$wtGgZJ3% z6auF}R%8y=fFL=dOx-%6$N61~z==QxNZ@ldV11gmF2TU(P##)JB0|BB3eorIjw@!S zX1d=pM-Dj&xLypl->5(pKFtcgASr1mWlyq>d)&j1Wbez#vVXXRwg-wc_1Unk3#*^! zXhHG}n)2Hj<#qSpfnxuk{usI2bRU{BduO<}dtXD&w81_;w8*71i2&O2U5nS-1Rl<( z&MjPcZc%kAm)m4EHwcO>Y?U{GeOJ*VIQAYhzdp}_Pn9%c>3{x&JQ3hAN z%drUuNi;e(IFXyQLDc|`HKMHEbW5$~jr4mb=d#{0c@MpD`hKrO;Q1mtzGu`s%EP`h?+)7hBYu^;0t# zQrn<`G!+tx$>y~`E?(EYu>1xycV3emx0d6amAuiUyOx69aj8*74zc&qvBGVQ@~kwi zqy|mf?r~X5f;fWJOe5T2MO)hRN4o>Ef$#q~tUjXJm_Qk{h5TFg>4Hhs0EOJt`VeX; zhdkYD##dQj{ABrs6y?n9L7okFNRu>INo9&74wD_J2{EZ0x9YK#`5}w!WUNV|dH1g| z%TPIBwydNf!3pRl!&`L_qhf4f6~BUz!_ShgsH4#8f|A2gZNcHERXE+KTP} z4|A)9Z*r z7LEy9&}*f0$IYJ|=ML2}p#+tqT0Y{5o+kr+WYsdm*J{83GQ1c+!CU-d8=l$c_IVfP zmOKeyI#_AL9ZR9R8EC)+e?#o&!B)hX%*X=qq`!qD<=fEJVM9uWMxe|Z=IGJe9|g1l z-;O551}FPIK>#!0xPMtG)3^n2_-G#e${?OTP%a#1FY6tbXjHuH?pmSViS|Qi9NlIK z6{{>N&8J`0qX5$80?dz*()eMzf6XcR{?qbzT%?C(2UiWwb4U`3Ji3T4HFH~l9-6fr-X4oy}3Dvd%v^`22)%vnAe#=Y>(D7ZbhqgIP$no!w6l250IfAo5G$$ z$9q{{|MHR(LopBYn7^-ON$rjqV2V`lT1K0zl9R#8ieSXo9~Rw)Eqlh-Wbo#o)1`NP zN=7BwF7>e4>B$twuU^R!_*8S4)_V^oBfY#Tr2r>D*uQ_1{z^?a`w7rtYkvIlK#dXT zV>Rl@5FZyfRM9A;ov;9iU?u*RarQAK9g)09V0iE$Rf*9tE#}Lz8#LSSQY(6h&}ONO z^IWAy2`(pR0Sr&D`z2QEY9=f%8256dk8VlY3vyekhkOKvWS}lKr&9uGc8+W+sue|U zrk|RoABNch~kx;Tje0EoUJ0e`-#9~j)murylVrDH}CbgoO4ghZ*A*E$odouv; zb0sZVip5O=)kmxB|5gtG|E@)-QUM*sOxcpl4pop=zKNs~^C6sPgWC7LUAX)xO9=uD z+|TQ(CjTzu9TtO!4LGP|4>?(m15@*}ggxY3ihnxGmE~SSR1MQ|N)|ctr7~#V5;VAI z|BovixoI1A|DI|~0vQv<-yw{T?hbyZCeB_8Q0oRUTi|QbKK*G+uR@3jU1O5|pXAQI zs^PMZY4FM7J74&ePjq+;tx!$s`%_iuk}xL-VAn5UYV|iFVwp};@mgyahN5= z@QH8;l1hQ0>Z#cibZZe7H>s{t0{PdTGT73pV?tc!Wa-hnM^_Wo6X9VY<}Jx#!5!({ zIBW%xJQ7aoieUaK4`71QwhF(dRdTpmI2Y3xzrGBp^E&(%2=6AkEy*XQCPcRrxUvXL zRO?g^+Nh5kS?eJ|Qn;T-=0(cAiEpxUY0!-!0D)NS9T9Wp{I*PR!+AFpq`9i+0X+Az zcCu1z$?<+-#?Uc$x83Y45NFy#x%fXd0Iz<`uaK?=(HSt&P33t)A$(L*C*icwll_4T zGKG8z5UX;~EKdd7xhJ()Db&az3KR2{0Kk1FS_#fstADUzeUlysD?(RWxx!8La^K>IK4&Nj=c6;#Bqa>2 z>b8V>JJrvp;F89xRx;qGVjeOm(>EhAjOdK38s7o*$SWx%L$#4-?rme@%zxh@hKMjZ zvfMh~u@rY)-HPV_>UQrS5Hd-nsv)aaKq9X#d6SX%FBw(t%a^s%8JE3N?Y)c3_$PI3 zSRP4t9rU>XaV0wHZ+jVRs$U4{A6UCu?@O2TPm?piGejt_(^sab8Yp(!)p|zW7xa#1 zC`vnkZIT}2#QME(`xnV`xoANC&Kgmm z=q5&WRZX+bMY5rL3&9m3msDg$5qZ^b?}6yF7}=r^4QEL(;My$tZg(@ILUJ@9>i1|) z&gyAB9N#R-He{n=(o+s-QIA6I@EkpIsL5nj|-c2?WPE2 zaXsT(atQ%Zh=_nfppG%E9RvO^z6sk7y`_=e41*STNYD@wDqOG5(QBFsZy2)+LSL>& zbM!T;j~&HPaEH zXg`(u1F=fQ&B3MoumO~&92#;DEuXA46U$WHh>x0CxlE2U#v#^N*eqEh#?`6c9IMRf z#x`?+Rqi^UhB=C6i zzHcMu`e72IV+K?X)KC(@G5aRlur+NAa()skiX314<{z^Fly^Y&C{=(F+cWkkCD?qv zUCxPX2q{LjH&_cNFyHRs9(Mc!fbH#QqI|y0HVt0Q60o_hj4#-N9JBkLdg)Z3E_qY% zM;v6)B_0r^D*q?+*a7z*u**1{v+4Ya-4KdF^`|-!IV<)3(Z@yIgQg8M!1O9pZ=F22dAh))?0aTS%1Gsx8nN zJ!XiSPxu2R)^$ru!wVm@E^+qwqyn7g;w}Mg@lgRGKrfyzqDC4wSyZBV25?D5#sMvq zl_J?HuIWZiN*D1Z_cKJRd+%0jaA+<>#Gs$NJdyH!xl_$37WUGD`by_@!iB@hYVQ-z zLlB7CS~1UCDBAcKI@Ppn%@$k)Iiz0y(4eUQ=P0tO<1OO`QFf2H6gPS`c3^Xl z0BByv;PxsC)lzN=rB>|TDwtcGQ`>5A$u9U^(9K}u}?JG%h}WA7AgY9L@yy`w#mQawYx2WNQAh&Gy9F;z*IR$AA~2&2|kX3&SSjt zXNYTD|6rkL^fycRlx^XY8v=vkgE;PXbb%gd5Cf4g;4q@R zncAQZxlidUA-BrJhU~;stg0OBfo44y87>}VRxVN*7Z-9k3wlRVD7M^x1FHF#pW4&N z-%0^EeA228|7D7?!@129GmQjAP0n8qI7ZNghb!4B}oCK)p)iqf>a0tb=ni3d6Qp2$xnN4MOrQ0=XSEEcpzoz-G= ztryVIst@>7rG>8{-mHC%28b{I@PA z0`DR));yl+jYhpu4YO#4#4i9osB>OM2W^S{4%B&SnNz{G3hLT0{7o72&hX7@9t9-v zu<^dNpro3Y3D29&?}DDGB;Fp#i}S`G+Ec|154zcITpw`@(eo|Lu(?*T7l8m$!nsUe zW9q=!(dy$3RtR_1e4O>=>rpfoudQAQYb}rWcgt?~1UU;{wH+8!8FejXx90I3xypF2Q_4=`$Wmq1$UW9gLy z?l2J^ETz{C#UW1_RX1J&X9vWnm1Zp`md08z%!VLM6fJi5?+?n%KZd!UmapRK9_G`u z0Sn3aKT}e(n%ws4e3~%8VmHnN^5-tAoA^d;ytPAavwf@5n>W9ptk3lik2ygFWyHHjiSL5(`^lSVeJs}Vw_Xt$*gcDe z2Ld+uYk50%I*J827omwJj)$U~8)V^8)vqWJ3sZ_S)WK+XdTFN$$T%8Ns46Fx`WJbx zq|}%vlR&|=zuN}F)eZeXh~$BGe0{}7fx2mPCpffG3 z9pELZv01kd290LHboP4Ki#;j@e@l~Fs$W<(^kv3oLZRc>CaQ1&7DI6NiE3cvnU zQ1^{R%Xp8e>GQ71q*h0)7WjoG@nMy=)a%t=sC1d6FeWjIHKIQQQlAhS&;V!n__BwC znYIt>3fTP%(vQ0QDCjwal zSH@%JWGE)CrA2#wadI}_bjJ=~Prvz4c>~NiU(su494Vc2e z-_-UeCAYBbWh%FzJ~9>YHT13gjUQes`r{jps>jw{L@$X~<11px*|A;tM~Okneep@n z$q!L@%7=JMz;BE|)}&TXuFY=&v!ZD@qTrYf2KI1G`miO#$w1G33DUjhTz{y&xKIPtC1LgyFCI{ON`YpiYXqO1E(km#XO3@ zv84lsP|w@tolok4dB1f-qAPU1NtKK&s~n%ghrEF#Q%NBdfJ`?vrDo|8wtnxw#kx-0 zkKWkoSa#N48r+-;&;nE1T3SmgY>*RBohs*sM3`*^5Pr^s#5rK!WXU|;X=nqJ8N{;v zkR+!AkcS;c2DS%8F9oQQKOa6acFIOumL<+YN8?s2-k|2bGJhN``2daat=#h_=}hT= zMZ6+(f;IrU9m?1-=j27o3YmnC4I3q*PUcVzT83!pE6f&!J6o*Y2ykz#ULYO~o%fDf zEYk@gf0pPq)XQM2Z6En1hE3gW6APgmCheYn3sdIaQoopTKRN^#k{%%36LaHJc6%Qb z@|2KoU0%DPTmOPQf081g08T>Qpe z;sv_J&SlBhl{oYQxy|{1i9|G4_olPaBVWsaID3RikAh2mnr0*@h_W;6^kKuyQR+_; zgEV~?>t2K`VD0~hZ?$YDOewFt*x`&zBy}_&4|=7jl1rdUW;^xm&_)a%3mrrB6#e$_Xohps4q+&=XpfhskO=PcD54r=pnOUgs?y1G4D$vj zR83n)Eui%3u2?dxsr)9%rKqmp*+CHJ-agPFyi?b_e=32K zT5EM`Z{^&ebvf0u<*uTF2Q(f;G45fsYh<^3nCQ9tTJN=v$~GsE_+dmXeNmOyMrO6F zk0AAlN>4w48UKd2-@I=aTRU9YN`uegM1e=J1;!V+LYI+6EOILHGgy#ZnHSqSzv-0& zc{9X?%ZA4}On{|b0h=mV8H|drQn8W2lZ)U!h**61Wi?DN%f3nKqSP&eSKM-u@hL>M?cs&}^{t zIq!gDkE}k%2iKA+54RPCjFNx;gP3ye9ycgEu)xX#*4;MiKT^Vl*)9!&dMl4FXN(Sw zu!)u?3hAx$>Y;>C_JFt{`FOwvK;<8P&k66fUXl_n5sctYk5d=SXf1k=fcbM=;lHk( z1fqJ-@+zr0GYFjxxb8@I_2Y3CR{>5fW=y>}t;kBV9GCfn_&99h3O!uwg*2&Zn*z^F z-s(mR)H}p8&^NV@Km?F7HeR$z0xbziDU+f7Na$qurN~)jI{AtL4A$EsNM!W3Jz9DI z@{1`Dm;7I3UPM_7kpcI)ik5cIGJ%DK&jQ7CwAhjTg3gIj%_dOsDBlHjqU_WN;@2*} z+l~5#{Ny1bnt0P{&W1-D&1pZb;Y&i?Bjn<+O*7eH*r-iA8niu)g+!9GK9it=U^li` zA~v^6Nz1V>oX01b>{9E~pCvSRy(pWF51Kt@p7|(=(jGYjlO$)mv<_ zPEb{quqfx30L5k;78GcoGJ5;Bk7zHUn}^Uu*-#`~Z*dg{@v$O^m-f zfv{^Xhv;pxYg+$V_Bj$`D0hdBYWtQxW`wXT89!5p@3OljaJmXW6-(!Yp32q%;SXgN zRnya#z1nYx0o>x64vaXUyzkVycE(7`xziOpDcBncHtYN9y=hHB2kv#%mn5c7kCDoV z3d{RHV2poD7`RSq24-yCXIdxDnV$`yBjqwtR^Mw*HiXSTo6BeVG$NqTo9c_@=K-N8 z3VuJ~g$E{lsmREHw5sv}irG_!aYl znIl({C4D?!?qcg8Z*8U8l|6H(bU=nsF}O7Mc_HZ$6Ldh&5tTDfSR{Ls5MPdV;NyJA zw8oHjNxWKyJu2x;heZYFt8^4b_|~1=LdS?=|MbRf;E`N6*LNgZ7x?5OMCH`@$xMGE zfkz9%=p38&fD4Vg*2RWgq}>U-N#v69kt$~IT+{+pa#8*80d5o zc0NC*H^OT^P7Ttsh3+OmB;5ceMXgdB$qouba-xqz@7fa)KSDHM?`e5);-DMBhu`Gj zx5)CiujykC3IxC<@qzoA@>2`y+{x_Z*9F`}&9dY)X`>o9^yQ=Q`PzL`X1)ixp{%`f z{l>R~P9T4ujIJs}w!J~sJjq}Z0jx38RfkbuuseFeIZ;gD_YT2STCMK$>i%*#gdM z_!d0ssrg*YAi%@7jRg>0SWXZW8|c2UBDMl9?nuxtRF}5R%ALR&!3)(w|K+Y(ATVfs z_}3D`VO4Po5BfhkyJM?J$I2wx4TVipFnaq~d+jcml436E>nAMeQC!tg@Euh+O_H}e zC>iu1v;%xRV7KYy({j0O^Q*O;G^n~XpkSy7N*O0^U7iGKUVJXrr*2PEwk8DN5L=?Z zhAw;|WtfegIyJ`V!RPM;uA8aZjAt!18SWfO&vH%#*`@QzhsYKRuY$$IFY;ruZY1a8 zi9Cd5Oo{2gKwnyoFKppK5q8!6IKE4};9MM=BtPh|kxDu^C33~ooawT9LyIzV%%Bo}8wC!aNeg6HKxiH|#{C{md<( zbFfq8*o>q!l?dWDmTsNC8pz@#${0Bf>#|rarJxstpJI?})0wvkW;Feak>E@vNCmGP zfg+RxBkEF$a~KZG+X&_nY)5~`0Qd9;tQV1TttDnVo^*e~hGbis)5$l|D-gu4nh^0_ zTb&7^{equHcB>&(nC{iJF*-)Fv8BA;^!i|O)1pkRC3$Ym=L0e>rJn0p0?na)0-_<* z6&h&F51PsDQ*JJ|_u*$ppUWRKChqJyx>^R7^fysTiz_}zvAGt8#yWV?o6Cqb&>n&z zJin{+#U-Z)p9OjI)oqWV#@6LEln(tD#hqb0wXr%NSpDO(=4#5DW>Z|zp;=smgr_E!L( ze4X5(GWDI4ck`m_!yj-$b4jo;qfSW8_TN@2x*SkH->v`T?hf6a)vG5JS&dBTW67yN zPWH=M^eSbIJB4UiH&5xuU4-z{mhBIGRB!|Ptu-(J-PpS6B`5*Q<>l+44aUl$8ZHE2 z4Inz`^ZwUpH6>DYiu*RK6V{@J!D0dteaMR_VY6l>0MT}L97K{t%zIp2OH*E_L+|K5Y_!)SWHFH2ZWZpKzE2&dB z;50B>Hb|L}O!qO#R3&Tp{@%B*V3j;@KpWrdQK%We3F*Je#3y!9d4EpPgWP_gm< z4#Fgi{*#j)g@=dba?o}9cQx_4;jL02n9aJYH$GWu+TCuV>_dn4Q2{cgNY~7?U1PY$ zkRDVzYI{^>G$a|ongtx_`Sx?`OGWfgmgMkHc(9{%{`*0t6kd~fTIIfFHsC<5Whm7o74S?!&eYF z#>(!G@UtClAu0LZJ$+2CmQ3NUpmu3$=TgEsH?$071!7hWZ2M+h(@Y@7di!eikm`bP zVvJg4iJ(JMr2;t|9jl2|sLbZeIg`AzPHQ;IBDLUDM(|K_OA! zy*9w(!{gucu25iK}Pn&x=QG<{d96*vSNtx;5gjFgSq904?A>ecG6P6xM+2}ZfeC* z@C27V^ri=D+fn?%;=K)_0GY=LR`I*gEP!<#ZNc0mW9y6vCzgr|NW_O5{EMJan& zv?obAQ^P1HLk7t^g@9Jnk#)3DMwCUGVhN-rOLbs`1OXA})J4f_GN*1Z=udOf1fowR=hv&r&}|7GRb0aw_f_i@{6 z!;Ml6iOcC3<0>G9Zx;R?Ici~z5SdOH$D-Arv@%L9gF>_gZ;7mZr=Bfl$UfI+<)6U=l)^PcHUyzm?9#4wWAABA zT#Jo1ueQtU6Yz}BfBVyHy>cU9&oI~5#ubvg$t#f>5H>VFn|gJ&dhZ+w`$wd7WzK3CPO75~j`bm(85q2jPQBVrw}Z(gSbB-l*T z%-F9T27ni@7D0(0K`-#zt3`u)?uZu1HAk)I6xr<}VN_7!OR#?*^s#wBwbn}jXi9rW zjna=wO3)YB7QNx-w<6R9@jBnWDiYAKXRGt;(vUcQ9()?iDJstPHzzB2f+^oYQpe&r zB$ZaE5qPnU$eziY6K<=}Q}H8J zuJ{FHl+=NBMy_*ue`H@~u0X*o0o*v`0=JV1#^i#`L>wi*?&cY=iVO#oDj}qftvnd@ zPkbs8+CYu3c1^#_^{4s+b?Z-+1d$k3<_w2JIPNS;vzJk6R*Wwsq>JeP|>QJibAp(XbCIQLif{Z641s95$sRf{L}}KRJmt zuv(UHNncoz(yBV^%1fI;)%pY077#@V&*x{He5hC`acvE+LbrSI(QLd8lw+tp=N4yT z;4q{S`KLt!Qgq3X`<#Dq_Z*=aFh=~ohwV~*&ZgBc9Vf%tqrxo|Y?K(P?rW>0q_}Q7 z$a;aO=j#hwpud|m4nyt)>pX7>5Q&y8$!F6aD<8%?j6gVxw9!8d)JnFhGGu|EGI5x` zrxFimXW59_D1A$Ae{c3J!npv|ihJJjFHUm9U;r-qX81OnVW9AYJ3P+pHqZ^WykN%z zU6We_7a>h!{2h+8!hn0VG(>^@cREP@#ykC(h;%zP9C&;gz%xQHS&6B*;RVcxKhr+p zdLOl|!%+MpKk^A@?T4gRI=dnJu*Ue-rz!CCooe_lvVvuY=KL${YXIg2D26?F)jN3O z^D3%H`mSfCk80?tND47ZfS}|7#FSi70A-_E)?4uEwA*}8@x6RZ<;^n9;-wvzd0WBx zD@&eV1UdGd5fd1xqXR^+q2c1P?_J|X3~xd9=qLdvJdU>V7D@_0a9^UI7?f6!)!wA? zpjuoqj18BSo4QsE-Q5Hlt-nhUoFZ|*q>$bbmYVpR9rILgad3L#6EfC7HLf_GzrS4n z{77k&?>cRF4E8PEYAZIPP64gFwBs1#KX2kF_v*Mp> z*hi(I(CdX9)>_@+@d2fAj^#-52$QB7S*CTM=zL`))1Un^Jc|4!DcQi9!E@##J@eC- z>-0yZwoQ)mO4n|#6|zC*LV*=u<;nchgV^Yg<9-h^Llnh!9wQe_>PVm!oIYJju5xb! z__i@+DIioIA*O#6GTR&C$y&!9<*ww;Vt|(|*rKVT3b{_Fm?kI6#1!uD@cP?xjPFc_ zh6#Q(HP~&ILPAABK=6$xb_Os7@G)udNcjZdb&pJuDt??1)2I!eh0ucXVW}A^^p2vR zEOKpv1D-}A!8~FPxFPO~P$$ua9pNk4t}sj2Q(%0nY7)Y)Vdd-+VWNWQOgkB)#n-_d zu9T#mhPh4v@fqdP;n&+(xP`QBbV+jGG4Lg%tE2lAeQXL)eA2;VcFx{MbUZjUqm0|g z_AG}T`2~!A-yxB;I1r`$SlFdhpF}JEf(qf3 zTq-2d^2BMQ{*3n%2Cq+MI7_x&rBGzQ)URTkE1g}J4b-dDVqCvFQ+#l57TW3JKdES& zT!xq>$L`Sd(8C+^UE$f`zoGJI2)1vPIeB3pPoF95m53mBPELztcS|$VyzVgePcBT_ zz36c>GFQI?bECdnGdVM$H}ML2`fybDp|O4bb;0F-g`rLUU|{ml{$(#)6gh1D#z0N- zUT3?|9P}S73Jl&=Rkk&wGlIFN!{nQ_4{t|_L&W%~9Go}Y^C%b@3|H9o1&W-o0;|^= z=^2AJ9h`Md$IJ5mel!jt2KZBVRj)e1a;3!*gL7#2Bb9nO!uL1fs)ZRdZb(2NDP6bUY9Q@NC;WPQ`m7_TL{jGFkZ`=B4+YIh zoGa+YV=i99_4A=U3`U$FonWs)&+K!wcwO`9kF9~0tTIhgkP^x{qlbu}(~}LvDDLFo zHWK6{s4W%~b62C1mLFpLX)JwBRIOFeRQ!n}se%hi3L&g2ZjmH%6(vVDUZO*w%Hvff z?|_v07QO|i)zZdD4(M+Fs$A3-djxjCF>N!#l8>D8-xql>j0oICO$ot2dC%)>FzD6!RVuGRJd1BC4-I&?BdNNB-Pjn{x zw{%YFq%;=8vmxUj`ukf*2KzO@MU(CDtlS-*=F-IYTG^Usc)n``8r`b?TNY4vQ~Vj` zklam-pxez0y!C7tA3J^FK+5QQ0A3CocS!QnW=DMafa?r|&Xh>VyTKTTiI_|uzAyqu z17VxA2Nz?RcZt*0>Q9x2{q$gr^$=1M_0;cY{1jq?HMB@8kTUz z3-6N?i;CWcM4x_lrQWY3aJjU8OiqeM}&iD&68U z2!3F=af=(!Y5??s&twE;LBqv2@-+p zq&%@`9X_N&`x%|Cm!En^3bPiYU?66jg_I3x!jQ)wmCo1aS1bo7q?in@nEYU>_`9X! zkO4$d>nJoBxUMgcN|qK{4>pahf@&fua1C={ja|R6%?9R-_HQ)!j#xGGz{}u0sl} zYJJ8Hk5ghKp9-@z6dQsX^*I^H<;Q(n!7LdT!Zix0BzZ_Hyx`hbwR0B@!QY}kQ2d`m zPkm(a@Vza!_2}tOJAy7mlOT)3P{RYd{wA9GB&A$Ey*EcYk7=u);;PIRJ#pt zXEZ`m>6wXnAGdQ+qWeU{A#sRLx>HAP2sI1mzo5E8W4~qT%qSvEPiFF95 z!=B~r`S@+yL>HY4?CFFgw%J4kg`9ny0u~56Q*?iRc?*5(x72}|=CjHIx-*n4;JZ-; z<7P>(k@m!yQEM!^m8t#v*4&z;#I^f4&gQ+>%{GVuWJwWvXed{JnLtIz z6}!WK!Nn1d=eHGV$de3e?67&IVE{98Uv|arsAZ0Qte$i*qBFHUnABCK-3F~M7PPDy zVq>VUit183$&Hf7FYU{3A~|4{Stu3#|CLiZ0t7`)gY`kZpQM@Jq-iL;{Y`6PcuPfu zaHS%#y+QYj)7R<(bV|9Z1CXX#+4rOn48C(w5X#y#_q8&D$w@Na2sSbB1-2F_m~M|p+8yScD=sr z`L)#5NN*JnvIEd91OvO%25VVUcb&b@;U7Gl8hr~}n}g?sxHah| zPvn;1Wh>p|9c!KGC&~nSD6=e1`1;#y#oP2WuI>E-0>%&@1ifHjBt=+I{mEpb*>#ir zAyL94B;8LY0}WVQfs_Z48v*u@_O)uAYorK82ogpewEM1tSIlQ3cU+cI%zQ*QAFPI7 z$QWl-hLcTb%-N;J5{-#Y3!0vGyrr))GOTH}eo?vcPVn}Efu6biuOLk)ED*0zsiPX_ zc*rlk`HjE75N7EM9&fX(m9(t^TEJ@fcm%yw6TX7?sLz%(&g+4v%vi!EbjQ3(5s=*( z@j;*lDxXB+nc^qEL*Gd{J;)84vY9UyXn1czTSf!lvl+XVxMV8na87mZagc39ux*U@ z@k54Mh&1cw9eG=2vc4m-mSRrpHV=*k`!`p8{)1EJ*dPAaRyf27o=*`3bZ|^G^hh<5$<_hHA6Vrc0`C4l)O=iK!iSUR%wGasn~-EpPlZkf zSEME0#+}%2EBV)t zI?Eshb_RPMAqOT}R9GA_IK@#H%4}0x=lhS)Xd(C+g_>epz4j1Iid9gZ=6j|u)COt}w@16+>3@ne9`u@* z5UCTu4#1P$t@)A~btzi6UuHGVfsS~Yf%2<{Zu#@4qJV8fW2?>ufmzcA6qpY=p(kS{ zu9XNyj)m;oz;b!pv>Y%`N~?2yThb zhArFqjBQ7G@K%K+XGY?$nb{u1(WeOM$DblkH)nZob?J8(iE!!5#LnoQ^SPX|=+$3lQQikrE{VkReYeVY6gsAm$d+fpFB> zmSCYM9DAWlV8j2~!Gh&CA@Z+nf5H0sx=>SV{^SX`)h^GLl+y^OdmfrU1DyH}s(m~& zlwB6GJq%pWke+uD;tlW6`3;S^JKL$* z2m~zLp{6+9;1|-=S?dU(*Mc`#G)WSXTNwUU9srV`Ju0?ZCTTpV#MzdJ>XxzQ560Sw zC+Y$*E{{>s_$kkRGtMvzG)_iG^p6#HmDsx7Z&a{r5zGS@pWxAoc2?}EuMd>8zXH$q+vU~>)9Ab`{Saq!+?d*rwr}9tCwK5_#aX-8fRIm@G zfM2vM32_^jlF(ZkTgt#$LQM-A*F~9%yC~3WohamMz>qaJ8e8~TVLx3!(xpPlk;}$m z-Zy>TysC9VcbOm4N`Dc!+)TTn1LZ`x485VwMl@@_U-N1vhkkjzl zHm3C+YlhOoZd9$3%$?CSK+a#vp2dy}lxMk<_JVR!oP?@xrJ`g_lhyS-gcn!Mr;y6pTWuT{3l+gKjnMW~jm zh@Z#3+=(LpJrN_l251aeTpZ$_CQ64S1N96_5KcT^K+koPT3v~DaFQr|1Xx-MSE8%w zjYgq9JjDB-qclt=Ynz$2!cvWyU}57O$1-kAptEC^K2OGmvy_%nN(FI3$%dN z70S!`Gxh9@2KPb^Le+Yz%S;1tJzx~%hxIC40qiOU#$faW{$Slw`e^`2*~-KT*zKUY zPZVOih5B>#>MsCiWu6J@Q}VqXm_qz@mDPp|Az=okQ+O6aRnCE=JNO#GNb={X`~~w_F7kp zvi^{Q{}bIO_IuO@ZwFoDs(kE$tJ%fjJ5DrY7Iflrnx}@cLgh>RT07NNm%&+ZGYUh! zXH>(cec2_pRmG_ob1pc(dOd+@=g7w5c!OU8!#3U5B3-u31$^EFQ7l(EgJU0SR6im_MfM zq>&pMdGpgu{*2e1QqSDog8wT;c8VL7sV3|sw5#v4zFNxy38-+~SdODk%4ll8q9bzG zuN7QvFLi~zGf#-G6BV9R)T34B<&ZJT%=6n+c6ft;5>vdoSyD-lF;SQ;0 zFd2kH(A3e3a@2n>LsvY;#CG>Nt&JoF4lM~WBFsPXyd)exwV1gZFWL&uP14Lasgn7C zYBdgVlvJ2i?>L21X&*>2c6Xf*(0jaEWOiKazFME|5+?wWnI9Gl%gK0? zqr?j(p{I_is8Brn?97x2Ht{^RmioPssf;TY%Qe0!9%11vOPDFF6UgE83<>RD8J5xy z^fg4+)O>%#*F3P2Z4X3Ng(tapR2T$exn(iU{1zUTv(CWA@?5R^c3I~m}L+S}lOj(yM+eDDTX=v0$vS!Rhk zy<|huy@xaSy0j(w{PKnm_x?I+$8L%>&yc9`i%!8IMCA6B?v8G5CyAEOuZ*0epG38M zj3Gvu2Ak@lQjc^oFC^hq#STnM`uL-=i@MV%d*$nm8yTHUCxEQRWQ>cYitGDHCPFtE zGWPueC?EhNl1Uv2!2}Q>fI$G`fC2N6d;ol@*g*&p8giBRN0{5&l!m$QX%Ol;=UGS= zi{56ZT>a6`1TA%))uPWe0Il#(#O-Yf#boQ?6DIU*JE|=4r2CU-vY~wnnaizSyYYl; zg|fc0qRp0Ea_}_|3XCV*qVldpi7d)uyjPO4AE@EP2bu2ut-1_rB(;Dvx6% z&aXkqqU{7a_{5D9!A{$3Xe$`(0u@>&UGpN3->)8`6UXV1e{&d5Z7`dk3OPs{r~$C{ z(L&)#RNY}^(4utROm8~XeIn$j(%50Y*py8fpNNwGFp6Z{jXXjDeh$~VjLG4<`(IWz z9QTHlboU(NTbu~X6%dpcZebiiF-kgnE>lqs!{Ubu&;5-%&yervF~(0+5viK_rH*V0 zBCB(#gP=;`%8{X)ugU6XpHXSuz)2 z3`Ka3UK{gG)XOusLh3YHLhkn!X)_5=_7oElcGxo%Hzp+O`22nmWJwsejBR?bSLm%C zyPKb0Rhoh}kD5qeXr&YRtGb$qOxFqI`CIX2p2%V%#oAH^FU*d0TFL_Y*`syus~E@v z$F$V-Vg#M63=ny$XmW^yn^_E&C_uW&@Cph1`62yS*lCUm^Tvsg@c`AlZGADM2%cb7 zOSZ|6fB)RN-9tuhr5&Xf6%iHetJgy^kgN93AV)Spvfrn_44=HtOxzso==5|eR%B-a zA&^Yp6!E@B7bFgx1CIp)mcCKuGn@>Yk>(z5n=GIGO0ldp&bYOb^ri83oz4P199TRR z>FErqz(I~fpzM}ETnv2&QohXZ*dCDEQ;zV-b_-utr}#Jug$tO>vMJn6p@|dVXn=|L zDUiq4V@K~r|GOqx&CDR%L8X@CiG(q4(j{wDK_xj}ggSiE0mvXio~{3(coc_!xR`dk zXa6LgZnGEvC!sfJ!CXLDWe5WPWfa|(V+)zC?0ZeaC#0I2mv?5c|++3OXi zs8i8D3+qxFZlrZWzdLdr=k3(^$k^dw4*B#t)l39*C`byQOIp8_mHD=OFk62E zqkGAW$_k><`RgSz(|2`0)`wn-6WpsdsHz4~3LEWPB$j&=vX(&$^`akJMfMdS*Kn{9 z?e~Cp<6qJ%^FY+(by23mCI~GTH8dyqe$QCAG|)b~>WnjXF=K$|3r|R7?vz}AVgH5D z@RD6E2NeduJc_^y∨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 z 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 z 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

^+bcY&4Ihj7KN|%stNb~W7K^m_W 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

EGjcK%)7dwWmNP#bp! z^37a5)U#?q%mSbXQV@yFA3PoHOi1QY5_7MH0Kk&*aVUlWGXPIT=9C^iw2w|B>R*0000DDPqc^=efYF zO%kgR1-gt9qYE$}PnWxdI-JSsdZ0#M-(L1%mbNLTjd50Op8b4K>vUNHBw!LP?boSn z1dSO&`YD_z_s>GS)hkQ^L!zF~cmkHerxw^Pbc`_5h=IgqalSB0#9rdxIeJfSR{bKH z!PhG{6;Pd(vZ0buvwQb(`#eg+zENevUouoYup#`o8r0tXV%L91l$1QIJ39`=D&>RU z4dln)z}@83LH#kHseEkjZ9Row4N#0+wTvj~H(2?@)|6+Zbg1dQ0qNgzsTKdrPIRN3D$*MP?0m@XOP@Yz?9Tc$rLaQ5UWM4rwq7+|&P^#&Ny&)StLTj~C`q4xY352?Z@>Vxb=iP{Gp^ z*%H-VfpxEzp%W<~C9A8j9m%7wod_Sd$Q-Zdj5`97KQCEZTe<07?nz+WRlV*fzTrO6Bbj|uIX}Nq&kbFgyKmh z<@I4qvSt%Zq>B_?Du9ud7{%MM9Im@`yOmZP{Vch0r7)!RAvIso9|%ID|9-&Dv+jP> z9XR0Uz^LzJHHYT(cflO1N&ptG6asn^DCII6%1V3-#_Q2YSwEr>iH8}#h}ZARHYXMc z7g7g*^%~UU7I3QZJ*>v0$0>MH@}Amfr|nHNKs5a!ved`eez^LsCu5X#r1iXBP^J1l1!%#cTzZ-i;EVT2{w?SXuvQV&;_x ziH}gxeGiq!X78asa9yNs1T}u|dmCTA!t9OG?+x&ma5J3REtV;0^5WA!Eb^^qR`)Dr zm<>^&tPjCmdm89sHSfqxo7YiPJ zpx1Y|eKe59J5eI4+a;>-&4iD985UDM>j?2zL-s`_>ouh@XVtQw;&?EKb**($gThB}dk z#ONaiK&Bb4nM8HU7itBvrlKQy25!NP0#TY9WqWbt?opE$xKyqzGO*8{pk^E`$?Z^Bo>0DXow_w8L z`=~z6;^|$9Xt4~rUY1S(uH#9D4P*<);C{IlZW^$3ldFl8O=@nev{$#N9pynZKXw9Y zwzVBlnh4_orJWL0HS`wgOQ$7I&y#~05!d#AY`T>5>Uy+zh!hMKQS!k&Clj$6Ql0$V ze_`o_(JGlx;h2x=1-iHqz)d+x!yS=p2i-BFseS6nfro}os2F|Ia$ljZPAuHV7yUs; zW0>nME*RM-iz_qA&cMd7!jd;Yt_oH39_ruTOoKO;|15N(HE&k^w=gJ8oo8YQchA&s z?Z?3)7ykaDhPdu*vmileCM2u!|5juo8KqcC<{3c`_9UQ6gT8_T5~3ab9||VMW{l=( z-yAO;6@=|3XoK2yW1^fOPDKw)tusPiil#Szr&l%ALoS&$`vI*_%W&9Qm)!A0t%o!& z)AmM_SLeTGS?6H2`=Mbn9`Jnq5eLb$9lbpMtAjYDu_4?qej}LqfCsE0;=5MzOIw5! z)h94dPfh|lBkH%0`@*%Zv9?@dki}{7K`VM5m^?%pcMSd3F`e(xNt!iJ-rx^@G$rV= zu04*p*8+_mt}b%SAKB!D?SC-x>xrC@3@L;Gw4~>i*eFAIC|ygbW)VUwT_?K16kp=x z*m0e$*FHI9cL{d!)XJkG5+vFe7o}Uyd$cdCBjK z?`5s^@u*SpjJ;f9Q@)4s^s?L>5ed`Y#l)M$qy#G!uy7`v$<6WWoS(2NlS!;Y9-W}sS=o4~bIn{iYN4o0bQ=!a8er0aVH?fPPAjPH zSo=yP&GS|b951g-J$E}F)QQAG&fTrW-JIK`ebXVd6Qr-q7w<^F?f}A^aoFMTE3T(j z-AcdL!BrypVklEBDXdnP`xXL~;6T*`CZ-eV?EM$VLLGLG-thi~lI4qBeH}T+gwYM4 zy+(z+5Peb`cRRghPp%5}&*U7I9GA%{2@I9rEyfM-Z$4#n$=A}QzA3fWXpV{A+VVU5 z>FPF-ji-*+Ma`t^VhWICd6!~C*Rf%J^uWG;R&G9A9OL%oX}-^2{E)zU#;cGK8UQ21 zhBzEQG{P7Jz{38pyopk$P~6Cem+V8p*Y?i9$w#G&=Tmy+NG(zSE3hA?ZsF-UDuUcz zBGnkk^dr1jm}VhkyGDWXED$Jou3KIL!GXVFD#a&y^C^V-@d&O4}CE)7mw7=!P;c>PPS z{&Uf;_BvEmLb#E}blH6w(*W}1XeasWH`LcIT{;(LHUiiGFM2dO9hYWbrAQ!z`Qi1@ z5y1;0S)3b~Lk78j7btnS83nE!wz0#*+a3s}11X2Cx|HUcBQ&>)7bVbu?^2PD;s>#h zO#(Y9QUKVhZHoBpU&_wpM4iIJZ_PoreLVm*E&n(M7+6O>ok9UXoWv`043Ad{%u0AL z>no`R^e#RF8UYO#p}x(c`u8SSqa)S zt}y`R<6j9~!;ukMqN@-DBfCVuBR~HM7?thlytSoOReQ*xxq#9OfUg4f@0HP>n^Y1O z*@p#?=~D0us>lQ(#P)ajC6TdzfZ=Kc*e@Cp1Lan7@;+cx>T|g}8M}hPQ`G+#<~?h& zoNH`B5>1r61QdyEmA0x~iL3tAyi%K;j2x$I7oC8ly6$g480i!FhRj4p&Hw==B5@Zr zoS<*6Ab+0!z_qJtbU3*ISPL$V<)7ZdwP`-#e4ohQLp$7T{PKk3QPpOE~Gffn@MmB(a+P2%RSrB z5ev0BE?{{g0urb`$x_pJ+QR;ya33M?QQ!tFm{gq- z@MF!t*N@h-(R%98l!LydG{{SG`{Hn=ktGZb2Xq_^K=@TVPHnljj)qUsNK?HnsD6Ao z?usuMTDnR?IQ;aem!}64PG&$mK9c(Q0p6@;+caR+d#EQ%DyBhKbUX(S> zU501Xj6p#m_brL*pqHQNZVH2{=Skp*S!h%}=m~whPk;ab4wYhu(pyM6(Hv?X_yoT- zm#*uG2uxbPzt)&9f9d*MZ$TC|B}ule0qs-EOLPMm8@CoB!;`Wii+W2j6i|D5SaJbf z8tag7*SicHN&5(EwPCa$b6a>|VigL7h2g)TThqp|SZ~O;{gFT)ZaILrRtDvb@|(fA z7dmY$47vhr{p#^k5&6*|Al8KRhG`rCK!;dOIw~9W5@QQwaDa!yB;L(`qjxE62K{mwfeJC#>Ojrc>0|gW{*XYvN!t%EoTumcqz00=Y2P10 z0NM{tN)GTTwH2G#GJpZHIRiZtl|k&IJv0^|rQV5=sl;+zm=5AA(y5bLahW&Io6qAZ zu}`yytSO2NcgxlEib;Hpzv+cWL^8$Tz1#%qPMky9*g%uY`7eP_W0V zj&$`bhMU~R5m6didJmFcIO+6SOusm}Tki)eqHU^=YV3)nxT5$(VeqDFuL~rWVO0yA zruZb{NmVKpua|aQ?mJ9iZ=#bNF=pFkp~4XsdnDJ|c?&8%7rROAoV~N{`b9vKa+`>Bx?a=6Zv8<_xuW5`I86#ggGP2d+rN*I(O1)JwuAK5r}M~sx!P>7;w6v0)P12~12 zRP^bVNIrrUq%Gy@+r^J9_Adk#7)Nt!{YP3&4+yR`D}YEz`WEEMFA9boAUkV3 zVI+X4l-$U>d6zB@{E(ctcvi+iFrIb)C-glW4R~L?K`;w;fDiGT%=LpDTJMwmXhjYF zj(Xv5gkcH~LxNwUU;N2#nRJq=LkeIQNxd;ym0w<$mlHo)8_q!CxwNfFN{ewtu%F1u zf{W3P9<+B}K#TJAVHmTmEhOn*7B?}CDM^9dcI}szr*tHnuCq;hS##5>rK)$YErAM# z>A*OSUw0BrNMx&5Az>Au$A}X@VBeJ>JqUTGy{abZ&Y~>X-iL_&Ctn^bL--d2&a_dg zgjtDVU=r&Pi!4~gp1{%H@DgAc(_=c67Q7@=ooOk$a3yAPew%4i^QSAFxg)ip=}Saf z1xKo*hSm2GwF&vdw6u)06dY1+`6m8EpVm1~gJq}#9rh%av`Wy+oX^XUO2DaJw_{zw z|L4|LMJA43NvppIyC6cN;Q;(QV0uWYiBf0^Nut~umQ$>r?+u@2WEi5S1qdMd`dyuZ zweHK*?s2IIXr0i3m2#282an=euaQp+0503Xqv%!E2+q5+iq>9SQs0~2ajp=jw9Hm3 zTetq7=Aw9GG$o<$ZW8VOr*=;_kd*MCWaRsu{%E;}H!xqFyHi>*8$GF&3B5^=xS{^f z?Sk3>U0XA^Fv>MM0i5EY)D6NpGR@t#KHjn!O2|F+nYz`Zt4J$m$>6{`g3IPyYVF_7<9V_8)%;u;qWC3{eG|4LtJ2VN z1Nulw8M;EPDnyLOK4X`LB5h|Onbx9l-SI2JZ4pArth?)!666nn#Ed{HokOj-$(K>E zJJn-BccC}^u0i`DLuJ=}%$A0$V zBu(|)ig}`43o;5AXz4JBL<7QT|BfHJ%tSU+{#7WFq=h-0EN@GoC;_Rqe+vDGza+QK zW}|e&yN5%{ay`HGjyURrOGap9eB;gpF=g;I|3cU$v16Wo=zt%FboC$8x{HD+?Lklr ze?~kq-&0)iquZ&c*($y^85~CHb|AnuHx*ms-HSUZ*XeS|*BN|rq5)gDOFjn$iWRQ5 z%+MY)|NY=8Zc10Rwcn9^VPAf`@@+eDJ zu&PzXj2eT7$5Qd+v2;H|`enbukcP^59l^eDel9@5C1OInR+4t{8MvX}e3EYa+H+XjrMIxjpz5IEP#n`GN5q9j|$38Xf z$U2pWqlww}$(IT2|_0{WPb(z)zzfPNU+4Z=yeUgPHYx1Fr3< z`$ImBj(q!xPUlKi;10o`KMq}&ssx*Ay<==2u14D~5XbcsMO`?7UC1e`{mpCUq7nAJ z^#kgCuIo*OT-KA=qC;HfHP)fK?3CD|qUw3f`>@BU1bPvXZv%YGbfu7K5*mZ;*U8ld zK=3r0g_$ZMm?>p-rlp`Np;vuy-o+5Fyxbfj3acdDg zt-Tt{6buC-JD4-c;ceB?pkMWEjv#8x0KO@Fig05%_i?U4Z+wSWY=UwhOm4_J|G{+g zC7YsjII>L@`|$lUuA152JmtJ(C_p%(nGw4b>-Wj!$U(lJVhP}$Q?Vas2c_(`q#y&7 zE2hHfC0obogLHCD?`)T>gD?9mP(TzkePHYar-FHTP^SMhfCKh?(|lzu-zy+4ru2wg zAI3o_n}b~r*RM6z6nRmzQqUfbh$Bg7Rg01f?80)uS%6*W3&eO3j2eVD^IQO7yYc~&hQ>R}VzulA>AqpS6tII_QtWvlR90QL+E zt+Zonl3dBDpLK>DW~oWDav%EZT10nzydf=%-;##abPnAXt!vJg7mzWR2|2HUn`+y( z)U-3zt|>7ZHROCl{|@v^=IEnarF|W*x~ssKPF`twitAc8 zae%3?XCz>pQ#O84jp#M)y#J5k>ok6%HR33Bg3Bm8hr5uDjwGEM3I~*9z4pL(g59>r z@)lBME(P|%{{rEfW6xJ`_mbn$>}$F62y3dv+0Lvj#QF1<3)(T;8S`fMG_=85tg8<) zwowiN9sDwXENQ~!I#rLF=faTV+&12Y-A%(Vn=4g26W@!u>-t(OmWwO zS^Hpy&2bQS81yPbGqs)LPi&sKNvzNDlUr5jh#9HD-ksQ^WPP!G@*XX$Rc_-{00owz zb{wNTU*8LFQU?EH=qO<<0VGWTvuA&JJ^{dUo#(Nq(08F^H4=yC$oA*0Bj(38lGaVE zjRLa~>e6ixDv>B-%n9*ICuj`Vi61&GCm_wD+wX&WKn}Wy%A=K`eC;Z9)5dnh4|iqP zCI#2=FY{@>xUlH^ZZSFyBUg;%!qN=DgTqa|o_dO7lb(_*7;EOMBg&s@0sBh|%qZ+M z?5?#uDMX+eYgeoco%FMM1E01_H-U#p`-MTH9+@=`hqFxnmuPLqekMQ}aFcm?w_`g0j*88j(JOFGrFVS%O5_nHV~KBtnJG%UPm zB6e7^gMiT})!O9lmnRwmjVN(aa$PAG8rv+!ebz6Z7bzOuRk6iiS;!M|fqRg>4C^~@ zP^`)IdN#!ws>dEyWp|*TeP-k@e2-Wpu#8z2H~oZ-en^p?bMsboUZaJ`ivNf|i%_?K z#`N2)CV**{OE2Lb8(HXB%*Gtnu?g@>BW?hEmHCEid^wk+V%`}8TN`B;0 z^ZmTw8iX0BrDlAXcxs8{=2K`}?Xnqosr@V65L$ukg0%%2q=)3a59d@J9?N{$MjZ)x zD6s4YEu@xTL5_&v`o4f~+uB}UMViFDKsgy-S?Vpgab~2wV=~d#Y#od1y7!NQ|G6z4 zw~iR#&O%-*085M=U^wP46!MYh>Xte4?>V#9%mn3Doe!oNxUqIz7`TuE!-J;>=mYN` zJGNs$Gr^N91izlOdu(l)0i=5ZPvN;?6SbmqLb{%y2PPQ8)OX7=`)G7En4G1;=o7Tr~&3m;5{SL#0GH^y>`isT{hy9q=3jLyn|$ln0= zEa_jj;dc12dude9&f2-@MWYFyp1mcN`}gps4j~IY<>njK_(f;YEQ*Jq>Cs2NC>1G# zrmYJ$vE$>cx@}~6HWv@_Jy1+B(V$n#23|~Vui>nxUeuC0sq`ZquEoA>AXf)caCQ+WvZ>VaSt&g<9-{xy_=E;?7N5D| z*k_5_wsUZ^4V?+ZLR2v6*Oy{av_dKxq=Ui%s2BMb;!tpFDD~S)ETLgpgrk8f9=T_m z9Q&T-IVTepPt7*$p+S(FTe1pWcm@`-20%BOaGPpyS!m_Wq4E$t_)0s+(XdkU&D0pl-{oJg#z9O++BExUkBp(*knCUKPtN`>lRtV?jvd346bW zM%O#CJkD9E$oB(4;4{v(JR5;lN~VfL$8L(_mX;GuxenMP83W~gS+u9?B^u!1!n%xf z2Kz`-waPg6BA|q*xm}r}3t589i#Ue+ho+6ip9Tz;IIyd|VO-_oo9x(jpv~Hv9MHU0 zt@+dV{Cgz}&d_-X3*70Ds6R2=#=3LM+#&6F&m4xe3j_)b8CEvKQ zDs&438UG3gadaZvOm%OgP5CQZ(B94woQR_Yi!#rHpNqO0?kt*kryvC?0n)J{9JB!* zB?%OTovp69{YWt~ga3FoD3lrv9beSK-0xDqtHhLlU9e$#s=;)B{K@^nL>Fy$KSzCiy$`d2VkHgD|^k76R>MaF2gWKbL{rkfx& zL+k>4iri>jqA0QwXt(XaWORsd(LM9nsUr@^dtCHcK4IpI?MGaY(OS|IZdtCMY!RTd zJ$K+v=BqIsIg{b89Fr%kBcz=5OGG(s6pAqSJk-(rFkD}N> zc%*Yhy(}aEqqL&|Sx=UFc7OE(aE1UtlJU_%1^^gyLn;gH!vQ_uzRusSJQNn}xW7wK zOaK4?003cL3csf#qL%IcNgBP45I^)cdrUH<<4Tq7;WdBVM-h2ccQ)*m#-D#DTHWd- z6THKu!Rj_b4>`;mdL3iDA2?ljDASIGS`f|yUQc!S$bT1;rm&lq{eH0rg?Yfm1e{1y*1vQ%f*9)w*|nItxmz#wg8 z>fAKOc!==tUbJ8g57Wo0DjD-S2Y>b7TTEYfUT)9Th1^(B^?+YE+=AJ22ME?gHavkg zN!#}{bh1SCP+qtBZON<)3=|;v0%FSVt9QesAjFL*{K5)Kog3P%is}S}QKQbiQDvq< zsaoGo$DJcKl>Jeo8Cuu}ZO@^)Mij}2)MY*m5(13FI&7wrz`PdTpUyq|x}=*<#yNg9 zJrUhf4P>B7PGtU$Upls?5`MXkA3fy!b7c_8JNyDt{SW6z=1I5M_YWr#_gVi%}ntZADAN&^MR3 zvZnExK{13U;Vz+(&21F+de%z-4`Hkvwd@n5!=f}~(OW97Fqc`@DCDsSo3N}7v54u2 zp2aeR;T6jF&&y9MWa&U)0&KLh^tFN=b@mfNCz@ZyjhJ~qf4o5#t*6RxEcN4Igd|d7 zX|S*@&13b}tH6;7_3aYv+Ig`88B%Ma6qg7pB%(Utj4;p$5Pg;=L*j=q zVPA+5Pg-Qk*YBP?iZq5X%!=8Y=#5j)B#U%E+E}{Dd*_&C!9ks@3GE&hLi`oW?PL`{ z9v6MWsObFbr}|MCfz*NpLaZIq<1I$Y4zDjJ2<(E%az+t9A39`wyX*GK?}vgok(|tt4MIN000030Zm1Gc!+ELT4l|qA_eEB z=uO$KUL+`uK(OGHiWB~L<;FuBc=ZLxL6D1i0jMd&FXHvWxl7=`$hv;iFqbC=@sM`t zM>`%F;FpIv)Q>vUm%XzXW#gBW{2U?_+zX2qfqF%U9Jl3o^9udYwoi{e#9@9-^H$xA zxtBoF`Qk;!)#9TMW3|_^P34VMI|aLyN^Kf*`=+zzx%>kQ>*wcv6UC-)9YwTTD!PjY zJ%@qf_S&MOx+sI{qW=pPb<*%~9BRp`%QnDN>#KT`jp9$54}*J=L`YMeZY8aJ8p;F? zmOl%I@v~k8aJe$Wa+8B%WqnIJETDMc&(skCXZon;B)5^E0d`?aC7%|G!l5%hJw@SI z`!CV4WvOtYoY1`Gom4jkHC=yo?^CM&-$9+~JA@VE--Dbq1d#LTMrZuCC{c{kc={qw z7Yat%SjzQ+`J+*p{gvEjnfykIK9!)4@?68cBxZI=J=wEI6pk8X&Y_7=cd6 zSbpt6cPKt}OTenOe#Sh`#-d(@zKtMcfY2n(ZN|8o|O+vBJ{yQ1Ais={Ck9jr!x0ZJWU$t&5Ps z^*ZRVeWTG4sRB_S;18{oB#{ib0Q|(r;}-M~tOO(^J_yWzQbf!Al);q|ggD3znWM z6=DTR*PT;AjfU{UKPi5w)OI|)RGGmEKGVXE{jpm}CbQT#=>1fKMSfWR8QsIbeE`74 zss^-^S#}T#u?1o_2p00B-E8(csb=Wr^r5nB-H3nm8Y#I*CU92dR_s!9o@@F z*CU&EB2j}A^|FaZ=T3YBj94zp#IR`hiL*)E5CX$yK<$oV(f_`lhY`-(nUL3Ny)T3q z@mswMMsh4tB0Ex-p&nOLR_AX?I=hASeJ6rdePtjma^O{^#$g|INWcI900wb$^m$#% zMZwCOWhglHijUrYNIe4rBwd_0zTNQxk4&h=S2Yss^@=rkX1yy}U#!L(0_@%o`a;K* zzo`KwAij-g%#2w=ST1nr^``lusQRt;1>F+Sy+t0nG#jJP?|H-~h^~AQjDPh#kuI*m zSgbNap$7S?B*~^kNBvkKkGb>&!VLqxY-PJOc0jPBo4kA5Kl(CT+0TTRc@+mX1Uk4? z-t1++EWc%|f%1?wfkiE>a0GPDZ%tV=Cy{s;X35VdfYhV^K)FA~pY=$=jiGks;XR7W z9pgumeu^Sr{il(Q$c%7)LS+Sg+~Fy&od&|KoAi5!adeO{L{|KZ^*7Dq?)wc3UdlIF zsFX%YzlrPI0 z6Ld#eE)5tWJsS`98DBf0xUDFRmGRtbBsz+_l^IS$;f4#0WcMC@O}tk!urj#0^kmzV zWxPB?YrwRAhykKhXyH&L)7apBMHyH^&I}(UKFkh82^~|Mt=w}f-s5Xp`6Oh>`9t0l zDRi)&B7$5fBhSg4dO!?g7Vcd6D6;zfv0FI0DQNnl467rBVctUB_Q=D&47}bLNk$>5 z-MJ1Jp)tg_(k=@NEG4aU4F~>) zh#XuC?_|bPOs!GF_eOr>sSh-xp(4(VXXndI*=1 zBTSTAqId>SAR|Z&kuPyM=u};+{LgX&dPuNy_9My%p1@RZaqUsG$U8Oy*vUq8?|cZA zZ4{)C-};cyYH48r)CDWq(I1vTh#Fqxghe$8qEnGZ79REi>{~$!8rHB$*_D-`$of~@tRRaIk)#+EAaPolBc@GIZ_CI%T{{W0bQHtmzFWhF!%!!3jCgjVS}`~ zWyRe&bP$0=0hu7xX)Mec4{JbZf>qvb`CA^|hBL?@^P)6C)!>c+y24(d#qjOxP=Y zQb(iwSNNT)P0zc$Kq?^Y4v7jWu*|kJVLu@hK;PQAZRo1FA_wJrHwR^ITv1V}tivriKbgadrjn>r0F%W^ zDG@+8+Ji1dWQis(9I`h#kwasPNl^+ZeeH0mU8LNl_oWkXsZB;K7jpgpBU%1IV*V}P zomqHlOd*uu()?4p;s3{Zfp6JeR$i>@d`L8D$#33#_txHC*;36Z69Cm^zgBp4L(nX~ox3r)bXCw8iXc?v= z53GaF&!XV+13s2UhPppH%DIB$ycL=}@a<#Tp=(+7WFKvucuxmW$n&I$R9USy1yrIx z#uWbRwQD)bY4TFcQI;wBNl>UPYU?CVZQdNXVWw>2BHHg8h=hUy-WCCRGz{q0A^YcN3t94Xn zBpjIKFmNI|OMHFBBf$}ChX@8YPd|z%#W{wkUDi+wf+6Z>j%yJjj6@=>Ab$~xXubK| zo`zTO5cy@9Rn#+4+vbyAcbS zGU-)hQQCrIpLF+@0v-w!1>R`0(qAgpcov-uAptr9&v#9!{_~2Q8djr#Yjd`GCWi4c zXMS)XfUja0xeJr{&8WACELZFEEk*EsjcU1heA`g<(gVO(ay(H9CT3L_lx6{ zKtP4e4muYkKprAg64%{v)b5>DGuvYwDFbP6t^MDr6ULF=Fw41V%>6itnPeZlM4E?H zpuwJ#T(+#J!CS?Pq=7(Lo!>zIBE{&|d$Yzg%_~3dXb@m6K%^ z#BH~!eCWi>5ZC*2$&wZTku{j`GVmY1Y4& zP0}y;`Ift<%4`%CjRdEIEllwi8Snrv;8F${rsKrd?Kp&F@h1Esps-!R&8txrOCN_wugFSXA(xrGzco?WAn82e0!Oier&FJk%qnJz;u5jb2Ff_~$x@>*Hn z^!I_b4k+|-wUpQX^u(z#L31^TYW`w&wlvNyG)H68gLXQT^ZS|~>Uu6{T^gEQxp0XV zc`1EYvaM;7Ca6&4E|f-&-#f}F0#0%`#eLV#44}Le+2cgQgK3unPvqBO$=T-8_ZsL_ zY?H%<&6a(bdMEFp$yyBIJ~zn-A<`>hKVL$7{i<1~xHNfYnX=x~{zB7Dn@O`Sg^Bf* zvT`MqAs_9I<>B9z4d}UYe>hXrYW5ler9zM~_eT)+-PJ2}`n=T>S9Ny^=@5)Rm=Ij3 z2a9Cv8CZIrhU0om;8C4eqi7rT*yjV?{$n_1WD8_oOnIx-C3mCoMR1g~-PfaBF*Z}* z&lx!e&`z^TZ*w^yHZKlAJh9+C#nM!5F9hWK+@+}YudkMm+N}caY)&;7z03tWfS=&J z!gw$zTbK}o?8#@gjSj%o8Hifa#p9V~F&rHkf%S2^7^Nl#C!k<(xq=gW;7&Q9mE(et zzwA&zVyZOh`Jt6fl>yh2WZqNJ&v+E`+k0612Uc(tR${wzWZaMzz-IpxTM(>n`0JT zXu9n`r3t8rf$@L2QUfyowDN%)C<<38&zQA%6j5=dO~=R&@>(gs0@Z3bc`gfQRGL(l zU@{=GxCLho24)+gXIKa_Jz8X)lcbo*yDuP_pacJ&HEE+=DsZb8OWjQkMF;Z^^>xB? zGa=vYJ1^zy6z0iQ5nXJe)0Xxzuuo$Ju%*S~GrT-Pe zA=0O?OK?NKRN(Ub8D0dSW!+V+Oa;wqQZT8& z0Q|w+qSiq8a)kUvfe>(Fzw-(n*%>0QiGr{X4Er4P)2XgAt$B{3Zy9eXRt>?zG3#<| zPw4nwkdBgX#tfo$9k(cJ%Y@P)n=Kd%3`R!W5%aT*5$kifnDMup-v4KkVz<8e9Dx{M zPMuMaK<1DBf|}O7iNM=7>P?Icz|&z37EkElzJklskuG9F9-3M#)ON^CA>ERyBPOqX z8N@7RK61Qic5P3J={G=*{#|@$0cN`hZ~@^OC2*eBM`7l*@8`a`EhXV{t)-i;b3c?9 zu{{nyKBz}wt{8xFW{liAtHAt2j_Z{Udp({W2q0CTTy7?^u}pji%pELR_EKy7$6Z{q zvX@b+GRc3UQ-)K$H24?&v*I$5YO`@u{szc{Uaq~S0tc3{2M&UiTL=w~395VS0 z01s-}NWeB}5S4ByrFquzggv8U44mk**aoELl{=kki6V)PbDOntC)5Is?fwZAjuOYb z%g&Vx^#K;=q6GQy;njUNL%zdA{RCAC*%f#{^~%*wdXWeEf5Qt=2F~N0bi`i>2*iob zhGXSN$Z-ph>K2ns%0MKB+5QRUX6O}RPd5Q3&<1NqbA5uK2eKr2@P-Zju8=7OQpfG?;Gx`&XPP>HgHEj(ZjSRbm!WBxAf89CqS@a?ax;2N7TU7x@J000CoX%qPfnJCQBaXHeH8yVYnqyp}xGTA6MpTUk@rX|86 zUE&uRQB1FKLW7ap$vVND5tgF;%lU{Sq;6$WITFZqRJag>6o&cV5WCoUq(>k=o7!Fy=xut9F8V3 zEBBT$m`};jcw7a-K^7~a{UcAaS;q9nC7A~I8|Nc{0$&_r-9L2KmPe$OjbdP>dQy&& z-17xAea~Z7`d~V{5pnih!sC!$Qe~EZ395)Or+b;>c%Mzfagco32BQ7CL1{0E4^W zH_4!}Tzy8N|09xb90EtyQASLwH7t#2RToe`cRFwdxDNUDVvGFDN0g3Yd(TI_>i3I* zck1kmly*>kmBL!SU5cGsUEc=ar__&WOc0|DG6cPY|K74v#Z(*RUFhaeA6P!qKSl;e zHr&9XT*+zFpzvRm_%HH)|40J>^Ce&B{baPw`ShQVIK2{zXK|opE0iY!8XeuJ%lwoq zpw(ZHXZy`87&F394$MSbbKT`xo^)GEjN=*e_min+O4zCu{zO#nNw9u2f^D-}+w}WkKHyPpChtrq+(^P7|rTI4g>8LtOcw`J_8?xUG=$R`9$os8mOHB zknxSN-y~>-Z$hoZI!I^!t?c70p+inlkHl*_uk%9Wsw0j=P`pGPKk@E-C>VOpy%Xh_ zr6x7uNf?Y>Mrt-HV2JepIyfT;cQy>2&u0vCIlj}-_ikZ?g7|%MYQUx8$x~3H-TG_{ z_-ES>!|&fu2DI@V9$b99Ql13AP+JTfYw};g#aoBs@gi8I4)#F_vP?z_E0x^NX*r6_ zzJMeogWvY&sk8MHnh0D8e~#T2BDQ$nCI} zwLKA~y{ly%eg+aW7GARUcU(ufbwh~ZBG{vT=-Bl&f=Qd1@W*v(GU1zAJDim95msAb zT9^I^B0~)5@cR^ohp}MLmp7y`p%Pk`tV<&iVQ@zdZrbOx42Aq`rNt2=iIjGYc?EebKgle6^DF=W z1W#L>VWjcjh+itf>Styk~P#mJXT ze<9+Clc3{|SFu0@UIS>I?T{xDIVH-Uq?A&;xIEr3vkr*s0t)}nS(9%tu&zQ<*tj!v zdeuY@;`ZWuVRP}c9LuEY`~Je|MBIJiM?@2(=y*SX4t+cg*5HkMn7Jrm`$lzu3jKcw zF;|CbBeNFGV%!G^@biifcL+N`4u+WIzPl5R4m0vE>(|>jZZIONZr4`YnI)s|Z54@G|U+QgLjSOzqd2yzapn_(hvG+RaPUxScWEC|DmbF`Qpm zdoVI=F;48QJWjw_X1MLr2u8haP;{#NqSjpj9gD}w(p51gNoxCn89C`AkL%r5k+BugIys2ndgn9ByNNl`&wZ`ibadAv@FDi+f#*uJj*+1VWT% zL(D-|=heZ9(t*AIdi*rZ?~}TLJkdD_@UW9<@F4KX8KN8s>B~g}xqVkWKp}ha-+2vq z%*P)`5g-QyuDdpPr{sp7bWoN7EFQ3@dNBVb#M`piGcE=`>EFAF$!lGNYYO3*zy=H0 z2tlRZ3xDB3jhY*y7LtLU0D$C}mytU=C*o6Vd=O8A9gw88QcpVJxw!5!u&2+@>Y6>bgSuq9_RIsb% znF5(b9$oBsY1}aS_l-toCr;**d2AP^@qL6aG19#-{whPUeNVp!0ut9yQYO%MH-`sjsah>I$2iep zA!xcCuUG;~+$y&zDgspUaHsdd6;1?*(#lVv0gmjbN2HGOpzwE_zAEf}e z4S=iLRji(^^$MUXK+WR_j4cir*)53lefMFt0eK`Go#~@>*SH7N!g^`@d_ioPs|eM? K-zC_7sU~LF+G(%= literal 0 HcmV?d00001 diff --git a/tests/golden/cli_divide/sample_fragmented/master.m3u8 b/tests/golden/cli_divide/sample_fragmented/master.m3u8 index f411998..e03e818 100644 --- a/tests/golden/cli_divide/sample_fragmented/master.m3u8 +++ b/tests/golden/cli_divide/sample_fragmented/master.m3u8 @@ -1,4 +1,4 @@ #EXTM3U #EXT-X-MEDIA:TYPE=AUDIO,URI="audio/playlist.m3u8",GROUP-ID="audio",NAME="audio",AUTOSELECT=YES,CHANNELS="2" -#EXT-X-STREAM-INF:BANDWIDTH=28320,CODECS="avc1.64001f,mp4a.40.2",RESOLUTION=1280x720,AUDIO="audio" +#EXT-X-STREAM-INF:BANDWIDTH=28320,CODECS="avc1.4d401f,mp4a.40.2",RESOLUTION=1280x720,AUDIO="audio" video/playlist.m3u8 diff --git a/tests/golden/cli_dump/sample.json b/tests/golden/cli_dump/sample.json new file mode 100644 index 0000000..55700d6 --- /dev/null +++ b/tests/golden/cli_dump/sample.json @@ -0,0 +1,2308 @@ +{ + "Boxes": [ + { + "BoxType": "ftyp", + "Path": "ftyp", + "Offset": 0, + "Size": 32, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "MajorBrand", + "ValueKind": "bytes", + "Value": [ + 105, + 115, + 111, + 109 + ], + "DisplayValue": "\"isom\"" + }, + { + "Name": "MinorVersion", + "ValueKind": "unsigned", + "Value": 512 + }, + { + "Name": "CompatibleBrands", + "ValueKind": "bytes", + "Value": [ + 105, + 115, + 111, + 109, + 105, + 115, + 111, + 50, + 97, + 118, + 99, + 49, + 109, + 112, + 52, + 49 + ], + "DisplayValue": "[{CompatibleBrand=\"isom\"}, {CompatibleBrand=\"iso2\"}, {CompatibleBrand=\"avc1\"}, {CompatibleBrand=\"mp41\"}]" + } + ], + "PayloadSummary": "MajorBrand=\"isom\" MinorVersion=512 CompatibleBrands=[{CompatibleBrand=\"isom\"}, {CompatibleBrand=\"iso2\"}, {CompatibleBrand=\"avc1\"}, {CompatibleBrand=\"mp41\"}]", + "Children": [ + ] + }, + { + "BoxType": "free", + "Path": "free", + "Offset": 32, + "Size": 8, + "Supported": true, + "PayloadStatus": "omitted", + "PayloadFields": [ + ], + "Children": [ + ] + }, + { + "BoxType": "mdat", + "Path": "mdat", + "Offset": 40, + "Size": 6402, + "Supported": true, + "PayloadStatus": "omitted", + "PayloadFields": [ + ], + "Children": [ + ] + }, + { + "BoxType": "moov", + "Path": "moov", + "Offset": 6442, + "Size": 1836, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "mvhd", + "Path": "moov/mvhd", + "Offset": 6450, + "Size": 108, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "CreationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "ModificationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Timescale", + "ValueKind": "unsigned", + "Value": 1000 + }, + { + "Name": "DurationV0", + "ValueKind": "unsigned", + "Value": 1024 + }, + { + "Name": "Rate", + "ValueKind": "signed", + "Value": 65536, + "DisplayValue": "1" + }, + { + "Name": "Volume", + "ValueKind": "signed", + "Value": 256 + }, + { + "Name": "Matrix", + "ValueKind": "signed_array", + "Value": [ + 65536, + 0, + 0, + 0, + 65536, + 0, + 0, + 0, + 1073741824 + ], + "DisplayValue": "[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000]" + }, + { + "Name": "PreDefined", + "ValueKind": "signed_array", + "Value": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + { + "Name": "NextTrackID", + "ValueKind": "unsigned", + "Value": 3 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=1000 DurationV0=1024 Rate=1 Volume=256 Matrix=[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000] PreDefined=[0, 0, 0, 0, 0, 0] NextTrackID=3", + "Children": [ + ] + }, + { + "BoxType": "trak", + "Path": "moov/trak", + "Offset": 6558, + "Size": 743, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "tkhd", + "Path": "moov/trak/tkhd", + "Offset": 6566, + "Size": 92, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 3, + "DisplayValue": "0x000003" + }, + { + "Name": "CreationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "ModificationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "TrackID", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "DurationV0", + "ValueKind": "unsigned", + "Value": 1000 + }, + { + "Name": "Layer", + "ValueKind": "signed", + "Value": 0 + }, + { + "Name": "AlternateGroup", + "ValueKind": "signed", + "Value": 0 + }, + { + "Name": "Volume", + "ValueKind": "signed", + "Value": 0 + }, + { + "Name": "Matrix", + "ValueKind": "signed_array", + "Value": [ + 65536, + 0, + 0, + 0, + 65536, + 0, + 0, + 0, + 1073741824 + ], + "DisplayValue": "[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000]" + }, + { + "Name": "Width", + "ValueKind": "unsigned", + "Value": 20971520, + "DisplayValue": "320" + }, + { + "Name": "Height", + "ValueKind": "unsigned", + "Value": 11796480, + "DisplayValue": "180" + } + ], + "PayloadSummary": "Version=0 Flags=0x000003 CreationTimeV0=0 ModificationTimeV0=0 TrackID=1 DurationV0=1000 Layer=0 AlternateGroup=0 Volume=0 Matrix=[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000] Width=320 Height=180", + "Children": [ + ] + }, + { + "BoxType": "edts", + "Path": "moov/trak/edts", + "Offset": 6658, + "Size": 36, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "elst", + "Path": "moov/trak/edts/elst", + "Offset": 6666, + "Size": 28, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 3, + 232, + 0, + 0, + 8, + 0, + 0, + 1, + 0, + 0 + ], + "DisplayValue": "[{SegmentDurationV0=1000 MediaTimeV0=2048 MediaRateInteger=1}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1 Entries=[{SegmentDurationV0=1000 MediaTimeV0=2048 MediaRateInteger=1}]", + "Children": [ + ] + } + ] + }, + { + "BoxType": "mdia", + "Path": "moov/trak/mdia", + "Offset": 6694, + "Size": 607, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "mdhd", + "Path": "moov/trak/mdia/mdhd", + "Offset": 6702, + "Size": 32, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "CreationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "ModificationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Timescale", + "ValueKind": "unsigned", + "Value": 10240 + }, + { + "Name": "DurationV0", + "ValueKind": "unsigned", + "Value": 10240 + }, + { + "Name": "Language", + "ValueKind": "unsigned_array", + "Value": [ + 5, + 14, + 7 + ], + "DisplayValue": "\"eng\"" + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=10240 DurationV0=10240 Language=\"eng\" PreDefined=0", + "Children": [ + ] + }, + { + "BoxType": "hdlr", + "Path": "moov/trak/mdia/hdlr", + "Offset": 6734, + "Size": 44, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "HandlerType", + "ValueKind": "bytes", + "Value": [ + 118, + 105, + 100, + 101 + ], + "DisplayValue": "\"vide\"" + }, + { + "Name": "Name", + "ValueKind": "string", + "Value": "VideoHandle", + "DisplayValue": "\"VideoHandle\"" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 PreDefined=0 HandlerType=\"vide\" Name=\"VideoHandle\"", + "Children": [ + ] + }, + { + "BoxType": "minf", + "Path": "moov/trak/mdia/minf", + "Offset": 6778, + "Size": 523, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "vmhd", + "Path": "moov/trak/mdia/minf/vmhd", + "Offset": 6786, + "Size": 20, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "0x000001" + }, + { + "Name": "Graphicsmode", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Opcolor", + "ValueKind": "unsigned_array", + "Value": [ + 0, + 0, + 0 + ] + } + ], + "PayloadSummary": "Version=0 Flags=0x000001 Graphicsmode=0 Opcolor=[0, 0, 0]", + "Children": [ + ] + }, + { + "BoxType": "dinf", + "Path": "moov/trak/mdia/minf/dinf", + "Offset": 6806, + "Size": 36, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "dref", + "Path": "moov/trak/mdia/minf/dinf/dref", + "Offset": 6814, + "Size": 28, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1", + "Children": [ + { + "BoxType": "url ", + "Path": "moov/trak/mdia/minf/dinf/dref/url ", + "Offset": 6830, + "Size": 12, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "0x000001" + } + ], + "PayloadSummary": "Version=0 Flags=0x000001", + "Children": [ + ] + } + ] + } + ] + }, + { + "BoxType": "stbl", + "Path": "moov/trak/mdia/minf/stbl", + "Offset": 6842, + "Size": 459, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "stsd", + "Path": "moov/trak/mdia/minf/stbl/stsd", + "Offset": 6850, + "Size": 167, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1", + "Children": [ + { + "BoxType": "avc1", + "Path": "moov/trak/mdia/minf/stbl/stsd/avc1", + "Offset": 6866, + "Size": 151, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "DataReferenceIndex", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "PreDefined2", + "ValueKind": "unsigned_array", + "Value": [ + 0, + 0, + 0 + ] + }, + { + "Name": "Width", + "ValueKind": "unsigned", + "Value": 320 + }, + { + "Name": "Height", + "ValueKind": "unsigned", + "Value": 180 + }, + { + "Name": "Horizresolution", + "ValueKind": "unsigned", + "Value": 4718592 + }, + { + "Name": "Vertresolution", + "ValueKind": "unsigned", + "Value": 4718592 + }, + { + "Name": "FrameCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "Compressorname", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "DisplayValue": "\"\"" + }, + { + "Name": "Depth", + "ValueKind": "unsigned", + "Value": 24 + }, + { + "Name": "PreDefined3", + "ValueKind": "signed", + "Value": -1 + } + ], + "PayloadSummary": "DataReferenceIndex=1 PreDefined=0 PreDefined2=[0, 0, 0] Width=320 Height=180 Horizresolution=4718592 Vertresolution=4718592 FrameCount=1 Compressorname=\"\" Depth=24 PreDefined3=-1", + "Children": [ + { + "BoxType": "avcC", + "Path": "moov/trak/mdia/minf/stbl/stsd/avc1/avcC", + "Offset": 6952, + "Size": 49, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "ConfigurationVersion", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "0x1" + }, + { + "Name": "Profile", + "ValueKind": "unsigned", + "Value": 100, + "DisplayValue": "0x64" + }, + { + "Name": "ProfileCompatibility", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x0" + }, + { + "Name": "Level", + "ValueKind": "unsigned", + "Value": 12, + "DisplayValue": "0xc" + }, + { + "Name": "LengthSizeMinusOne", + "ValueKind": "unsigned", + "Value": 3, + "DisplayValue": "0x3" + }, + { + "Name": "NumOfSequenceParameterSets", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "0x1" + }, + { + "Name": "SequenceParameterSets", + "ValueKind": "bytes", + "Value": [ + 0, + 25, + 103, + 100, + 0, + 12, + 172, + 217, + 65, + 65, + 159, + 159, + 1, + 108, + 128, + 0, + 0, + 3, + 0, + 128, + 0, + 0, + 10, + 7, + 138, + 20, + 203 + ], + "DisplayValue": "[{Length=25 NALUnit=[0x67, 0x64, 0x0, 0xc, 0xac, 0xd9, 0x41, 0x41, 0x9f, 0x9f, 0x1, 0x6c, 0x80, 0x0, 0x0, 0x3, 0x0, 0x80, 0x0, 0x0, 0xa, 0x7, 0x8a, 0x14, 0xcb]}]" + }, + { + "Name": "NumOfPictureParameterSets", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "0x1" + }, + { + "Name": "PictureParameterSets", + "ValueKind": "bytes", + "Value": [ + 0, + 5, + 104, + 235, + 236, + 178, + 44 + ], + "DisplayValue": "[{Length=5 NALUnit=[0x68, 0xeb, 0xec, 0xb2, 0x2c]}]" + } + ], + "PayloadSummary": "ConfigurationVersion=0x1 Profile=0x64 ProfileCompatibility=0x0 Level=0xc LengthSizeMinusOne=0x3 NumOfSequenceParameterSets=0x1 SequenceParameterSets=[{Length=25 NALUnit=[0x67, 0x64, 0x0, 0xc, 0xac, 0xd9, 0x41, 0x41, 0x9f, 0x9f, 0x1, 0x6c, 0x80, 0x0, 0x0, 0x3, 0x0, 0x80, 0x0, 0x0, 0xa, 0x7, 0x8a, 0x14, 0xcb]}] NumOfPictureParameterSets=0x1 PictureParameterSets=[{Length=5 NALUnit=[0x68, 0xeb, 0xec, 0xb2, 0x2c]}]", + "Children": [ + ] + }, + { + "BoxType": "pasp", + "Path": "moov/trak/mdia/minf/stbl/stsd/avc1/pasp", + "Offset": 7001, + "Size": 16, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "HSpacing", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "VSpacing", + "ValueKind": "unsigned", + "Value": 1 + } + ], + "PayloadSummary": "HSpacing=1 VSpacing=1", + "Children": [ + ] + } + ] + } + ] + }, + { + "BoxType": "stts", + "Path": "moov/trak/mdia/minf/stbl/stts", + "Offset": 7017, + "Size": 24, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 10, + 0, + 0, + 4, + 0 + ], + "DisplayValue": "[{SampleCount=10 SampleDelta=1024}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1 Entries=[{SampleCount=10 SampleDelta=1024}]", + "Children": [ + ] + }, + { + "BoxType": "stss", + "Path": "moov/trak/mdia/minf/stbl/stss", + "Offset": 7041, + "Size": 20, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "SampleNumber", + "ValueKind": "unsigned_array", + "Value": [ + 1 + ] + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1 SampleNumber=[1]", + "Children": [ + ] + }, + { + "BoxType": "ctts", + "Path": "moov/trak/mdia/minf/stbl/ctts", + "Offset": 7061, + "Size": 88, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 9 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 2, + 0, + 0, + 8, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 20, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 8, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 12, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 12, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 4, + 0 + ], + "DisplayValue": "[{SampleCount=2 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=5120}, {SampleCount=1 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=0}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=9 Entries=[{SampleCount=2 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=5120}, {SampleCount=1 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=0}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}]", + "Children": [ + ] + }, + { + "BoxType": "stsc", + "Path": "moov/trak/mdia/minf/stbl/stsc", + "Offset": 7149, + "Size": 40, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 2 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1 + ], + "DisplayValue": "[{FirstChunk=1 SamplesPerChunk=2 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=1 SampleDescriptionIndex=1}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=2 Entries=[{FirstChunk=1 SamplesPerChunk=2 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=1 SampleDescriptionIndex=1}]", + "Children": [ + ] + }, + { + "BoxType": "stsz", + "Path": "moov/trak/mdia/minf/stbl/stsz", + "Offset": 7189, + "Size": 60, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "SampleSize", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "SampleCount", + "ValueKind": "unsigned", + "Value": 10 + }, + { + "Name": "EntrySize", + "ValueKind": "unsigned_array", + "Value": [ + 3679, + 86, + 545, + 180, + 69, + 60, + 182, + 22, + 204, + 15 + ] + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 SampleSize=0 SampleCount=10 EntrySize=[3679, 86, 545, 180, 69, 60, 182, 22, 204, 15]", + "Children": [ + ] + }, + { + "BoxType": "stco", + "Path": "moov/trak/mdia/minf/stbl/stco", + "Offset": 7249, + "Size": 52, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 9 + }, + { + "Name": "ChunkOffset", + "ValueKind": "unsigned_array", + "Value": [ + 48, + 3836, + 4527, + 4864, + 5043, + 5227, + 5560, + 5702, + 6038 + ] + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=9 ChunkOffset=[48, 3836, 4527, 4864, 5043, 5227, 5560, 5702, 6038]", + "Children": [ + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "BoxType": "trak", + "Path": "moov/trak", + "Offset": 7301, + "Size": 844, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "tkhd", + "Path": "moov/trak/tkhd", + "Offset": 7309, + "Size": 92, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 3, + "DisplayValue": "0x000003" + }, + { + "Name": "CreationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "ModificationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "TrackID", + "ValueKind": "unsigned", + "Value": 2 + }, + { + "Name": "DurationV0", + "ValueKind": "unsigned", + "Value": 1024 + }, + { + "Name": "Layer", + "ValueKind": "signed", + "Value": 0 + }, + { + "Name": "AlternateGroup", + "ValueKind": "signed", + "Value": 1 + }, + { + "Name": "Volume", + "ValueKind": "signed", + "Value": 256 + }, + { + "Name": "Matrix", + "ValueKind": "signed_array", + "Value": [ + 65536, + 0, + 0, + 0, + 65536, + 0, + 0, + 0, + 1073741824 + ], + "DisplayValue": "[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000]" + }, + { + "Name": "Width", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0" + }, + { + "Name": "Height", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0" + } + ], + "PayloadSummary": "Version=0 Flags=0x000003 CreationTimeV0=0 ModificationTimeV0=0 TrackID=2 DurationV0=1024 Layer=0 AlternateGroup=1 Volume=256 Matrix=[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000] Width=0 Height=0", + "Children": [ + ] + }, + { + "BoxType": "edts", + "Path": "moov/trak/edts", + "Offset": 7401, + "Size": 36, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "elst", + "Path": "moov/trak/edts/elst", + "Offset": 7409, + "Size": 28, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 3, + 232, + 0, + 0, + 4, + 0, + 0, + 1, + 0, + 0 + ], + "DisplayValue": "[{SegmentDurationV0=1000 MediaTimeV0=1024 MediaRateInteger=1}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1 Entries=[{SegmentDurationV0=1000 MediaTimeV0=1024 MediaRateInteger=1}]", + "Children": [ + ] + } + ] + }, + { + "BoxType": "mdia", + "Path": "moov/trak/mdia", + "Offset": 7437, + "Size": 708, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "mdhd", + "Path": "moov/trak/mdia/mdhd", + "Offset": 7445, + "Size": 32, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "CreationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "ModificationTimeV0", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Timescale", + "ValueKind": "unsigned", + "Value": 44100 + }, + { + "Name": "DurationV0", + "ValueKind": "unsigned", + "Value": 45124 + }, + { + "Name": "Language", + "ValueKind": "unsigned_array", + "Value": [ + 5, + 14, + 7 + ], + "DisplayValue": "\"eng\"" + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=44100 DurationV0=45124 Language=\"eng\" PreDefined=0", + "Children": [ + ] + }, + { + "BoxType": "hdlr", + "Path": "moov/trak/mdia/hdlr", + "Offset": 7477, + "Size": 44, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "HandlerType", + "ValueKind": "bytes", + "Value": [ + 115, + 111, + 117, + 110 + ], + "DisplayValue": "\"soun\"" + }, + { + "Name": "Name", + "ValueKind": "string", + "Value": "SoundHandle", + "DisplayValue": "\"SoundHandle\"" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 PreDefined=0 HandlerType=\"soun\" Name=\"SoundHandle\"", + "Children": [ + ] + }, + { + "BoxType": "minf", + "Path": "moov/trak/mdia/minf", + "Offset": 7521, + "Size": 624, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "smhd", + "Path": "moov/trak/mdia/minf/smhd", + "Offset": 7529, + "Size": 16, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "Balance", + "ValueKind": "signed", + "Value": 0, + "DisplayValue": "0" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 Balance=0", + "Children": [ + ] + }, + { + "BoxType": "dinf", + "Path": "moov/trak/mdia/minf/dinf", + "Offset": 7545, + "Size": 36, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "dref", + "Path": "moov/trak/mdia/minf/dinf/dref", + "Offset": 7553, + "Size": 28, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1", + "Children": [ + { + "BoxType": "url ", + "Path": "moov/trak/mdia/minf/dinf/dref/url ", + "Offset": 7569, + "Size": 12, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "0x000001" + } + ], + "PayloadSummary": "Version=0 Flags=0x000001", + "Children": [ + ] + } + ] + } + ] + }, + { + "BoxType": "stbl", + "Path": "moov/trak/mdia/minf/stbl", + "Offset": 7581, + "Size": 564, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "stsd", + "Path": "moov/trak/mdia/minf/stbl/stsd", + "Offset": 7589, + "Size": 106, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=1", + "Children": [ + { + "BoxType": "mp4a", + "Path": "moov/trak/mdia/minf/stbl/stsd/mp4a", + "Offset": 7605, + "Size": 90, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "DataReferenceIndex", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "EntryVersion", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "ChannelCount", + "ValueKind": "unsigned", + "Value": 2 + }, + { + "Name": "SampleSize", + "ValueKind": "unsigned", + "Value": 16 + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "SampleRate", + "ValueKind": "unsigned", + "Value": 2890137600, + "DisplayValue": "44100" + } + ], + "PayloadSummary": "DataReferenceIndex=1 EntryVersion=0 ChannelCount=2 SampleSize=16 PreDefined=0 SampleRate=44100", + "Children": [ + { + "BoxType": "esds", + "Path": "moov/trak/mdia/minf/stbl/stsd/mp4a/esds", + "Offset": 7641, + "Size": 54, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "Descriptors", + "ValueKind": "bytes", + "Value": [ + 3, + 128, + 128, + 128, + 37, + 0, + 2, + 0, + 4, + 128, + 128, + 128, + 23, + 64, + 21, + 0, + 0, + 0, + 0, + 0, + 41, + 74, + 0, + 0, + 41, + 74, + 5, + 128, + 128, + 128, + 5, + 18, + 16, + 86, + 229, + 0, + 6, + 128, + 128, + 128, + 1, + 2 + ], + "DisplayValue": "[{Tag=ESDescr Size=37 ESID=2 StreamDependenceFlag=false UrlFlag=false OcrStreamFlag=false StreamPriority=0}, {Tag=DecoderConfigDescr Size=23 ObjectTypeIndication=0x40 StreamType=5 UpStream=false Reserved=true BufferSizeDB=0 MaxBitrate=10570 AvgBitrate=10570}, {Tag=DecSpecificInfo Size=5 Data=[0x12, 0x10, 0x56, 0xe5, 0x0]}, {Tag=SLConfigDescr Size=1 Data=[0x2]}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 Descriptors=[{Tag=ESDescr Size=37 ESID=2 StreamDependenceFlag=false UrlFlag=false OcrStreamFlag=false StreamPriority=0}, {Tag=DecoderConfigDescr Size=23 ObjectTypeIndication=0x40 StreamType=5 UpStream=false Reserved=true BufferSizeDB=0 MaxBitrate=10570 AvgBitrate=10570}, {Tag=DecSpecificInfo Size=5 Data=[0x12, 0x10, 0x56, 0xe5, 0x0]}, {Tag=SLConfigDescr Size=1 Data=[0x2]}]", + "Children": [ + ] + } + ] + } + ] + }, + { + "BoxType": "stts", + "Path": "moov/trak/mdia/minf/stbl/stts", + "Offset": 7695, + "Size": 48, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 4 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 1, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 5, + 225, + 0, + 0, + 0, + 41, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 2, + 99 + ], + "DisplayValue": "[{SampleCount=1 SampleDelta=1024}, {SampleCount=1 SampleDelta=1505}, {SampleCount=41 SampleDelta=1024}, {SampleCount=1 SampleDelta=611}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=4 Entries=[{SampleCount=1 SampleDelta=1024}, {SampleCount=1 SampleDelta=1505}, {SampleCount=41 SampleDelta=1024}, {SampleCount=1 SampleDelta=611}]", + "Children": [ + ] + }, + { + "BoxType": "stsc", + "Path": "moov/trak/mdia/minf/stbl/stsc", + "Offset": 7743, + "Size": 100, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 7 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 3, + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 6, + 0, + 0, + 0, + 5, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 7, + 0, + 0, + 0, + 4, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 9, + 0, + 0, + 0, + 13, + 0, + 0, + 0, + 1 + ], + "DisplayValue": "[{FirstChunk=1 SamplesPerChunk=1 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=3 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=4 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=6 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=7 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=9 SamplesPerChunk=13 SampleDescriptionIndex=1}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=7 Entries=[{FirstChunk=1 SamplesPerChunk=1 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=3 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=4 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=6 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=7 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=9 SamplesPerChunk=13 SampleDescriptionIndex=1}]", + "Children": [ + ] + }, + { + "BoxType": "stsz", + "Path": "moov/trak/mdia/minf/stbl/stsz", + "Offset": 7843, + "Size": 196, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "SampleSize", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "SampleCount", + "ValueKind": "unsigned", + "Value": 44 + }, + { + "Name": "EntrySize", + "ValueKind": "unsigned_array", + "Value": [ + 23, + 39, + 35, + 36, + 36, + 35, + 31, + 33, + 28, + 30, + 33, + 29, + 23, + 25, + 25, + 36, + 27, + 36, + 35, + 25, + 28, + 36, + 27, + 29, + 28, + 29, + 34, + 36, + 30, + 32, + 34, + 29, + 24, + 34, + 35, + 28, + 24, + 30, + 29, + 29, + 27, + 31, + 34, + 35 + ] + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 SampleSize=0 SampleCount=44 EntrySize=[23, 39, 35, 36, 36, 35, 31, 33, 28, 30, 33, 29, 23, 25, 25, 36, 27, 36, 35, 25, 28, 36, 27, 29, 28, 29, 34, 36, 30, 32, 34, 29, 24, 34, 35, 28, 24, 30, 29, 29, 27, 31, 34, 35]", + "Children": [ + ] + }, + { + "BoxType": "stco", + "Path": "moov/trak/mdia/minf/stbl/stco", + "Offset": 8039, + "Size": 52, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 9 + }, + { + "Name": "ChunkOffset", + "ValueKind": "unsigned_array", + "Value": [ + 3813, + 4381, + 4707, + 4933, + 5103, + 5409, + 5582, + 5906, + 6053 + ] + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 EntryCount=9 ChunkOffset=[3813, 4381, 4707, 4933, 5103, 5409, 5582, 5906, 6053]", + "Children": [ + ] + }, + { + "BoxType": "sgpd", + "Path": "moov/trak/mdia/minf/stbl/sgpd", + "Offset": 8091, + "Size": 26, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "GroupingType", + "ValueKind": "bytes", + "Value": [ + 114, + 111, + 108, + 108 + ], + "DisplayValue": "\"roll\"" + }, + { + "Name": "DefaultLength", + "ValueKind": "unsigned", + "Value": 2 + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "RollDistances", + "ValueKind": "bytes", + "Value": [ + 255, + 255 + ], + "DisplayValue": "[-1]" + } + ], + "PayloadSummary": "Version=1 Flags=0x000000 GroupingType=\"roll\" DefaultLength=2 EntryCount=1 RollDistances=[-1]", + "Children": [ + ] + }, + { + "BoxType": "sbgp", + "Path": "moov/trak/mdia/minf/stbl/sbgp", + "Offset": 8117, + "Size": 28, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "GroupingType", + "ValueKind": "unsigned", + "Value": 1919904876 + }, + { + "Name": "EntryCount", + "ValueKind": "unsigned", + "Value": 1 + }, + { + "Name": "Entries", + "ValueKind": "bytes", + "Value": [ + 0, + 0, + 0, + 44, + 0, + 0, + 0, + 1 + ], + "DisplayValue": "[{SampleCount=44 GroupDescriptionIndex=1}]" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 GroupingType=1919904876 EntryCount=1 Entries=[{SampleCount=44 GroupDescriptionIndex=1}]", + "Children": [ + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "BoxType": "udta", + "Path": "moov/udta", + "Offset": 8145, + "Size": 133, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "meta", + "Path": "moov/udta/meta", + "Offset": 8153, + "Size": 90, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000", + "Children": [ + { + "BoxType": "hdlr", + "Path": "moov/udta/meta/hdlr", + "Offset": 8165, + "Size": 33, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "Version", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "Flags", + "ValueKind": "unsigned", + "Value": 0, + "DisplayValue": "0x000000" + }, + { + "Name": "PreDefined", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "HandlerType", + "ValueKind": "bytes", + "Value": [ + 109, + 100, + 105, + 114 + ], + "DisplayValue": "\"mdir\"" + }, + { + "Name": "Name", + "ValueKind": "string", + "Value": "", + "DisplayValue": "\"\"" + } + ], + "PayloadSummary": "Version=0 Flags=0x000000 PreDefined=0 HandlerType=\"mdir\" Name=\"\"", + "Children": [ + ] + }, + { + "BoxType": "ilst", + "Path": "moov/udta/meta/ilst", + "Offset": 8198, + "Size": 45, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "(c)too", + "Path": "moov/udta/meta/ilst/(c)too", + "Offset": 8206, + "Size": 37, + "Supported": true, + "PayloadStatus": "empty", + "PayloadFields": [ + ], + "Children": [ + { + "BoxType": "data", + "Path": "moov/udta/meta/ilst/(c)too/data", + "Offset": 8214, + "Size": 29, + "Supported": true, + "PayloadStatus": "summary", + "PayloadFields": [ + { + "Name": "DataType", + "ValueKind": "unsigned", + "Value": 1, + "DisplayValue": "UTF8" + }, + { + "Name": "DataLang", + "ValueKind": "unsigned", + "Value": 0 + }, + { + "Name": "EncodingTool", + "ValueKind": "bytes", + "Value": [ + 76, + 97, + 118, + 102, + 53, + 56, + 46, + 50, + 57, + 46, + 49, + 48, + 48 + ], + "DisplayValue": "\"Lavf58.29.100\"" + } + ], + "PayloadSummary": "DataType=UTF8 DataLang=0 EncodingTool=\"Lavf58.29.100\"", + "Children": [ + ] + } + ] + } + ] + } + ] + }, + { + "BoxType": "loci", + "Path": "moov/udta/loci", + "Offset": 8243, + "Size": 35, + "Supported": false, + "PayloadStatus": "omitted", + "PayloadFields": [ + ], + "Children": [ + ] + } + ] + } + ] + } + ] +} diff --git a/tests/golden/cli_dump/sample.yaml b/tests/golden/cli_dump/sample.yaml new file mode 100644 index 0000000..17f0cab --- /dev/null +++ b/tests/golden/cli_dump/sample.yaml @@ -0,0 +1,1666 @@ +boxes: +- box_type: ftyp + path: ftyp + offset: 0 + size: 32 + supported: true + payload_status: summary + payload_fields: + - name: MajorBrand + value_kind: bytes + value: + - 105 + - 115 + - 111 + - 109 + display_value: '"isom"' + - name: MinorVersion + value_kind: unsigned + value: 512 + - name: CompatibleBrands + value_kind: bytes + value: + - 105 + - 115 + - 111 + - 109 + - 105 + - 115 + - 111 + - 50 + - 97 + - 118 + - 99 + - 49 + - 109 + - 112 + - 52 + - 49 + display_value: '[{CompatibleBrand="isom"}, {CompatibleBrand="iso2"}, {CompatibleBrand="avc1"}, {CompatibleBrand="mp41"}]' + payload_summary: 'MajorBrand="isom" MinorVersion=512 CompatibleBrands=[{CompatibleBrand="isom"}, {CompatibleBrand="iso2"}, {CompatibleBrand="avc1"}, {CompatibleBrand="mp41"}]' + children: [] +- box_type: free + path: free + offset: 32 + size: 8 + supported: true + payload_status: omitted + payload_fields: [] + children: [] +- box_type: mdat + path: mdat + offset: 40 + size: 6402 + supported: true + payload_status: omitted + payload_fields: [] + children: [] +- box_type: moov + path: moov + offset: 6442 + size: 1836 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: mvhd + path: moov/mvhd + offset: 6450 + size: 108 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: CreationTimeV0 + value_kind: unsigned + value: 0 + - name: ModificationTimeV0 + value_kind: unsigned + value: 0 + - name: Timescale + value_kind: unsigned + value: 1000 + - name: DurationV0 + value_kind: unsigned + value: 1024 + - name: Rate + value_kind: signed + value: 65536 + display_value: 1 + - name: Volume + value_kind: signed + value: 256 + - name: Matrix + value_kind: signed_array + value: + - 65536 + - 0 + - 0 + - 0 + - 65536 + - 0 + - 0 + - 0 + - 1073741824 + display_value: '[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000]' + - name: PreDefined + value_kind: signed_array + value: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - name: NextTrackID + value_kind: unsigned + value: 3 + payload_summary: 'Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=1000 DurationV0=1024 Rate=1 Volume=256 Matrix=[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000] PreDefined=[0, 0, 0, 0, 0, 0] NextTrackID=3' + children: [] + - box_type: trak + path: moov/trak + offset: 6558 + size: 743 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: tkhd + path: moov/trak/tkhd + offset: 6566 + size: 92 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 3 + display_value: 0x000003 + - name: CreationTimeV0 + value_kind: unsigned + value: 0 + - name: ModificationTimeV0 + value_kind: unsigned + value: 0 + - name: TrackID + value_kind: unsigned + value: 1 + - name: DurationV0 + value_kind: unsigned + value: 1000 + - name: Layer + value_kind: signed + value: 0 + - name: AlternateGroup + value_kind: signed + value: 0 + - name: Volume + value_kind: signed + value: 0 + - name: Matrix + value_kind: signed_array + value: + - 65536 + - 0 + - 0 + - 0 + - 65536 + - 0 + - 0 + - 0 + - 1073741824 + display_value: '[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000]' + - name: Width + value_kind: unsigned + value: 20971520 + display_value: 320 + - name: Height + value_kind: unsigned + value: 11796480 + display_value: 180 + payload_summary: 'Version=0 Flags=0x000003 CreationTimeV0=0 ModificationTimeV0=0 TrackID=1 DurationV0=1000 Layer=0 AlternateGroup=0 Volume=0 Matrix=[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000] Width=320 Height=180' + children: [] + - box_type: edts + path: moov/trak/edts + offset: 6658 + size: 36 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: elst + path: moov/trak/edts/elst + offset: 6666 + size: 28 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 3 + - 232 + - 0 + - 0 + - 8 + - 0 + - 0 + - 1 + - 0 + - 0 + display_value: '[{SegmentDurationV0=1000 MediaTimeV0=2048 MediaRateInteger=1}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1 Entries=[{SegmentDurationV0=1000 MediaTimeV0=2048 MediaRateInteger=1}]' + children: [] + - box_type: mdia + path: moov/trak/mdia + offset: 6694 + size: 607 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: mdhd + path: moov/trak/mdia/mdhd + offset: 6702 + size: 32 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: CreationTimeV0 + value_kind: unsigned + value: 0 + - name: ModificationTimeV0 + value_kind: unsigned + value: 0 + - name: Timescale + value_kind: unsigned + value: 10240 + - name: DurationV0 + value_kind: unsigned + value: 10240 + - name: Language + value_kind: unsigned_array + value: + - 5 + - 14 + - 7 + display_value: '"eng"' + - name: PreDefined + value_kind: unsigned + value: 0 + payload_summary: 'Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=10240 DurationV0=10240 Language="eng" PreDefined=0' + children: [] + - box_type: hdlr + path: moov/trak/mdia/hdlr + offset: 6734 + size: 44 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: PreDefined + value_kind: unsigned + value: 0 + - name: HandlerType + value_kind: bytes + value: + - 118 + - 105 + - 100 + - 101 + display_value: '"vide"' + - name: Name + value_kind: string + value: VideoHandle + display_value: '"VideoHandle"' + payload_summary: 'Version=0 Flags=0x000000 PreDefined=0 HandlerType="vide" Name="VideoHandle"' + children: [] + - box_type: minf + path: moov/trak/mdia/minf + offset: 6778 + size: 523 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: vmhd + path: moov/trak/mdia/minf/vmhd + offset: 6786 + size: 20 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 1 + display_value: 0x000001 + - name: Graphicsmode + value_kind: unsigned + value: 0 + - name: Opcolor + value_kind: unsigned_array + value: + - 0 + - 0 + - 0 + payload_summary: 'Version=0 Flags=0x000001 Graphicsmode=0 Opcolor=[0, 0, 0]' + children: [] + - box_type: dinf + path: moov/trak/mdia/minf/dinf + offset: 6806 + size: 36 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: dref + path: moov/trak/mdia/minf/dinf/dref + offset: 6814 + size: 28 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1' + children: + - box_type: 'url ' + path: 'moov/trak/mdia/minf/dinf/dref/url ' + offset: 6830 + size: 12 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 1 + display_value: 0x000001 + payload_summary: 'Version=0 Flags=0x000001' + children: [] + - box_type: stbl + path: moov/trak/mdia/minf/stbl + offset: 6842 + size: 459 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: stsd + path: moov/trak/mdia/minf/stbl/stsd + offset: 6850 + size: 167 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1' + children: + - box_type: avc1 + path: moov/trak/mdia/minf/stbl/stsd/avc1 + offset: 6866 + size: 151 + supported: true + payload_status: summary + payload_fields: + - name: DataReferenceIndex + value_kind: unsigned + value: 1 + - name: PreDefined + value_kind: unsigned + value: 0 + - name: PreDefined2 + value_kind: unsigned_array + value: + - 0 + - 0 + - 0 + - name: Width + value_kind: unsigned + value: 320 + - name: Height + value_kind: unsigned + value: 180 + - name: Horizresolution + value_kind: unsigned + value: 4718592 + - name: Vertresolution + value_kind: unsigned + value: 4718592 + - name: FrameCount + value_kind: unsigned + value: 1 + - name: Compressorname + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + display_value: '""' + - name: Depth + value_kind: unsigned + value: 24 + - name: PreDefined3 + value_kind: signed + value: -1 + payload_summary: 'DataReferenceIndex=1 PreDefined=0 PreDefined2=[0, 0, 0] Width=320 Height=180 Horizresolution=4718592 Vertresolution=4718592 FrameCount=1 Compressorname="" Depth=24 PreDefined3=-1' + children: + - box_type: avcC + path: moov/trak/mdia/minf/stbl/stsd/avc1/avcC + offset: 6952 + size: 49 + supported: true + payload_status: summary + payload_fields: + - name: ConfigurationVersion + value_kind: unsigned + value: 1 + display_value: 0x1 + - name: Profile + value_kind: unsigned + value: 100 + display_value: 0x64 + - name: ProfileCompatibility + value_kind: unsigned + value: 0 + display_value: 0x0 + - name: Level + value_kind: unsigned + value: 12 + display_value: 0xc + - name: LengthSizeMinusOne + value_kind: unsigned + value: 3 + display_value: 0x3 + - name: NumOfSequenceParameterSets + value_kind: unsigned + value: 1 + display_value: 0x1 + - name: SequenceParameterSets + value_kind: bytes + value: + - 0 + - 25 + - 103 + - 100 + - 0 + - 12 + - 172 + - 217 + - 65 + - 65 + - 159 + - 159 + - 1 + - 108 + - 128 + - 0 + - 0 + - 3 + - 0 + - 128 + - 0 + - 0 + - 10 + - 7 + - 138 + - 20 + - 203 + display_value: '[{Length=25 NALUnit=[0x67, 0x64, 0x0, 0xc, 0xac, 0xd9, 0x41, 0x41, 0x9f, 0x9f, 0x1, 0x6c, 0x80, 0x0, 0x0, 0x3, 0x0, 0x80, 0x0, 0x0, 0xa, 0x7, 0x8a, 0x14, 0xcb]}]' + - name: NumOfPictureParameterSets + value_kind: unsigned + value: 1 + display_value: 0x1 + - name: PictureParameterSets + value_kind: bytes + value: + - 0 + - 5 + - 104 + - 235 + - 236 + - 178 + - 44 + display_value: '[{Length=5 NALUnit=[0x68, 0xeb, 0xec, 0xb2, 0x2c]}]' + payload_summary: 'ConfigurationVersion=0x1 Profile=0x64 ProfileCompatibility=0x0 Level=0xc LengthSizeMinusOne=0x3 NumOfSequenceParameterSets=0x1 SequenceParameterSets=[{Length=25 NALUnit=[0x67, 0x64, 0x0, 0xc, 0xac, 0xd9, 0x41, 0x41, 0x9f, 0x9f, 0x1, 0x6c, 0x80, 0x0, 0x0, 0x3, 0x0, 0x80, 0x0, 0x0, 0xa, 0x7, 0x8a, 0x14, 0xcb]}] NumOfPictureParameterSets=0x1 PictureParameterSets=[{Length=5 NALUnit=[0x68, 0xeb, 0xec, 0xb2, 0x2c]}]' + children: [] + - box_type: pasp + path: moov/trak/mdia/minf/stbl/stsd/avc1/pasp + offset: 7001 + size: 16 + supported: true + payload_status: summary + payload_fields: + - name: HSpacing + value_kind: unsigned + value: 1 + - name: VSpacing + value_kind: unsigned + value: 1 + payload_summary: 'HSpacing=1 VSpacing=1' + children: [] + - box_type: stts + path: moov/trak/mdia/minf/stbl/stts + offset: 7017 + size: 24 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 10 + - 0 + - 0 + - 4 + - 0 + display_value: '[{SampleCount=10 SampleDelta=1024}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1 Entries=[{SampleCount=10 SampleDelta=1024}]' + children: [] + - box_type: stss + path: moov/trak/mdia/minf/stbl/stss + offset: 7041 + size: 20 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + - name: SampleNumber + value_kind: unsigned_array + value: + - 1 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1 SampleNumber=[1]' + children: [] + - box_type: ctts + path: moov/trak/mdia/minf/stbl/ctts + offset: 7061 + size: 88 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 9 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 2 + - 0 + - 0 + - 8 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 20 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 8 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 12 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 12 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 4 + - 0 + display_value: '[{SampleCount=2 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=5120}, {SampleCount=1 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=0}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=9 Entries=[{SampleCount=2 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=5120}, {SampleCount=1 SampleOffsetV0=2048}, {SampleCount=1 SampleOffsetV0=0}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}, {SampleCount=1 SampleOffsetV0=3072}, {SampleCount=1 SampleOffsetV0=1024}]' + children: [] + - box_type: stsc + path: moov/trak/mdia/minf/stbl/stsc + offset: 7149 + size: 40 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 2 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 2 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 2 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 1 + display_value: '[{FirstChunk=1 SamplesPerChunk=2 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=1 SampleDescriptionIndex=1}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=2 Entries=[{FirstChunk=1 SamplesPerChunk=2 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=1 SampleDescriptionIndex=1}]' + children: [] + - box_type: stsz + path: moov/trak/mdia/minf/stbl/stsz + offset: 7189 + size: 60 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: SampleSize + value_kind: unsigned + value: 0 + - name: SampleCount + value_kind: unsigned + value: 10 + - name: EntrySize + value_kind: unsigned_array + value: + - 3679 + - 86 + - 545 + - 180 + - 69 + - 60 + - 182 + - 22 + - 204 + - 15 + payload_summary: 'Version=0 Flags=0x000000 SampleSize=0 SampleCount=10 EntrySize=[3679, 86, 545, 180, 69, 60, 182, 22, 204, 15]' + children: [] + - box_type: stco + path: moov/trak/mdia/minf/stbl/stco + offset: 7249 + size: 52 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 9 + - name: ChunkOffset + value_kind: unsigned_array + value: + - 48 + - 3836 + - 4527 + - 4864 + - 5043 + - 5227 + - 5560 + - 5702 + - 6038 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=9 ChunkOffset=[48, 3836, 4527, 4864, 5043, 5227, 5560, 5702, 6038]' + children: [] + - box_type: trak + path: moov/trak + offset: 7301 + size: 844 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: tkhd + path: moov/trak/tkhd + offset: 7309 + size: 92 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 3 + display_value: 0x000003 + - name: CreationTimeV0 + value_kind: unsigned + value: 0 + - name: ModificationTimeV0 + value_kind: unsigned + value: 0 + - name: TrackID + value_kind: unsigned + value: 2 + - name: DurationV0 + value_kind: unsigned + value: 1024 + - name: Layer + value_kind: signed + value: 0 + - name: AlternateGroup + value_kind: signed + value: 1 + - name: Volume + value_kind: signed + value: 256 + - name: Matrix + value_kind: signed_array + value: + - 65536 + - 0 + - 0 + - 0 + - 65536 + - 0 + - 0 + - 0 + - 1073741824 + display_value: '[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000]' + - name: Width + value_kind: unsigned + value: 0 + display_value: 0 + - name: Height + value_kind: unsigned + value: 0 + display_value: 0 + payload_summary: 'Version=0 Flags=0x000003 CreationTimeV0=0 ModificationTimeV0=0 TrackID=2 DurationV0=1024 Layer=0 AlternateGroup=1 Volume=256 Matrix=[0x10000, 0x0, 0x0, 0x0, 0x10000, 0x0, 0x0, 0x0, 0x40000000] Width=0 Height=0' + children: [] + - box_type: edts + path: moov/trak/edts + offset: 7401 + size: 36 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: elst + path: moov/trak/edts/elst + offset: 7409 + size: 28 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 3 + - 232 + - 0 + - 0 + - 4 + - 0 + - 0 + - 1 + - 0 + - 0 + display_value: '[{SegmentDurationV0=1000 MediaTimeV0=1024 MediaRateInteger=1}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1 Entries=[{SegmentDurationV0=1000 MediaTimeV0=1024 MediaRateInteger=1}]' + children: [] + - box_type: mdia + path: moov/trak/mdia + offset: 7437 + size: 708 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: mdhd + path: moov/trak/mdia/mdhd + offset: 7445 + size: 32 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: CreationTimeV0 + value_kind: unsigned + value: 0 + - name: ModificationTimeV0 + value_kind: unsigned + value: 0 + - name: Timescale + value_kind: unsigned + value: 44100 + - name: DurationV0 + value_kind: unsigned + value: 45124 + - name: Language + value_kind: unsigned_array + value: + - 5 + - 14 + - 7 + display_value: '"eng"' + - name: PreDefined + value_kind: unsigned + value: 0 + payload_summary: 'Version=0 Flags=0x000000 CreationTimeV0=0 ModificationTimeV0=0 Timescale=44100 DurationV0=45124 Language="eng" PreDefined=0' + children: [] + - box_type: hdlr + path: moov/trak/mdia/hdlr + offset: 7477 + size: 44 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: PreDefined + value_kind: unsigned + value: 0 + - name: HandlerType + value_kind: bytes + value: + - 115 + - 111 + - 117 + - 110 + display_value: '"soun"' + - name: Name + value_kind: string + value: SoundHandle + display_value: '"SoundHandle"' + payload_summary: 'Version=0 Flags=0x000000 PreDefined=0 HandlerType="soun" Name="SoundHandle"' + children: [] + - box_type: minf + path: moov/trak/mdia/minf + offset: 7521 + size: 624 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: smhd + path: moov/trak/mdia/minf/smhd + offset: 7529 + size: 16 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: Balance + value_kind: signed + value: 0 + display_value: 0 + payload_summary: 'Version=0 Flags=0x000000 Balance=0' + children: [] + - box_type: dinf + path: moov/trak/mdia/minf/dinf + offset: 7545 + size: 36 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: dref + path: moov/trak/mdia/minf/dinf/dref + offset: 7553 + size: 28 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1' + children: + - box_type: 'url ' + path: 'moov/trak/mdia/minf/dinf/dref/url ' + offset: 7569 + size: 12 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 1 + display_value: 0x000001 + payload_summary: 'Version=0 Flags=0x000001' + children: [] + - box_type: stbl + path: moov/trak/mdia/minf/stbl + offset: 7581 + size: 564 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: stsd + path: moov/trak/mdia/minf/stbl/stsd + offset: 7589 + size: 106 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 1 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=1' + children: + - box_type: mp4a + path: moov/trak/mdia/minf/stbl/stsd/mp4a + offset: 7605 + size: 90 + supported: true + payload_status: summary + payload_fields: + - name: DataReferenceIndex + value_kind: unsigned + value: 1 + - name: EntryVersion + value_kind: unsigned + value: 0 + - name: ChannelCount + value_kind: unsigned + value: 2 + - name: SampleSize + value_kind: unsigned + value: 16 + - name: PreDefined + value_kind: unsigned + value: 0 + - name: SampleRate + value_kind: unsigned + value: 2890137600 + display_value: 44100 + payload_summary: 'DataReferenceIndex=1 EntryVersion=0 ChannelCount=2 SampleSize=16 PreDefined=0 SampleRate=44100' + children: + - box_type: esds + path: moov/trak/mdia/minf/stbl/stsd/mp4a/esds + offset: 7641 + size: 54 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: Descriptors + value_kind: bytes + value: + - 3 + - 128 + - 128 + - 128 + - 37 + - 0 + - 2 + - 0 + - 4 + - 128 + - 128 + - 128 + - 23 + - 64 + - 21 + - 0 + - 0 + - 0 + - 0 + - 0 + - 41 + - 74 + - 0 + - 0 + - 41 + - 74 + - 5 + - 128 + - 128 + - 128 + - 5 + - 18 + - 16 + - 86 + - 229 + - 0 + - 6 + - 128 + - 128 + - 128 + - 1 + - 2 + display_value: '[{Tag=ESDescr Size=37 ESID=2 StreamDependenceFlag=false UrlFlag=false OcrStreamFlag=false StreamPriority=0}, {Tag=DecoderConfigDescr Size=23 ObjectTypeIndication=0x40 StreamType=5 UpStream=false Reserved=true BufferSizeDB=0 MaxBitrate=10570 AvgBitrate=10570}, {Tag=DecSpecificInfo Size=5 Data=[0x12, 0x10, 0x56, 0xe5, 0x0]}, {Tag=SLConfigDescr Size=1 Data=[0x2]}]' + payload_summary: 'Version=0 Flags=0x000000 Descriptors=[{Tag=ESDescr Size=37 ESID=2 StreamDependenceFlag=false UrlFlag=false OcrStreamFlag=false StreamPriority=0}, {Tag=DecoderConfigDescr Size=23 ObjectTypeIndication=0x40 StreamType=5 UpStream=false Reserved=true BufferSizeDB=0 MaxBitrate=10570 AvgBitrate=10570}, {Tag=DecSpecificInfo Size=5 Data=[0x12, 0x10, 0x56, 0xe5, 0x0]}, {Tag=SLConfigDescr Size=1 Data=[0x2]}]' + children: [] + - box_type: stts + path: moov/trak/mdia/minf/stbl/stts + offset: 7695 + size: 48 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 4 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 5 + - 225 + - 0 + - 0 + - 0 + - 41 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 2 + - 99 + display_value: '[{SampleCount=1 SampleDelta=1024}, {SampleCount=1 SampleDelta=1505}, {SampleCount=41 SampleDelta=1024}, {SampleCount=1 SampleDelta=611}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=4 Entries=[{SampleCount=1 SampleDelta=1024}, {SampleCount=1 SampleDelta=1505}, {SampleCount=41 SampleDelta=1024}, {SampleCount=1 SampleDelta=611}]' + children: [] + - box_type: stsc + path: moov/trak/mdia/minf/stbl/stsc + offset: 7743 + size: 100 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 7 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 2 + - 0 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 3 + - 0 + - 0 + - 0 + - 5 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 6 + - 0 + - 0 + - 0 + - 5 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 7 + - 0 + - 0 + - 0 + - 4 + - 0 + - 0 + - 0 + - 1 + - 0 + - 0 + - 0 + - 9 + - 0 + - 0 + - 0 + - 13 + - 0 + - 0 + - 0 + - 1 + display_value: '[{FirstChunk=1 SamplesPerChunk=1 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=3 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=4 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=6 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=7 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=9 SamplesPerChunk=13 SampleDescriptionIndex=1}]' + payload_summary: 'Version=0 Flags=0x000000 EntryCount=7 Entries=[{FirstChunk=1 SamplesPerChunk=1 SampleDescriptionIndex=1}, {FirstChunk=2 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=3 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=4 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=6 SamplesPerChunk=5 SampleDescriptionIndex=1}, {FirstChunk=7 SamplesPerChunk=4 SampleDescriptionIndex=1}, {FirstChunk=9 SamplesPerChunk=13 SampleDescriptionIndex=1}]' + children: [] + - box_type: stsz + path: moov/trak/mdia/minf/stbl/stsz + offset: 7843 + size: 196 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: SampleSize + value_kind: unsigned + value: 0 + - name: SampleCount + value_kind: unsigned + value: 44 + - name: EntrySize + value_kind: unsigned_array + value: + - 23 + - 39 + - 35 + - 36 + - 36 + - 35 + - 31 + - 33 + - 28 + - 30 + - 33 + - 29 + - 23 + - 25 + - 25 + - 36 + - 27 + - 36 + - 35 + - 25 + - 28 + - 36 + - 27 + - 29 + - 28 + - 29 + - 34 + - 36 + - 30 + - 32 + - 34 + - 29 + - 24 + - 34 + - 35 + - 28 + - 24 + - 30 + - 29 + - 29 + - 27 + - 31 + - 34 + - 35 + payload_summary: 'Version=0 Flags=0x000000 SampleSize=0 SampleCount=44 EntrySize=[23, 39, 35, 36, 36, 35, 31, 33, 28, 30, 33, 29, 23, 25, 25, 36, 27, 36, 35, 25, 28, 36, 27, 29, 28, 29, 34, 36, 30, 32, 34, 29, 24, 34, 35, 28, 24, 30, 29, 29, 27, 31, 34, 35]' + children: [] + - box_type: stco + path: moov/trak/mdia/minf/stbl/stco + offset: 8039 + size: 52 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: EntryCount + value_kind: unsigned + value: 9 + - name: ChunkOffset + value_kind: unsigned_array + value: + - 3813 + - 4381 + - 4707 + - 4933 + - 5103 + - 5409 + - 5582 + - 5906 + - 6053 + payload_summary: 'Version=0 Flags=0x000000 EntryCount=9 ChunkOffset=[3813, 4381, 4707, 4933, 5103, 5409, 5582, 5906, 6053]' + children: [] + - box_type: sgpd + path: moov/trak/mdia/minf/stbl/sgpd + offset: 8091 + size: 26 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 1 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: GroupingType + value_kind: bytes + value: + - 114 + - 111 + - 108 + - 108 + display_value: '"roll"' + - name: DefaultLength + value_kind: unsigned + value: 2 + - name: EntryCount + value_kind: unsigned + value: 1 + - name: RollDistances + value_kind: bytes + value: + - 255 + - 255 + display_value: '[-1]' + payload_summary: 'Version=1 Flags=0x000000 GroupingType="roll" DefaultLength=2 EntryCount=1 RollDistances=[-1]' + children: [] + - box_type: sbgp + path: moov/trak/mdia/minf/stbl/sbgp + offset: 8117 + size: 28 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: GroupingType + value_kind: unsigned + value: 1919904876 + - name: EntryCount + value_kind: unsigned + value: 1 + - name: Entries + value_kind: bytes + value: + - 0 + - 0 + - 0 + - 44 + - 0 + - 0 + - 0 + - 1 + display_value: '[{SampleCount=44 GroupDescriptionIndex=1}]' + payload_summary: 'Version=0 Flags=0x000000 GroupingType=1919904876 EntryCount=1 Entries=[{SampleCount=44 GroupDescriptionIndex=1}]' + children: [] + - box_type: udta + path: moov/udta + offset: 8145 + size: 133 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: meta + path: moov/udta/meta + offset: 8153 + size: 90 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + payload_summary: 'Version=0 Flags=0x000000' + children: + - box_type: hdlr + path: moov/udta/meta/hdlr + offset: 8165 + size: 33 + supported: true + payload_status: summary + payload_fields: + - name: Version + value_kind: unsigned + value: 0 + - name: Flags + value_kind: unsigned + value: 0 + display_value: 0x000000 + - name: PreDefined + value_kind: unsigned + value: 0 + - name: HandlerType + value_kind: bytes + value: + - 109 + - 100 + - 105 + - 114 + display_value: '"mdir"' + - name: Name + value_kind: string + value: '' + display_value: '""' + payload_summary: 'Version=0 Flags=0x000000 PreDefined=0 HandlerType="mdir" Name=""' + children: [] + - box_type: ilst + path: moov/udta/meta/ilst + offset: 8198 + size: 45 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: '(c)too' + path: 'moov/udta/meta/ilst/(c)too' + offset: 8206 + size: 37 + supported: true + payload_status: empty + payload_fields: [] + children: + - box_type: data + path: 'moov/udta/meta/ilst/(c)too/data' + offset: 8214 + size: 29 + supported: true + payload_status: summary + payload_fields: + - name: DataType + value_kind: unsigned + value: 1 + display_value: UTF8 + - name: DataLang + value_kind: unsigned + value: 0 + - name: EncodingTool + value_kind: bytes + value: + - 76 + - 97 + - 118 + - 102 + - 53 + - 56 + - 46 + - 50 + - 57 + - 46 + - 49 + - 48 + - 48 + display_value: '"Lavf58.29.100"' + payload_summary: 'DataType=UTF8 DataLang=0 EncodingTool="Lavf58.29.100"' + children: [] + - box_type: loci + path: moov/udta/loci + offset: 8243 + size: 35 + supported: false + payload_status: omitted + payload_fields: [] + children: [] diff --git a/tests/golden/cli_probe/sample.json b/tests/golden/cli_probe/sample.json index e237386..637a1e0 100644 --- a/tests/golden/cli_probe/sample.json +++ b/tests/golden/cli_probe/sample.json @@ -18,14 +18,32 @@ "Duration": 10240, "DurationSeconds": 1, "Codec": "avc1.64000C", + "CodecFamily": "avc", "Encrypted": false, + "HandlerType": "vide", + "Language": "eng", + "SampleEntryType": "avc1", "Width": 320, "Height": 180, "SampleNum": 10, "ChunkNum": 9, "IDRFrameNum": 1, "Bitrate": 40336, - "MaxBitrate": 40336 + "MaxBitrate": 40336, + "CodecDetails": { + "Kind": "avc", + "ConfigurationVersion": 1, + "Profile": 100, + "ProfileCompatibility": 0, + "Level": 12, + "LengthSize": 4 + }, + "MediaCharacteristics": { + "PixelAspectRatio": { + "HSpacing": 1, + "VSpacing": 1 + } + } }, { "TrackID": 2, @@ -33,11 +51,24 @@ "Duration": 45124, "DurationSeconds": 1.02322, "Codec": "mp4a.40.2", + "CodecFamily": "mp4_audio", "Encrypted": false, + "HandlerType": "soun", + "Language": "eng", + "SampleEntryType": "mp4a", + "ChannelCount": 2, + "SampleRate": 44100, "SampleNum": 44, "ChunkNum": 9, "Bitrate": 10570, - "MaxBitrate": 10632 + "MaxBitrate": 10632, + "CodecDetails": { + "Kind": "mp4_audio", + "ObjectTypeIndication": 64, + "AudioObjectType": 2, + "ChannelCount": 2, + "SampleRate": 44100 + } } ] } diff --git a/tests/golden/cli_probe/sample.yaml b/tests/golden/cli_probe/sample.yaml index 4ab5581..7050bb8 100644 --- a/tests/golden/cli_probe/sample.yaml +++ b/tests/golden/cli_probe/sample.yaml @@ -15,7 +15,11 @@ tracks: duration: 10240 duration_seconds: 1 codec: avc1.64000C + codec_family: avc encrypted: false + handler_type: vide + language: eng + sample_entry_type: avc1 width: 320 height: 180 sample_num: 10 @@ -23,13 +27,36 @@ tracks: idr_frame_num: 1 bitrate: 40336 max_bitrate: 40336 + codec_details: + kind: avc + configuration_version: 1 + profile: 100 + profile_compatibility: 0 + level: 12 + length_size: 4 + media_characteristics: + pixel_aspect_ratio: + h_spacing: 1 + v_spacing: 1 - track_id: 2 timescale: 44100 duration: 45124 duration_seconds: 1.02322 codec: mp4a.40.2 + codec_family: mp4_audio encrypted: false + handler_type: soun + language: eng + sample_entry_type: mp4a + channel_count: 2 + sample_rate: 44100 sample_num: 44 chunk_num: 9 bitrate: 10570 max_bitrate: 10632 + codec_details: + kind: mp4_audio + object_type_indication: 64 + audio_object_type: 2 + channel_count: 2 + sample_rate: 44100 diff --git a/tests/golden/cli_probe/sample_light.json b/tests/golden/cli_probe/sample_light.json new file mode 100644 index 0000000..71eb789 --- /dev/null +++ b/tests/golden/cli_probe/sample_light.json @@ -0,0 +1,65 @@ +{ + "MajorBrand": "isom", + "MinorVersion": 512, + "CompatibleBrands": [ + "isom", + "iso2", + "avc1", + "mp41" + ], + "FastStart": false, + "Timescale": 1000, + "Duration": 1024, + "DurationSeconds": 1.024, + "Tracks": [ + { + "TrackID": 1, + "Timescale": 10240, + "Duration": 10240, + "DurationSeconds": 1, + "Codec": "avc1.64000C", + "CodecFamily": "avc", + "Encrypted": false, + "HandlerType": "vide", + "Language": "eng", + "SampleEntryType": "avc1", + "Width": 320, + "Height": 180, + "CodecDetails": { + "Kind": "avc", + "ConfigurationVersion": 1, + "Profile": 100, + "ProfileCompatibility": 0, + "Level": 12, + "LengthSize": 4 + }, + "MediaCharacteristics": { + "PixelAspectRatio": { + "HSpacing": 1, + "VSpacing": 1 + } + } + }, + { + "TrackID": 2, + "Timescale": 44100, + "Duration": 45124, + "DurationSeconds": 1.02322, + "Codec": "mp4a.40.2", + "CodecFamily": "mp4_audio", + "Encrypted": false, + "HandlerType": "soun", + "Language": "eng", + "SampleEntryType": "mp4a", + "ChannelCount": 2, + "SampleRate": 44100, + "CodecDetails": { + "Kind": "mp4_audio", + "ObjectTypeIndication": 64, + "AudioObjectType": 2, + "ChannelCount": 2, + "SampleRate": 44100 + } + } + ] +} diff --git a/tests/golden/cli_probe/sample_light.yaml b/tests/golden/cli_probe/sample_light.yaml new file mode 100644 index 0000000..096dda3 --- /dev/null +++ b/tests/golden/cli_probe/sample_light.yaml @@ -0,0 +1,53 @@ +major_brand: isom +minor_version: 512 +compatible_brands: +- isom +- iso2 +- avc1 +- mp41 +fast_start: false +timescale: 1000 +duration: 1024 +duration_seconds: 1.024 +tracks: +- track_id: 1 + timescale: 10240 + duration: 10240 + duration_seconds: 1 + codec: avc1.64000C + codec_family: avc + encrypted: false + handler_type: vide + language: eng + sample_entry_type: avc1 + width: 320 + height: 180 + codec_details: + kind: avc + configuration_version: 1 + profile: 100 + profile_compatibility: 0 + level: 12 + length_size: 4 + media_characteristics: + pixel_aspect_ratio: + h_spacing: 1 + v_spacing: 1 +- track_id: 2 + timescale: 44100 + duration: 45124 + duration_seconds: 1.02322 + codec: mp4a.40.2 + codec_family: mp4_audio + encrypted: false + handler_type: soun + language: eng + sample_entry_type: mp4a + channel_count: 2 + sample_rate: 44100 + codec_details: + kind: mp4_audio + object_type_indication: 64 + audio_object_type: 2 + channel_count: 2 + sample_rate: 44100 diff --git a/tests/golden/cli_psshdump/filtered_kid.yaml b/tests/golden/cli_psshdump/filtered_kid.yaml new file mode 100644 index 0000000..34f8245 --- /dev/null +++ b/tests/golden/cli_psshdump/filtered_kid.yaml @@ -0,0 +1,16 @@ +entries: +- index: 1 + path: moof/pssh + offset: 89 + size: 54 + version: 1 + flags: 0 + system_id: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed + kid_count: 1 + kids: + - fedcba98-7654-3210-fedc-ba9876543210 + data_size: 2 + data_bytes: + - 187 + - 204 + raw_box_base64: 'AAAANnBzc2gBAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAH+3LqYdlQyEP7cuph2VDIQAAAAArvM' diff --git a/tests/golden/cli_psshdump/filtered_path.json b/tests/golden/cli_psshdump/filtered_path.json new file mode 100644 index 0000000..8cab3b7 --- /dev/null +++ b/tests/golden/cli_psshdump/filtered_path.json @@ -0,0 +1,23 @@ +{ + "Entries": [ + { + "Index": 1, + "Path": "moof/pssh", + "Offset": 89, + "Size": 54, + "Version": 1, + "Flags": 0, + "SystemId": "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + "KidCount": 1, + "Kids": [ + "fedcba98-7654-3210-fedc-ba9876543210" + ], + "DataSize": 2, + "DataBytes": [ + 187, + 204 + ], + "RawBoxBase64": "AAAANnBzc2gBAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAH+3LqYdlQyEP7cuph2VDIQAAAAArvM" + } + ] +} diff --git a/tests/golden/cli_psshdump/filtered_system_id.json b/tests/golden/cli_psshdump/filtered_system_id.json new file mode 100644 index 0000000..8cab3b7 --- /dev/null +++ b/tests/golden/cli_psshdump/filtered_system_id.json @@ -0,0 +1,23 @@ +{ + "Entries": [ + { + "Index": 1, + "Path": "moof/pssh", + "Offset": 89, + "Size": 54, + "Version": 1, + "Flags": 0, + "SystemId": "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", + "KidCount": 1, + "Kids": [ + "fedcba98-7654-3210-fedc-ba9876543210" + ], + "DataSize": 2, + "DataBytes": [ + 187, + 204 + ], + "RawBoxBase64": "AAAANnBzc2gBAAAA7e+LqXnWSs6jyCfc1R0h7QAAAAH+3LqYdlQyEP7cuph2VDIQAAAAArvM" + } + ] +} diff --git a/tests/golden/cli_psshdump/sample_init.json b/tests/golden/cli_psshdump/sample_init.json new file mode 100644 index 0000000..84b1c53 --- /dev/null +++ b/tests/golden/cli_psshdump/sample_init.json @@ -0,0 +1,21 @@ +{ + "Entries": [ + { + "Index": 0, + "Path": "moov/pssh", + "Offset": 1307, + "Size": 52, + "Version": 1, + "Flags": 0, + "SystemId": "1077efec-c0b2-4d02-ace3-3c1e52e2fb4b", + "KidCount": 1, + "Kids": [ + "01234567-89ab-cdef-0123-456789abcdef" + ], + "DataSize": 0, + "DataBytes": [ + ], + "RawBoxBase64": "AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniavN7wEjRWeJq83vAAAAAA==" + } + ] +} diff --git a/tests/golden/cli_psshdump/sample_init.yaml b/tests/golden/cli_psshdump/sample_init.yaml new file mode 100644 index 0000000..8163668 --- /dev/null +++ b/tests/golden/cli_psshdump/sample_init.yaml @@ -0,0 +1,14 @@ +entries: +- index: 0 + path: moov/pssh + offset: 1307 + size: 52 + version: 1 + flags: 0 + system_id: 1077efec-c0b2-4d02-ace3-3c1e52e2fb4b + kid_count: 1 + kids: + - 01234567-89ab-cdef-0123-456789abcdef + data_size: 0 + data_bytes: [] + raw_box_base64: 'AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAEBI0VniavN7wEjRWeJq83vAAAAAA==' diff --git a/tests/parity_harness.rs b/tests/parity_harness.rs index fa3b6ec..fcd91b6 100644 --- a/tests/parity_harness.rs +++ b/tests/parity_harness.rs @@ -9,8 +9,8 @@ use mp4forge::FourCc; use mp4forge::cli::{divide, edit, extract, probe as cli_probe, pssh}; use mp4forge::extract::extract_box; use mp4forge::probe::{ - ProbeError, TrackCodec, average_sample_bitrate, average_segment_bitrate, find_idr_frames, - max_sample_bitrate, max_segment_bitrate, probe, + ProbeError, ProbeOptions, TrackCodec, average_sample_bitrate, average_segment_bitrate, + find_idr_frames, max_sample_bitrate, max_segment_bitrate, probe, probe_with_options, }; use mp4forge::walk::BoxPath; @@ -301,6 +301,61 @@ fn probe_report_matches_library_summary_across_shared_fixtures() { } } +#[test] +fn lightweight_probe_report_matches_library_summary_across_representative_fixtures() { + for file_name in ["sample.mp4", "sample_fragmented.mp4", "sample_qt.mp4"] { + let path = fixture_path(file_name); + + let mut summary_file = fs::File::open(&path).unwrap(); + let summary = probe_with_options(&mut summary_file, ProbeOptions::lightweight()).unwrap(); + + let mut report_file = fs::File::open(&path).unwrap(); + let report = cli_probe::build_report_with_options( + &mut report_file, + cli_probe::ProbeReportOptions::lightweight(), + ) + .unwrap(); + + assert_eq!(report.major_brand, summary.major_brand.to_string()); + assert_eq!( + report.compatible_brands, + summary + .compatible_brands + .iter() + .map(ToString::to_string) + .collect::>() + ); + assert_eq!(report.fast_start, summary.fast_start); + assert_eq!(report.timescale, summary.timescale); + assert_eq!(report.duration, summary.duration); + assert!(summary.segments.is_empty()); + assert_eq!(report.tracks.len(), summary.tracks.len()); + + for (summary_track, report_track) in summary.tracks.iter().zip(report.tracks.iter()) { + assert!(summary_track.samples.is_empty()); + assert!(summary_track.chunks.is_empty()); + + assert_eq!(report_track.track_id, summary_track.track_id); + assert_eq!(report_track.timescale, summary_track.timescale); + assert_eq!(report_track.duration, summary_track.duration); + assert_eq!(report_track.encrypted, summary_track.encrypted); + assert_eq!( + report_track.width, + summary_track.avc.as_ref().map(|avc| avc.width) + ); + assert_eq!( + report_track.height, + summary_track.avc.as_ref().map(|avc| avc.height) + ); + assert_eq!(report_track.sample_num, None); + assert_eq!(report_track.chunk_num, None); + assert_eq!(report_track.idr_frame_num, None); + assert_eq!(report_track.bitrate, None); + assert_eq!(report_track.max_bitrate, None); + } + } +} + #[test] fn extract_command_matches_library_box_boundaries_on_shared_fixtures() { let cases = [ diff --git a/tests/probe.rs b/tests/probe.rs index 2843ea1..8f96fda 100644 --- a/tests/probe.rs +++ b/tests/probe.rs @@ -3,22 +3,37 @@ use std::io::Cursor; use mp4forge::boxes::AnyTypeBox; +use mp4forge::boxes::av1::AV1CodecConfiguration; +use mp4forge::boxes::etsi_ts_102_366::Dac3; use mp4forge::boxes::iso14496_12::{ - AVCDecoderConfiguration, AudioSampleEntry, Ctts, CttsEntry, Edts, Elst, ElstEntry, Ftyp, Mdhd, - Mdia, Minf, Moof, Moov, Mvhd, SampleEntry, Stbl, Stco, Stsc, StscEntry, Stsd, Stsz, Stts, - SttsEntry, TFHD_DEFAULT_SAMPLE_DURATION_PRESENT, TFHD_DEFAULT_SAMPLE_SIZE_PRESENT, + AVCDecoderConfiguration, AudioSampleEntry, Btrt, Colr, Ctts, CttsEntry, Edts, Elst, ElstEntry, + Fiel, Frma, Ftyp, HEVCDecoderConfiguration, Hdlr, Mdhd, Mdia, Minf, Moof, Moov, Mvhd, Pasp, + SampleEntry, Schm, Sinf, Stbl, Stco, Stsc, StscEntry, Stsd, Stsz, Stts, SttsEntry, + TFHD_DEFAULT_SAMPLE_DURATION_PRESENT, TFHD_DEFAULT_SAMPLE_SIZE_PRESENT, TRUN_SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT, TRUN_SAMPLE_DURATION_PRESENT, - TRUN_SAMPLE_SIZE_PRESENT, Tfdt, Tfhd, Tkhd, Traf, Trak, Trun, TrunEntry, VisualSampleEntry, + TRUN_SAMPLE_SIZE_PRESENT, TextSubtitleSampleEntry, Tfdt, Tfhd, Tkhd, Traf, Trak, Trun, + TrunEntry, VisualSampleEntry, XMLSubtitleSampleEntry, }; use mp4forge::boxes::iso14496_14::{ DECODER_CONFIG_DESCRIPTOR_TAG, DECODER_SPECIFIC_INFO_TAG, DecoderConfigDescriptor, Descriptor, Esds, }; +use mp4forge::boxes::iso14496_30::{WVTTSampleEntry, WebVTTConfigurationBox, WebVTTSourceLabelBox}; +use mp4forge::boxes::iso23001_5::PcmC; +use mp4forge::boxes::opus::DOps; +use mp4forge::boxes::vp::VpCodecConfiguration; use mp4forge::codec::{CodecBox, MutableBox, marshal}; use mp4forge::probe::{ - AacProfileInfo, EditListEntry, TrackCodec, average_sample_bitrate, average_segment_bitrate, - detect_aac_profile, find_idr_frames, max_sample_bitrate, max_segment_bitrate, probe, - probe_bytes, probe_fra, probe_fra_bytes, + AacProfileInfo, EditListEntry, ProbeOptions, TrackCodec, TrackCodecDetails, TrackCodecFamily, + average_sample_bitrate, average_segment_bitrate, detect_aac_profile, find_idr_frames, + max_sample_bitrate, max_segment_bitrate, probe, probe_bytes, probe_bytes_with_options, + probe_codec_detailed, probe_codec_detailed_bytes, probe_codec_detailed_bytes_with_options, + probe_codec_detailed_with_options, probe_detailed, probe_detailed_bytes, + probe_detailed_bytes_with_options, probe_detailed_with_options, probe_fra, probe_fra_bytes, + probe_fra_codec_detailed, probe_fra_codec_detailed_bytes, probe_fra_detailed, + probe_fra_detailed_bytes, probe_media_characteristics, probe_media_characteristics_bytes, + probe_media_characteristics_bytes_with_options, probe_media_characteristics_with_options, + probe_with_options, }; use mp4forge::{BoxInfo, FourCc}; @@ -128,6 +143,156 @@ fn probe_bytes_matches_cursor_based_probe() { assert_eq!(actual, expected); } +#[test] +fn probe_with_options_skips_expensive_expansions_but_preserves_core_summary() { + let file = build_movie_file(); + let mut reader = Cursor::new(file.clone()); + + let full = probe(&mut Cursor::new(file)).unwrap(); + let info = probe_with_options(&mut reader, ProbeOptions::lightweight()).unwrap(); + + assert_eq!(info.major_brand, full.major_brand); + assert_eq!(info.minor_version, full.minor_version); + assert_eq!(info.compatible_brands, full.compatible_brands); + assert_eq!(info.fast_start, full.fast_start); + assert_eq!(info.timescale, full.timescale); + assert_eq!(info.duration, full.duration); + assert_eq!(info.segments, Vec::new()); + assert_eq!(info.tracks.len(), full.tracks.len()); + + for (light_track, full_track) in info.tracks.iter().zip(full.tracks.iter()) { + assert_eq!(light_track.track_id, full_track.track_id); + assert_eq!(light_track.timescale, full_track.timescale); + assert_eq!(light_track.duration, full_track.duration); + assert_eq!(light_track.codec, full_track.codec); + assert_eq!(light_track.encrypted, full_track.encrypted); + assert_eq!(light_track.edit_list, full_track.edit_list); + assert_eq!(light_track.avc, full_track.avc); + assert_eq!(light_track.mp4a, full_track.mp4a); + assert!(light_track.samples.is_empty()); + assert!(light_track.chunks.is_empty()); + } +} + +#[test] +fn probe_with_options_bytes_matches_cursor_based_probe() { + let file = build_movie_file(); + let options = ProbeOptions::lightweight(); + let expected = probe_with_options(&mut Cursor::new(file.clone()), options).unwrap(); + let actual = probe_bytes_with_options(&file, options).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn probe_detailed_exposes_handler_language_sample_entry_and_codec_family() { + let file = build_movie_file(); + let mut reader = Cursor::new(file); + + let info = probe_detailed(&mut reader).unwrap(); + + assert_eq!(info.tracks.len(), 2); + + let video = &info.tracks[0]; + assert_eq!(video.summary.track_id, 1); + assert_eq!(video.codec_family, TrackCodecFamily::Avc); + assert_eq!(video.handler_type, Some(fourcc("vide"))); + assert_eq!(video.language.as_deref(), Some("eng")); + assert_eq!(video.sample_entry_type, Some(fourcc("avc1"))); + assert_eq!(video.original_format, None); + assert_eq!(video.display_width, Some(320)); + assert_eq!(video.display_height, Some(180)); + assert_eq!(video.channel_count, None); + assert_eq!(video.sample_rate, None); + + let audio = &info.tracks[1]; + assert_eq!(audio.summary.track_id, 2); + assert_eq!(audio.codec_family, TrackCodecFamily::Mp4Audio); + assert_eq!(audio.handler_type, Some(fourcc("soun"))); + assert_eq!(audio.language.as_deref(), Some("eng")); + assert_eq!(audio.sample_entry_type, Some(fourcc("mp4a"))); + assert_eq!(audio.original_format, None); + assert_eq!(audio.display_width, None); + assert_eq!(audio.display_height, None); + assert_eq!(audio.channel_count, Some(2)); + assert_eq!(audio.sample_rate, Some(48_000)); +} + +#[test] +fn probe_detailed_bytes_matches_cursor_based_probe_detailed() { + let file = build_movie_file(); + let expected = probe_detailed(&mut Cursor::new(file.clone())).unwrap(); + let actual = probe_detailed_bytes(&file).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn probe_detailed_with_options_preserves_metadata_without_sample_tables() { + let file = build_av01_movie_file(); + let mut reader = Cursor::new(file.clone()); + + let full = probe_detailed(&mut Cursor::new(file)).unwrap(); + let info = probe_detailed_with_options(&mut reader, ProbeOptions::lightweight()).unwrap(); + + assert_eq!(info.major_brand, full.major_brand); + assert_eq!(info.timescale, full.timescale); + assert_eq!(info.duration, full.duration); + assert_eq!(info.segments, Vec::new()); + assert_eq!(info.tracks.len(), 1); + assert_eq!(info.tracks[0].codec_family, full.tracks[0].codec_family); + assert_eq!(info.tracks[0].handler_type, full.tracks[0].handler_type); + assert_eq!(info.tracks[0].language, full.tracks[0].language); + assert_eq!( + info.tracks[0].sample_entry_type, + full.tracks[0].sample_entry_type + ); + assert_eq!(info.tracks[0].display_width, full.tracks[0].display_width); + assert_eq!(info.tracks[0].display_height, full.tracks[0].display_height); + assert!(info.tracks[0].summary.samples.is_empty()); + assert!(info.tracks[0].summary.chunks.is_empty()); +} + +#[test] +fn probe_detailed_bytes_with_options_matches_cursor_based_probe_detailed() { + let file = build_movie_file(); + let options = ProbeOptions::lightweight(); + let expected = probe_detailed_with_options(&mut Cursor::new(file.clone()), options).unwrap(); + let actual = probe_detailed_bytes_with_options(&file, options).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn probe_codec_detailed_bytes_matches_cursor_based_probe_codec_detailed() { + let file = build_hevc_movie_file(); + let expected = probe_codec_detailed(&mut Cursor::new(file.clone())).unwrap(); + let actual = probe_codec_detailed_bytes(&file).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn probe_codec_detailed_with_options_skips_fragment_segments() { + let file = build_fragment_file(); + let mut reader = Cursor::new(file.clone()); + + let full = probe_codec_detailed(&mut Cursor::new(file)).unwrap(); + let info = probe_codec_detailed_with_options(&mut reader, ProbeOptions::lightweight()).unwrap(); + + assert_eq!(info.major_brand, full.major_brand); + assert_eq!(info.timescale, full.timescale); + assert_eq!(info.duration, full.duration); + assert!(!full.segments.is_empty()); + assert!(info.segments.is_empty()); +} + +#[test] +fn probe_codec_detailed_bytes_with_options_matches_cursor_based_probe_codec_detailed() { + let file = build_hevc_movie_file(); + let options = ProbeOptions::lightweight(); + let expected = + probe_codec_detailed_with_options(&mut Cursor::new(file.clone()), options).unwrap(); + let actual = probe_codec_detailed_bytes_with_options(&file, options).unwrap(); + assert_eq!(actual, expected); +} + #[test] fn probe_and_probe_fra_summarize_fragment_runs() { let file = build_fragment_file(); @@ -166,6 +331,22 @@ fn probe_and_probe_fra_summarize_fragment_runs() { assert_eq!(second.size, 36); } +#[test] +fn probe_fra_detailed_bytes_matches_cursor_based_probe_fra_detailed() { + let file = build_fragment_file(); + let expected = probe_fra_detailed(&mut Cursor::new(file.clone())).unwrap(); + let actual = probe_fra_detailed_bytes(&file).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn probe_fra_codec_detailed_bytes_matches_cursor_based_probe_fra_codec_detailed() { + let file = build_fragment_file(); + let expected = probe_fra_codec_detailed(&mut Cursor::new(file.clone())).unwrap(); + let actual = probe_fra_codec_detailed_bytes(&file).unwrap(); + assert_eq!(actual, expected); +} + #[test] fn probe_fra_bytes_matches_cursor_based_probe_fra() { let file = build_fragment_file(); @@ -174,6 +355,317 @@ fn probe_fra_bytes_matches_cursor_based_probe_fra() { assert_eq!(actual, expected); } +#[test] +fn probe_detailed_recognizes_av01_track_family() { + let file = build_av01_movie_file(); + let mut reader = Cursor::new(file); + + let info = probe_detailed(&mut reader).unwrap(); + + assert_eq!(info.tracks.len(), 1); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec, TrackCodec::Unknown); + assert_eq!(track.codec_family, TrackCodecFamily::Av1); + assert_eq!(track.handler_type, Some(fourcc("vide"))); + assert_eq!(track.language.as_deref(), Some("eng")); + assert_eq!(track.sample_entry_type, Some(fourcc("av01"))); + assert_eq!(track.original_format, None); + assert_eq!(track.display_width, Some(640)); + assert_eq!(track.display_height, Some(360)); + assert_eq!(track.summary.samples.len(), 1); +} + +#[test] +fn probe_codec_detailed_exposes_richer_landed_codec_details() { + { + let mut reader = Cursor::new(build_hevc_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::Hevc); + match &track.codec_details { + TrackCodecDetails::Hevc(details) => { + assert_eq!(details.configuration_version, 1); + assert_eq!(details.profile_space, 1); + assert!(details.tier_flag); + assert_eq!(details.profile_idc, 2); + assert_eq!(details.profile_compatibility_mask, 0x4000_0000); + assert_eq!(details.constraint_indicator, [1, 2, 3, 4, 5, 6]); + assert_eq!(details.level_idc, 120); + assert_eq!(details.chroma_format_idc, 1); + assert_eq!(details.bit_depth_luma, 10); + assert_eq!(details.bit_depth_chroma, 10); + assert_eq!(details.avg_frame_rate, 30_000); + assert_eq!(details.length_size, 4); + } + other => panic!("expected HEVC details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_av01_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::Av1); + match &track.codec_details { + TrackCodecDetails::Av1(details) => { + assert_eq!(details.seq_profile, 0); + assert_eq!(details.seq_level_idx_0, 13); + assert_eq!(details.seq_tier_0, 1); + assert_eq!(details.bit_depth, 10); + assert!(!details.monochrome); + assert_eq!(details.chroma_subsampling_x, 1); + assert_eq!(details.chroma_subsampling_y, 0); + assert_eq!(details.chroma_sample_position, 2); + assert_eq!(details.initial_presentation_delay_minus_one, Some(3)); + } + other => panic!("expected AV1 details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_vp09_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::Vp9); + match &track.codec_details { + TrackCodecDetails::Vp9(details) => { + assert_eq!(details.profile, 2); + assert_eq!(details.level, 31); + assert_eq!(details.bit_depth, 10); + assert_eq!(details.chroma_subsampling, 1); + assert!(details.full_range); + assert_eq!(details.colour_primaries, 9); + assert_eq!(details.transfer_characteristics, 16); + assert_eq!(details.matrix_coefficients, 9); + assert_eq!(details.codec_initialization_data_size, 3); + } + other => panic!("expected VP9 details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_opus_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::Opus); + match &track.codec_details { + TrackCodecDetails::Opus(details) => { + assert_eq!(details.output_channel_count, 2); + assert_eq!(details.pre_skip, 312); + assert_eq!(details.input_sample_rate, 48_000); + assert_eq!(details.output_gain, 0); + assert_eq!(details.channel_mapping_family, 1); + assert_eq!(details.stream_count, Some(2)); + assert_eq!(details.coupled_count, Some(1)); + assert_eq!(details.channel_mapping, vec![0, 1]); + } + other => panic!("expected Opus details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_ac3_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::Ac3); + match &track.codec_details { + TrackCodecDetails::Ac3(details) => { + assert_eq!(details.sample_rate_code, 1); + assert_eq!(details.bit_stream_identification, 8); + assert_eq!(details.bit_stream_mode, 3); + assert_eq!(details.audio_coding_mode, 7); + assert!(details.lfe_on); + assert_eq!(details.bit_rate_code, 10); + } + other => panic!("expected AC-3 details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_pcm_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::Pcm); + match &track.codec_details { + TrackCodecDetails::Pcm(details) => { + assert_eq!(details.format_flags, 1); + assert_eq!(details.sample_size, 24); + } + other => panic!("expected PCM details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_stpp_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::XmlSubtitle); + match &track.codec_details { + TrackCodecDetails::XmlSubtitle(details) => { + assert_eq!(details.namespace, "urn:ebu:tt:metadata"); + assert_eq!(details.schema_location, "urn:ebu:tt:schema"); + assert_eq!(details.auxiliary_mime_types, "application/ttml+xml"); + } + other => panic!("expected XML subtitle details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_sbtt_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::TextSubtitle); + match &track.codec_details { + TrackCodecDetails::TextSubtitle(details) => { + assert_eq!(details.content_encoding, "utf-8"); + assert_eq!(details.mime_format, "text/plain"); + } + other => panic!("expected text subtitle details, got {other:?}"), + } + } + + { + let mut reader = Cursor::new(build_wvtt_movie_file()); + let info = probe_codec_detailed(&mut reader).unwrap(); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec_family, TrackCodecFamily::WebVtt); + match &track.codec_details { + TrackCodecDetails::WebVtt(details) => { + assert_eq!(details.config.as_deref(), Some("WEBVTT")); + assert_eq!(details.source_label.as_deref(), Some("eng")); + } + other => panic!("expected WebVTT details, got {other:?}"), + } + } +} + +#[test] +fn probe_detailed_reports_protected_sample_entry_metadata() { + let file = build_encrypted_video_movie_file(); + let mut reader = Cursor::new(file); + + let info = probe_detailed(&mut reader).unwrap(); + + assert_eq!(info.tracks.len(), 1); + let track = &info.tracks[0]; + assert_eq!(track.summary.codec, TrackCodec::Avc1); + assert!(track.summary.encrypted); + assert_eq!(track.codec_family, TrackCodecFamily::Avc); + assert_eq!(track.handler_type, Some(fourcc("vide"))); + assert_eq!(track.language.as_deref(), Some("eng")); + assert_eq!(track.sample_entry_type, Some(fourcc("encv"))); + assert_eq!(track.original_format, Some(fourcc("avc1"))); + assert_eq!( + track + .protection_scheme + .as_ref() + .map(|value| (value.scheme_type, value.scheme_version)), + Some((fourcc("cenc"), 0x0001_0000)) + ); + assert_eq!(track.display_width, Some(320)); + assert_eq!(track.display_height, Some(180)); +} + +#[test] +fn probe_codec_detailed_reports_protected_hevc_codec_details() { + let file = build_encrypted_hevc_movie_file(); + let mut reader = Cursor::new(file); + + let info = probe_codec_detailed(&mut reader).unwrap(); + + assert_eq!(info.tracks.len(), 1); + let track = &info.tracks[0]; + assert_eq!(track.summary.summary.codec, TrackCodec::Avc1); + assert!(track.summary.summary.encrypted); + assert_eq!(track.summary.codec_family, TrackCodecFamily::Hevc); + assert_eq!(track.summary.sample_entry_type, Some(fourcc("encv"))); + assert_eq!(track.summary.original_format, Some(fourcc("hvc1"))); + match &track.codec_details { + TrackCodecDetails::Hevc(details) => { + assert_eq!(details.profile_idc, 2); + assert_eq!(details.length_size, 4); + } + other => panic!("expected HEVC details, got {other:?}"), + } +} + +#[test] +fn probe_media_characteristics_exposes_sample_entry_side_metadata() { + let file = build_media_characteristics_movie_file(); + let mut reader = Cursor::new(file); + + let info = probe_media_characteristics(&mut reader).unwrap(); + + assert_eq!(info.tracks.len(), 1); + let track = &info.tracks[0]; + assert_eq!(track.summary.summary.track_id, 1); + assert_eq!(track.summary.codec_family, TrackCodecFamily::Avc); + assert_eq!(track.summary.sample_entry_type, Some(fourcc("avc1"))); + assert_eq!( + track.media_characteristics.declared_bitrate, + Some(mp4forge::probe::DeclaredBitrateInfo { + buffer_size_db: 32_768, + max_bitrate: 4_000_000, + avg_bitrate: 2_500_000, + }) + ); + assert_eq!( + track.media_characteristics.color, + Some(mp4forge::probe::ColorInfo { + colour_type: fourcc("nclx"), + colour_primaries: Some(9), + transfer_characteristics: Some(16), + matrix_coefficients: Some(9), + full_range: Some(true), + profile_size: None, + unknown_size: None, + }) + ); + assert_eq!( + track.media_characteristics.pixel_aspect_ratio, + Some(mp4forge::probe::PixelAspectRatioInfo { + h_spacing: 4, + v_spacing: 3, + }) + ); + assert_eq!( + track.media_characteristics.field_order, + Some(mp4forge::probe::FieldOrderInfo { + field_count: 2, + field_ordering: 6, + interlaced: true, + }) + ); +} + +#[test] +fn probe_media_characteristics_bytes_matches_cursor_based_probe() { + let file = build_media_characteristics_movie_file(); + let expected = probe_media_characteristics(&mut Cursor::new(file.clone())).unwrap(); + let actual = probe_media_characteristics_bytes(&file).unwrap(); + assert_eq!(actual, expected); +} + +#[test] +fn probe_media_characteristics_with_options_preserves_media_fields_without_sample_tables() { + let file = build_media_characteristics_movie_file(); + let options = ProbeOptions::lightweight(); + let expected = + probe_media_characteristics_with_options(&mut Cursor::new(file.clone()), options).unwrap(); + let actual = probe_media_characteristics_bytes_with_options(&file, options).unwrap(); + + assert_eq!(actual, expected); + assert_eq!(actual.tracks.len(), 1); + assert!(actual.tracks[0].summary.summary.samples.is_empty()); + assert!(actual.tracks[0].summary.summary.chunks.is_empty()); + assert!( + actual.tracks[0] + .media_characteristics + .declared_bitrate + .is_some() + ); +} + #[test] fn probe_bytes_propagates_decode_errors() { let file = encode_raw_box(fourcc("ftyp"), &[0x69, 0x73]); @@ -316,6 +808,7 @@ fn build_video_trak(chunk_offsets: &[u64; 2]) -> Vec { mdhd.duration_v0 = 3_072; mdhd.language = [5, 14, 7]; let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("vide", "VideoHandler"); let mut stsd = Stsd::default(); stsd.entry_count = 1; @@ -377,7 +870,7 @@ fn build_video_trak(chunk_offsets: &[u64; 2]) -> Vec { let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, ctts, stsc, stsz].concat()); let minf = encode_supported_box(&Minf, &stbl); - let mdia = encode_supported_box(&Mdia, &[mdhd, minf].concat()); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); encode_supported_box(&Trak, &[tkhd, edts, mdia].concat()) } @@ -392,6 +885,7 @@ fn build_audio_trak(chunk_offsets: &[u64; 2]) -> Vec { mdhd.duration_v0 = 2_048; mdhd.language = [5, 14, 7]; let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("soun", "SoundHandler"); let mut stsd = Stsd::default(); stsd.entry_count = 1; @@ -430,7 +924,7 @@ fn build_audio_trak(chunk_offsets: &[u64; 2]) -> Vec { let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, stsc, stsz].concat()); let minf = encode_supported_box(&Minf, &stbl); - let mdia = encode_supported_box(&Mdia, &[mdhd, minf].concat()); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); encode_supported_box(&Trak, &[tkhd, mdia].concat()) } @@ -513,6 +1007,626 @@ fn build_fragment_moof_two() -> Vec { encode_supported_box(&Moof, &traf) } +fn build_av01_movie_file() -> Vec { + let ftyp = encode_supported_box( + &Ftyp { + major_brand: fourcc("isom"), + minor_version: 0x0200, + compatible_brands: vec![fourcc("isom"), fourcc("iso8"), fourcc("av01")], + }, + &[], + ); + + let placeholder_moov = build_av01_moov(&[0]); + let mdat_payload = vec![0x12, 0x34, 0x56, 0x78]; + let mdat_data_offset = ftyp.len() as u64 + placeholder_moov.len() as u64 + 8; + let moov = build_av01_moov(&[mdat_data_offset]); + let mdat = encode_raw_box(fourcc("mdat"), &mdat_payload); + [ftyp, moov, mdat].concat() +} + +fn build_av01_moov(chunk_offsets: &[u64; 1]) -> Vec { + let mut mvhd = Mvhd::default(); + mvhd.timescale = 1_000; + mvhd.duration_v0 = 1_000; + mvhd.rate = 1 << 16; + mvhd.volume = 1 << 8; + mvhd.next_track_id = 2; + let mvhd = encode_supported_box(&mvhd, &[]); + let video = build_av01_trak(chunk_offsets); + encode_supported_box(&Moov, &[mvhd, video].concat()) +} + +fn build_av01_trak(chunk_offsets: &[u64; 1]) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = 1; + tkhd.duration_v0 = 1_000; + tkhd.width = u32::from(640_u16) << 16; + tkhd.height = u32::from(360_u16) << 16; + let tkhd = encode_supported_box(&tkhd, &[]); + + let mut mdhd = Mdhd::default(); + mdhd.timescale = 1_000; + mdhd.duration_v0 = 1_000; + mdhd.language = [5, 14, 7]; + let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("vide", "VideoHandler"); + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let av01 = encode_supported_box( + &video_sample_entry_with_type("av01", 640, 360), + &encode_supported_box(&av1_config(), &[]), + ); + let stsd = encode_supported_box(&stsd, &av01); + + let mut stco = Stco::default(); + stco.entry_count = 1; + stco.chunk_offset = chunk_offsets.to_vec(); + let stco = encode_supported_box(&stco, &[]); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: 1_000, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_count = 1; + stsz.entry_size = vec![4]; + let stsz = encode_supported_box(&stsz, &[]); + + let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, stsc, stsz].concat()); + let minf = encode_supported_box(&Minf, &stbl); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); + encode_supported_box(&Trak, &[tkhd, mdia].concat()) +} + +fn build_encrypted_video_movie_file() -> Vec { + let ftyp = encode_supported_box( + &Ftyp { + major_brand: fourcc("iso6"), + minor_version: 1, + compatible_brands: vec![fourcc("iso6"), fourcc("dash"), fourcc("cenc")], + }, + &[], + ); + + let placeholder_moov = build_encrypted_video_moov(&[0]); + let mdat_payload = [avc_sample(5)].concat(); + let mdat_data_offset = ftyp.len() as u64 + placeholder_moov.len() as u64 + 8; + let moov = build_encrypted_video_moov(&[mdat_data_offset]); + let mdat = encode_raw_box(fourcc("mdat"), &mdat_payload); + [ftyp, moov, mdat].concat() +} + +fn build_encrypted_video_moov(chunk_offsets: &[u64; 1]) -> Vec { + let mut mvhd = Mvhd::default(); + mvhd.timescale = 1_000; + mvhd.duration_v0 = 1_000; + mvhd.rate = 1 << 16; + mvhd.volume = 1 << 8; + mvhd.next_track_id = 2; + let mvhd = encode_supported_box(&mvhd, &[]); + let video = build_encrypted_video_trak(chunk_offsets); + encode_supported_box(&Moov, &[mvhd, video].concat()) +} + +fn build_encrypted_video_trak(chunk_offsets: &[u64; 1]) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = 1; + tkhd.duration_v0 = 1_000; + tkhd.width = u32::from(320_u16) << 16; + tkhd.height = u32::from(180_u16) << 16; + let tkhd = encode_supported_box(&tkhd, &[]); + + let mut mdhd = Mdhd::default(); + mdhd.timescale = 1_000; + mdhd.duration_v0 = 1_000; + mdhd.language = [5, 14, 7]; + let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("vide", "VideoHandler"); + + let mut schm = Schm::default(); + schm.set_version(0); + schm.scheme_type = fourcc("cenc"); + schm.scheme_version = 0x0001_0000; + let sinf = encode_supported_box( + &Sinf, + &[ + encode_supported_box( + &Frma { + data_format: fourcc("avc1"), + }, + &[], + ), + encode_supported_box(&schm, &[]), + ] + .concat(), + ); + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let encv = encode_supported_box( + &video_sample_entry_with_type("encv", 320, 180), + &[encode_supported_box(&avc_config(), &[]), sinf].concat(), + ); + let stsd = encode_supported_box(&stsd, &encv); + + let mut stco = Stco::default(); + stco.entry_count = 1; + stco.chunk_offset = chunk_offsets.to_vec(); + let stco = encode_supported_box(&stco, &[]); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: 1_000, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_count = 1; + stsz.entry_size = vec![5]; + let stsz = encode_supported_box(&stsz, &[]); + + let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, stsc, stsz].concat()); + let minf = encode_supported_box(&Minf, &stbl); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); + encode_supported_box(&Trak, &[tkhd, mdia].concat()) +} + +fn build_single_track_movie_file( + compatible_brands: Vec, + track_builder: fn(&[u64; 1]) -> Vec, + mdat_payload: Vec, +) -> Vec { + let ftyp = encode_supported_box( + &Ftyp { + major_brand: fourcc("isom"), + minor_version: 0x0200, + compatible_brands, + }, + &[], + ); + + let placeholder_moov = build_single_track_moov(track_builder(&[0])); + let mdat_data_offset = ftyp.len() as u64 + placeholder_moov.len() as u64 + 8; + let moov = build_single_track_moov(track_builder(&[mdat_data_offset])); + let mdat = encode_raw_box(fourcc("mdat"), &mdat_payload); + [ftyp, moov, mdat].concat() +} + +fn build_single_track_moov(track: Vec) -> Vec { + let mut mvhd = Mvhd::default(); + mvhd.timescale = 1_000; + mvhd.duration_v0 = 1_000; + mvhd.rate = 1 << 16; + mvhd.volume = 1 << 8; + mvhd.next_track_id = 2; + let mvhd = encode_supported_box(&mvhd, &[]); + encode_supported_box(&Moov, &[mvhd, track].concat()) +} + +fn build_hevc_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("hvc1")], + build_hevc_trak, + vec![0x12, 0x34, 0x56, 0x78], + ) +} + +fn build_hevc_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &video_sample_entry_with_type("hvc1", 640, 360), + &encode_supported_box(&hevc_config(), &[]), + ); + build_single_sample_video_trak(1, 1_000, 1_000, (640, 360), sample_entry, chunk_offsets, 4) +} + +fn build_vp09_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("vp09")], + build_vp09_trak, + vec![0xaa, 0xbb, 0xcc, 0xdd], + ) +} + +fn build_vp09_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &video_sample_entry_with_type("vp09", 640, 360), + &encode_supported_box(&vp9_config(), &[]), + ); + build_single_sample_video_trak(1, 1_000, 1_000, (640, 360), sample_entry, chunk_offsets, 4) +} + +fn build_opus_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("Opus")], + build_opus_trak, + vec![0x11, 0x22, 0x33, 0x44], + ) +} + +fn build_opus_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &audio_sample_entry_with_type("Opus", 2, 48_000), + &encode_supported_box(&opus_config(), &[]), + ); + build_single_sample_audio_trak(1, 48_000, 1_024, sample_entry, chunk_offsets, 4) +} + +fn build_ac3_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("ac-3")], + build_ac3_trak, + vec![0x21, 0x22, 0x23, 0x24], + ) +} + +fn build_ac3_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &audio_sample_entry_with_type("ac-3", 6, 48_000), + &encode_supported_box(&ac3_config(), &[]), + ); + build_single_sample_audio_trak(1, 48_000, 1_536, sample_entry, chunk_offsets, 4) +} + +fn build_pcm_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("ipcm")], + build_pcm_trak, + vec![0x31, 0x32, 0x33, 0x34], + ) +} + +fn build_pcm_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &audio_sample_entry_with_type("ipcm", 2, 48_000), + &encode_supported_box(&pcm_config(), &[]), + ); + build_single_sample_audio_trak(1, 48_000, 1_024, sample_entry, chunk_offsets, 4) +} + +fn build_stpp_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("stpp")], + build_stpp_trak, + vec![0x41, 0x42, 0x43, 0x44], + ) +} + +fn build_stpp_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box(&xml_subtitle_sample_entry(), &[]); + build_single_sample_subtitle_trak(1, 1_000, 1_000, sample_entry, chunk_offsets, 4) +} + +fn build_sbtt_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("sbtt")], + build_sbtt_trak, + vec![0x51, 0x52, 0x53, 0x54], + ) +} + +fn build_sbtt_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box(&text_subtitle_sample_entry(), &[]); + build_single_sample_subtitle_trak(1, 1_000, 1_000, sample_entry, chunk_offsets, 4) +} + +fn build_wvtt_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("wvtt")], + build_wvtt_trak, + vec![0x61, 0x62, 0x63, 0x64], + ) +} + +fn build_wvtt_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &wvtt_sample_entry(), + &[ + encode_supported_box( + &WebVTTConfigurationBox { + config: "WEBVTT".to_string(), + }, + &[], + ), + encode_supported_box( + &WebVTTSourceLabelBox { + source_label: "eng".to_string(), + }, + &[], + ), + ] + .concat(), + ); + build_single_sample_subtitle_trak(1, 1_000, 1_000, sample_entry, chunk_offsets, 4) +} + +fn build_encrypted_hevc_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("iso6"), fourcc("dash"), fourcc("cenc")], + build_encrypted_hevc_trak, + vec![0x71, 0x72, 0x73, 0x74], + ) +} + +fn build_encrypted_hevc_trak(chunk_offsets: &[u64; 1]) -> Vec { + let mut schm = Schm::default(); + schm.set_version(0); + schm.scheme_type = fourcc("cenc"); + schm.scheme_version = 0x0001_0000; + let sinf = encode_supported_box( + &Sinf, + &[ + encode_supported_box( + &Frma { + data_format: fourcc("hvc1"), + }, + &[], + ), + encode_supported_box(&schm, &[]), + ] + .concat(), + ); + + let sample_entry = encode_supported_box( + &video_sample_entry_with_type("encv", 640, 360), + &[encode_supported_box(&hevc_config(), &[]), sinf].concat(), + ); + build_single_sample_video_trak(1, 1_000, 1_000, (640, 360), sample_entry, chunk_offsets, 4) +} + +fn build_media_characteristics_movie_file() -> Vec { + build_single_track_movie_file( + vec![fourcc("isom"), fourcc("iso8"), fourcc("avc1")], + build_media_characteristics_trak, + vec![0x81, 0x82, 0x83, 0x84], + ) +} + +fn build_media_characteristics_trak(chunk_offsets: &[u64; 1]) -> Vec { + let sample_entry = encode_supported_box( + &video_sample_entry_with_type("avc1", 640, 360), + &[ + encode_supported_box(&avc_config(), &[]), + encode_supported_box( + &Btrt { + buffer_size_db: 32_768, + max_bitrate: 4_000_000, + avg_bitrate: 2_500_000, + }, + &[], + ), + encode_supported_box( + &Colr { + colour_type: fourcc("nclx"), + colour_primaries: 9, + transfer_characteristics: 16, + matrix_coefficients: 9, + full_range_flag: true, + reserved: 0, + profile: Vec::new(), + unknown: Vec::new(), + }, + &[], + ), + encode_supported_box( + &Pasp { + h_spacing: 4, + v_spacing: 3, + }, + &[], + ), + encode_supported_box( + &Fiel { + field_count: 2, + field_ordering: 6, + }, + &[], + ), + ] + .concat(), + ); + build_single_sample_video_trak(1, 1_000, 1_000, (640, 360), sample_entry, chunk_offsets, 4) +} + +fn build_single_sample_video_trak( + track_id: u32, + timescale: u32, + duration: u32, + dimensions: (u16, u16), + sample_entry: Vec, + chunk_offsets: &[u64; 1], + sample_size: u32, +) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = track_id; + tkhd.duration_v0 = duration; + tkhd.width = u32::from(dimensions.0) << 16; + tkhd.height = u32::from(dimensions.1) << 16; + let tkhd = encode_supported_box(&tkhd, &[]); + + let mut mdhd = Mdhd::default(); + mdhd.timescale = timescale; + mdhd.duration_v0 = duration; + mdhd.language = [5, 14, 7]; + let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("vide", "VideoHandler"); + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let stsd = encode_supported_box(&stsd, &sample_entry); + + let mut stco = Stco::default(); + stco.entry_count = 1; + stco.chunk_offset = chunk_offsets.to_vec(); + let stco = encode_supported_box(&stco, &[]); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: duration, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_count = 1; + stsz.entry_size = vec![u64::from(sample_size)]; + let stsz = encode_supported_box(&stsz, &[]); + + let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, stsc, stsz].concat()); + let minf = encode_supported_box(&Minf, &stbl); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); + encode_supported_box(&Trak, &[tkhd, mdia].concat()) +} + +fn build_single_sample_audio_trak( + track_id: u32, + timescale: u32, + duration: u32, + sample_entry: Vec, + chunk_offsets: &[u64; 1], + sample_size: u32, +) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = track_id; + tkhd.duration_v0 = duration; + let tkhd = encode_supported_box(&tkhd, &[]); + + let mut mdhd = Mdhd::default(); + mdhd.timescale = timescale; + mdhd.duration_v0 = duration; + mdhd.language = [5, 14, 7]; + let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("soun", "SoundHandler"); + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let stsd = encode_supported_box(&stsd, &sample_entry); + + let mut stco = Stco::default(); + stco.entry_count = 1; + stco.chunk_offset = chunk_offsets.to_vec(); + let stco = encode_supported_box(&stco, &[]); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: duration, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_count = 1; + stsz.entry_size = vec![u64::from(sample_size)]; + let stsz = encode_supported_box(&stsz, &[]); + + let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, stsc, stsz].concat()); + let minf = encode_supported_box(&Minf, &stbl); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); + encode_supported_box(&Trak, &[tkhd, mdia].concat()) +} + +fn build_single_sample_subtitle_trak( + track_id: u32, + timescale: u32, + duration: u32, + sample_entry: Vec, + chunk_offsets: &[u64; 1], + sample_size: u32, +) -> Vec { + let mut tkhd = Tkhd::default(); + tkhd.track_id = track_id; + tkhd.duration_v0 = duration; + let tkhd = encode_supported_box(&tkhd, &[]); + + let mut mdhd = Mdhd::default(); + mdhd.timescale = timescale; + mdhd.duration_v0 = duration; + mdhd.language = [5, 14, 7]; + let mdhd = encode_supported_box(&mdhd, &[]); + let hdlr = handler_box("subt", "SubtitleHandler"); + + let mut stsd = Stsd::default(); + stsd.entry_count = 1; + let stsd = encode_supported_box(&stsd, &sample_entry); + + let mut stco = Stco::default(); + stco.entry_count = 1; + stco.chunk_offset = chunk_offsets.to_vec(); + let stco = encode_supported_box(&stco, &[]); + + let mut stts = Stts::default(); + stts.entry_count = 1; + stts.entries = vec![SttsEntry { + sample_count: 1, + sample_delta: duration, + }]; + let stts = encode_supported_box(&stts, &[]); + + let mut stsc = Stsc::default(); + stsc.entry_count = 1; + stsc.entries = vec![StscEntry { + first_chunk: 1, + samples_per_chunk: 1, + sample_description_index: 1, + }]; + let stsc = encode_supported_box(&stsc, &[]); + + let mut stsz = Stsz::default(); + stsz.sample_count = 1; + stsz.entry_size = vec![u64::from(sample_size)]; + let stsz = encode_supported_box(&stsz, &[]); + + let stbl = encode_supported_box(&Stbl, &[stsd, stco, stts, stsc, stsz].concat()); + let minf = encode_supported_box(&Minf, &stbl); + let mdia = encode_supported_box(&Mdia, &[mdhd, hdlr, minf].concat()); + encode_supported_box(&Trak, &[tkhd, mdia].concat()) +} + fn avc_config() -> AVCDecoderConfiguration { AVCDecoderConfiguration { configuration_version: 1, @@ -524,36 +1638,178 @@ fn avc_config() -> AVCDecoderConfiguration { } } -fn video_sample_entry() -> VisualSampleEntry { +fn hevc_config() -> HEVCDecoderConfiguration { + let mut general_profile_compatibility = [false; 32]; + general_profile_compatibility[1] = true; + + HEVCDecoderConfiguration { + configuration_version: 1, + general_profile_space: 1, + general_tier_flag: true, + general_profile_idc: 2, + general_profile_compatibility, + general_constraint_indicator: [1, 2, 3, 4, 5, 6], + general_level_idc: 120, + min_spatial_segmentation_idc: 0, + parallelism_type: 0, + chroma_format_idc: 1, + bit_depth_luma_minus8: 2, + bit_depth_chroma_minus8: 2, + avg_frame_rate: 30_000, + constant_frame_rate: 0, + num_temporal_layers: 1, + temporal_id_nested: 1, + length_size_minus_one: 3, + num_of_nalu_arrays: 0, + nalu_arrays: Vec::new(), + } +} + +fn av1_config() -> AV1CodecConfiguration { + AV1CodecConfiguration { + seq_profile: 0, + seq_level_idx_0: 13, + seq_tier_0: 1, + high_bitdepth: 1, + twelve_bit: 0, + monochrome: 0, + chroma_subsampling_x: 1, + chroma_subsampling_y: 0, + chroma_sample_position: 2, + initial_presentation_delay_present: 1, + initial_presentation_delay_minus_one: 3, + config_obus: vec![0x12, 0x34, 0x56], + } +} + +fn vp9_config() -> VpCodecConfiguration { + let mut config = VpCodecConfiguration::default(); + config.profile = 2; + config.level = 31; + config.bit_depth = 10; + config.chroma_subsampling = 1; + config.video_full_range_flag = 1; + config.colour_primaries = 9; + config.transfer_characteristics = 16; + config.matrix_coefficients = 9; + config.codec_initialization_data_size = 3; + config.codec_initialization_data = vec![0x01, 0x02, 0x03]; + config +} + +fn opus_config() -> DOps { + DOps { + version: 0, + output_channel_count: 2, + pre_skip: 312, + input_sample_rate: 48_000, + output_gain: 0, + channel_mapping_family: 1, + stream_count: 2, + coupled_count: 1, + channel_mapping: vec![0, 1], + } +} + +fn ac3_config() -> Dac3 { + Dac3 { + fscod: 1, + bsid: 8, + bsmod: 3, + acmod: 7, + lfe_on: 1, + bit_rate_code: 10, + } +} + +fn pcm_config() -> PcmC { + let mut config = PcmC::default(); + config.format_flags = 1; + config.pcm_sample_size = 24; + config +} + +fn handler_box(handler_type: &str, name: &str) -> Vec { + let mut hdlr = Hdlr::default(); + hdlr.handler_type = fourcc(handler_type); + hdlr.name = name.to_string(); + encode_supported_box(&hdlr, &[]) +} + +fn video_sample_entry_with_type(box_type: &str, width: u16, height: u16) -> VisualSampleEntry { let mut entry = VisualSampleEntry { sample_entry: SampleEntry { - box_type: fourcc("avc1"), + box_type: fourcc(box_type), data_reference_index: 1, }, - width: 320, - height: 180, + width, + height, frame_count: 1, ..VisualSampleEntry::default() }; - entry.set_box_type(fourcc("avc1")); + entry.set_box_type(fourcc(box_type)); entry } +fn video_sample_entry() -> VisualSampleEntry { + video_sample_entry_with_type("avc1", 320, 180) +} + fn audio_sample_entry() -> AudioSampleEntry { + audio_sample_entry_with_type("mp4a", 2, 48_000) +} + +fn audio_sample_entry_with_type( + box_type: &str, + channel_count: u16, + sample_rate: u32, +) -> AudioSampleEntry { let mut entry = AudioSampleEntry { sample_entry: SampleEntry { - box_type: fourcc("mp4a"), + box_type: fourcc(box_type), data_reference_index: 1, }, - channel_count: 2, + channel_count, sample_size: 16, - sample_rate: 48_000_u32 << 16, + sample_rate: sample_rate << 16, ..AudioSampleEntry::default() }; - entry.set_box_type(fourcc("mp4a")); + entry.set_box_type(fourcc(box_type)); entry } +fn xml_subtitle_sample_entry() -> XMLSubtitleSampleEntry { + XMLSubtitleSampleEntry { + sample_entry: SampleEntry { + box_type: fourcc("stpp"), + data_reference_index: 1, + }, + namespace: "urn:ebu:tt:metadata".to_string(), + schema_location: "urn:ebu:tt:schema".to_string(), + auxiliary_mime_types: "application/ttml+xml".to_string(), + } +} + +fn text_subtitle_sample_entry() -> TextSubtitleSampleEntry { + TextSubtitleSampleEntry { + sample_entry: SampleEntry { + box_type: fourcc("sbtt"), + data_reference_index: 1, + }, + content_encoding: "utf-8".to_string(), + mime_format: "text/plain".to_string(), + } +} + +fn wvtt_sample_entry() -> WVTTSampleEntry { + WVTTSampleEntry { + sample_entry: SampleEntry { + box_type: fourcc("wvtt"), + data_reference_index: 1, + }, + } +} + fn aac_profile_esds(object_type_indication: u8, decoder_specific_info: &[u8]) -> Esds { let mut esds = Esds::default(); esds.descriptors = vec![ diff --git a/tests/serde_reports.rs b/tests/serde_reports.rs new file mode 100644 index 0000000..0a18fe9 --- /dev/null +++ b/tests/serde_reports.rs @@ -0,0 +1,273 @@ +#![cfg(feature = "serde")] + +mod support; + +use std::fmt::Debug; + +use mp4forge::cli::dump::{ + DumpPayloadStatus, FieldStructuredDumpBoxReport, FieldStructuredDumpReport, + StructuredDumpBoxReport, StructuredDumpFieldReport, StructuredDumpReport, +}; +use mp4forge::cli::probe::{ + CodecDetailedProbeReport, CodecDetailedProbeTrackReport, DetailedProbeReport, + DetailedProbeTrackReport, MediaCharacteristicsProbeReport, + MediaCharacteristicsProbeTrackReport, ProbeReport, ProbeTrackReport, +}; +use mp4forge::codec::FieldValue; +use mp4forge::probe::{ + Av1CodecDetails, ColorInfo, DeclaredBitrateInfo, FieldOrderInfo, PixelAspectRatioInfo, + TrackCodecDetails, TrackMediaCharacteristics, +}; + +use support::fourcc; + +#[test] +fn probe_report_types_roundtrip_with_serde_json() { + assert_json_roundtrip(ProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso2".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![ProbeTrackReport { + track_id: 1, + timescale: 90_000, + duration: 3_072, + duration_seconds: 0.034133334, + codec: "avc1.64001F".to_string(), + encrypted: false, + width: Some(320), + height: Some(180), + sample_num: Some(3), + chunk_num: Some(2), + idr_frame_num: Some(1), + bitrate: Some(20_000), + max_bitrate: Some(32_000), + }], + }); + + assert_json_roundtrip(DetailedProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso8".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![DetailedProbeTrackReport { + track_id: 1, + timescale: 1_000, + duration: 1_000, + duration_seconds: 1.0, + codec: "av01".to_string(), + codec_family: "av1".to_string(), + encrypted: false, + handler_type: Some("vide".to_string()), + language: Some("eng".to_string()), + sample_entry_type: Some("av01".to_string()), + original_format: None, + protection_scheme_type: None, + protection_scheme_version: None, + width: Some(640), + height: Some(360), + channel_count: None, + sample_rate: None, + sample_num: Some(1), + chunk_num: Some(1), + idr_frame_num: None, + bitrate: Some(32_000), + max_bitrate: Some(32_000), + }], + }); + + let codec_report = CodecDetailedProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso8".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![CodecDetailedProbeTrackReport { + track_id: 1, + timescale: 1_000, + duration: 1_000, + duration_seconds: 1.0, + codec: "av01".to_string(), + codec_family: "av1".to_string(), + codec_details: TrackCodecDetails::Av1(Av1CodecDetails { + seq_profile: 0, + seq_level_idx_0: 13, + seq_tier_0: 1, + bit_depth: 10, + monochrome: false, + chroma_subsampling_x: 1, + chroma_subsampling_y: 0, + chroma_sample_position: 2, + initial_presentation_delay_minus_one: Some(3), + }), + encrypted: false, + handler_type: Some("vide".to_string()), + language: Some("eng".to_string()), + sample_entry_type: Some("av01".to_string()), + original_format: None, + protection_scheme_type: None, + protection_scheme_version: None, + width: Some(640), + height: Some(360), + channel_count: None, + sample_rate: None, + sample_num: Some(1), + chunk_num: Some(1), + idr_frame_num: None, + bitrate: Some(32_000), + max_bitrate: Some(32_000), + }], + }; + let codec_json = serde_json::to_value(&codec_report).unwrap(); + assert_eq!(codec_json["tracks"][0]["codec_details"]["kind"], "av1"); + assert_eq!( + codec_json["tracks"][0]["codec_details"]["value"]["bit_depth"], + 10 + ); + assert_eq!( + serde_json::from_value::(codec_json).unwrap(), + codec_report + ); + + let media_report = MediaCharacteristicsProbeReport { + major_brand: "isom".to_string(), + minor_version: 512, + compatible_brands: vec!["isom".to_string(), "iso8".to_string()], + fast_start: true, + timescale: 1_000, + duration: 2_000, + duration_seconds: 2.0, + tracks: vec![MediaCharacteristicsProbeTrackReport { + track_id: 1, + timescale: 1_000, + duration: 1_000, + duration_seconds: 1.0, + codec: "avc1.64001F".to_string(), + codec_family: "avc".to_string(), + codec_details: TrackCodecDetails::Unknown, + media_characteristics: TrackMediaCharacteristics { + declared_bitrate: Some(DeclaredBitrateInfo { + buffer_size_db: 32_768, + max_bitrate: 4_000_000, + avg_bitrate: 2_500_000, + }), + color: Some(ColorInfo { + colour_type: fourcc("nclx"), + colour_primaries: Some(9), + transfer_characteristics: Some(16), + matrix_coefficients: Some(9), + full_range: Some(true), + profile_size: None, + unknown_size: None, + }), + pixel_aspect_ratio: Some(PixelAspectRatioInfo { + h_spacing: 4, + v_spacing: 3, + }), + field_order: Some(FieldOrderInfo { + field_count: 2, + field_ordering: 6, + interlaced: true, + }), + }, + encrypted: false, + handler_type: Some("vide".to_string()), + language: Some("eng".to_string()), + sample_entry_type: Some("avc1".to_string()), + original_format: None, + protection_scheme_type: None, + protection_scheme_version: None, + width: Some(640), + height: Some(360), + channel_count: None, + sample_rate: None, + sample_num: Some(1), + chunk_num: Some(1), + idr_frame_num: None, + bitrate: Some(32_000), + max_bitrate: Some(32_000), + }], + }; + let media_json = serde_json::to_value(&media_report).unwrap(); + assert_eq!( + media_json["tracks"][0]["media_characteristics"]["color"]["colour_type"], + "nclx" + ); + assert_eq!( + serde_json::from_value::(media_json).unwrap(), + media_report + ); +} + +#[test] +fn dump_report_types_roundtrip_with_serde_json() { + assert_json_roundtrip(StructuredDumpReport { + boxes: vec![StructuredDumpBoxReport { + box_type: "ftyp".to_string(), + path: "ftyp".to_string(), + offset: 0, + size: 20, + supported: true, + payload_status: DumpPayloadStatus::Summary, + payload_summary: Some("MajorBrand=\"isom\"".to_string()), + payload_bytes: None, + children: Vec::new(), + }], + }); + + let report = FieldStructuredDumpReport { + boxes: vec![FieldStructuredDumpBoxReport { + box_type: "ftyp".to_string(), + path: "ftyp".to_string(), + offset: 0, + size: 20, + supported: true, + payload_status: DumpPayloadStatus::Summary, + payload_fields: vec![ + StructuredDumpFieldReport { + name: "MajorBrand".to_string(), + value: FieldValue::String("isom".to_string()), + display_value: None, + }, + StructuredDumpFieldReport { + name: "CompatibleBrands".to_string(), + value: FieldValue::Bytes(vec![105, 115, 111, 109]), + display_value: Some("[{CompatibleBrand=\"isom\"}]".to_string()), + }, + ], + payload_summary: Some("MajorBrand=\"isom\"".to_string()), + payload_bytes: None, + children: Vec::new(), + }], + }; + let json = serde_json::to_value(&report).unwrap(); + assert_eq!(json["boxes"][0]["payload_status"], "summary"); + assert_eq!( + json["boxes"][0]["payload_fields"][1]["value"]["kind"], + "bytes" + ); + assert_eq!( + json["boxes"][0]["payload_fields"][1]["value"]["value"][0], + 105 + ); + assert_eq!( + serde_json::from_value::(json).unwrap(), + report + ); +} + +fn assert_json_roundtrip(value: T) +where + T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + Debug, +{ + let json = serde_json::to_value(&value).unwrap(); + assert_eq!(serde_json::from_value::(json).unwrap(), value); +} From 772b6aebb069803c195aa41870176005218872f7 Mon Sep 17 00:00:00 2001 From: bakgio <76126058+bakgio@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:00:00 +0300 Subject: [PATCH 2/2] Clippy Fixes --- src/probe.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/probe.rs b/src/probe.rs index 505d2f8..69edaae 100644 --- a/src/probe.rs +++ b/src/probe.rs @@ -944,10 +944,8 @@ where .tracks .push(probe_trak_codec_detailed(reader, &info, options)?); } - MOOF => { - if options.include_segments { - summary.segments.push(probe_moof(reader, &info)?); - } + MOOF if options.include_segments => { + summary.segments.push(probe_moof(reader, &info)?); } MDAT => { mdat_appeared = true; @@ -1004,10 +1002,8 @@ where .tracks .push(probe_trak_media_characteristics(reader, &info, options)?); } - MOOF => { - if options.include_segments { - summary.segments.push(probe_moof(reader, &info)?); - } + MOOF if options.include_segments => { + summary.segments.push(probe_moof(reader, &info)?); } MDAT => { mdat_appeared = true;

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∨Q#N%#5I0K;8K~JYoBT?l>S!A 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 z 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 Ky$8@)81itR`}{rt literal 0 HcmV?d00001 diff --git a/tests/fixtures/opus_audio.mp4 b/tests/fixtures/opus_audio.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..06c080b79658203518dc27681f83f020dbcdbcf3 GIT binary patch literal 12159 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 O)q6V|ip*Iw+y4Qzre}iy literal 0 HcmV?d00001 diff --git a/tests/fixtures/pcm_audio.mp4 b/tests/fixtures/pcm_audio.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..e1c2361a2b5e8ed94a71f98d05a613f3ae898fb9 GIT binary patch literal 192746 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:Lk|SIp;k&8H8gF{ihiZ@ZE?KI}T7&m8YI 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 D^k>;#R3cvqbf|PMlg{doq5pd5_vs!7*_&KJ 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 Xi~TnhzqcP4S*&2b0{IFSDDb}kP&CpS literal 0 HcmV?d00001 diff --git a/tests/fixtures/vp9_opus.mp4 b/tests/fixtures/vp9_opus.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..9bf2911d9ceef06dff6f575fffa63de55a365af0 GIT binary patch literal 134436 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%0Ak!ng(%^Me5np)H3{ji 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⋙`Hl{UE* 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