use std::collections::BTreeMap;
use std::error::Error;
use std::fmt;
use std::io::{self, Cursor, Read, Seek, SeekFrom};
use crate::BoxInfo;
use crate::FourCc;
#[cfg(feature = "async")]
use crate::async_io::AsyncReadSeek;
use crate::bitio::BitReader;
use crate::boxes::av1::AV1CodecConfiguration;
use crate::boxes::etsi_ts_102_366::Dac3;
use crate::boxes::iso14496_12::{
AVCDecoderConfiguration, AudioSampleEntry, Btrt, Clap, Co64, CoLL, Colr, Ctts, Elng,
EventMessageSampleEntry, Fiel, GenericMediaSampleEntry, HEVCDecoderConfiguration, Mvhd, Pasp,
SmDm, 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;
#[cfg(feature = "async")]
use crate::codec::unmarshal_async;
use crate::codec::{CodecBox, CodecError, ImmutableBox, unmarshal};
use crate::extract::{ExtractError, ExtractedBox, extract_boxes, extract_boxes_with_payload};
#[cfg(feature = "async")]
use crate::extract::{extract_boxes_async, extract_boxes_with_payload_async};
use crate::header::HeaderError;
use crate::walk::BoxPath;
use miniz_oxide::inflate::decompress_to_vec_zlib;
#[cfg(feature = "async")]
use tokio::io::{AsyncReadExt, AsyncSeekExt};
const FTYP: FourCc = FourCc::from_bytes(*b"ftyp");
const MOOV: FourCc = FourCc::from_bytes(*b"moov");
const CMOV: FourCc = FourCc::from_bytes(*b"cmov");
const DCOM: FourCc = FourCc::from_bytes(*b"dcom");
const CMVD: FourCc = FourCc::from_bytes(*b"cmvd");
const ZLIB: FourCc = FourCc::from_bytes(*b"zlib");
const MVHD: FourCc = FourCc::from_bytes(*b"mvhd");
const TRAK: FourCc = FourCc::from_bytes(*b"trak");
const MOOF: FourCc = FourCc::from_bytes(*b"moof");
const MDAT: FourCc = FourCc::from_bytes(*b"mdat");
const TKHD: FourCc = FourCc::from_bytes(*b"tkhd");
const EDTS: FourCc = FourCc::from_bytes(*b"edts");
const ELNG: FourCc = FourCc::from_bytes(*b"elng");
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 DVHE: FourCc = FourCc::from_bytes(*b"dvhe");
const DVH1: FourCc = FourCc::from_bytes(*b"dvh1");
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 VVC1: FourCc = FourCc::from_bytes(*b"vvc1");
const VVI1: FourCc = FourCc::from_bytes(*b"vvi1");
const VVCC: FourCc = FourCc::from_bytes(*b"vvcC");
const AVS3: FourCc = FourCc::from_bytes(*b"avs3");
const AV3C: FourCc = FourCc::from_bytes(*b"av3c");
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 VP10: FourCc = FourCc::from_bytes(*b"vp10");
const VPCC: FourCc = FourCc::from_bytes(*b"vpcC");
const DIV3_ENTRY: FourCc = FourCc::from_bytes(*b"DIV3");
const DIV4_ENTRY: FourCc = FourCc::from_bytes(*b"DIV4");
const BGR3_ENTRY: FourCc = FourCc::from_bytes(*b"BGR3");
const H263_ENTRY_ALIAS: FourCc = FourCc::from_bytes(*b"H263");
const JPEG_ENTRY: FourCc = FourCc::from_bytes(*b"jpeg");
const MJPG_ENTRY_ALIAS: FourCc = FourCc::from_bytes(*b"MJPG");
const MPEG_ENTRY: FourCc = FourCc::from_bytes(*b"MPEG");
const PNG_ENTRY: FourCc = FourCc::from_bytes(*b"png ");
const PNG_ENTRY_ALIAS: FourCc = FourCc::from_bytes(*b"PNG ");
const ENCV: FourCc = FourCc::from_bytes(*b"encv");
const BTRT: FourCc = FourCc::from_bytes(*b"btrt");
const CLAP: FourCc = FourCc::from_bytes(*b"clap");
const COLL: FourCc = FourCc::from_bytes(*b"CoLL");
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 SMDM: FourCc = FourCc::from_bytes(*b"SmDm");
const MP4A: FourCc = FourCc::from_bytes(*b"mp4a");
const MP4V: FourCc = FourCc::from_bytes(*b"mp4v");
const DOT_MP3: FourCc = FourCc::from_bytes(*b".mp3");
const ALAW: FourCc = FourCc::from_bytes(*b"alaw");
const MLAW: FourCc = FourCc::from_bytes(*b"MLAW");
const OPUS: FourCc = FourCc::from_bytes(*b"Opus");
const SPEX: FourCc = FourCc::from_bytes(*b"spex");
const SAMR: FourCc = FourCc::from_bytes(*b"samr");
const SAWB: FourCc = FourCc::from_bytes(*b"sawb");
const SQCP: FourCc = FourCc::from_bytes(*b"sqcp");
const SEVC: FourCc = FourCc::from_bytes(*b"sevc");
const SSMV: FourCc = FourCc::from_bytes(*b"ssmv");
const ULAW: FourCc = FourCc::from_bytes(*b"ulaw");
const S263: FourCc = FourCc::from_bytes(*b"s263");
const DOPS: FourCc = FourCc::from_bytes(*b"dOps");
const AC_3: FourCc = FourCc::from_bytes(*b"ac-3");
const EC_3: FourCc = FourCc::from_bytes(*b"ec-3");
const DAC3: FourCc = FourCc::from_bytes(*b"dac3");
const DEC3: FourCc = FourCc::from_bytes(*b"dec3");
const AC_4: FourCc = FourCc::from_bytes(*b"ac-4");
const DAC4: FourCc = FourCc::from_bytes(*b"dac4");
const ALAC: FourCc = FourCc::from_bytes(*b"alac");
const MLPA: FourCc = FourCc::from_bytes(*b"mlpa");
const DTSC: FourCc = FourCc::from_bytes(*b"dtsc");
const DTSE: FourCc = FourCc::from_bytes(*b"dtse");
const DTSH: FourCc = FourCc::from_bytes(*b"dtsh");
const DTSL: FourCc = FourCc::from_bytes(*b"dtsl");
const DTSM: FourCc = FourCc::from_bytes(*b"dtsm");
const DTS_MINUS: FourCc = FourCc::from_bytes(*b"dts-");
const DTSX: FourCc = FourCc::from_bytes(*b"dtsx");
const DTSY: FourCc = FourCc::from_bytes(*b"dtsy");
const FLAC: FourCc = FourCc::from_bytes(*b"fLaC");
const DFLA: FourCc = FourCc::from_bytes(*b"dfLa");
const IAMF: FourCc = FourCc::from_bytes(*b"iamf");
const MHA1: FourCc = FourCc::from_bytes(*b"mha1");
const MHA2: FourCc = FourCc::from_bytes(*b"mha2");
const MHM1: FourCc = FourCc::from_bytes(*b"mhm1");
const MHM2: FourCc = FourCc::from_bytes(*b"mhm2");
const MHAC: FourCc = FourCc::from_bytes(*b"mhaC");
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 DVBS: FourCc = FourCc::from_bytes(*b"dvbs");
const DVBT: FourCc = FourCc::from_bytes(*b"dvbt");
const MP4S: FourCc = FourCc::from_bytes(*b"mp4s");
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 EVTE: FourCc = FourCc::from_bytes(*b"evte");
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");
const CTTS: FourCc = FourCc::from_bytes(*b"ctts");
const STSC: FourCc = FourCc::from_bytes(*b"stsc");
const STSZ: FourCc = FourCc::from_bytes(*b"stsz");
const TRAF: FourCc = FourCc::from_bytes(*b"traf");
const TFHD: FourCc = FourCc::from_bytes(*b"tfhd");
const TFDT: FourCc = FourCc::from_bytes(*b"tfdt");
const TRUN: FourCc = FourCc::from_bytes(*b"trun");
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProbeInfo {
pub major_brand: FourCc,
pub minor_version: u32,
pub compatible_brands: Vec<FourCc>,
pub fast_start: bool,
pub timescale: u32,
pub duration: u64,
pub tracks: Vec<TrackInfo>,
pub segments: Vec<SegmentInfo>,
}
impl Default for ProbeInfo {
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(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ProbeOptions {
pub expand_samples: bool,
pub expand_chunks: bool,
pub include_segments: bool,
}
impl ProbeOptions {
pub const fn full() -> Self {
Self {
expand_samples: true,
expand_chunks: true,
include_segments: true,
}
}
pub const fn lightweight() -> Self {
Self {
expand_samples: false,
expand_chunks: false,
include_segments: false,
}
}
}
impl Default for ProbeOptions {
fn default() -> Self {
Self::full()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TrackInfo {
pub track_id: u32,
pub timescale: u32,
pub duration: u64,
pub codec: TrackCodec,
pub encrypted: bool,
pub edit_list: Vec<EditListEntry>,
pub samples: Vec<SampleInfo>,
pub chunks: Vec<ChunkInfo>,
pub avc: Option<AvcDecoderConfigInfo>,
pub mp4a: Option<Mp4aInfo>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DetailedProbeInfo {
pub major_brand: FourCc,
pub minor_version: u32,
pub compatible_brands: Vec<FourCc>,
pub fast_start: bool,
pub timescale: u32,
pub duration: u64,
pub tracks: Vec<DetailedTrackInfo>,
pub segments: Vec<SegmentInfo>,
}
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(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DetailedTrackInfo {
pub summary: TrackInfo,
pub codec_family: TrackCodecFamily,
pub handler_type: Option<FourCc>,
pub language: Option<String>,
pub sample_entry_type: Option<FourCc>,
pub original_format: Option<FourCc>,
pub protection_scheme: Option<ProtectionSchemeInfo>,
pub display_width: Option<u16>,
pub display_height: Option<u16>,
pub channel_count: Option<u16>,
pub sample_rate: Option<u16>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CodecDetailedProbeInfo {
pub major_brand: FourCc,
pub minor_version: u32,
pub compatible_brands: Vec<FourCc>,
pub fast_start: bool,
pub timescale: u32,
pub duration: u64,
pub tracks: Vec<CodecDetailedTrackInfo>,
pub segments: Vec<SegmentInfo>,
}
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(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CodecDetailedTrackInfo {
pub summary: DetailedTrackInfo,
pub codec_details: TrackCodecDetails,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MediaCharacteristicsProbeInfo {
pub major_brand: FourCc,
pub minor_version: u32,
pub compatible_brands: Vec<FourCc>,
pub fast_start: bool,
pub timescale: u32,
pub duration: u64,
pub tracks: Vec<MediaCharacteristicsTrackInfo>,
pub segments: Vec<SegmentInfo>,
}
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(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExtendedMediaCharacteristicsProbeInfo {
pub major_brand: FourCc,
pub minor_version: u32,
pub compatible_brands: Vec<FourCc>,
pub fast_start: bool,
pub timescale: u32,
pub duration: u64,
pub tracks: Vec<ExtendedMediaCharacteristicsTrackInfo>,
pub segments: Vec<SegmentInfo>,
}
impl Default for ExtendedMediaCharacteristicsProbeInfo {
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(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MediaCharacteristicsTrackInfo {
pub summary: DetailedTrackInfo,
pub codec_details: TrackCodecDetails,
pub media_characteristics: TrackMediaCharacteristics,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ExtendedMediaCharacteristicsTrackInfo {
pub summary: DetailedTrackInfo,
pub codec_details: TrackCodecDetails,
pub media_characteristics: TrackMediaCharacteristics,
pub visual_metadata: TrackVisualMetadata,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TrackMediaCharacteristics {
pub declared_bitrate: Option<DeclaredBitrateInfo>,
pub color: Option<ColorInfo>,
pub pixel_aspect_ratio: Option<PixelAspectRatioInfo>,
pub field_order: Option<FieldOrderInfo>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TrackVisualMetadata {
pub clean_aperture: Option<CleanApertureInfo>,
pub content_light_level: Option<ContentLightLevelInfo>,
pub mastering_display: Option<MasteringDisplayInfo>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DeclaredBitrateInfo {
pub buffer_size_db: u32,
pub max_bitrate: u32,
pub avg_bitrate: u32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ColorInfo {
pub colour_type: FourCc,
pub colour_primaries: Option<u16>,
pub transfer_characteristics: Option<u16>,
pub matrix_coefficients: Option<u16>,
pub full_range: Option<bool>,
pub profile_size: Option<usize>,
pub unknown_size: Option<usize>,
}
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,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CleanApertureInfo {
pub width_numerator: u32,
pub width_denominator: u32,
pub height_numerator: u32,
pub height_denominator: u32,
pub horizontal_offset_numerator: u32,
pub horizontal_offset_denominator: u32,
pub vertical_offset_numerator: u32,
pub vertical_offset_denominator: u32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ContentLightLevelInfo {
pub max_cll: u16,
pub max_fall: u16,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PixelAspectRatioInfo {
pub h_spacing: u32,
pub v_spacing: u32,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct FieldOrderInfo {
pub field_count: u8,
pub field_ordering: u8,
pub interlaced: bool,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MasteringDisplayInfo {
pub primary_r_chromaticity_x: u16,
pub primary_r_chromaticity_y: u16,
pub primary_g_chromaticity_x: u16,
pub primary_g_chromaticity_y: u16,
pub primary_b_chromaticity_x: u16,
pub primary_b_chromaticity_y: u16,
pub white_point_chromaticity_x: u16,
pub white_point_chromaticity_y: u16,
pub luminance_max: u32,
pub luminance_min: u32,
}
#[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 {
#[default]
Unknown,
Avc(AvcCodecDetails),
Hevc(HevcCodecDetails),
Av1(Av1CodecDetails),
Vp8(VpCodecDetails),
Vp9(VpCodecDetails),
Mp4Audio(Mp4AudioCodecDetails),
Opus(OpusCodecDetails),
Ac3(Ac3CodecDetails),
Pcm(PcmCodecDetails),
XmlSubtitle(XmlSubtitleCodecDetails),
TextSubtitle(TextSubtitleCodecDetails),
WebVtt(WebVttCodecDetails),
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AvcCodecDetails {
pub configuration_version: u8,
pub profile: u8,
pub profile_compatibility: u8,
pub level: u8,
pub length_size: u16,
pub chroma_format: Option<u8>,
pub bit_depth_luma: Option<u8>,
pub bit_depth_chroma: Option<u8>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct HevcCodecDetails {
pub configuration_version: u8,
pub profile_space: u8,
pub tier_flag: bool,
pub profile_idc: u8,
pub profile_compatibility_mask: u32,
pub constraint_indicator: [u8; 6],
pub level_idc: u8,
pub min_spatial_segmentation_idc: u16,
pub parallelism_type: u8,
pub chroma_format_idc: u8,
pub bit_depth_luma: u8,
pub bit_depth_chroma: u8,
pub avg_frame_rate: u16,
pub constant_frame_rate: u8,
pub num_temporal_layers: u8,
pub temporal_id_nested: u8,
pub length_size: u16,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Av1CodecDetails {
pub seq_profile: u8,
pub seq_level_idx_0: u8,
pub seq_tier_0: u8,
pub bit_depth: u8,
pub monochrome: bool,
pub chroma_subsampling_x: u8,
pub chroma_subsampling_y: u8,
pub chroma_sample_position: u8,
pub initial_presentation_delay_minus_one: Option<u8>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct VpCodecDetails {
pub profile: u8,
pub level: u8,
pub bit_depth: u8,
pub chroma_subsampling: u8,
pub full_range: bool,
pub colour_primaries: u8,
pub transfer_characteristics: u8,
pub matrix_coefficients: u8,
pub codec_initialization_data_size: u16,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Mp4AudioCodecDetails {
pub object_type_indication: u8,
pub audio_object_type: u8,
pub channel_count: u16,
pub sample_rate: Option<u16>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OpusCodecDetails {
pub output_channel_count: u8,
pub pre_skip: u16,
pub input_sample_rate: u32,
pub output_gain: i16,
pub channel_mapping_family: u8,
pub stream_count: Option<u8>,
pub coupled_count: Option<u8>,
pub channel_mapping: Vec<u8>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Ac3CodecDetails {
pub sample_rate_code: u8,
pub bit_stream_identification: u8,
pub bit_stream_mode: u8,
pub audio_coding_mode: u8,
pub lfe_on: bool,
pub bit_rate_code: u8,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PcmCodecDetails {
pub format_flags: u8,
pub sample_size: u8,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct XmlSubtitleCodecDetails {
pub namespace: String,
pub schema_location: String,
pub auxiliary_mime_types: String,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TextSubtitleCodecDetails {
pub content_encoding: String,
pub mime_format: String,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct WebVttCodecDetails {
pub config: Option<String>,
pub source_label: Option<String>,
}
#[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>,
clap: Option<&'a Clap>,
coll: Option<&'a CoLL>,
colr: Option<&'a Colr>,
pasp: Option<&'a Pasp>,
fiel: Option<&'a Fiel>,
smdm: Option<&'a SmDm>,
}
struct ParsedRichTrackInfo {
summary: DetailedTrackInfo,
codec_details: TrackCodecDetails,
media_characteristics: TrackMediaCharacteristics,
visual_metadata: TrackVisualMetadata,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TrackCodec {
#[default]
Unknown,
Avc1,
Mp4a,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TrackCodecFamily {
#[default]
Unknown,
Avc,
Hevc,
Av1,
Vp8,
Vp9,
Mp4Audio,
Opus,
Ac3,
Pcm,
XmlSubtitle,
TextSubtitle,
WebVtt,
}
pub fn normalized_codec_family_name(
codec_family: TrackCodecFamily,
sample_entry_type: Option<FourCc>,
original_format: Option<FourCc>,
) -> &'static str {
match codec_family {
TrackCodecFamily::Unknown => match original_format.or(sample_entry_type) {
Some(VVC1 | VVI1) => "vvc",
Some(AVS3) => "avs3",
Some(EC_3) => "eac3",
Some(AC_4) => "ac4",
Some(ALAC) => "alac",
Some(DOT_MP3) => "mp3",
Some(SPEX) => "speex",
Some(SAMR) => "amr",
Some(SAWB) => "amr_wb",
Some(SQCP) => "qcelp",
Some(SEVC) => "evrc",
Some(SSMV) => "smv",
Some(MLPA) => "truehd",
Some(DTSC | DTSE | DTSH | DTSL | DTSM | DTS_MINUS | DTSX | DTSY) => "dts",
Some(FLAC) => "flac",
Some(IAMF) => "iamf",
Some(MHA1 | MHA2 | MHM1 | MHM2) => "mpeg_h",
Some(JPEG_ENTRY | MJPG_ENTRY_ALIAS) => "jpeg",
Some(S263 | H263_ENTRY_ALIAS) => "h263",
Some(MPEG_ENTRY) => "mpeg2_video",
Some(MP4V) => "mpeg4_visual",
Some(PNG_ENTRY | PNG_ENTRY_ALIAS) => "png",
Some(VP10) => "vp10",
Some(DVBS) => "dvb_subtitle",
Some(DVBT) => "dvb_teletext",
Some(MP4S) => "subpicture",
Some(STPP) => "xml_subtitle",
Some(SBTT) => "text_subtitle",
Some(WVTT) => "webvtt",
_ => "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",
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProtectionSchemeInfo {
pub scheme_type: FourCc,
pub scheme_version: u32,
}
impl Default for ProtectionSchemeInfo {
fn default() -> Self {
Self {
scheme_type: FourCc::ANY,
scheme_version: 0,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct EditListEntry {
pub media_time: i64,
pub segment_duration: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SampleInfo {
pub size: u32,
pub time_delta: u32,
pub composition_time_offset: i64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChunkInfo {
pub data_offset: u64,
pub samples_per_chunk: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct AvcDecoderConfigInfo {
pub configuration_version: u8,
pub profile: u8,
pub profile_compatibility: u8,
pub level: u8,
pub length_size: u16,
pub width: u16,
pub height: u16,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct AacProfileInfo {
pub object_type_indication: u8,
pub audio_object_type: u8,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Mp4aInfo {
pub object_type_indication: u8,
pub audio_object_type: u8,
pub channel_count: u16,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SegmentInfo {
pub track_id: u32,
pub moof_offset: u64,
pub base_media_decode_time: u64,
pub default_sample_duration: u32,
pub sample_count: u32,
pub duration: u32,
pub composition_time_offset: i32,
pub size: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct ParsedMoofSegment {
summary: SegmentInfo,
zero_duration_sample_count: u32,
sample_durations: Vec<u32>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) struct FragmentedTrackWarningDiagnostics {
pub zero_duration_sample_count: u64,
pub sample_duration_change_count: u64,
pub min_non_zero_sample_duration: Option<u32>,
pub max_non_zero_sample_duration: Option<u32>,
last_non_zero_sample_duration: Option<u32>,
}
pub fn probe<R>(reader: &mut R) -> Result<ProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_with_options(reader, ProbeOptions::default())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_async<R>(reader: &mut R) -> Result<ProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_with_options_async(reader, ProbeOptions::default()).await
}
pub fn probe_with_options<R>(reader: &mut R, options: ProbeOptions) -> Result<ProbeInfo, ProbeError>
where
R: Read + Seek,
{
Ok(strip_probe_details(probe_detailed_with_options(
reader, options,
)?))
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_with_options_async<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<ProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
Ok(strip_probe_details(
probe_detailed_with_options_async(reader, options).await?,
))
}
pub fn probe_detailed<R>(reader: &mut R) -> Result<DetailedProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_detailed_with_options(reader, ProbeOptions::default())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_detailed_async<R>(reader: &mut R) -> Result<DetailedProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_detailed_with_options_async(reader, ProbeOptions::default()).await
}
pub fn probe_detailed_with_options<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<DetailedProbeInfo, ProbeError>
where
R: Read + Seek,
{
Ok(strip_codec_details(probe_codec_detailed_with_options(
reader, options,
)?))
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_detailed_with_options_async<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<DetailedProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
Ok(strip_codec_details(
probe_codec_detailed_with_options_async(reader, options).await?,
))
}
pub fn probe_codec_detailed<R>(reader: &mut R) -> Result<CodecDetailedProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_codec_detailed_with_options(reader, ProbeOptions::default())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_codec_detailed_async<R>(
reader: &mut R,
) -> Result<CodecDetailedProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_codec_detailed_with_options_async(reader, ProbeOptions::default()).await
}
pub fn probe_codec_detailed_with_options<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<CodecDetailedProbeInfo, ProbeError>
where
R: Read + Seek,
{
let paths = root_probe_box_paths(options);
let infos = match extract_boxes(reader, None, &paths) {
Ok(infos) => infos,
Err(error) => {
if let Some(root_bytes) = extract_compressed_movie_root_bytes_sync(reader)? {
let mut cursor = Cursor::new(root_bytes);
return probe_codec_detailed_with_options(&mut cursor, options);
}
return Err(error.into());
}
};
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;
}
_ => {}
}
}
if (summary.tracks.is_empty() || summary.timescale == 0)
&& let Some(root_bytes) = extract_compressed_movie_root_bytes_sync(reader)?
{
let mut cursor = Cursor::new(root_bytes);
let fallback = probe_codec_detailed_with_options(&mut cursor, options)?;
if !fallback.tracks.is_empty() || fallback.timescale != 0 {
return Ok(fallback);
}
}
Ok(summary)
}
pub(crate) fn extract_compressed_movie_root_bytes_sync<R>(
reader: &mut R,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: Read + Seek,
{
let ftyp_bytes = extract_root_box_bytes_sync(reader, FTYP)?;
let Some(moov_info) = find_root_box_info_sync(reader, MOOV)? else {
return Ok(None);
};
let Some(decoded_moov_box_bytes) =
decode_compressed_movie_moov_box_bytes_sync(reader, moov_info)?
else {
return Ok(None);
};
let mut root_bytes =
Vec::with_capacity(ftyp_bytes.as_ref().map_or(0, Vec::len) + decoded_moov_box_bytes.len());
if let Some(ftyp_box_bytes) = ftyp_bytes {
root_bytes.extend_from_slice(&ftyp_box_bytes);
}
root_bytes.extend_from_slice(&decoded_moov_box_bytes);
Ok(Some(root_bytes))
}
#[cfg(feature = "async")]
pub(crate) async fn extract_compressed_movie_root_bytes_async<R>(
reader: &mut R,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: AsyncReadSeek,
{
let ftyp_bytes = extract_root_box_bytes_async(reader, FTYP).await?;
let Some(moov_info) = find_root_box_info_async(reader, MOOV).await? else {
return Ok(None);
};
let Some(decoded_moov_box_bytes) =
decode_compressed_movie_moov_box_bytes_async(reader, moov_info).await?
else {
return Ok(None);
};
let mut root_bytes =
Vec::with_capacity(ftyp_bytes.as_ref().map_or(0, Vec::len) + decoded_moov_box_bytes.len());
if let Some(ftyp_box_bytes) = ftyp_bytes {
root_bytes.extend_from_slice(&ftyp_box_bytes);
}
root_bytes.extend_from_slice(&decoded_moov_box_bytes);
Ok(Some(root_bytes))
}
fn decode_compressed_movie_moov_box_bytes_sync<R>(
reader: &mut R,
moov_info: BoxInfo,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: Read + Seek,
{
let Some(cmov_info) = find_child_box_info_sync(reader, moov_info, CMOV)? else {
return Ok(None);
};
let Some(dcom_payload) = read_child_box_payload_bytes_sync(reader, cmov_info, DCOM)? else {
return Err(ProbeError::MissingRequiredBox("dcom"));
};
if dcom_payload.as_slice() != ZLIB.as_bytes() {
return Err(ProbeError::Io(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"unsupported compressed movie method `{}`",
String::from_utf8_lossy(&dcom_payload)
),
)));
}
let Some(cmvd_payload) = read_child_box_payload_bytes_sync(reader, cmov_info, CMVD)? else {
return Err(ProbeError::MissingRequiredBox("cmvd"));
};
decode_compressed_movie_cmvd_payload(&cmvd_payload)
}
#[cfg(feature = "async")]
async fn decode_compressed_movie_moov_box_bytes_async<R>(
reader: &mut R,
moov_info: BoxInfo,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: AsyncReadSeek,
{
let Some(cmov_info) = find_child_box_info_async(reader, moov_info, CMOV).await? else {
return Ok(None);
};
let Some(dcom_payload) = read_child_box_payload_bytes_async(reader, cmov_info, DCOM).await?
else {
return Err(ProbeError::MissingRequiredBox("dcom"));
};
if dcom_payload.as_slice() != ZLIB.as_bytes() {
return Err(ProbeError::Io(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"unsupported compressed movie method `{}`",
String::from_utf8_lossy(&dcom_payload)
),
)));
}
let Some(cmvd_payload) = read_child_box_payload_bytes_async(reader, cmov_info, CMVD).await?
else {
return Err(ProbeError::MissingRequiredBox("cmvd"));
};
decode_compressed_movie_cmvd_payload(&cmvd_payload)
}
fn decode_compressed_movie_cmvd_payload(
cmvd_payload: &[u8],
) -> Result<Option<Vec<u8>>, ProbeError> {
if cmvd_payload.len() < 4 {
return Err(ProbeError::Io(io::Error::new(
io::ErrorKind::InvalidData,
"compressed movie payload is truncated before the encoded size field",
)));
}
let declared_len = u32::from_be_bytes(cmvd_payload[..4].try_into().unwrap());
let decompressed = decompress_to_vec_zlib(&cmvd_payload[4..]).map_err(|error| {
ProbeError::Io(io::Error::new(
io::ErrorKind::InvalidData,
format!("failed to inflate compressed movie payload: {error:?}"),
))
})?;
if decompressed.len() != usize::try_from(declared_len).unwrap_or(usize::MAX) {
return Err(ProbeError::Io(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"compressed movie payload declared {} bytes but inflated to {} bytes",
declared_len,
decompressed.len()
),
)));
}
let mut inflated_cursor = Cursor::new(decompressed.as_slice());
let inflated_moov_info = BoxInfo::read(&mut inflated_cursor)?;
if inflated_moov_info.box_type() != MOOV {
return Err(ProbeError::Io(io::Error::new(
io::ErrorKind::InvalidData,
"inflated compressed movie payload did not yield a moov box",
)));
}
Ok(Some(decompressed))
}
fn find_root_box_info_sync<R>(
reader: &mut R,
box_type: FourCc,
) -> Result<Option<BoxInfo>, ProbeError>
where
R: Read + Seek,
{
reader.seek(SeekFrom::Start(0))?;
loop {
let start = reader.stream_position()?;
let info = match BoxInfo::read(reader) {
Ok(info) => info,
Err(HeaderError::Io(error)) if error.kind() == io::ErrorKind::UnexpectedEof => {
reader.seek(SeekFrom::Start(start))?;
return Ok(None);
}
Err(error) => return Err(error.into()),
};
if info.box_type() == box_type {
return Ok(Some(info));
}
info.seek_to_end(reader)?;
}
}
#[cfg(feature = "async")]
async fn find_root_box_info_async<R>(
reader: &mut R,
box_type: FourCc,
) -> Result<Option<BoxInfo>, ProbeError>
where
R: AsyncReadSeek,
{
reader.seek(SeekFrom::Start(0)).await?;
loop {
let start = reader.stream_position().await?;
let info = match BoxInfo::read_async(reader).await {
Ok(info) => info,
Err(HeaderError::Io(error)) if error.kind() == io::ErrorKind::UnexpectedEof => {
reader.seek(SeekFrom::Start(start)).await?;
return Ok(None);
}
Err(error) => return Err(error.into()),
};
if info.box_type() == box_type {
return Ok(Some(info));
}
info.seek_to_end_async(reader).await?;
}
}
fn extract_root_box_bytes_sync<R>(
reader: &mut R,
box_type: FourCc,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: Read + Seek,
{
let Some(info) = find_root_box_info_sync(reader, box_type)? else {
return Ok(None);
};
validate_box_fits_stream_sync(reader, info, "root box")?;
info.seek_to_start(reader)?;
let mut bytes = vec![
0_u8;
usize::try_from(info.size()).map_err(|_| ProbeError::NumericOverflow {
field_name: "box size",
})?
];
reader.read_exact(&mut bytes)?;
Ok(Some(bytes))
}
#[cfg(feature = "async")]
async fn extract_root_box_bytes_async<R>(
reader: &mut R,
box_type: FourCc,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: AsyncReadSeek,
{
let Some(info) = find_root_box_info_async(reader, box_type).await? else {
return Ok(None);
};
validate_box_fits_stream_async(reader, info, "root box").await?;
info.seek_to_start_async(reader).await?;
let mut bytes = vec![
0_u8;
usize::try_from(info.size()).map_err(|_| ProbeError::NumericOverflow {
field_name: "box size",
})?
];
tokio::io::AsyncReadExt::read_exact(reader, &mut bytes).await?;
Ok(Some(bytes))
}
fn find_child_box_info_sync<R>(
reader: &mut R,
parent_info: BoxInfo,
child_type: FourCc,
) -> Result<Option<BoxInfo>, ProbeError>
where
R: Read + Seek,
{
let parent_end = parent_info.offset() + parent_info.size();
reader.seek(SeekFrom::Start(
parent_info.offset() + parent_info.header_size(),
))?;
while reader.stream_position()? < parent_end {
let child_info = BoxInfo::read(reader)?;
if child_info.box_type() == child_type {
return Ok(Some(child_info));
}
child_info.seek_to_end(reader)?;
}
Ok(None)
}
#[cfg(feature = "async")]
async fn find_child_box_info_async<R>(
reader: &mut R,
parent_info: BoxInfo,
child_type: FourCc,
) -> Result<Option<BoxInfo>, ProbeError>
where
R: AsyncReadSeek,
{
let parent_end = parent_info.offset() + parent_info.size();
reader
.seek(SeekFrom::Start(
parent_info.offset() + parent_info.header_size(),
))
.await?;
while reader.stream_position().await? < parent_end {
let child_info = BoxInfo::read_async(reader).await?;
if child_info.box_type() == child_type {
return Ok(Some(child_info));
}
child_info.seek_to_end_async(reader).await?;
}
Ok(None)
}
fn read_child_box_payload_bytes_sync<R>(
reader: &mut R,
parent_info: BoxInfo,
child_type: FourCc,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: Read + Seek,
{
let Some(child_info) = find_child_box_info_sync(reader, parent_info, child_type)? else {
return Ok(None);
};
validate_child_box_fits_parent(child_info, parent_info)?;
validate_box_fits_stream_sync(reader, child_info, "child box")?;
child_info.seek_to_payload(reader)?;
let mut bytes = vec![
0_u8;
usize::try_from(child_info.payload_size()?).map_err(|_| {
ProbeError::NumericOverflow {
field_name: "child box payload size",
}
})?
];
reader.read_exact(&mut bytes)?;
Ok(Some(bytes))
}
#[cfg(feature = "async")]
async fn read_child_box_payload_bytes_async<R>(
reader: &mut R,
parent_info: BoxInfo,
child_type: FourCc,
) -> Result<Option<Vec<u8>>, ProbeError>
where
R: AsyncReadSeek,
{
let Some(child_info) = find_child_box_info_async(reader, parent_info, child_type).await? else {
return Ok(None);
};
validate_child_box_fits_parent(child_info, parent_info)?;
validate_box_fits_stream_async(reader, child_info, "child box").await?;
child_info.seek_to_payload_async(reader).await?;
let mut bytes = vec![
0_u8;
usize::try_from(child_info.payload_size()?).map_err(|_| {
ProbeError::NumericOverflow {
field_name: "child box payload size",
}
})?
];
tokio::io::AsyncReadExt::read_exact(reader, &mut bytes).await?;
Ok(Some(bytes))
}
fn validate_child_box_fits_parent(
child_info: BoxInfo,
parent_info: BoxInfo,
) -> Result<(), ProbeError> {
let child_end = checked_box_end(child_info, "child box end")?;
let parent_end = checked_box_end(parent_info, "parent box end")?;
if child_end > parent_end {
return Err(truncated_box_error("child box"));
}
Ok(())
}
fn validate_box_fits_stream_sync<R>(
reader: &mut R,
info: BoxInfo,
label: &'static str,
) -> Result<(), ProbeError>
where
R: Seek,
{
let position = reader.stream_position()?;
let stream_len = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(position))?;
validate_box_end_within_stream(info, stream_len, label)
}
#[cfg(feature = "async")]
async fn validate_box_fits_stream_async<R>(
reader: &mut R,
info: BoxInfo,
label: &'static str,
) -> Result<(), ProbeError>
where
R: AsyncReadSeek,
{
let position = reader.stream_position().await?;
let stream_len = reader.seek(SeekFrom::End(0)).await?;
reader.seek(SeekFrom::Start(position)).await?;
validate_box_end_within_stream(info, stream_len, label)
}
fn validate_box_end_within_stream(
info: BoxInfo,
stream_len: u64,
label: &'static str,
) -> Result<(), ProbeError> {
if checked_box_end(info, "box end")? > stream_len {
return Err(truncated_box_error(label));
}
Ok(())
}
fn checked_box_end(info: BoxInfo, field_name: &'static str) -> Result<u64, ProbeError> {
info.offset()
.checked_add(info.size())
.ok_or(ProbeError::NumericOverflow { field_name })
}
fn truncated_box_error(label: &'static str) -> ProbeError {
ProbeError::Io(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!("declared {label} extends beyond input"),
))
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_codec_detailed_with_options_async<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<CodecDetailedProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
let paths = root_probe_box_paths(options);
let infos = match extract_boxes_async(reader, None, &paths).await {
Ok(infos) => infos,
Err(error) => {
if let Some(root_bytes) = extract_compressed_movie_root_bytes_async(reader).await? {
let mut cursor = Cursor::new(root_bytes);
return probe_codec_detailed_with_options(&mut cursor, options);
}
return Err(error.into());
}
};
let mut summary = CodecDetailedProbeInfo::default();
let mut mdat_appeared = false;
for info in infos {
match info.box_type() {
FTYP => {
let ftyp =
read_payload_as_async::<_, crate::boxes::iso14496_12::Ftyp>(reader, info)
.await?;
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_async::<_, Mvhd>(reader, info).await?;
summary.timescale = mvhd.timescale;
summary.duration = mvhd.duration();
}
TRAK => {
summary
.tracks
.push(probe_trak_codec_detailed_async(reader, info, options).await?);
}
MOOF if options.include_segments => {
summary.segments.push(probe_moof_async(reader, info).await?);
}
MDAT => {
mdat_appeared = true;
}
_ => {}
}
}
if (summary.tracks.is_empty() || summary.timescale == 0)
&& let Some(root_bytes) = extract_compressed_movie_root_bytes_async(reader).await?
{
let mut cursor = Cursor::new(root_bytes);
let fallback = probe_codec_detailed_with_options(&mut cursor, options)?;
if !fallback.tracks.is_empty() || fallback.timescale != 0 {
return Ok(fallback);
}
}
Ok(summary)
}
pub fn probe_media_characteristics<R>(
reader: &mut R,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_media_characteristics_with_options(reader, ProbeOptions::default())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_media_characteristics_async<R>(
reader: &mut R,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_media_characteristics_with_options_async(reader, ProbeOptions::default()).await
}
pub fn probe_media_characteristics_with_options<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError>
where
R: Read + Seek,
{
let paths = root_probe_box_paths(options);
let infos = extract_boxes(reader, None, &paths)?;
let mut summary = MediaCharacteristicsProbeInfo::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_media_characteristics(reader, &info, options)?);
}
MOOF if options.include_segments => {
summary.segments.push(probe_moof(reader, &info)?);
}
MDAT => {
mdat_appeared = true;
}
_ => {}
}
}
Ok(summary)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_media_characteristics_with_options_async<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
let paths = root_probe_box_paths(options);
let infos = extract_boxes_async(reader, None, &paths).await?;
let mut summary = MediaCharacteristicsProbeInfo::default();
let mut mdat_appeared = false;
for info in infos {
match info.box_type() {
FTYP => {
let ftyp =
read_payload_as_async::<_, crate::boxes::iso14496_12::Ftyp>(reader, info)
.await?;
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_async::<_, Mvhd>(reader, info).await?;
summary.timescale = mvhd.timescale;
summary.duration = mvhd.duration();
}
TRAK => {
summary
.tracks
.push(probe_trak_media_characteristics_async(reader, info, options).await?);
}
MOOF if options.include_segments => {
summary.segments.push(probe_moof_async(reader, info).await?);
}
MDAT => {
mdat_appeared = true;
}
_ => {}
}
}
Ok(summary)
}
pub fn probe_extended_media_characteristics<R>(
reader: &mut R,
) -> Result<ExtendedMediaCharacteristicsProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_extended_media_characteristics_with_options(reader, ProbeOptions::default())
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_extended_media_characteristics_async<R>(
reader: &mut R,
) -> Result<ExtendedMediaCharacteristicsProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_extended_media_characteristics_with_options_async(reader, ProbeOptions::default()).await
}
pub fn probe_extended_media_characteristics_with_options<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<ExtendedMediaCharacteristicsProbeInfo, ProbeError>
where
R: Read + Seek,
{
let paths = root_probe_box_paths(options);
let infos = extract_boxes(reader, None, &paths)?;
let mut summary = ExtendedMediaCharacteristicsProbeInfo::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_extended_media_characteristics(
reader, &info, options,
)?);
}
MOOF if options.include_segments => {
summary.segments.push(probe_moof(reader, &info)?);
}
MDAT => {
mdat_appeared = true;
}
_ => {}
}
}
Ok(summary)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_extended_media_characteristics_with_options_async<R>(
reader: &mut R,
options: ProbeOptions,
) -> Result<ExtendedMediaCharacteristicsProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
let paths = root_probe_box_paths(options);
let infos = extract_boxes_async(reader, None, &paths).await?;
let mut summary = ExtendedMediaCharacteristicsProbeInfo::default();
let mut mdat_appeared = false;
for info in infos {
match info.box_type() {
FTYP => {
let ftyp =
read_payload_as_async::<_, crate::boxes::iso14496_12::Ftyp>(reader, info)
.await?;
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_async::<_, Mvhd>(reader, info).await?;
summary.timescale = mvhd.timescale;
summary.duration = mvhd.duration();
}
TRAK => {
summary.tracks.push(
probe_trak_extended_media_characteristics_async(reader, info, options).await?,
);
}
MOOF if options.include_segments => {
summary.segments.push(probe_moof_async(reader, info).await?);
}
MDAT => {
mdat_appeared = true;
}
_ => {}
}
}
Ok(summary)
}
pub fn probe_bytes(input: &[u8]) -> Result<ProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe(&mut reader)
}
pub fn probe_bytes_with_options(
input: &[u8],
options: ProbeOptions,
) -> Result<ProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_with_options(&mut reader, options)
}
pub(crate) fn fragmented_track_warning_diagnostics<R>(
reader: &mut R,
) -> Result<BTreeMap<u32, FragmentedTrackWarningDiagnostics>, ProbeError>
where
R: Read + Seek,
{
let infos = extract_boxes(reader, None, &[BoxPath::from([MOOF])])?;
let mut diagnostics = BTreeMap::new();
for info in infos {
let parsed = probe_moof_parsed(reader, &info)?;
let entry = diagnostics
.entry(parsed.summary.track_id)
.or_insert_with(FragmentedTrackWarningDiagnostics::default);
entry.zero_duration_sample_count += u64::from(parsed.zero_duration_sample_count);
for sample_duration in parsed.sample_durations {
if sample_duration == 0 {
continue;
}
if let Some(previous_duration) = entry.last_non_zero_sample_duration
&& previous_duration != sample_duration
{
entry.sample_duration_change_count += 1;
}
entry.last_non_zero_sample_duration = Some(sample_duration);
entry.min_non_zero_sample_duration = Some(
entry
.min_non_zero_sample_duration
.map_or(sample_duration, |value| value.min(sample_duration)),
);
entry.max_non_zero_sample_duration = Some(
entry
.max_non_zero_sample_duration
.map_or(sample_duration, |value| value.max(sample_duration)),
);
}
}
Ok(diagnostics)
}
pub fn probe_detailed_bytes(input: &[u8]) -> Result<DetailedProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_detailed(&mut reader)
}
pub fn probe_detailed_bytes_with_options(
input: &[u8],
options: ProbeOptions,
) -> Result<DetailedProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_detailed_with_options(&mut reader, options)
}
pub fn probe_codec_detailed_bytes(input: &[u8]) -> Result<CodecDetailedProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_codec_detailed(&mut reader)
}
pub fn probe_codec_detailed_bytes_with_options(
input: &[u8],
options: ProbeOptions,
) -> Result<CodecDetailedProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_codec_detailed_with_options(&mut reader, options)
}
pub fn probe_media_characteristics_bytes(
input: &[u8],
) -> Result<MediaCharacteristicsProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_media_characteristics(&mut reader)
}
pub fn probe_media_characteristics_bytes_with_options(
input: &[u8],
options: ProbeOptions,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_media_characteristics_with_options(&mut reader, options)
}
pub fn probe_extended_media_characteristics_bytes(
input: &[u8],
) -> Result<ExtendedMediaCharacteristicsProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_extended_media_characteristics(&mut reader)
}
pub fn probe_extended_media_characteristics_bytes_with_options(
input: &[u8],
options: ProbeOptions,
) -> Result<ExtendedMediaCharacteristicsProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_extended_media_characteristics_with_options(&mut reader, options)
}
pub fn probe_fra<R>(reader: &mut R) -> Result<ProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe(reader)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_fra_async<R>(reader: &mut R) -> Result<ProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_async(reader).await
}
pub fn probe_fra_detailed<R>(reader: &mut R) -> Result<DetailedProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_detailed(reader)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_fra_detailed_async<R>(reader: &mut R) -> Result<DetailedProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_detailed_async(reader).await
}
pub fn probe_fra_codec_detailed<R>(reader: &mut R) -> Result<CodecDetailedProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_codec_detailed(reader)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_fra_codec_detailed_async<R>(
reader: &mut R,
) -> Result<CodecDetailedProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_codec_detailed_async(reader).await
}
pub fn probe_fra_media_characteristics<R>(
reader: &mut R,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError>
where
R: Read + Seek,
{
probe_media_characteristics(reader)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn probe_fra_media_characteristics_async<R>(
reader: &mut R,
) -> Result<MediaCharacteristicsProbeInfo, ProbeError>
where
R: AsyncReadSeek,
{
probe_media_characteristics_async(reader).await
}
pub fn probe_fra_bytes(input: &[u8]) -> Result<ProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_fra(&mut reader)
}
pub fn probe_fra_detailed_bytes(input: &[u8]) -> Result<DetailedProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_fra_detailed(&mut reader)
}
pub fn probe_fra_codec_detailed_bytes(input: &[u8]) -> Result<CodecDetailedProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_fra_codec_detailed(&mut reader)
}
pub fn probe_fra_media_characteristics_bytes(
input: &[u8],
) -> Result<MediaCharacteristicsProbeInfo, ProbeError> {
let mut reader = Cursor::new(input);
probe_fra_media_characteristics(&mut reader)
}
pub fn detect_aac_profile(esds: &Esds) -> Result<Option<AacProfileInfo>, ProbeError> {
let Some(decoder_config) = esds.decoder_config_descriptor() else {
return Ok(None);
};
if decoder_config.object_type_indication != 0x40 {
return Ok(Some(AacProfileInfo {
object_type_indication: decoder_config.object_type_indication,
audio_object_type: 0,
}));
}
let specific_info = esds
.decoder_specific_info()
.ok_or(ProbeError::MissingDescriptor(
"decoder specific info descriptor",
))?;
let mut reader = BitReader::new(Cursor::new(specific_info));
let (audio_object_type, mut bit_offset) = get_audio_object_type(&mut reader)?;
let sampling_frequency_index = read_bits_u8(&mut reader, 4)?;
bit_offset = bit_offset.saturating_add(4);
if sampling_frequency_index == 0x0f {
let _ = read_bits_u32(&mut reader, 24)?;
bit_offset = bit_offset.saturating_add(24);
}
if audio_object_type == 2
&& let Some(extension) =
detect_aac_sync_extension_info(specific_info, bit_offset.saturating_add(4))
{
return Ok(Some(AacProfileInfo {
object_type_indication: 0x40,
audio_object_type: extension.audio_object_type,
}));
}
Ok(Some(AacProfileInfo {
object_type_indication: 0x40,
audio_object_type,
}))
}
pub fn detect_aac_effective_sample_rate(esds: &Esds) -> Result<Option<u32>, ProbeError> {
let Some(decoder_config) = esds.decoder_config_descriptor() else {
return Ok(None);
};
if decoder_config.object_type_indication != 0x40 {
return Ok(None);
}
let specific_info = esds
.decoder_specific_info()
.ok_or(ProbeError::MissingDescriptor(
"decoder specific info descriptor",
))?;
let mut reader = BitReader::new(Cursor::new(specific_info));
let (audio_object_type, mut bit_offset) = get_audio_object_type(&mut reader)?;
let (sample_rate, sample_rate_bits) = read_aac_sample_rate_with_bits(&mut reader)?;
bit_offset = bit_offset.saturating_add(sample_rate_bits);
let _ = read_bits_u8(&mut reader, 4)?;
bit_offset = bit_offset.saturating_add(4);
if matches!(audio_object_type, 5 | 29) {
return Ok(Some(read_aac_sample_rate(&mut reader)?));
}
if audio_object_type == 2
&& let Some(extension) = detect_aac_sync_extension_info(specific_info, bit_offset)
&& let Some(sample_rate) = extension.sample_rate
{
return Ok(Some(sample_rate));
}
Ok(Some(sample_rate))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
struct AacSyncExtensionInfo {
audio_object_type: u8,
sample_rate: Option<u32>,
}
fn detect_aac_sync_extension_info(
specific_info: &[u8],
search_start_bit: usize,
) -> Option<AacSyncExtensionInfo> {
let total_bits = specific_info.len().checked_mul(8)?;
if search_start_bit
.checked_add(17)
.is_none_or(|minimum| minimum > total_bits)
{
return None;
}
for sync_bit in search_start_bit..=total_bits.saturating_sub(17) {
if read_bits_from_slice(specific_info, sync_bit, 11)? != 0x02b7 {
continue;
}
let (ext_audio_object_type, ext_aot_bits) =
get_audio_object_type_from_slice(specific_info, sync_bit.saturating_add(11))?;
if ext_audio_object_type != 5 && ext_audio_object_type != 22 {
continue;
}
let mut bit_offset = sync_bit.saturating_add(11).saturating_add(ext_aot_bits);
let sbr = read_bits_from_slice(specific_info, bit_offset, 1)?;
bit_offset = bit_offset.saturating_add(1);
if sbr == 0 {
continue;
}
if ext_audio_object_type == 5 {
let ext_sampling_frequency_index =
read_bits_from_slice(specific_info, bit_offset, 4)? as u8;
bit_offset = bit_offset.saturating_add(4);
let sample_rate = if ext_sampling_frequency_index == 0x0f {
let sample_rate = read_bits_from_slice(specific_info, bit_offset, 24)?;
bit_offset = bit_offset.saturating_add(24);
if bit_offset > total_bits {
continue;
}
Some(sample_rate)
} else {
aac_sampling_frequency(ext_sampling_frequency_index)
};
if ext_sampling_frequency_index == 0x0f && sample_rate.is_none() {
continue;
}
if bit_offset.saturating_add(12) <= total_bits
&& read_bits_from_slice(specific_info, bit_offset, 11)? == 0x0548
&& read_bits_from_slice(specific_info, bit_offset.saturating_add(11), 1)? != 0
{
return Some(AacSyncExtensionInfo {
audio_object_type: 29,
sample_rate,
});
}
return Some(AacSyncExtensionInfo {
audio_object_type: 5,
sample_rate,
});
}
return Some(AacSyncExtensionInfo {
audio_object_type: 5,
sample_rate: None,
});
}
None
}
fn read_aac_sample_rate<R>(reader: &mut BitReader<R>) -> Result<u32, ProbeError>
where
R: Read,
{
Ok(read_aac_sample_rate_with_bits(reader)?.0)
}
fn read_aac_sample_rate_with_bits<R>(reader: &mut BitReader<R>) -> Result<(u32, usize), ProbeError>
where
R: Read,
{
let sampling_frequency_index = read_bits_u8(reader, 4)?;
if sampling_frequency_index == 0x0f {
return Ok((read_bits_u32(reader, 24)?, 28));
}
Ok((
aac_sampling_frequency(sampling_frequency_index).ok_or(ProbeError::MissingDescriptor(
"supported AAC sampling-frequency index",
))?,
4,
))
}
const fn aac_sampling_frequency(index: u8) -> Option<u32> {
match index {
0 => Some(96_000),
1 => Some(88_200),
2 => Some(64_000),
3 => Some(48_000),
4 => Some(44_100),
5 => Some(32_000),
6 => Some(24_000),
7 => Some(22_050),
8 => Some(16_000),
9 => Some(12_000),
10 => Some(11_025),
11 => Some(8_000),
12 => Some(7_350),
_ => None,
}
}
fn get_audio_object_type_from_slice(data: &[u8], bit_offset: usize) -> Option<(u8, usize)> {
let audio_object_type = u8::try_from(read_bits_from_slice(data, bit_offset, 5)?).ok()?;
if audio_object_type != 0x1f {
return Some((audio_object_type, 5));
}
let extended =
u8::try_from(read_bits_from_slice(data, bit_offset.saturating_add(5), 6)?).ok()?;
Some((extended.saturating_add(32), 11))
}
fn read_bits_from_slice(data: &[u8], bit_offset: usize, width: usize) -> Option<u32> {
let end = bit_offset.checked_add(width)?;
if end > data.len().checked_mul(8)? || width > 32 {
return None;
}
let mut value = 0_u32;
for index in bit_offset..end {
let byte = *data.get(index / 8)?;
let shift = 7_u8.saturating_sub(u8::try_from(index % 8).ok()?);
value = (value << 1) | u32::from((byte >> shift) & 1);
}
Some(value)
}
pub fn find_idr_frames<R>(reader: &mut R, track: &TrackInfo) -> Result<Vec<usize>, ProbeError>
where
R: Read + Seek,
{
let Some(avc) = track.avc.as_ref() else {
return Ok(Vec::new());
};
let length_size = u32::from(avc.length_size);
let mut sample_index = 0usize;
let mut indices = Vec::new();
for chunk in &track.chunks {
let end = sample_index.saturating_add(chunk.samples_per_chunk as usize);
let mut data_offset = chunk.data_offset;
while sample_index < end && sample_index < track.samples.len() {
let sample = &track.samples[sample_index];
if sample.size != 0 {
let mut nal_offset = 0_u32;
while nal_offset.saturating_add(length_size).saturating_add(1) <= sample.size {
reader.seek(SeekFrom::Start(data_offset + u64::from(nal_offset)))?;
let mut data = vec![0_u8; length_size as usize + 1];
reader.read_exact(&mut data)?;
let mut nal_length = 0_u32;
for byte in &data[..length_size as usize] {
nal_length = (nal_length << 8) | u32::from(*byte);
}
if data[length_size as usize] & 0x1f == 5 {
indices.push(sample_index);
break;
}
nal_offset = nal_offset
.saturating_add(length_size)
.saturating_add(nal_length);
}
}
data_offset = data_offset.saturating_add(u64::from(sample.size));
sample_index += 1;
}
}
Ok(indices)
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn find_idr_frames_async<R>(
reader: &mut R,
track: &TrackInfo,
) -> Result<Vec<usize>, ProbeError>
where
R: AsyncReadSeek,
{
let Some(avc) = track.avc.as_ref() else {
return Ok(Vec::new());
};
let length_size = u32::from(avc.length_size);
let mut sample_index = 0usize;
let mut indices = Vec::new();
for chunk in &track.chunks {
let end = sample_index.saturating_add(chunk.samples_per_chunk as usize);
let mut data_offset = chunk.data_offset;
while sample_index < end && sample_index < track.samples.len() {
let sample = &track.samples[sample_index];
if sample.size != 0 {
let mut nal_offset = 0_u32;
while nal_offset.saturating_add(length_size).saturating_add(1) <= sample.size {
reader
.seek(SeekFrom::Start(data_offset + u64::from(nal_offset)))
.await?;
let mut data = vec![0_u8; length_size as usize + 1];
reader.read_exact(&mut data).await?;
let mut nal_length = 0_u32;
for byte in &data[..length_size as usize] {
nal_length = (nal_length << 8) | u32::from(*byte);
}
if data[length_size as usize] & 0x1f == 5 {
indices.push(sample_index);
break;
}
nal_offset = nal_offset
.saturating_add(length_size)
.saturating_add(nal_length);
}
}
data_offset = data_offset.saturating_add(u64::from(sample.size));
sample_index += 1;
}
}
Ok(indices)
}
pub fn average_sample_bitrate(samples: &[SampleInfo], timescale: u32) -> u64 {
let total_size = samples
.iter()
.map(|sample| u64::from(sample.size))
.sum::<u64>();
let total_duration = samples
.iter()
.map(|sample| u64::from(sample.time_delta))
.sum::<u64>();
if total_duration == 0 {
return 0;
}
8 * total_size * u64::from(timescale) / total_duration
}
pub fn max_sample_bitrate(samples: &[SampleInfo], timescale: u32, window_time_delta: u64) -> u64 {
if window_time_delta == 0 || samples.is_empty() {
return 0;
}
let mut max_bitrate = 0_u64;
let mut size = 0_u64;
let mut duration = 0_u64;
let mut begin = 0usize;
let mut end = 0usize;
while end < samples.len() {
while end < samples.len() {
size += u64::from(samples[end].size);
duration += u64::from(samples[end].time_delta);
end += 1;
if duration >= window_time_delta {
break;
}
}
if let Some(bitrate) = size
.checked_mul(8)
.and_then(|bits| bits.checked_mul(u64::from(timescale)))
.and_then(|scaled_bits| scaled_bits.checked_div(duration))
{
max_bitrate = max_bitrate.max(bitrate);
}
while duration >= window_time_delta && begin < end {
size -= u64::from(samples[begin].size);
duration -= u64::from(samples[begin].time_delta);
begin += 1;
}
}
max_bitrate
}
pub fn average_segment_bitrate(segments: &[SegmentInfo], track_id: u32, timescale: u32) -> u64 {
let mut total_size = 0_u64;
let mut total_duration = 0_u64;
for segment in segments {
if segment.track_id == track_id {
total_size += u64::from(segment.size);
total_duration += u64::from(segment.duration);
}
}
if total_duration == 0 {
return 0;
}
8 * total_size * u64::from(timescale) / total_duration
}
pub fn max_segment_bitrate(segments: &[SegmentInfo], track_id: u32, timescale: u32) -> u64 {
let mut max_bitrate = 0_u64;
for segment in segments {
if segment.track_id == track_id && segment.duration != 0 {
let bitrate =
8 * u64::from(segment.size) * u64::from(timescale) / u64::from(segment.duration);
max_bitrate = max_bitrate.max(bitrate);
}
}
max_bitrate
}
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<BoxPath> {
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<BoxPath> {
let visual_sample_entries = [
AVC1,
HEV1,
HVC1,
DVHE,
DVH1,
VVC1,
VVI1,
AVS3,
AV01,
JPEG_ENTRY,
MJPG_ENTRY_ALIAS,
MP4V,
DIV3_ENTRY,
DIV4_ENTRY,
BGR3_ENTRY,
S263,
H263_ENTRY_ALIAS,
PNG_ENTRY,
PNG_ENTRY_ALIAS,
VP08,
VP09,
VP10,
ENCV,
];
let audio_sample_entries = [
MP4A, DOT_MP3, ALAW, MLAW, OPUS, SPEX, SAMR, SAWB, SQCP, SEVC, SSMV, ULAW, AC_3, EC_3,
AC_4, ALAC, MLPA, DTSC, DTSE, DTSH, DTSL, DTSM, DTS_MINUS, DTSX, DTSY, FLAC, IAMF, MHA1,
MHA2, MHM1, MHM2, IPCM, FPCM, ENCA,
];
let mut paths = vec![
BoxPath::from([TKHD]),
BoxPath::from([EDTS, ELST]),
BoxPath::from([MDIA, MDHD]),
BoxPath::from([MDIA, ELNG]),
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, DVHE]),
BoxPath::from([MDIA, MINF, STBL, STSD, DVHE, HVCC]),
BoxPath::from([MDIA, MINF, STBL, STSD, DVH1]),
BoxPath::from([MDIA, MINF, STBL, STSD, DVH1, HVCC]),
BoxPath::from([MDIA, MINF, STBL, STSD, VVC1]),
BoxPath::from([MDIA, MINF, STBL, STSD, VVC1, VVCC]),
BoxPath::from([MDIA, MINF, STBL, STSD, VVI1]),
BoxPath::from([MDIA, MINF, STBL, STSD, VVI1, VVCC]),
BoxPath::from([MDIA, MINF, STBL, STSD, AVS3]),
BoxPath::from([MDIA, MINF, STBL, STSD, AVS3, AV3C]),
BoxPath::from([MDIA, MINF, STBL, STSD, AV01]),
BoxPath::from([MDIA, MINF, STBL, STSD, AV01, AV1C]),
BoxPath::from([MDIA, MINF, STBL, STSD, JPEG_ENTRY]),
BoxPath::from([MDIA, MINF, STBL, STSD, MJPG_ENTRY_ALIAS]),
BoxPath::from([MDIA, MINF, STBL, STSD, MP4V]),
BoxPath::from([MDIA, MINF, STBL, STSD, MP4V, ESDS]),
BoxPath::from([MDIA, MINF, STBL, STSD, DIV3_ENTRY]),
BoxPath::from([MDIA, MINF, STBL, STSD, DIV4_ENTRY]),
BoxPath::from([MDIA, MINF, STBL, STSD, BGR3_ENTRY]),
BoxPath::from([MDIA, MINF, STBL, STSD, S263]),
BoxPath::from([MDIA, MINF, STBL, STSD, H263_ENTRY_ALIAS]),
BoxPath::from([MDIA, MINF, STBL, STSD, PNG_ENTRY]),
BoxPath::from([MDIA, MINF, STBL, STSD, PNG_ENTRY_ALIAS]),
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, VP10]),
BoxPath::from([MDIA, MINF, STBL, STSD, VP10, 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, VVCC]),
BoxPath::from([MDIA, MINF, STBL, STSD, ENCV, AV3C]),
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, DOT_MP3]),
BoxPath::from([MDIA, MINF, STBL, STSD, ALAW]),
BoxPath::from([MDIA, MINF, STBL, STSD, MLAW]),
BoxPath::from([MDIA, MINF, STBL, STSD, OPUS]),
BoxPath::from([MDIA, MINF, STBL, STSD, OPUS, DOPS]),
BoxPath::from([MDIA, MINF, STBL, STSD, SPEX]),
BoxPath::from([MDIA, MINF, STBL, STSD, SAMR]),
BoxPath::from([MDIA, MINF, STBL, STSD, SAWB]),
BoxPath::from([MDIA, MINF, STBL, STSD, SQCP]),
BoxPath::from([MDIA, MINF, STBL, STSD, SEVC]),
BoxPath::from([MDIA, MINF, STBL, STSD, SSMV]),
BoxPath::from([MDIA, MINF, STBL, STSD, ULAW]),
BoxPath::from([MDIA, MINF, STBL, STSD, AC_3]),
BoxPath::from([MDIA, MINF, STBL, STSD, AC_3, DAC3]),
BoxPath::from([MDIA, MINF, STBL, STSD, EC_3]),
BoxPath::from([MDIA, MINF, STBL, STSD, EC_3, DEC3]),
BoxPath::from([MDIA, MINF, STBL, STSD, AC_4]),
BoxPath::from([MDIA, MINF, STBL, STSD, AC_4, DAC4]),
BoxPath::from([MDIA, MINF, STBL, STSD, ALAC]),
BoxPath::from([MDIA, MINF, STBL, STSD, MLPA]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSC]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSE]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSH]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSL]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSM]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTS_MINUS]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSX]),
BoxPath::from([MDIA, MINF, STBL, STSD, DTSY]),
BoxPath::from([MDIA, MINF, STBL, STSD, FLAC]),
BoxPath::from([MDIA, MINF, STBL, STSD, FLAC, DFLA]),
BoxPath::from([MDIA, MINF, STBL, STSD, IAMF]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHA1]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHA1, MHAC]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHA2]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHA2, MHAC]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHM1]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHM1, MHAC]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHM2]),
BoxPath::from([MDIA, MINF, STBL, STSD, MHM2, MHAC]),
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, DEC3]),
BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, DAC4]),
BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, DFLA]),
BoxPath::from([MDIA, MINF, STBL, STSD, ENCA, MHAC]),
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, DVBS]),
BoxPath::from([MDIA, MINF, STBL, STSD, DVBT]),
BoxPath::from([MDIA, MINF, STBL, STSD, WVTT]),
BoxPath::from([MDIA, MINF, STBL, STSD, EVTE]),
BoxPath::from([MDIA, MINF, STBL, STSD, EVTE, BTRT]),
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, CLAP]),
BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, COLL]),
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]),
BoxPath::from([MDIA, MINF, STBL, STSD, sample_entry, SMDM]),
]);
}
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, STSZ]),
]);
}
paths
}
fn probe_trak_codec_detailed<R>(
reader: &mut R,
parent: &BoxInfo,
options: ProbeOptions,
) -> Result<CodecDetailedTrackInfo, ProbeError>
where
R: Read + Seek,
{
let track = probe_trak_rich_details(reader, parent, options)?;
Ok(CodecDetailedTrackInfo {
summary: track.summary,
codec_details: track.codec_details,
})
}
#[cfg(feature = "async")]
async fn probe_trak_codec_detailed_async<R>(
reader: &mut R,
parent: BoxInfo,
options: ProbeOptions,
) -> Result<CodecDetailedTrackInfo, ProbeError>
where
R: AsyncReadSeek,
{
let track = probe_trak_rich_details_async(reader, parent, options).await?;
Ok(CodecDetailedTrackInfo {
summary: track.summary,
codec_details: track.codec_details,
})
}
fn probe_trak_media_characteristics<R>(
reader: &mut R,
parent: &BoxInfo,
options: ProbeOptions,
) -> Result<MediaCharacteristicsTrackInfo, ProbeError>
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,
})
}
#[cfg(feature = "async")]
async fn probe_trak_media_characteristics_async<R>(
reader: &mut R,
parent: BoxInfo,
options: ProbeOptions,
) -> Result<MediaCharacteristicsTrackInfo, ProbeError>
where
R: AsyncReadSeek,
{
let track = probe_trak_rich_details_async(reader, parent, options).await?;
Ok(MediaCharacteristicsTrackInfo {
summary: track.summary,
codec_details: track.codec_details,
media_characteristics: track.media_characteristics,
})
}
fn probe_trak_extended_media_characteristics<R>(
reader: &mut R,
parent: &BoxInfo,
options: ProbeOptions,
) -> Result<ExtendedMediaCharacteristicsTrackInfo, ProbeError>
where
R: Read + Seek,
{
let track = probe_trak_rich_details(reader, parent, options)?;
Ok(ExtendedMediaCharacteristicsTrackInfo {
summary: track.summary,
codec_details: track.codec_details,
media_characteristics: track.media_characteristics,
visual_metadata: track.visual_metadata,
})
}
#[cfg(feature = "async")]
async fn probe_trak_extended_media_characteristics_async<R>(
reader: &mut R,
parent: BoxInfo,
options: ProbeOptions,
) -> Result<ExtendedMediaCharacteristicsTrackInfo, ProbeError>
where
R: AsyncReadSeek,
{
let track = probe_trak_rich_details_async(reader, parent, options).await?;
Ok(ExtendedMediaCharacteristicsTrackInfo {
summary: track.summary,
codec_details: track.codec_details,
media_characteristics: track.media_characteristics,
visual_metadata: track.visual_metadata,
})
}
fn probe_trak_rich_details<R>(
reader: &mut R,
parent: &BoxInfo,
options: ProbeOptions,
) -> Result<ParsedRichTrackInfo, ProbeError>
where
R: Read + Seek,
{
let paths = track_probe_box_paths(options);
let boxes = extract_boxes_with_payload(reader, Some(parent), &paths)?;
parse_trak_rich_details(boxes, options)
}
#[cfg(feature = "async")]
async fn probe_trak_rich_details_async<R>(
reader: &mut R,
parent: BoxInfo,
options: ProbeOptions,
) -> Result<ParsedRichTrackInfo, ProbeError>
where
R: AsyncReadSeek,
{
let paths = track_probe_box_paths(options);
let boxes = extract_boxes_with_payload_async(reader, Some(&parent), &paths).await?;
parse_trak_rich_details(boxes, options)
}
fn parse_trak_rich_details(
boxes: Vec<ExtractedBox>,
options: ProbeOptions,
) -> Result<ParsedRichTrackInfo, ProbeError> {
let mut track = DetailedTrackInfo::default();
let mut tkhd = None;
let mut mdhd = None;
let mut elng = 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 clap = None;
let mut coll = None;
let mut colr = None;
let mut pasp = None;
let mut fiel = None;
let mut smdm = None;
let mut original_format = None;
let mut stco = None;
let mut co64 = None;
let mut stts = None;
let mut ctts = None;
let mut stsc = None;
let mut stsz = None;
for extracted in boxes {
match extracted.info.box_type() {
TKHD => {
let payload = downcast_clone::<Tkhd>(&extracted)?;
track.summary.track_id = payload.track_id;
tkhd = Some(payload);
}
ELST => {
let elst = downcast_clone::<crate::boxes::iso14496_12::Elst>(&extracted)?;
track.summary.edit_list = elst
.entries
.iter()
.enumerate()
.map(|(index, _)| EditListEntry {
media_time: elst.media_time(index),
segment_duration: elst.segment_duration(index),
})
.collect();
}
MDHD => {
let payload = downcast_clone::<crate::boxes::iso14496_12::Mdhd>(&extracted)?;
track.summary.timescale = payload.timescale;
track.summary.duration = payload.duration();
if elng.is_none() {
track.language = Some(decode_language(payload.language));
}
mdhd = Some(payload);
}
ELNG => {
let payload = downcast_clone::<Elng>(&extracted)?;
track.language = Some(payload.extended_language.clone());
elng = Some(payload);
}
HDLR => {
let payload = downcast_clone::<Hdlr>(&extracted)?;
track.handler_type = Some(payload.handler_type);
}
AVC1 => {
track.summary.codec = TrackCodec::Avc1;
track.codec_family = TrackCodecFamily::Avc;
track.sample_entry_type = Some(AVC1);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
AVCC => {
avcc = Some(downcast_clone::<AVCDecoderConfiguration>(&extracted)?);
}
HVCC => {
hvcc = Some(downcast_clone::<HEVCDecoderConfiguration>(&extracted)?);
}
HEV1 => {
track.codec_family = TrackCodecFamily::Hevc;
track.sample_entry_type = Some(HEV1);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
HVC1 => {
track.codec_family = TrackCodecFamily::Hevc;
track.sample_entry_type = Some(HVC1);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
DVHE => {
track.codec_family = TrackCodecFamily::Hevc;
track.sample_entry_type = Some(DVHE);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
DVH1 => {
track.codec_family = TrackCodecFamily::Hevc;
track.sample_entry_type = Some(DVH1);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
VVC1 => {
track.sample_entry_type = Some(VVC1);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
VVI1 => {
track.sample_entry_type = Some(VVI1);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
AVS3 => {
track.sample_entry_type = Some(AVS3);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
AV01 => {
track.codec_family = TrackCodecFamily::Av1;
track.sample_entry_type = Some(AV01);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
JPEG_ENTRY | MJPG_ENTRY_ALIAS => {
track.sample_entry_type = Some(extracted.info.box_type());
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
MPEG_ENTRY | MP4V => {
track.sample_entry_type = Some(extracted.info.box_type());
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
S263 | H263_ENTRY_ALIAS => {
track.sample_entry_type = Some(extracted.info.box_type());
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
PNG_ENTRY | PNG_ENTRY_ALIAS => {
track.sample_entry_type = Some(extracted.info.box_type());
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
ENCV => {
track.summary.codec = TrackCodec::Avc1;
track.summary.encrypted = true;
track.sample_entry_type = Some(ENCV);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
other if extracted.payload.as_any().is::<VisualSampleEntry>() => {
track.sample_entry_type = Some(other);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
AV1C => {
av1c = Some(downcast_clone::<AV1CodecConfiguration>(&extracted)?);
}
VP08 => {
track.codec_family = TrackCodecFamily::Vp8;
track.sample_entry_type = Some(VP08);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
VP09 => {
track.codec_family = TrackCodecFamily::Vp9;
track.sample_entry_type = Some(VP09);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
VP10 => {
track.sample_entry_type = Some(VP10);
visual_sample_entry = Some(downcast_clone::<VisualSampleEntry>(&extracted)?);
}
VPCC => {
vpcc = Some(downcast_clone::<VpCodecConfiguration>(&extracted)?);
}
MP4A => {
track.summary.codec = TrackCodec::Mp4a;
track.codec_family = TrackCodecFamily::Mp4Audio;
track.sample_entry_type = Some(MP4A);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DOT_MP3 => {
track.sample_entry_type = Some(DOT_MP3);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
ALAW => {
track.codec_family = TrackCodecFamily::Pcm;
track.sample_entry_type = Some(ALAW);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
MLAW => {
track.codec_family = TrackCodecFamily::Pcm;
track.sample_entry_type = Some(MLAW);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
ENCA => {
track.summary.codec = TrackCodec::Mp4a;
track.summary.encrypted = true;
track.sample_entry_type = Some(ENCA);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
OPUS => {
track.codec_family = TrackCodecFamily::Opus;
track.sample_entry_type = Some(OPUS);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
SPEX => {
track.sample_entry_type = Some(SPEX);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
SAMR => {
track.sample_entry_type = Some(SAMR);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
SAWB => {
track.sample_entry_type = Some(SAWB);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
SQCP => {
track.sample_entry_type = Some(SQCP);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
SEVC => {
track.sample_entry_type = Some(SEVC);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
SSMV => {
track.sample_entry_type = Some(SSMV);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
ULAW => {
track.codec_family = TrackCodecFamily::Pcm;
track.sample_entry_type = Some(ULAW);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DOPS => {
dops = Some(downcast_clone::<DOps>(&extracted)?);
}
AC_3 => {
track.codec_family = TrackCodecFamily::Ac3;
track.sample_entry_type = Some(AC_3);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
EC_3 => {
track.sample_entry_type = Some(EC_3);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
AC_4 => {
track.sample_entry_type = Some(AC_4);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
ALAC => {
track.sample_entry_type = Some(ALAC);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
MLPA => {
track.sample_entry_type = Some(MLPA);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSC => {
track.sample_entry_type = Some(DTSC);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSE => {
track.sample_entry_type = Some(DTSE);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSH => {
track.sample_entry_type = Some(DTSH);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSL => {
track.sample_entry_type = Some(DTSL);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSM => {
track.sample_entry_type = Some(DTSM);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTS_MINUS => {
track.sample_entry_type = Some(DTS_MINUS);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSX => {
track.sample_entry_type = Some(DTSX);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DTSY => {
track.sample_entry_type = Some(DTSY);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
FLAC => {
track.sample_entry_type = Some(FLAC);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
IAMF => {
track.sample_entry_type = Some(IAMF);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
MHA1 => {
track.sample_entry_type = Some(MHA1);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
MHA2 => {
track.sample_entry_type = Some(MHA2);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
MHM1 => {
track.sample_entry_type = Some(MHM1);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
MHM2 => {
track.sample_entry_type = Some(MHM2);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
DAC3 => {
dac3 = Some(downcast_clone::<Dac3>(&extracted)?);
}
IPCM => {
track.codec_family = TrackCodecFamily::Pcm;
track.sample_entry_type = Some(IPCM);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
FPCM => {
track.codec_family = TrackCodecFamily::Pcm;
track.sample_entry_type = Some(FPCM);
audio_sample_entry = Some(downcast_clone::<AudioSampleEntry>(&extracted)?);
}
PCMC => {
pcmc = Some(downcast_clone::<PcmC>(&extracted)?);
}
STPP => {
track.codec_family = TrackCodecFamily::XmlSubtitle;
track.sample_entry_type = Some(STPP);
xml_subtitle_sample_entry =
Some(downcast_clone::<XMLSubtitleSampleEntry>(&extracted)?);
}
SBTT => {
track.codec_family = TrackCodecFamily::TextSubtitle;
track.sample_entry_type = Some(SBTT);
text_subtitle_sample_entry =
Some(downcast_clone::<TextSubtitleSampleEntry>(&extracted)?);
}
DVBS => {
track.sample_entry_type = Some(DVBS);
let _ = downcast_clone::<GenericMediaSampleEntry>(&extracted)?;
}
DVBT => {
track.sample_entry_type = Some(DVBT);
let _ = downcast_clone::<GenericMediaSampleEntry>(&extracted)?;
}
MP4S => {
track.sample_entry_type = Some(MP4S);
let _ = downcast_clone::<GenericMediaSampleEntry>(&extracted)?;
}
EVTE => {
track.sample_entry_type = Some(EVTE);
let _ = downcast_clone::<EventMessageSampleEntry>(&extracted)?;
}
WVTT => {
track.codec_family = TrackCodecFamily::WebVtt;
track.sample_entry_type = Some(WVTT);
}
VTTC_CONFIG => {
webvtt_configuration = Some(downcast_clone::<WebVTTConfigurationBox>(&extracted)?);
}
VLAB => {
webvtt_source_label = Some(downcast_clone::<WebVTTSourceLabelBox>(&extracted)?);
}
BTRT => {
btrt = Some(downcast_clone::<Btrt>(&extracted)?);
}
CLAP => {
clap = Some(downcast_clone::<Clap>(&extracted)?);
}
COLL => {
coll = Some(downcast_clone::<CoLL>(&extracted)?);
}
COLR => {
colr = Some(downcast_clone::<Colr>(&extracted)?);
}
PASP => {
pasp = Some(downcast_clone::<Pasp>(&extracted)?);
}
FIEL => {
fiel = Some(downcast_clone::<Fiel>(&extracted)?);
}
SMDM => {
smdm = Some(downcast_clone::<SmDm>(&extracted)?);
}
ESDS => {
esds = Some(downcast_clone::<Esds>(&extracted)?);
}
FRMA => {
let payload = downcast_clone::<Frma>(&extracted)?;
original_format = Some(payload.data_format);
track.original_format = Some(payload.data_format);
}
SCHM => {
let payload = downcast_clone::<Schm>(&extracted)?;
track.protection_scheme = Some(ProtectionSchemeInfo {
scheme_type: payload.scheme_type,
scheme_version: payload.scheme_version,
});
}
STCO => {
stco = Some(downcast_clone::<Stco>(&extracted)?);
}
CO64 => {
co64 = Some(downcast_clone::<Co64>(&extracted)?);
}
STTS => {
stts = Some(downcast_clone::<Stts>(&extracted)?);
}
CTTS => {
ctts = Some(downcast_clone::<Ctts>(&extracted)?);
}
STSC => {
stsc = Some(downcast_clone::<Stsc>(&extracted)?);
}
STSZ => {
stsz = Some(downcast_clone::<Stsz>(&extracted)?);
}
_ => {}
}
}
if tkhd.is_none() {
return Err(ProbeError::MissingRequiredBox("tkhd"));
}
if mdhd.is_none() {
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.summary.avc = Some(AvcDecoderConfigInfo {
configuration_version: avcc.configuration_version,
profile: avcc.profile,
profile_compatibility: avcc.profile_compatibility,
level: avcc.level,
length_size: u16::from(avcc.length_size_minus_one) + 1,
width: entry.width,
height: entry.height,
});
}
if let (Some(entry), Some(esds)) = (audio_sample_entry.as_ref(), esds.as_ref())
&& let Some(profile) = detect_aac_profile(esds)?
{
track.summary.mp4a = Some(Mp4aInfo {
object_type_indication: profile.object_type_indication,
audio_object_type: profile.audio_object_type,
channel_count: entry.channel_count,
});
}
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 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;
}
}
}
}
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()
});
}
}
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(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",
})?;
}
}
}
}
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_refs = TrackMediaCharacteristicRefs {
btrt: btrt.as_ref(),
clap: clap.as_ref(),
coll: coll.as_ref(),
colr: colr.as_ref(),
pasp: pasp.as_ref(),
fiel: fiel.as_ref(),
smdm: smdm.as_ref(),
};
let media_characteristics = build_track_media_characteristics(&media_refs);
let visual_metadata = build_track_visual_metadata(&media_refs);
Ok(ParsedRichTrackInfo {
summary: track,
codec_details,
media_characteristics,
visual_metadata,
})
}
fn codec_family_from_sample_entry(sample_entry_type: FourCc) -> TrackCodecFamily {
match sample_entry_type {
AVC1 => TrackCodecFamily::Avc,
HEV1 | HVC1 | DVHE | DVH1 => TrackCodecFamily::Hevc,
AV01 => TrackCodecFamily::Av1,
VP08 => TrackCodecFamily::Vp8,
VP09 => TrackCodecFamily::Vp9,
MP4A => TrackCodecFamily::Mp4Audio,
OPUS => TrackCodecFamily::Opus,
AC_3 => TrackCodecFamily::Ac3,
ALAW | MLAW | ULAW | IPCM | FPCM => TrackCodecFamily::Pcm,
MP4S => TrackCodecFamily::Unknown,
STPP => TrackCodecFamily::XmlSubtitle,
SBTT => TrackCodecFamily::TextSubtitle,
WVTT => TrackCodecFamily::WebVtt,
_ => TrackCodecFamily::Unknown,
}
}
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,
});
}
}
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 build_track_visual_metadata(refs: &TrackMediaCharacteristicRefs<'_>) -> TrackVisualMetadata {
TrackVisualMetadata {
clean_aperture: refs.clap.map(track_clean_aperture_info),
content_light_level: refs.coll.map(|value| ContentLightLevelInfo {
max_cll: value.max_cll,
max_fall: value.max_fall,
}),
mastering_display: refs.smdm.map(track_mastering_display_info),
}
}
fn track_clean_aperture_info(value: &Clap) -> CleanApertureInfo {
CleanApertureInfo {
width_numerator: value.clean_aperture_width_n,
width_denominator: value.clean_aperture_width_d,
height_numerator: value.clean_aperture_height_n,
height_denominator: value.clean_aperture_height_d,
horizontal_offset_numerator: value.horiz_off_n,
horizontal_offset_denominator: value.horiz_off_d,
vertical_offset_numerator: value.vert_off_n,
vertical_offset_denominator: value.vert_off_d,
}
}
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_mastering_display_info(value: &SmDm) -> MasteringDisplayInfo {
MasteringDisplayInfo {
primary_r_chromaticity_x: value.primary_r_chromaticity_x,
primary_r_chromaticity_y: value.primary_r_chromaticity_y,
primary_g_chromaticity_x: value.primary_g_chromaticity_x,
primary_g_chromaticity_y: value.primary_g_chromaticity_y,
primary_b_chromaticity_x: value.primary_b_chromaticity_x,
primary_b_chromaticity_y: value.primary_b_chromaticity_y,
white_point_chromaticity_x: value.white_point_chromaticity_x,
white_point_chromaticity_y: value.white_point_chromaticity_y,
luminance_max: value.luminance_max,
luminance_min: value.luminance_min,
}
}
fn track_field_order_info(value: &Fiel) -> FieldOrderInfo {
FieldOrderInfo {
field_count: value.field_count,
field_ordering: value.field_ordering,
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,
}
}
fn decode_language(language: [u8; 3]) -> String {
language
.into_iter()
.map(|value| char::from(value.saturating_add(0x60)))
.collect()
}
fn probe_moof<R>(reader: &mut R, parent: &BoxInfo) -> Result<SegmentInfo, ProbeError>
where
R: Read + Seek,
{
Ok(probe_moof_parsed(reader, parent)?.summary)
}
#[cfg(feature = "async")]
async fn probe_moof_async<R>(reader: &mut R, parent: BoxInfo) -> Result<SegmentInfo, ProbeError>
where
R: AsyncReadSeek,
{
let boxes = extract_boxes_with_payload_async(
reader,
Some(&parent),
&[
BoxPath::from([TRAF, TFHD]),
BoxPath::from([TRAF, TFDT]),
BoxPath::from([TRAF, TRUN]),
],
)
.await?;
Ok(parse_moof_segment(boxes, parent.offset())?.summary)
}
fn probe_moof_parsed<R>(reader: &mut R, parent: &BoxInfo) -> Result<ParsedMoofSegment, ProbeError>
where
R: Read + Seek,
{
let boxes = extract_boxes_with_payload(
reader,
Some(parent),
&[
BoxPath::from([TRAF, TFHD]),
BoxPath::from([TRAF, TFDT]),
BoxPath::from([TRAF, TRUN]),
],
)?;
parse_moof_segment(boxes, parent.offset())
}
fn parse_moof_segment(
boxes: Vec<ExtractedBox>,
moof_offset: u64,
) -> Result<ParsedMoofSegment, ProbeError> {
let mut tfhd = None;
let mut tfdt = None;
let mut trun = None;
for extracted in boxes {
match extracted.info.box_type() {
TFHD => tfhd = Some(downcast_clone::<Tfhd>(&extracted)?),
TFDT => tfdt = Some(downcast_clone::<Tfdt>(&extracted)?),
TRUN => trun = Some(downcast_clone::<Trun>(&extracted)?),
_ => {}
}
}
let tfhd = tfhd.ok_or(ProbeError::MissingRequiredBox("tfhd"))?;
let mut parsed = ParsedMoofSegment {
summary: SegmentInfo {
track_id: tfhd.track_id,
moof_offset,
default_sample_duration: tfhd.default_sample_duration,
..SegmentInfo::default()
},
..ParsedMoofSegment::default()
};
if let Some(tfdt) = tfdt.as_ref() {
parsed.summary.base_media_decode_time = tfdt.base_media_decode_time();
}
if let Some(trun) = trun.as_ref() {
parsed.summary.sample_count = trun.sample_count;
if trun.flags() & crate::boxes::iso14496_12::TRUN_SAMPLE_DURATION_PRESENT != 0 {
parsed.sample_durations = trun
.entries
.iter()
.map(|entry| entry.sample_duration)
.collect();
parsed.summary.duration = trun
.entries
.iter()
.map(|entry| entry.sample_duration)
.sum::<u32>();
parsed.zero_duration_sample_count = parsed
.sample_durations
.iter()
.filter(|sample_duration| **sample_duration == 0)
.count()
.try_into()
.map_err(|_| ProbeError::NumericOverflow {
field_name: "segment zero-duration sample count",
})?;
} else {
parsed.sample_durations =
vec![tfhd.default_sample_duration; parsed.summary.sample_count as usize];
parsed.summary.duration = tfhd
.default_sample_duration
.saturating_mul(parsed.summary.sample_count);
if tfhd.default_sample_duration == 0 {
parsed.zero_duration_sample_count = parsed.summary.sample_count;
}
}
if trun.flags() & crate::boxes::iso14496_12::TRUN_SAMPLE_SIZE_PRESENT != 0 {
parsed.summary.size = trun
.entries
.iter()
.map(|entry| entry.sample_size)
.sum::<u32>();
} else {
parsed.summary.size = tfhd
.default_sample_size
.saturating_mul(parsed.summary.sample_count);
}
let mut duration = 0_u32;
let mut min_offset = None;
for (index, entry) in trun.entries.iter().enumerate() {
let offset = i64::from(duration) + trun.sample_composition_time_offset(index);
min_offset = Some(min_offset.map_or(offset, |current: i64| current.min(offset)));
duration = duration.saturating_add(
if trun.flags() & crate::boxes::iso14496_12::TRUN_SAMPLE_DURATION_PRESENT != 0 {
entry.sample_duration
} else {
tfhd.default_sample_duration
},
);
}
if let Some(offset) = min_offset {
parsed.summary.composition_time_offset =
offset.try_into().map_err(|_| ProbeError::NumericOverflow {
field_name: "segment composition time offset",
})?;
}
}
Ok(parsed)
}
fn read_payload_as<R, B>(reader: &mut R, info: &BoxInfo) -> Result<B, ProbeError>
where
R: Read + Seek,
B: CodecBox + Default,
{
info.seek_to_payload(reader)?;
let mut decoded = B::default();
unmarshal(reader, info.payload_size()?, &mut decoded, None)?;
Ok(decoded)
}
#[cfg(feature = "async")]
async fn read_payload_as_async<R, B>(reader: &mut R, info: BoxInfo) -> Result<B, ProbeError>
where
R: AsyncReadSeek,
B: CodecBox + Default + Send,
{
reader
.seek(SeekFrom::Start(info.offset() + info.header_size()))
.await?;
let mut decoded = B::default();
unmarshal_async(reader, info.payload_size()?, &mut decoded, None).await?;
Ok(decoded)
}
fn downcast_clone<T>(extracted: &ExtractedBox) -> Result<T, ProbeError>
where
T: Clone + 'static,
{
extracted
.payload
.as_ref()
.as_any()
.downcast_ref::<T>()
.cloned()
.ok_or(ProbeError::UnexpectedPayloadType {
box_type: extracted.info.box_type(),
})
}
fn get_audio_object_type<R>(reader: &mut BitReader<R>) -> Result<(u8, usize), ProbeError>
where
R: Read,
{
let audio_object_type = read_bits_u8(reader, 5)?;
if audio_object_type != 0x1f {
return Ok((audio_object_type, 5));
}
let extended = read_bits_u8(reader, 6)?;
Ok((extended.saturating_add(32), 11))
}
fn read_bits_u8<R>(reader: &mut BitReader<R>, width: usize) -> Result<u8, ProbeError>
where
R: Read,
{
let bits = reader.read_bits(width).map_err(ProbeError::Io)?;
let mut value = 0_u16;
for byte in bits {
value = (value << 8) | u16::from(byte);
}
u8::try_from(value).map_err(|_| ProbeError::NumericOverflow {
field_name: "bitfield read",
})
}
fn read_bits_u32<R>(reader: &mut BitReader<R>, width: usize) -> Result<u32, ProbeError>
where
R: Read,
{
let bits = reader.read_bits(width).map_err(ProbeError::Io)?;
let mut value = 0_u64;
for byte in bits {
value = (value << 8) | u64::from(byte);
}
u32::try_from(value).map_err(|_| ProbeError::NumericOverflow {
field_name: "bitfield read",
})
}
#[derive(Debug)]
pub enum ProbeError {
Io(io::Error),
Header(HeaderError),
Codec(CodecError),
Extract(ExtractError),
MissingRequiredBox(&'static str),
MissingDescriptor(&'static str),
UnexpectedPayloadType { box_type: FourCc },
NumericOverflow { field_name: &'static str },
}
impl fmt::Display for ProbeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(error) => error.fmt(f),
Self::Header(error) => error.fmt(f),
Self::Codec(error) => error.fmt(f),
Self::Extract(error) => error.fmt(f),
Self::MissingRequiredBox(name) => write!(f, "{name} box not found"),
Self::MissingDescriptor(name) => write!(f, "{name} not found"),
Self::UnexpectedPayloadType { box_type } => {
write!(f, "unexpected payload type for {box_type}")
}
Self::NumericOverflow { field_name } => {
write!(f, "numeric value does not fit while reading {field_name}")
}
}
}
}
impl Error for ProbeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Io(error) => Some(error),
Self::Header(error) => Some(error),
Self::Codec(error) => Some(error),
Self::Extract(error) => Some(error),
Self::MissingRequiredBox(..)
| Self::MissingDescriptor(..)
| Self::UnexpectedPayloadType { .. }
| Self::NumericOverflow { .. } => None,
}
}
}
impl From<io::Error> for ProbeError {
fn from(value: io::Error) -> Self {
Self::Io(value)
}
}
impl From<HeaderError> for ProbeError {
fn from(value: HeaderError) -> Self {
Self::Header(value)
}
}
impl From<CodecError> for ProbeError {
fn from(value: CodecError) -> Self {
Self::Codec(value)
}
}
impl From<ExtractError> for ProbeError {
fn from(value: ExtractError) -> Self {
Self::Extract(value)
}
}