use std::io::{Read, Seek, SeekFrom};
use oxideav_core::{
CodecParameters, CodecResolver, CodecTag, Error, MediaType, Packet, ProbeContext, Result,
SampleFormat, StreamInfo, TimeBase,
};
use oxideav_core::{Demuxer, ReadSeek};
use crate::codec_id::{from_matroska, strip_bitmapinfoheader};
use crate::ebml::{
crc32_ieee, read_bytes, read_element_header, read_float, read_string, read_uint, read_vint,
skip, VINT_UNKNOWN_SIZE,
};
use crate::ids;
pub fn open(input: Box<dyn ReadSeek>, codecs: &dyn CodecResolver) -> Result<Box<dyn Demuxer>> {
open_typed(input, codecs).map(|d| Box::new(d) as Box<dyn Demuxer>)
}
pub fn open_typed(mut input: Box<dyn ReadSeek>, codecs: &dyn CodecResolver) -> Result<MkvDemuxer> {
let hdr = read_element_header(&mut *input)?;
if hdr.id != ids::EBML_HEADER {
return Err(Error::invalid(format!(
"MKV: expected EBML header at start, got id 0x{:X}",
hdr.id
)));
}
let mut doc_type = String::from("matroska");
let ebml_end = input.stream_position()?.saturating_add(hdr.size);
while input.stream_position()? < ebml_end {
let e = read_element_header(&mut *input)?;
match e.id {
ids::EBML_DOC_TYPE => {
doc_type = read_string(&mut *input, e.size as usize)?;
}
_ => skip(&mut *input, e.size)?,
}
}
if doc_type != "matroska" && doc_type != "webm" {
return Err(Error::unsupported(format!(
"MKV: unsupported DocType '{doc_type}'"
)));
}
let seg = read_element_header(&mut *input)?;
if seg.id != ids::SEGMENT {
return Err(Error::invalid(format!(
"MKV: expected Segment after EBML header, got id 0x{:X}",
seg.id
)));
}
let segment_data_start = input.stream_position()?;
let segment_data_end = if seg.size == VINT_UNKNOWN_SIZE {
let cur = input.stream_position()?;
let end = input.seek(SeekFrom::End(0))?;
input.seek(SeekFrom::Start(cur))?;
end
} else {
segment_data_start + seg.size
};
let mut info = SegmentInfo::default();
let mut tracks: Vec<TrackEntry> = Vec::new();
let mut first_cluster_offset: Option<u64> = None;
let mut metadata: Vec<(String, String)> = Vec::new();
let mut cues: Vec<CueEntry> = Vec::new();
let mut cue_points: Vec<CuePoint> = Vec::new();
let mut chapter_uid_to_index: std::collections::HashMap<u64, u32> =
std::collections::HashMap::new();
let mut attachment_uid_to_index: std::collections::HashMap<u64, u32> =
std::collections::HashMap::new();
let mut edition_uid_to_index: std::collections::HashMap<u64, u32> =
std::collections::HashMap::new();
let mut pending_tags: Vec<RawTag> = Vec::new();
let mut editions: Vec<Edition> = Vec::new();
let mut attachments: Vec<Attachment> = Vec::new();
let mut crc_status: Vec<CrcStatus> = Vec::new();
while input.stream_position()? < segment_data_end {
let e = read_element_header(&mut *input)?;
let body_start = input.stream_position()?;
let body_end_known = if e.size == VINT_UNKNOWN_SIZE {
None
} else {
Some(body_start.saturating_add(e.size))
};
if let Some(end) = body_end_known {
if matches!(
e.id,
ids::INFO
| ids::TRACKS
| ids::TAGS
| ids::CUES
| ids::CHAPTERS
| ids::ATTACHMENTS
| ids::SEEK_HEAD
) {
if let Some(s) = validate_top_level_crc(&mut *input, e.id, body_start, end)? {
crc_status.push(s);
}
}
}
match e.id {
ids::INFO => {
let end = body_end_known.unwrap_or(segment_data_end);
parse_info(&mut *input, end, &mut info, &mut metadata)?;
}
ids::TRACKS => {
let end = body_end_known.unwrap_or(segment_data_end);
parse_tracks(&mut *input, end, &mut tracks)?;
}
ids::TAGS => {
let end = body_end_known.unwrap_or(segment_data_end);
parse_tags(&mut *input, end, &mut pending_tags)?;
}
ids::CUES => {
let end = body_end_known.unwrap_or(segment_data_end);
parse_cues(&mut *input, end, &mut cues, &mut cue_points)?;
}
ids::CHAPTERS => {
let end = body_end_known.unwrap_or(segment_data_end);
parse_chapters_typed(
&mut *input,
end,
&mut metadata,
&mut chapter_uid_to_index,
&mut edition_uid_to_index,
&mut editions,
)?;
}
ids::ATTACHMENTS => {
let end = body_end_known.unwrap_or(segment_data_end);
parse_attachments(
&mut *input,
end,
&mut metadata,
&mut attachment_uid_to_index,
&mut attachments,
)?;
}
ids::CLUSTER => {
if first_cluster_offset.is_none() {
first_cluster_offset = Some(body_start - e.header_len as u64);
}
input.seek(SeekFrom::Start(body_start - e.header_len as u64))?;
break;
}
_ => {
if let Some(end) = body_end_known {
input.seek(SeekFrom::Start(end))?;
} else {
return Err(Error::unsupported(
"MKV: unknown-size element other than Cluster",
));
}
}
}
}
if cues.is_empty() {
if let Some(first_cluster) = first_cluster_offset {
let resume_pos = input.stream_position()?;
if scan_cues_from(
&mut *input,
first_cluster,
segment_data_end,
&mut cues,
&mut cue_points,
&mut crc_status,
)
.is_err()
{
cues.clear();
cue_points.clear();
}
input.seek(SeekFrom::Start(resume_pos))?;
}
}
cues.sort_by(|a, b| a.track.cmp(&b.track).then(a.time.cmp(&b.time)));
if tracks.is_empty() {
return Err(Error::invalid("MKV: no tracks found"));
}
let timecode_scale_ns = if info.timecode_scale == 0 {
1_000_000
} else {
info.timecode_scale
};
let time_base = TimeBase::new(timecode_scale_ns as i64, 1_000_000_000);
let mut streams: Vec<StreamInfo> = Vec::new();
let mut track_index_by_number: std::collections::HashMap<u64, u32> =
std::collections::HashMap::new();
for t in &tracks {
let idx = streams.len() as u32;
track_index_by_number.insert(t.number, idx);
let tag = CodecTag::matroska(t.codec_id_string.clone());
let mut ctx = ProbeContext::new(&tag);
if !t.codec_private.is_empty() {
ctx = ctx.header(&t.codec_private);
}
if t.bit_depth > 0 {
ctx = ctx.bits(t.bit_depth as u16);
}
if t.channels > 0 {
ctx = ctx.channels(t.channels as u16);
}
let sr = t.sample_rate.round() as u32;
if sr > 0 {
ctx = ctx.sample_rate(sr);
}
if t.width > 0 {
ctx = ctx.width(t.width as u32);
}
if t.height > 0 {
ctx = ctx.height(t.height as u32);
}
let mut codec_id = codecs.resolve_tag(&ctx);
if codec_id.is_none()
&& t.codec_id_string == "V_MS/VFW/FOURCC"
&& t.codec_private.len() >= 20
{
let mut fcc = [0u8; 4];
fcc.copy_from_slice(&t.codec_private[16..20]);
let fcc_tag = CodecTag::fourcc(&fcc);
let mut fcc_ctx = ProbeContext::new(&fcc_tag).header(&t.codec_private);
if t.width > 0 {
fcc_ctx = fcc_ctx.width(t.width as u32);
}
if t.height > 0 {
fcc_ctx = fcc_ctx.height(t.height as u32);
}
codec_id = codecs.resolve_tag(&fcc_ctx);
}
let codec_id =
codec_id.unwrap_or_else(|| from_matroska(&t.codec_id_string, &t.codec_private));
let mut params = match t.track_type {
ids::TRACK_TYPE_VIDEO => CodecParameters::video(codec_id.clone()),
ids::TRACK_TYPE_AUDIO => CodecParameters::audio(codec_id.clone()),
ids::TRACK_TYPE_SUBTITLE => CodecParameters::subtitle(codec_id.clone()),
_ => {
let mut p = CodecParameters::audio(codec_id.clone());
p.media_type = MediaType::Data;
p
}
};
let stripped = strip_bitmapinfoheader(&t.codec_id_string, &t.codec_private);
params.extradata = match codec_id.as_str() {
"flac" if stripped.starts_with(b"fLaC") => stripped[4..].to_vec(),
_ => stripped,
};
if t.track_type == ids::TRACK_TYPE_AUDIO {
params.sample_rate = Some(t.sample_rate.round() as u32);
params.channels = Some(t.channels as u16);
params.sample_format = match (params.codec_id.as_str(), t.bit_depth) {
("pcm_s16le", _) => Some(SampleFormat::S16),
("pcm_s16be", _) => Some(SampleFormat::S16),
("pcm_f32le", _) => Some(SampleFormat::F32),
("flac", 8) => Some(SampleFormat::U8),
("flac", 16) => Some(SampleFormat::S16),
("flac", 24) => Some(SampleFormat::S24),
("flac", 32) => Some(SampleFormat::S32),
_ => None,
};
}
if t.track_type == ids::TRACK_TYPE_VIDEO {
params.width = Some(t.width as u32);
params.height = Some(t.height as u32);
}
if let Some(lang) = t.language.clone() {
params.language = Some(lang);
}
streams.push(StreamInfo {
index: idx,
time_base,
duration: if info.duration > 0.0 {
Some(info.duration as i64)
} else {
None
},
start_time: Some(0),
params,
});
}
let track_uid_to_index: std::collections::HashMap<u64, u32> = tracks
.iter()
.enumerate()
.filter(|(_, t)| t.uid != 0)
.map(|(i, t)| (t.uid, i as u32))
.collect();
let mut typed_tags: Vec<Tag> = Vec::new();
resolve_tags(
pending_tags,
&track_uid_to_index,
&chapter_uid_to_index,
&attachment_uid_to_index,
&edition_uid_to_index,
&mut metadata,
&mut typed_tags,
);
let resolve_ref = |uid: u64| TrackRef {
track_uid: uid,
stream_index: track_uid_to_index.get(&uid).copied(),
};
let track_operations: Vec<Option<TrackOperation>> = tracks
.iter()
.map(|t| {
t.track_operation.as_ref().map(|raw| TrackOperation {
planes: raw
.planes
.iter()
.map(|&(uid, ty)| TrackPlane {
track: resolve_ref(uid),
plane_type: TrackPlaneType::from_raw(ty),
})
.collect(),
join_tracks: raw.join_uids.iter().map(|&uid| resolve_ref(uid)).collect(),
})
})
.collect();
let content_encodings: Vec<Option<ContentEncodings>> =
tracks.iter().map(|t| t.content_encodings.clone()).collect();
let block_addition_mappings: Vec<Vec<BlockAdditionMapping>> = tracks
.iter()
.map(|t| t.block_addition_mappings.clone())
.collect();
let max_block_addition_ids: Vec<u64> = tracks.iter().map(|t| t.max_block_addition_id).collect();
let track_audience_flags: Vec<TrackAudienceFlags> = tracks
.iter()
.map(|t| {
let raw = t.audience_flags_raw;
TrackAudienceFlags {
forced: raw.forced.map(audience_flag_to_bool).unwrap_or(false),
hearing_impaired: raw.hearing_impaired.map(audience_flag_to_bool),
visual_impaired: raw.visual_impaired.map(audience_flag_to_bool),
text_descriptions: raw.text_descriptions.map(audience_flag_to_bool),
original: raw.original.map(audience_flag_to_bool),
commentary: raw.commentary.map(audience_flag_to_bool),
}
})
.collect();
let track_audio: Vec<Option<TrackAudio>> = tracks
.iter()
.map(|t| {
t.audio_raw.map(|raw| TrackAudio {
sampling_frequency: raw.sampling_frequency.unwrap_or(8000.0),
output_sampling_frequency_explicit: raw.output_sampling_frequency,
channels: raw.channels.unwrap_or(1),
bit_depth: raw.bit_depth,
})
})
.collect();
let track_timing: Vec<TrackTiming> = tracks
.iter()
.map(|t| TrackTiming {
default_duration: t.timing_raw.default_duration,
default_decoded_field_duration: t.timing_raw.default_decoded_field_duration,
track_timestamp_scale_explicit: t.timing_raw.track_timestamp_scale,
})
.collect();
let track_codec_timing: Vec<TrackCodecTiming> = tracks
.iter()
.map(|t| TrackCodecTiming {
codec_delay_explicit: t.codec_timing_raw.0,
seek_pre_roll_explicit: t.codec_timing_raw.1,
})
.collect();
let video_interlacings: Vec<Option<VideoInterlacing>> = tracks
.iter()
.map(|t| {
t.interlacing_raw.map(|(flag, fo)| VideoInterlacing {
flag: FlagInterlaced::from_raw(flag),
field_order_raw: fo,
})
})
.collect();
let video_colours: Vec<Option<VideoColour>> = tracks
.iter()
.map(|t| {
t.colour_raw.as_ref().map(|c| VideoColour {
matrix_coefficients: MatrixCoefficients::from_raw(c.matrix_coefficients),
bits_per_channel: c.bits_per_channel,
chroma_subsampling_horz: c.chroma_subsampling_horz,
chroma_subsampling_vert: c.chroma_subsampling_vert,
cb_subsampling_horz: c.cb_subsampling_horz,
cb_subsampling_vert: c.cb_subsampling_vert,
chroma_siting_horz: ChromaSitingHorz::from_raw(c.chroma_siting_horz),
chroma_siting_vert: ChromaSitingVert::from_raw(c.chroma_siting_vert),
range: ColourRange::from_raw(c.range),
transfer_characteristics: TransferCharacteristics::from_raw(
c.transfer_characteristics,
),
primaries: Primaries::from_raw(c.primaries),
max_cll: c.max_cll,
max_fall: c.max_fall,
mastering_metadata: c.mastering_metadata,
})
})
.collect();
let video_stereo_modes: Vec<Option<StereoMode>> = tracks
.iter()
.map(|t| t.stereo_mode_raw.map(StereoMode::from_raw))
.collect();
let video_projections: Vec<Option<Projection>> = tracks
.iter()
.map(|t| {
t.projection_raw.as_ref().map(|p| Projection {
projection_type: ProjectionType::from_raw(p.projection_type_raw),
private: p.private.clone(),
pose_yaw: p.pose_yaw,
pose_pitch: p.pose_pitch,
pose_roll: p.pose_roll,
})
})
.collect();
let video_alpha_modes: Vec<Option<AlphaMode>> = tracks
.iter()
.map(|t| t.alpha_mode_raw.map(AlphaMode::from_raw))
.collect();
let video_aspect_ratio_types: Vec<Option<u64>> =
tracks.iter().map(|t| t.aspect_ratio_type_raw).collect();
let video_uncompressed_fourccs: Vec<Option<UncompressedFourCC>> = tracks
.iter()
.map(|t| {
t.uncompressed_fourcc_raw
.as_ref()
.map(|raw| UncompressedFourCC { bytes: raw.clone() })
})
.collect();
let video_geometries: Vec<Option<VideoGeometry>> = tracks
.iter()
.map(|t| {
t.geometry_raw
.map(|(top, bottom, left, right, dw_raw, dh_raw, unit_raw)| {
let unit = DisplayUnit::from_raw(unit_raw);
let display_width = if dw_raw != 0 {
Some(dw_raw)
} else if matches!(unit, DisplayUnit::Pixels) {
t.width.checked_sub(left).and_then(|v| v.checked_sub(right))
} else {
None
};
let display_height = if dh_raw != 0 {
Some(dh_raw)
} else if matches!(unit, DisplayUnit::Pixels) {
t.height
.checked_sub(top)
.and_then(|v| v.checked_sub(bottom))
} else {
None
};
VideoGeometry {
pixel_crop_top: top,
pixel_crop_bottom: bottom,
pixel_crop_left: left,
pixel_crop_right: right,
display_width,
display_height,
display_unit: unit,
}
})
})
.collect();
let header_strip_prefixes: Vec<Vec<u8>> = content_encodings
.iter()
.map(|ce| {
ce.as_ref()
.and_then(compute_header_strip_prefix)
.unwrap_or_default()
})
.collect();
let cluster_pos = first_cluster_offset.ok_or_else(|| Error::invalid("MKV: no clusters"))?;
input.seek(SeekFrom::Start(cluster_pos))?;
let duration_micros: i64 = if info.duration > 0.0 {
(info.duration * (timecode_scale_ns as f64) / 1_000.0) as i64
} else {
0
};
let mut track_number_by_index: Vec<u64> = vec![0; streams.len()];
for (num, &idx) in &track_index_by_number {
track_number_by_index[idx as usize] = *num;
}
Ok(MkvDemuxer {
input,
streams,
track_index_by_number,
track_number_by_index,
segment_data_start,
segment_data_end,
cluster_state: ClusterState::Idle,
out_queue: std::collections::VecDeque::new(),
time_base,
metadata,
duration_micros,
cues,
cue_points,
timecode_scale_ns,
tags: typed_tags,
editions,
attachments,
crc_status,
validated_cluster_starts: std::collections::HashSet::new(),
track_operations,
content_encodings,
header_strip_prefixes,
video_interlacings,
video_geometries,
video_colours,
video_stereo_modes,
video_projections,
video_alpha_modes,
video_aspect_ratio_types,
video_uncompressed_fourccs,
block_addition_mappings,
max_block_addition_ids,
last_block_additions: None,
track_audience_flags,
track_audio,
track_timing,
track_codec_timing,
cluster_records: Vec::new(),
cluster_record_by_offset: std::collections::HashMap::new(),
})
}
#[derive(Default)]
struct SegmentInfo {
timecode_scale: u64,
duration: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CrcStatus {
pub element_id: u32,
pub stored: u32,
pub computed: u32,
}
impl CrcStatus {
pub fn is_valid(&self) -> bool {
self.stored == self.computed
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ClusterRecord {
pub body_offset: u64,
pub position: Option<u64>,
pub prev_size: Option<u64>,
}
fn validate_top_level_crc(
r: &mut dyn ReadSeek,
element_id: u32,
body_start: u64,
body_end: u64,
) -> Result<Option<CrcStatus>> {
let len = body_end.saturating_sub(body_start);
if len < 6 {
r.seek(SeekFrom::Start(body_start))?;
return Ok(None);
}
r.seek(SeekFrom::Start(body_start))?;
let body = read_bytes(r, len as usize)?;
r.seek(SeekFrom::Start(body_start))?;
let mut cur = std::io::Cursor::new(&body[..]);
let (id, _) = match read_vint(&mut cur, true) {
Ok(v) => v,
Err(_) => return Ok(None),
};
if id != ids::CRC32 as u64 {
return Ok(None);
}
let (size, _) = match read_vint(&mut cur, false) {
Ok(v) => v,
Err(_) => return Ok(None),
};
if size != 4 {
return Ok(None);
}
let header_len = cur.position() as usize;
if header_len + 4 > body.len() {
return Ok(None);
}
let stored = u32::from_le_bytes([
body[header_len],
body[header_len + 1],
body[header_len + 2],
body[header_len + 3],
]);
let rest = &body[header_len + 4..];
let computed = crc32_ieee(rest);
Ok(Some(CrcStatus {
element_id,
stored,
computed,
}))
}
#[derive(Default)]
struct TrackEntry {
number: u64,
uid: u64,
track_type: u64,
codec_id_string: String,
codec_private: Vec<u8>,
sample_rate: f64,
channels: u64,
bit_depth: u64,
width: u64,
height: u64,
interlacing_raw: Option<(u64, u64)>,
geometry_raw: Option<(u64, u64, u64, u64, u64, u64, u64)>,
track_operation: Option<RawTrackOperation>,
content_encodings: Option<ContentEncodings>,
colour_raw: Option<RawColour>,
stereo_mode_raw: Option<u64>,
projection_raw: Option<RawProjection>,
alpha_mode_raw: Option<u64>,
aspect_ratio_type_raw: Option<u64>,
uncompressed_fourcc_raw: Option<Vec<u8>>,
language: Option<String>,
block_addition_mappings: Vec<BlockAdditionMapping>,
max_block_addition_id: u64,
audience_flags_raw: RawAudienceFlags,
audio_raw: Option<RawTrackAudio>,
timing_raw: RawTrackTiming,
codec_timing_raw: (Option<u64>, Option<u64>),
}
#[derive(Clone, Copy, Debug, Default)]
struct RawTrackTiming {
default_duration: Option<u64>,
default_decoded_field_duration: Option<u64>,
track_timestamp_scale: Option<f64>,
}
#[derive(Clone, Copy, Debug, Default)]
struct RawAudienceFlags {
forced: Option<u64>,
hearing_impaired: Option<u64>,
visual_impaired: Option<u64>,
text_descriptions: Option<u64>,
original: Option<u64>,
commentary: Option<u64>,
}
#[derive(Clone, Copy, Debug, Default)]
struct RawTrackAudio {
sampling_frequency: Option<f64>,
output_sampling_frequency: Option<f64>,
channels: Option<u64>,
bit_depth: Option<u64>,
}
#[derive(Default)]
struct RawColour {
matrix_coefficients: u64,
bits_per_channel: u64,
chroma_subsampling_horz: Option<u64>,
chroma_subsampling_vert: Option<u64>,
cb_subsampling_horz: Option<u64>,
cb_subsampling_vert: Option<u64>,
chroma_siting_horz: u64,
chroma_siting_vert: u64,
range: u64,
transfer_characteristics: u64,
primaries: u64,
max_cll: Option<u64>,
max_fall: Option<u64>,
mastering_metadata: Option<MasteringMetadata>,
}
#[derive(Default)]
struct RawProjection {
projection_type_raw: u64,
private: Option<Vec<u8>>,
pose_yaw: f64,
pose_pitch: f64,
pose_roll: f64,
}
#[derive(Default)]
struct RawTrackOperation {
planes: Vec<(u64, u64)>,
join_uids: Vec<u64>,
}
fn parse_info(
r: &mut dyn ReadSeek,
end: u64,
out: &mut SegmentInfo,
metadata: &mut Vec<(String, String)>,
) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TIMECODE_SCALE => out.timecode_scale = read_uint(r, e.size as usize)?,
ids::DURATION => out.duration = read_float(r, e.size as usize)?,
ids::TITLE => {
let s = read_string(r, e.size as usize)?;
if !s.is_empty() {
metadata.push(("title".into(), s));
}
}
ids::MUXING_APP => {
let s = read_string(r, e.size as usize)?;
if !s.is_empty() {
metadata.push(("muxer".into(), s));
}
}
ids::WRITING_APP => {
let s = read_string(r, e.size as usize)?;
if !s.is_empty() {
metadata.push(("encoder".into(), s));
}
}
ids::DATE_UTC => {
if e.size == 8 {
let ns = read_uint(r, 8)? as i64;
let secs_since_2001 = ns / 1_000_000_000;
let unix_2001: i64 = 978_307_200;
let unix = unix_2001 + secs_since_2001;
metadata.push(("date".into(), format_iso8601(unix)));
} else {
skip(r, e.size)?;
}
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
struct RawTag {
track_uids: Vec<u64>,
edition_uids: Vec<u64>,
chapter_uids: Vec<u64>,
attachment_uids: Vec<u64>,
target_type_value: Option<u64>,
target_type: Option<String>,
simple_tags: Vec<RawSimpleTag>,
}
#[derive(Default)]
struct RawSimpleTag {
name: String,
value: SimpleTagValue,
language: String,
language_bcp47: Option<String>,
default: bool,
}
fn parse_tags(r: &mut dyn ReadSeek, end: u64, out: &mut Vec<RawTag>) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TAG => {
let tag_end = r.stream_position()?.saturating_add(e.size);
let mut t = RawTag {
track_uids: Vec::new(),
edition_uids: Vec::new(),
chapter_uids: Vec::new(),
attachment_uids: Vec::new(),
target_type_value: None,
target_type: None,
simple_tags: Vec::new(),
};
parse_tag(r, tag_end, &mut t)?;
if !t.simple_tags.is_empty() {
out.push(t);
}
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_tag(r: &mut dyn ReadSeek, end: u64, t: &mut RawTag) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TARGETS => {
let tg_end = r.stream_position()?.saturating_add(e.size);
parse_targets(r, tg_end, t)?;
}
ids::SIMPLE_TAG => {
let st_end = r.stream_position()?.saturating_add(e.size);
let mut s = RawSimpleTag {
name: String::new(),
value: SimpleTagValue::None,
language: String::from("und"),
language_bcp47: None,
default: true,
};
parse_simple_tag(r, st_end, &mut s)?;
if !s.name.is_empty() {
t.simple_tags.push(s);
}
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_targets(r: &mut dyn ReadSeek, end: u64, t: &mut RawTag) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TAG_TRACK_UID => {
let v = read_uint(r, e.size as usize)?;
t.track_uids.push(v);
}
ids::TAG_EDITION_UID => {
let v = read_uint(r, e.size as usize)?;
t.edition_uids.push(v);
}
ids::TAG_CHAPTER_UID => {
let v = read_uint(r, e.size as usize)?;
t.chapter_uids.push(v);
}
ids::TAG_ATTACHMENT_UID => {
let v = read_uint(r, e.size as usize)?;
t.attachment_uids.push(v);
}
ids::TARGET_TYPE_VALUE => {
t.target_type_value = Some(read_uint(r, e.size as usize)?);
}
ids::TARGET_TYPE => {
let s = read_string(r, e.size as usize)?;
if !s.is_empty() {
t.target_type = Some(s);
}
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_simple_tag(r: &mut dyn ReadSeek, end: u64, s: &mut RawSimpleTag) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TAG_NAME => s.name = read_string(r, e.size as usize)?,
ids::TAG_STRING => {
let v = read_string(r, e.size as usize)?;
s.value = SimpleTagValue::String(v);
}
ids::TAG_BINARY => {
let v = read_bytes(r, e.size as usize)?;
s.value = SimpleTagValue::Binary(v);
}
ids::TAG_LANGUAGE => {
let v = read_string(r, e.size as usize)?;
if !v.is_empty() {
s.language = v;
}
}
ids::TAG_LANGUAGE_BCP47 => {
let v = read_string(r, e.size as usize)?;
if !v.is_empty() {
s.language_bcp47 = Some(v);
}
}
ids::TAG_DEFAULT => {
let v = read_uint(r, e.size as usize)?;
s.default = v != 0;
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn resolve_tags(
raw_tags: Vec<RawTag>,
track_uid_to_index: &std::collections::HashMap<u64, u32>,
chapter_uid_to_index: &std::collections::HashMap<u64, u32>,
attachment_uid_to_index: &std::collections::HashMap<u64, u32>,
edition_uid_to_index: &std::collections::HashMap<u64, u32>,
metadata: &mut Vec<(String, String)>,
tags_out: &mut Vec<Tag>,
) {
for tag in raw_tags {
let mut resolved_uids: Vec<TargetUid> = Vec::new();
let mut had_any_uid = false;
for &uid in &tag.track_uids {
had_any_uid = true;
if uid == 0 {
continue; }
if let Some(&idx) = track_uid_to_index.get(&uid) {
resolved_uids.push(TargetUid::Track {
stream_index: idx,
track_uid: uid,
});
}
}
for &uid in &tag.edition_uids {
had_any_uid = true;
if uid == 0 {
continue;
}
if let Some(&idx) = edition_uid_to_index.get(&uid) {
resolved_uids.push(TargetUid::Edition {
edition_index: idx,
edition_uid: uid,
});
}
}
for &uid in &tag.chapter_uids {
had_any_uid = true;
if uid == 0 {
continue;
}
if let Some(&idx) = chapter_uid_to_index.get(&uid) {
resolved_uids.push(TargetUid::Chapter {
chapter_index: idx,
chapter_uid: uid,
});
}
}
for &uid in &tag.attachment_uids {
had_any_uid = true;
if uid == 0 {
continue;
}
if let Some(&idx) = attachment_uid_to_index.get(&uid) {
resolved_uids.push(TargetUid::Attachment {
attachment_index: idx,
attachment_uid: uid,
});
}
}
if had_any_uid && resolved_uids.is_empty() {
continue;
}
let prefix: String = if let Some(t) = resolved_uids.iter().find_map(|u| match u {
TargetUid::Track { stream_index, .. } => Some(*stream_index),
_ => None,
}) {
format!("tag:track:{t}:")
} else if let Some(e) = resolved_uids.iter().find_map(|u| match u {
TargetUid::Edition { edition_index, .. } => Some(*edition_index),
_ => None,
}) {
format!("tag:edition:{e}:")
} else if let Some(c) = resolved_uids.iter().find_map(|u| match u {
TargetUid::Chapter { chapter_index, .. } => Some(*chapter_index),
_ => None,
}) {
format!("tag:chapter:{c}:")
} else if let Some(a) = resolved_uids.iter().find_map(|u| match u {
TargetUid::Attachment {
attachment_index, ..
} => Some(*attachment_index),
_ => None,
}) {
format!("tag:attachment:{a}:")
} else {
String::new()
};
let mut typed_simple: Vec<SimpleTag> = Vec::with_capacity(tag.simple_tags.len());
for raw in &tag.simple_tags {
typed_simple.push(SimpleTag {
name: raw.name.clone(),
value: raw.value.clone(),
language: raw.language.clone(),
language_bcp47: raw.language_bcp47.clone(),
default: raw.default,
});
if let SimpleTagValue::String(ref v) = raw.value {
if !raw.name.is_empty() && !v.is_empty() {
let key = format!("{prefix}{}", raw.name.to_ascii_lowercase());
metadata.push((key, v.clone()));
}
}
}
tags_out.push(Tag {
targets: Targets {
target_type_value: tag.target_type_value,
target_type: tag.target_type.clone(),
uids: resolved_uids,
},
simple_tags: typed_simple,
});
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Tag {
pub targets: Targets,
pub simple_tags: Vec<SimpleTag>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Targets {
pub target_type_value: Option<u64>,
pub target_type: Option<String>,
pub uids: Vec<TargetUid>,
}
impl Targets {
pub fn target_level(&self) -> Option<TargetLevel> {
self.target_type_value.map(TargetLevel::from_raw)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TargetLevel {
Shot,
Subtrack,
Track,
Part,
Album,
Edition,
Collection,
Other(u64),
}
impl TargetLevel {
pub fn from_raw(v: u64) -> Self {
match v {
10 => TargetLevel::Shot,
20 => TargetLevel::Subtrack,
30 => TargetLevel::Track,
40 => TargetLevel::Part,
50 => TargetLevel::Album,
60 => TargetLevel::Edition,
70 => TargetLevel::Collection,
other => TargetLevel::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
TargetLevel::Shot => 10,
TargetLevel::Subtrack => 20,
TargetLevel::Track => 30,
TargetLevel::Part => 40,
TargetLevel::Album => 50,
TargetLevel::Edition => 60,
TargetLevel::Collection => 70,
TargetLevel::Other(v) => v,
}
}
pub fn canonical_label(self) -> Option<&'static str> {
match self {
TargetLevel::Shot => Some("SHOT"),
TargetLevel::Subtrack => Some("SUBTRACK"),
TargetLevel::Track => Some("TRACK"),
TargetLevel::Part => Some("PART"),
TargetLevel::Album => Some("ALBUM"),
TargetLevel::Edition => Some("EDITION"),
TargetLevel::Collection => Some("COLLECTION"),
TargetLevel::Other(_) => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TargetUid {
Track { stream_index: u32, track_uid: u64 },
Edition {
edition_index: u32,
edition_uid: u64,
},
Chapter {
chapter_index: u32,
chapter_uid: u64,
},
Attachment {
attachment_index: u32,
attachment_uid: u64,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct SimpleTag {
pub name: String,
pub value: SimpleTagValue,
pub language: String,
pub language_bcp47: Option<String>,
pub default: bool,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum SimpleTagValue {
String(String),
Binary(Vec<u8>),
#[default]
None,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct TrackOperation {
pub planes: Vec<TrackPlane>,
pub join_tracks: Vec<TrackRef>,
}
impl TrackOperation {
pub fn is_empty(&self) -> bool {
self.planes.is_empty() && self.join_tracks.is_empty()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrackPlane {
pub track: TrackRef,
pub plane_type: TrackPlaneType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TrackPlaneType {
LeftEye,
RightEye,
Background,
Other(u64),
}
impl TrackPlaneType {
pub fn from_raw(v: u64) -> Self {
match v {
ids::TRACK_PLANE_TYPE_LEFT_EYE => TrackPlaneType::LeftEye,
ids::TRACK_PLANE_TYPE_RIGHT_EYE => TrackPlaneType::RightEye,
ids::TRACK_PLANE_TYPE_BACKGROUND => TrackPlaneType::Background,
other => TrackPlaneType::Other(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrackRef {
pub track_uid: u64,
pub stream_index: Option<u32>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlockAdditionMapping {
pub value: Option<u64>,
pub name: Option<String>,
pub addid_type: u64,
pub extra_data: Option<Vec<u8>>,
}
impl BlockAdditionMapping {
pub fn is_codec_defined(&self) -> bool {
self.addid_type == 0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlockAddition {
id: u64,
data: Vec<u8>,
}
impl BlockAddition {
pub fn block_add_id(&self) -> u64 {
self.id
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn is_codec_defined(&self) -> bool {
self.id == 1
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TrackAudienceFlags {
forced: bool,
hearing_impaired: Option<bool>,
visual_impaired: Option<bool>,
text_descriptions: Option<bool>,
original: Option<bool>,
commentary: Option<bool>,
}
impl TrackAudienceFlags {
pub fn forced(&self) -> bool {
self.forced
}
pub fn hearing_impaired(&self) -> Option<bool> {
self.hearing_impaired
}
pub fn visual_impaired(&self) -> Option<bool> {
self.visual_impaired
}
pub fn text_descriptions(&self) -> Option<bool> {
self.text_descriptions
}
pub fn original(&self) -> Option<bool> {
self.original
}
pub fn commentary(&self) -> Option<bool> {
self.commentary
}
pub fn is_default_presentation(&self) -> bool {
!self.forced
&& !matches!(self.hearing_impaired, Some(true))
&& !matches!(self.visual_impaired, Some(true))
&& !matches!(self.text_descriptions, Some(true))
&& !matches!(self.original, Some(true))
&& !matches!(self.commentary, Some(true))
}
pub fn is_accessibility(&self) -> bool {
matches!(self.hearing_impaired, Some(true))
|| matches!(self.visual_impaired, Some(true))
|| matches!(self.text_descriptions, Some(true))
}
}
#[inline]
fn audience_flag_to_bool(v: u64) -> bool {
v != 0
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TrackAudio {
sampling_frequency: f64,
output_sampling_frequency_explicit: Option<f64>,
channels: u64,
bit_depth: Option<u64>,
}
impl TrackAudio {
pub fn sampling_frequency(&self) -> f64 {
self.sampling_frequency
}
pub fn output_sampling_frequency(&self) -> f64 {
self.output_sampling_frequency_explicit
.unwrap_or(self.sampling_frequency)
}
pub fn output_sampling_frequency_explicit(&self) -> Option<f64> {
self.output_sampling_frequency_explicit
}
pub fn channels(&self) -> u64 {
self.channels
}
pub fn bit_depth(&self) -> Option<u64> {
self.bit_depth
}
pub fn is_sbr(&self) -> bool {
match self.output_sampling_frequency_explicit {
Some(v) => v > self.sampling_frequency,
None => false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TrackTiming {
default_duration: Option<u64>,
default_decoded_field_duration: Option<u64>,
track_timestamp_scale_explicit: Option<f64>,
}
impl TrackTiming {
pub fn default_duration(&self) -> Option<u64> {
self.default_duration
}
pub fn default_decoded_field_duration(&self) -> Option<u64> {
self.default_decoded_field_duration
}
pub fn track_timestamp_scale(&self) -> f64 {
self.track_timestamp_scale_explicit.unwrap_or(1.0)
}
pub fn track_timestamp_scale_explicit(&self) -> Option<f64> {
self.track_timestamp_scale_explicit
}
pub fn nominal_frame_rate(&self) -> Option<f64> {
self.default_duration
.map(|ns| 1_000_000_000.0_f64 / ns as f64)
}
pub fn is_empty(&self) -> bool {
self.default_duration.is_none()
&& self.default_decoded_field_duration.is_none()
&& self.track_timestamp_scale_explicit.is_none()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct TrackCodecTiming {
codec_delay_explicit: Option<u64>,
seek_pre_roll_explicit: Option<u64>,
}
impl TrackCodecTiming {
pub fn codec_delay(&self) -> u64 {
self.codec_delay_explicit.unwrap_or(0)
}
pub fn codec_delay_explicit(&self) -> Option<u64> {
self.codec_delay_explicit
}
pub fn seek_pre_roll(&self) -> u64 {
self.seek_pre_roll_explicit.unwrap_or(0)
}
pub fn seek_pre_roll_explicit(&self) -> Option<u64> {
self.seek_pre_roll_explicit
}
pub fn is_empty(&self) -> bool {
self.codec_delay_explicit.is_none() && self.seek_pre_roll_explicit.is_none()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VideoInterlacing {
flag: FlagInterlaced,
field_order_raw: u64,
}
impl VideoInterlacing {
pub fn flag(&self) -> FlagInterlaced {
self.flag
}
pub fn field_order(&self) -> Option<FieldOrder> {
match self.flag {
FlagInterlaced::Interlaced => Some(FieldOrder::from_raw(self.field_order_raw)),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum FlagInterlaced {
#[default]
Undetermined,
Interlaced,
Progressive,
Other(u64),
}
impl FlagInterlaced {
pub fn from_raw(v: u64) -> Self {
match v {
ids::FLAG_INTERLACED_UNDETERMINED => FlagInterlaced::Undetermined,
ids::FLAG_INTERLACED_INTERLACED => FlagInterlaced::Interlaced,
ids::FLAG_INTERLACED_PROGRESSIVE => FlagInterlaced::Progressive,
other => FlagInterlaced::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
FlagInterlaced::Undetermined => ids::FLAG_INTERLACED_UNDETERMINED,
FlagInterlaced::Interlaced => ids::FLAG_INTERLACED_INTERLACED,
FlagInterlaced::Progressive => ids::FLAG_INTERLACED_PROGRESSIVE,
FlagInterlaced::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FieldOrder {
Progressive,
Tff,
Undetermined,
Bff,
TffInterleaved,
BffInterleaved,
Other(u64),
}
impl FieldOrder {
pub fn from_raw(v: u64) -> Self {
match v {
ids::FIELD_ORDER_PROGRESSIVE => FieldOrder::Progressive,
ids::FIELD_ORDER_TFF => FieldOrder::Tff,
ids::FIELD_ORDER_UNDETERMINED => FieldOrder::Undetermined,
ids::FIELD_ORDER_BFF => FieldOrder::Bff,
ids::FIELD_ORDER_TFF_INTERLEAVED => FieldOrder::TffInterleaved,
ids::FIELD_ORDER_BFF_INTERLEAVED => FieldOrder::BffInterleaved,
other => FieldOrder::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
FieldOrder::Progressive => ids::FIELD_ORDER_PROGRESSIVE,
FieldOrder::Tff => ids::FIELD_ORDER_TFF,
FieldOrder::Undetermined => ids::FIELD_ORDER_UNDETERMINED,
FieldOrder::Bff => ids::FIELD_ORDER_BFF,
FieldOrder::TffInterleaved => ids::FIELD_ORDER_TFF_INTERLEAVED,
FieldOrder::BffInterleaved => ids::FIELD_ORDER_BFF_INTERLEAVED,
FieldOrder::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum StereoMode {
#[default]
Mono,
SideBySideLeftFirst,
TopBottomRightFirst,
TopBottomLeftFirst,
CheckboardRightFirst,
CheckboardLeftFirst,
RowInterleavedRightFirst,
RowInterleavedLeftFirst,
ColumnInterleavedRightFirst,
ColumnInterleavedLeftFirst,
AnaglyphCyanRed,
SideBySideRightFirst,
AnaglyphGreenMagenta,
BothEyesLacedLeftFirst,
BothEyesLacedRightFirst,
Other(u64),
}
impl StereoMode {
pub fn from_raw(v: u64) -> Self {
match v {
ids::STEREO_MODE_MONO => StereoMode::Mono,
ids::STEREO_MODE_SIDE_BY_SIDE_LEFT_FIRST => StereoMode::SideBySideLeftFirst,
ids::STEREO_MODE_TOP_BOTTOM_RIGHT_FIRST => StereoMode::TopBottomRightFirst,
ids::STEREO_MODE_TOP_BOTTOM_LEFT_FIRST => StereoMode::TopBottomLeftFirst,
ids::STEREO_MODE_CHECKBOARD_RIGHT_FIRST => StereoMode::CheckboardRightFirst,
ids::STEREO_MODE_CHECKBOARD_LEFT_FIRST => StereoMode::CheckboardLeftFirst,
ids::STEREO_MODE_ROW_INTERLEAVED_RIGHT_FIRST => StereoMode::RowInterleavedRightFirst,
ids::STEREO_MODE_ROW_INTERLEAVED_LEFT_FIRST => StereoMode::RowInterleavedLeftFirst,
ids::STEREO_MODE_COLUMN_INTERLEAVED_RIGHT_FIRST => {
StereoMode::ColumnInterleavedRightFirst
}
ids::STEREO_MODE_COLUMN_INTERLEAVED_LEFT_FIRST => {
StereoMode::ColumnInterleavedLeftFirst
}
ids::STEREO_MODE_ANAGLYPH_CYAN_RED => StereoMode::AnaglyphCyanRed,
ids::STEREO_MODE_SIDE_BY_SIDE_RIGHT_FIRST => StereoMode::SideBySideRightFirst,
ids::STEREO_MODE_ANAGLYPH_GREEN_MAGENTA => StereoMode::AnaglyphGreenMagenta,
ids::STEREO_MODE_BOTH_EYES_LACED_LEFT_FIRST => StereoMode::BothEyesLacedLeftFirst,
ids::STEREO_MODE_BOTH_EYES_LACED_RIGHT_FIRST => StereoMode::BothEyesLacedRightFirst,
other => StereoMode::Other(other),
}
}
pub fn is_stereo(&self) -> bool {
!matches!(self, StereoMode::Mono)
}
pub fn to_raw(self) -> u64 {
match self {
StereoMode::Mono => ids::STEREO_MODE_MONO,
StereoMode::SideBySideLeftFirst => ids::STEREO_MODE_SIDE_BY_SIDE_LEFT_FIRST,
StereoMode::TopBottomRightFirst => ids::STEREO_MODE_TOP_BOTTOM_RIGHT_FIRST,
StereoMode::TopBottomLeftFirst => ids::STEREO_MODE_TOP_BOTTOM_LEFT_FIRST,
StereoMode::CheckboardRightFirst => ids::STEREO_MODE_CHECKBOARD_RIGHT_FIRST,
StereoMode::CheckboardLeftFirst => ids::STEREO_MODE_CHECKBOARD_LEFT_FIRST,
StereoMode::RowInterleavedRightFirst => ids::STEREO_MODE_ROW_INTERLEAVED_RIGHT_FIRST,
StereoMode::RowInterleavedLeftFirst => ids::STEREO_MODE_ROW_INTERLEAVED_LEFT_FIRST,
StereoMode::ColumnInterleavedRightFirst => {
ids::STEREO_MODE_COLUMN_INTERLEAVED_RIGHT_FIRST
}
StereoMode::ColumnInterleavedLeftFirst => {
ids::STEREO_MODE_COLUMN_INTERLEAVED_LEFT_FIRST
}
StereoMode::AnaglyphCyanRed => ids::STEREO_MODE_ANAGLYPH_CYAN_RED,
StereoMode::SideBySideRightFirst => ids::STEREO_MODE_SIDE_BY_SIDE_RIGHT_FIRST,
StereoMode::AnaglyphGreenMagenta => ids::STEREO_MODE_ANAGLYPH_GREEN_MAGENTA,
StereoMode::BothEyesLacedLeftFirst => ids::STEREO_MODE_BOTH_EYES_LACED_LEFT_FIRST,
StereoMode::BothEyesLacedRightFirst => ids::STEREO_MODE_BOTH_EYES_LACED_RIGHT_FIRST,
StereoMode::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ProjectionType {
#[default]
Rectangular,
Equirectangular,
Cubemap,
Mesh,
Other(u64),
}
impl ProjectionType {
pub fn from_raw(v: u64) -> Self {
match v {
ids::PROJECTION_TYPE_RECTANGULAR => ProjectionType::Rectangular,
ids::PROJECTION_TYPE_EQUIRECTANGULAR => ProjectionType::Equirectangular,
ids::PROJECTION_TYPE_CUBEMAP => ProjectionType::Cubemap,
ids::PROJECTION_TYPE_MESH => ProjectionType::Mesh,
other => ProjectionType::Other(other),
}
}
pub fn is_spherical(&self) -> bool {
!matches!(self, ProjectionType::Rectangular)
}
pub fn to_raw(self) -> u64 {
match self {
ProjectionType::Rectangular => ids::PROJECTION_TYPE_RECTANGULAR,
ProjectionType::Equirectangular => ids::PROJECTION_TYPE_EQUIRECTANGULAR,
ProjectionType::Cubemap => ids::PROJECTION_TYPE_CUBEMAP,
ProjectionType::Mesh => ids::PROJECTION_TYPE_MESH,
ProjectionType::Other(v) => v,
}
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct Projection {
projection_type: ProjectionType,
private: Option<Vec<u8>>,
pose_yaw: f64,
pose_pitch: f64,
pose_roll: f64,
}
impl Projection {
pub fn projection_type(&self) -> ProjectionType {
self.projection_type
}
pub fn private(&self) -> Option<&[u8]> {
self.private.as_deref()
}
pub fn pose_yaw(&self) -> f64 {
self.pose_yaw
}
pub fn pose_pitch(&self) -> f64 {
self.pose_pitch
}
pub fn pose_roll(&self) -> f64 {
self.pose_roll
}
pub fn is_rotated(&self) -> bool {
self.pose_yaw != 0.0 || self.pose_pitch != 0.0 || self.pose_roll != 0.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum AlphaMode {
#[default]
None,
Present,
Other(u64),
}
impl AlphaMode {
pub fn from_raw(v: u64) -> Self {
match v {
ids::ALPHA_MODE_NONE => AlphaMode::None,
ids::ALPHA_MODE_PRESENT => AlphaMode::Present,
other => AlphaMode::Other(other),
}
}
pub fn has_alpha(&self) -> bool {
matches!(self, AlphaMode::Present)
}
pub fn to_raw(self) -> u64 {
match self {
AlphaMode::None => ids::ALPHA_MODE_NONE,
AlphaMode::Present => ids::ALPHA_MODE_PRESENT,
AlphaMode::Other(v) => v,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UncompressedFourCC {
bytes: Vec<u8>,
}
impl UncompressedFourCC {
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn fourcc(&self) -> Option<[u8; 4]> {
if self.bytes.len() == 4 {
Some([self.bytes[0], self.bytes[1], self.bytes[2], self.bytes[3]])
} else {
None
}
}
pub fn as_str(&self) -> Option<String> {
if self.bytes.len() == 4 {
Some(String::from_utf8_lossy(&self.bytes).into_owned())
} else {
None
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VideoGeometry {
pixel_crop_top: u64,
pixel_crop_bottom: u64,
pixel_crop_left: u64,
pixel_crop_right: u64,
display_width: Option<u64>,
display_height: Option<u64>,
display_unit: DisplayUnit,
}
impl VideoGeometry {
pub fn pixel_crop_top(&self) -> u64 {
self.pixel_crop_top
}
pub fn pixel_crop_bottom(&self) -> u64 {
self.pixel_crop_bottom
}
pub fn pixel_crop_left(&self) -> u64 {
self.pixel_crop_left
}
pub fn pixel_crop_right(&self) -> u64 {
self.pixel_crop_right
}
pub fn display_width(&self) -> Option<u64> {
self.display_width
}
pub fn display_height(&self) -> Option<u64> {
self.display_height
}
pub fn display_unit(&self) -> DisplayUnit {
self.display_unit
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum DisplayUnit {
#[default]
Pixels,
Centimeters,
Inches,
DisplayAspectRatio,
Unknown,
Other(u64),
}
impl DisplayUnit {
pub fn from_raw(v: u64) -> Self {
match v {
ids::DISPLAY_UNIT_PIXELS => DisplayUnit::Pixels,
ids::DISPLAY_UNIT_CENTIMETERS => DisplayUnit::Centimeters,
ids::DISPLAY_UNIT_INCHES => DisplayUnit::Inches,
ids::DISPLAY_UNIT_DAR => DisplayUnit::DisplayAspectRatio,
ids::DISPLAY_UNIT_UNKNOWN => DisplayUnit::Unknown,
other => DisplayUnit::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
DisplayUnit::Pixels => ids::DISPLAY_UNIT_PIXELS,
DisplayUnit::Centimeters => ids::DISPLAY_UNIT_CENTIMETERS,
DisplayUnit::Inches => ids::DISPLAY_UNIT_INCHES,
DisplayUnit::DisplayAspectRatio => ids::DISPLAY_UNIT_DAR,
DisplayUnit::Unknown => ids::DISPLAY_UNIT_UNKNOWN,
DisplayUnit::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VideoColour {
matrix_coefficients: MatrixCoefficients,
bits_per_channel: u64,
chroma_subsampling_horz: Option<u64>,
chroma_subsampling_vert: Option<u64>,
cb_subsampling_horz: Option<u64>,
cb_subsampling_vert: Option<u64>,
chroma_siting_horz: ChromaSitingHorz,
chroma_siting_vert: ChromaSitingVert,
range: ColourRange,
transfer_characteristics: TransferCharacteristics,
primaries: Primaries,
max_cll: Option<u64>,
max_fall: Option<u64>,
mastering_metadata: Option<MasteringMetadata>,
}
impl VideoColour {
pub fn matrix_coefficients(&self) -> MatrixCoefficients {
self.matrix_coefficients
}
pub fn bits_per_channel(&self) -> u64 {
self.bits_per_channel
}
pub fn chroma_subsampling_horz(&self) -> Option<u64> {
self.chroma_subsampling_horz
}
pub fn chroma_subsampling_vert(&self) -> Option<u64> {
self.chroma_subsampling_vert
}
pub fn cb_subsampling_horz(&self) -> Option<u64> {
self.cb_subsampling_horz
}
pub fn cb_subsampling_vert(&self) -> Option<u64> {
self.cb_subsampling_vert
}
pub fn chroma_siting_horz(&self) -> ChromaSitingHorz {
self.chroma_siting_horz
}
pub fn chroma_siting_vert(&self) -> ChromaSitingVert {
self.chroma_siting_vert
}
pub fn range(&self) -> ColourRange {
self.range
}
pub fn transfer_characteristics(&self) -> TransferCharacteristics {
self.transfer_characteristics
}
pub fn primaries(&self) -> Primaries {
self.primaries
}
pub fn max_cll(&self) -> Option<u64> {
self.max_cll
}
pub fn max_fall(&self) -> Option<u64> {
self.max_fall
}
pub fn mastering_metadata(&self) -> Option<&MasteringMetadata> {
self.mastering_metadata.as_ref()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatrixCoefficients {
Identity,
BT709,
Unspecified,
Reserved,
UsFcc73682,
BT470Bg,
Smpte170M,
Smpte240M,
YCoCg,
BT2020NonConstantLuminance,
BT2020ConstantLuminance,
SmpteSt2085,
ChromaDerivedNonConstantLuminance,
ChromaDerivedConstantLuminance,
BT2100,
Other(u64),
}
impl MatrixCoefficients {
pub fn from_raw(v: u64) -> Self {
match v {
0 => Self::Identity,
1 => Self::BT709,
2 => Self::Unspecified,
3 => Self::Reserved,
4 => Self::UsFcc73682,
5 => Self::BT470Bg,
6 => Self::Smpte170M,
7 => Self::Smpte240M,
8 => Self::YCoCg,
9 => Self::BT2020NonConstantLuminance,
10 => Self::BT2020ConstantLuminance,
11 => Self::SmpteSt2085,
12 => Self::ChromaDerivedNonConstantLuminance,
13 => Self::ChromaDerivedConstantLuminance,
14 => Self::BT2100,
other => Self::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
Self::Identity => 0,
Self::BT709 => 1,
Self::Unspecified => 2,
Self::Reserved => 3,
Self::UsFcc73682 => 4,
Self::BT470Bg => 5,
Self::Smpte170M => 6,
Self::Smpte240M => 7,
Self::YCoCg => 8,
Self::BT2020NonConstantLuminance => 9,
Self::BT2020ConstantLuminance => 10,
Self::SmpteSt2085 => 11,
Self::ChromaDerivedNonConstantLuminance => 12,
Self::ChromaDerivedConstantLuminance => 13,
Self::BT2100 => 14,
Self::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ChromaSitingHorz {
#[default]
Unspecified,
LeftCollocated,
Half,
Other(u64),
}
impl ChromaSitingHorz {
pub fn from_raw(v: u64) -> Self {
match v {
ids::CHROMA_SITING_UNSPECIFIED => Self::Unspecified,
ids::CHROMA_SITING_HORZ_LEFT_COLLOCATED => Self::LeftCollocated,
ids::CHROMA_SITING_HALF => Self::Half,
other => Self::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
Self::Unspecified => ids::CHROMA_SITING_UNSPECIFIED,
Self::LeftCollocated => ids::CHROMA_SITING_HORZ_LEFT_COLLOCATED,
Self::Half => ids::CHROMA_SITING_HALF,
Self::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ChromaSitingVert {
#[default]
Unspecified,
TopCollocated,
Half,
Other(u64),
}
impl ChromaSitingVert {
pub fn from_raw(v: u64) -> Self {
match v {
ids::CHROMA_SITING_UNSPECIFIED => Self::Unspecified,
ids::CHROMA_SITING_VERT_TOP_COLLOCATED => Self::TopCollocated,
ids::CHROMA_SITING_HALF => Self::Half,
other => Self::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
Self::Unspecified => ids::CHROMA_SITING_UNSPECIFIED,
Self::TopCollocated => ids::CHROMA_SITING_VERT_TOP_COLLOCATED,
Self::Half => ids::CHROMA_SITING_HALF,
Self::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ColourRange {
#[default]
Unspecified,
Broadcast,
Full,
DefinedByMatrixAndTransfer,
Other(u64),
}
impl ColourRange {
pub fn from_raw(v: u64) -> Self {
match v {
ids::COLOUR_RANGE_UNSPECIFIED => Self::Unspecified,
ids::COLOUR_RANGE_BROADCAST => Self::Broadcast,
ids::COLOUR_RANGE_FULL => Self::Full,
ids::COLOUR_RANGE_DEFINED_BY_MATRIX_AND_TRANSFER => Self::DefinedByMatrixAndTransfer,
other => Self::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
Self::Unspecified => ids::COLOUR_RANGE_UNSPECIFIED,
Self::Broadcast => ids::COLOUR_RANGE_BROADCAST,
Self::Full => ids::COLOUR_RANGE_FULL,
Self::DefinedByMatrixAndTransfer => ids::COLOUR_RANGE_DEFINED_BY_MATRIX_AND_TRANSFER,
Self::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TransferCharacteristics {
Reserved0,
BT709,
Unspecified,
Reserved3,
Gamma22BT470M,
Gamma28BT470Bg,
Smpte170M,
Smpte240M,
Linear,
Log,
LogSqrt,
Iec61966_2_4,
BT1361ExtendedColourGamut,
Iec61966_2_1,
BT2020TenBit,
BT2020TwelveBit,
BT2100Pq,
SmpteSt428_1,
AribStdB67Hlg,
Other(u64),
}
impl TransferCharacteristics {
pub fn from_raw(v: u64) -> Self {
match v {
0 => Self::Reserved0,
1 => Self::BT709,
2 => Self::Unspecified,
3 => Self::Reserved3,
4 => Self::Gamma22BT470M,
5 => Self::Gamma28BT470Bg,
6 => Self::Smpte170M,
7 => Self::Smpte240M,
8 => Self::Linear,
9 => Self::Log,
10 => Self::LogSqrt,
11 => Self::Iec61966_2_4,
12 => Self::BT1361ExtendedColourGamut,
13 => Self::Iec61966_2_1,
14 => Self::BT2020TenBit,
15 => Self::BT2020TwelveBit,
16 => Self::BT2100Pq,
17 => Self::SmpteSt428_1,
18 => Self::AribStdB67Hlg,
other => Self::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
Self::Reserved0 => 0,
Self::BT709 => 1,
Self::Unspecified => 2,
Self::Reserved3 => 3,
Self::Gamma22BT470M => 4,
Self::Gamma28BT470Bg => 5,
Self::Smpte170M => 6,
Self::Smpte240M => 7,
Self::Linear => 8,
Self::Log => 9,
Self::LogSqrt => 10,
Self::Iec61966_2_4 => 11,
Self::BT1361ExtendedColourGamut => 12,
Self::Iec61966_2_1 => 13,
Self::BT2020TenBit => 14,
Self::BT2020TwelveBit => 15,
Self::BT2100Pq => 16,
Self::SmpteSt428_1 => 17,
Self::AribStdB67Hlg => 18,
Self::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Primaries {
Reserved0,
BT709,
Unspecified,
Reserved3,
BT470M,
BT470Bg,
BT601_525Smpte170M,
Smpte240M,
Film,
BT2020,
SmpteSt428_1,
SmpteRp432_2,
SmpteEg432_2,
EbuTech3213EJedecP22Phosphors,
Other(u64),
}
impl Primaries {
pub fn from_raw(v: u64) -> Self {
match v {
0 => Self::Reserved0,
1 => Self::BT709,
2 => Self::Unspecified,
3 => Self::Reserved3,
4 => Self::BT470M,
5 => Self::BT470Bg,
6 => Self::BT601_525Smpte170M,
7 => Self::Smpte240M,
8 => Self::Film,
9 => Self::BT2020,
10 => Self::SmpteSt428_1,
11 => Self::SmpteRp432_2,
12 => Self::SmpteEg432_2,
22 => Self::EbuTech3213EJedecP22Phosphors,
other => Self::Other(other),
}
}
pub fn to_raw(self) -> u64 {
match self {
Self::Reserved0 => 0,
Self::BT709 => 1,
Self::Unspecified => 2,
Self::Reserved3 => 3,
Self::BT470M => 4,
Self::BT470Bg => 5,
Self::BT601_525Smpte170M => 6,
Self::Smpte240M => 7,
Self::Film => 8,
Self::BT2020 => 9,
Self::SmpteSt428_1 => 10,
Self::SmpteRp432_2 => 11,
Self::SmpteEg432_2 => 12,
Self::EbuTech3213EJedecP22Phosphors => 22,
Self::Other(v) => v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct MasteringMetadata {
primary_r_chromaticity_x: Option<f64>,
primary_r_chromaticity_y: Option<f64>,
primary_g_chromaticity_x: Option<f64>,
primary_g_chromaticity_y: Option<f64>,
primary_b_chromaticity_x: Option<f64>,
primary_b_chromaticity_y: Option<f64>,
white_point_chromaticity_x: Option<f64>,
white_point_chromaticity_y: Option<f64>,
luminance_max: Option<f64>,
luminance_min: Option<f64>,
}
impl MasteringMetadata {
pub fn primary_r_chromaticity_x(&self) -> Option<f64> {
self.primary_r_chromaticity_x
}
pub fn primary_r_chromaticity_y(&self) -> Option<f64> {
self.primary_r_chromaticity_y
}
pub fn primary_g_chromaticity_x(&self) -> Option<f64> {
self.primary_g_chromaticity_x
}
pub fn primary_g_chromaticity_y(&self) -> Option<f64> {
self.primary_g_chromaticity_y
}
pub fn primary_b_chromaticity_x(&self) -> Option<f64> {
self.primary_b_chromaticity_x
}
pub fn primary_b_chromaticity_y(&self) -> Option<f64> {
self.primary_b_chromaticity_y
}
pub fn white_point_chromaticity_x(&self) -> Option<f64> {
self.white_point_chromaticity_x
}
pub fn white_point_chromaticity_y(&self) -> Option<f64> {
self.white_point_chromaticity_y
}
pub fn luminance_max(&self) -> Option<f64> {
self.luminance_max
}
pub fn luminance_min(&self) -> Option<f64> {
self.luminance_min
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ContentEncodings {
pub encodings: Vec<ContentEncoding>,
}
impl ContentEncodings {
pub fn is_empty(&self) -> bool {
self.encodings.is_empty()
}
}
fn compute_header_strip_prefix(enc: &ContentEncodings) -> Option<Vec<u8>> {
let mut prefix: Vec<u8> = Vec::new();
let mut saw_strip = false;
for e in &enc.encodings {
if !e.scope.block() {
continue;
}
match &e.transform {
ContentEncodingTransform::Compression {
algo: ContentCompAlgo::HeaderStripping,
settings,
} => {
let mut combined = settings.clone();
combined.extend_from_slice(&prefix);
prefix = combined;
saw_strip = true;
}
_ => return None,
}
}
if saw_strip {
Some(prefix)
} else {
None
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ContentEncoding {
pub order: u64,
pub scope: ContentEncodingScope,
pub transform: ContentEncodingTransform,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ContentEncodingScope(pub u64);
impl ContentEncodingScope {
pub fn block(self) -> bool {
self.0 & ids::CONTENT_ENCODING_SCOPE_BLOCK != 0
}
pub fn private(self) -> bool {
self.0 & ids::CONTENT_ENCODING_SCOPE_PRIVATE != 0
}
pub fn next(self) -> bool {
self.0 & ids::CONTENT_ENCODING_SCOPE_NEXT != 0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ContentEncodingTransform {
Compression {
algo: ContentCompAlgo,
settings: Vec<u8>,
},
Encryption {
algo: ContentEncAlgo,
key_id: Vec<u8>,
aes_cipher_mode: Option<AesCipherMode>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ContentCompAlgo {
Zlib,
Bzlib,
Lzo1x,
HeaderStripping,
Other(u64),
}
impl ContentCompAlgo {
pub fn from_raw(v: u64) -> Self {
match v {
ids::CONTENT_COMP_ALGO_ZLIB => ContentCompAlgo::Zlib,
ids::CONTENT_COMP_ALGO_BZLIB => ContentCompAlgo::Bzlib,
ids::CONTENT_COMP_ALGO_LZO1X => ContentCompAlgo::Lzo1x,
ids::CONTENT_COMP_ALGO_HEADER_STRIPPING => ContentCompAlgo::HeaderStripping,
other => ContentCompAlgo::Other(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ContentEncAlgo {
None,
Des,
TripleDes,
Twofish,
Blowfish,
Aes,
Other(u64),
}
impl ContentEncAlgo {
pub fn from_raw(v: u64) -> Self {
match v {
ids::CONTENT_ENC_ALGO_NONE => ContentEncAlgo::None,
ids::CONTENT_ENC_ALGO_DES => ContentEncAlgo::Des,
ids::CONTENT_ENC_ALGO_3DES => ContentEncAlgo::TripleDes,
ids::CONTENT_ENC_ALGO_TWOFISH => ContentEncAlgo::Twofish,
ids::CONTENT_ENC_ALGO_BLOWFISH => ContentEncAlgo::Blowfish,
ids::CONTENT_ENC_ALGO_AES => ContentEncAlgo::Aes,
other => ContentEncAlgo::Other(other),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AesCipherMode {
Ctr,
Cbc,
Other(u64),
}
impl AesCipherMode {
pub fn from_raw(v: u64) -> Self {
match v {
ids::AES_CIPHER_MODE_CTR => AesCipherMode::Ctr,
ids::AES_CIPHER_MODE_CBC => AesCipherMode::Cbc,
other => AesCipherMode::Other(other),
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Edition {
pub uid: Option<u64>,
pub default: bool,
pub ordered: bool,
pub chapters: Vec<Chapter>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Chapter {
pub index: u32,
pub uid: Option<u64>,
pub string_uid: Option<String>,
pub time_start_ns: u64,
pub time_end_ns: Option<u64>,
pub hidden: bool,
pub enabled: bool,
pub segment_uuid: Option<Vec<u8>>,
pub segment_edition_uid: Option<u64>,
pub physical_equiv: Option<u64>,
pub displays: Vec<ChapterDisplay>,
pub chap_processes: Vec<ChapProcess>,
pub children: Vec<Chapter>,
}
impl Default for Chapter {
fn default() -> Self {
Self {
index: 0,
uid: None,
string_uid: None,
time_start_ns: 0,
time_end_ns: None,
hidden: false,
enabled: true,
segment_uuid: None,
segment_edition_uid: None,
physical_equiv: None,
displays: Vec::new(),
chap_processes: Vec::new(),
children: Vec::new(),
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChapterDisplay {
pub string: String,
pub language: String,
pub language_bcp47: Option<String>,
pub country: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChapProcess {
pub codec_id: u64,
pub private: Option<Vec<u8>>,
pub commands: Vec<ChapProcessCommand>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChapProcessCommand {
pub time: u64,
pub data: Vec<u8>,
}
#[allow(clippy::too_many_arguments)]
fn parse_chapters_typed(
r: &mut dyn ReadSeek,
end: u64,
metadata: &mut Vec<(String, String)>,
chapter_uid_to_index: &mut std::collections::HashMap<u64, u32>,
edition_uid_to_index: &mut std::collections::HashMap<u64, u32>,
editions: &mut Vec<Edition>,
) -> Result<()> {
let mut chapter_index: u32 = 0;
let mut edition_index: u32 = 0;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::EDITION_ENTRY => {
let ee_end = r.stream_position()?.saturating_add(e.size);
edition_index += 1;
let edition = parse_edition_entry(
r,
ee_end,
metadata,
&mut chapter_index,
edition_index,
chapter_uid_to_index,
edition_uid_to_index,
)?;
editions.push(edition);
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn parse_edition_entry(
r: &mut dyn ReadSeek,
end: u64,
metadata: &mut Vec<(String, String)>,
chapter_index: &mut u32,
edition_index: u32,
chapter_uid_to_index: &mut std::collections::HashMap<u64, u32>,
edition_uid_to_index: &mut std::collections::HashMap<u64, u32>,
) -> Result<Edition> {
let mut edition = Edition::default();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::EDITION_UID => {
let uid = read_uint(r, e.size as usize)?;
if uid != 0 {
edition_uid_to_index.insert(uid, edition_index);
edition.uid = Some(uid);
}
}
ids::EDITION_FLAG_DEFAULT => edition.default = read_uint(r, e.size as usize)? != 0,
ids::EDITION_FLAG_ORDERED => edition.ordered = read_uint(r, e.size as usize)? != 0,
ids::CHAPTER_ATOM => {
let ca_end = r.stream_position()?.saturating_add(e.size);
let atom = parse_chapter_atom(
r,
ca_end,
metadata,
chapter_index,
chapter_uid_to_index,
0,
)?;
edition.chapters.push(atom);
}
_ => skip(r, e.size)?,
}
}
Ok(edition)
}
const MAX_CHAPTER_NESTING: u32 = 64;
fn parse_chapter_atom(
r: &mut dyn ReadSeek,
end: u64,
metadata: &mut Vec<(String, String)>,
chapter_index: &mut u32,
chapter_uid_to_index: &mut std::collections::HashMap<u64, u32>,
depth: u32,
) -> Result<Chapter> {
if depth >= MAX_CHAPTER_NESTING {
return Err(Error::invalid(format!(
"MKV: ChapterAtom nesting exceeds {MAX_CHAPTER_NESTING}"
)));
}
*chapter_index += 1;
let index = *chapter_index;
let mut atom = Chapter {
index,
..Chapter::default()
};
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CHAPTER_UID => {
let uid = read_uint(r, e.size as usize)?;
if uid != 0 {
chapter_uid_to_index.insert(uid, index);
atom.uid = Some(uid);
}
}
ids::CHAPTER_STRING_UID => {
atom.string_uid = Some(read_string(r, e.size as usize)?);
}
ids::CHAPTER_TIME_START => atom.time_start_ns = read_uint(r, e.size as usize)?,
ids::CHAPTER_TIME_END => atom.time_end_ns = Some(read_uint(r, e.size as usize)?),
ids::CHAPTER_FLAG_HIDDEN => atom.hidden = read_uint(r, e.size as usize)? != 0,
ids::CHAPTER_FLAG_ENABLED => atom.enabled = read_uint(r, e.size as usize)? != 0,
ids::CHAPTER_SEGMENT_UUID => {
atom.segment_uuid = Some(crate::ebml::read_bytes(r, e.size as usize)?);
}
ids::CHAPTER_SEGMENT_EDITION_UID => {
let v = read_uint(r, e.size as usize)?;
if v != 0 {
atom.segment_edition_uid = Some(v);
}
}
ids::CHAPTER_PHYSICAL_EQUIV => {
atom.physical_equiv = Some(read_uint(r, e.size as usize)?);
}
ids::CHAPTER_DISPLAY => {
let cd_end = r.stream_position()?.saturating_add(e.size);
if let Some(disp) = parse_chapter_display(r, cd_end)? {
atom.displays.push(disp);
}
}
ids::CHAP_PROCESS => {
let cp_end = r.stream_position()?.saturating_add(e.size);
atom.chap_processes.push(parse_chap_process(r, cp_end)?);
}
ids::CHAPTER_ATOM => {
let ca_end = r.stream_position()?.saturating_add(e.size);
let child = parse_chapter_atom(
r,
ca_end,
metadata,
chapter_index,
chapter_uid_to_index,
depth + 1,
)?;
atom.children.push(child);
}
_ => skip(r, e.size)?,
}
}
metadata.push((
format!("chapter:{index}:start_ms"),
(atom.time_start_ns / 1_000_000).to_string(),
));
if let Some(ns) = atom.time_end_ns {
metadata.push((
format!("chapter:{index}:end_ms"),
(ns / 1_000_000).to_string(),
));
}
if let Some(t) = atom
.displays
.iter()
.map(|d| &d.string)
.find(|s| !s.is_empty())
{
metadata.push((format!("chapter:{index}:title"), t.clone()));
}
Ok(atom)
}
fn parse_chapter_display(r: &mut dyn ReadSeek, end: u64) -> Result<Option<ChapterDisplay>> {
let mut string: Option<String> = None;
let mut language: Option<String> = None;
let mut language_bcp47: Option<String> = None;
let mut country: Option<String> = None;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CHAP_STRING => {
let v = read_string(r, e.size as usize)?;
if string.is_none() {
string = Some(v);
}
}
ids::CHAP_LANGUAGE => language = Some(read_string(r, e.size as usize)?),
ids::CHAP_LANGUAGE_BCP47 => language_bcp47 = Some(read_string(r, e.size as usize)?),
ids::CHAP_COUNTRY => country = Some(read_string(r, e.size as usize)?),
_ => skip(r, e.size)?,
}
}
let string = match string {
Some(s) if !s.is_empty() => s,
_ => return Ok(None),
};
Ok(Some(ChapterDisplay {
string,
language: language.unwrap_or_else(|| "eng".to_string()),
language_bcp47,
country,
}))
}
fn parse_chap_process(r: &mut dyn ReadSeek, end: u64) -> Result<ChapProcess> {
let mut proc = ChapProcess::default();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CHAP_PROCESS_CODEC_ID => proc.codec_id = read_uint(r, e.size as usize)?,
ids::CHAP_PROCESS_PRIVATE => {
proc.private = Some(crate::ebml::read_bytes(r, e.size as usize)?);
}
ids::CHAP_PROCESS_COMMAND => {
let cc_end = r.stream_position()?.saturating_add(e.size);
proc.commands.push(parse_chap_process_command(r, cc_end)?);
}
_ => skip(r, e.size)?,
}
}
Ok(proc)
}
fn parse_chap_process_command(r: &mut dyn ReadSeek, end: u64) -> Result<ChapProcessCommand> {
let mut cmd = ChapProcessCommand::default();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CHAP_PROCESS_TIME => cmd.time = read_uint(r, e.size as usize)?,
ids::CHAP_PROCESS_DATA => cmd.data = crate::ebml::read_bytes(r, e.size as usize)?,
_ => skip(r, e.size)?,
}
}
Ok(cmd)
}
fn parse_attachments(
r: &mut dyn ReadSeek,
end: u64,
metadata: &mut Vec<(String, String)>,
attachment_uid_to_index: &mut std::collections::HashMap<u64, u32>,
attachments: &mut Vec<Attachment>,
) -> Result<()> {
let mut idx: u32 = 0;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::ATTACHED_FILE => {
let af_end = r.stream_position()?.saturating_add(e.size);
idx += 1;
parse_attached_file(
r,
af_end,
metadata,
idx,
attachment_uid_to_index,
attachments,
)?;
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_attached_file(
r: &mut dyn ReadSeek,
end: u64,
metadata: &mut Vec<(String, String)>,
index: u32,
attachment_uid_to_index: &mut std::collections::HashMap<u64, u32>,
attachments: &mut Vec<Attachment>,
) -> Result<()> {
let mut filename: Option<String> = None;
let mut mime: Option<String> = None;
let mut description: Option<String> = None;
let mut uid: u64 = 0;
let mut data_offset: u64 = 0;
let mut data_size: u64 = 0;
let mut has_data = false;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::FILE_NAME => filename = Some(read_string(r, e.size as usize)?),
ids::FILE_MIME_TYPE => mime = Some(read_string(r, e.size as usize)?),
ids::FILE_DESCRIPTION => description = Some(read_string(r, e.size as usize)?),
ids::FILE_UID => {
let v = read_uint(r, e.size as usize)?;
if v != 0 {
uid = v;
attachment_uid_to_index.insert(v, index);
}
}
ids::FILE_DATA => {
data_offset = r.stream_position()?;
data_size = e.size;
has_data = true;
skip(r, e.size)?;
}
_ => skip(r, e.size)?,
}
}
if let Some(ref n) = filename {
if !n.is_empty() {
metadata.push((format!("attachment:{index}:filename"), n.clone()));
}
}
if let Some(ref m) = mime {
if !m.is_empty() {
metadata.push((format!("attachment:{index}:mime_type"), m.clone()));
}
}
if has_data {
metadata.push((
format!("attachment:{index}:size_bytes"),
data_size.to_string(),
));
}
if let Some(ref d) = description {
if !d.is_empty() {
metadata.push((format!("attachment:{index}:description"), d.clone()));
}
}
attachments.push(Attachment {
index,
filename: filename.unwrap_or_default(),
mime_type: mime.unwrap_or_default(),
description: description.unwrap_or_default(),
uid,
data_offset,
data_size,
});
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Attachment {
pub index: u32,
pub filename: String,
pub mime_type: String,
pub description: String,
pub uid: u64,
pub data_offset: u64,
pub data_size: u64,
}
fn format_iso8601(unix_secs: i64) -> String {
let (y, m, d, hh, mm, ss) = civil_from_days_seconds(unix_secs);
format!("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", y, m, d, hh, mm, ss)
}
fn civil_from_days_seconds(unix_secs: i64) -> (i64, u32, u32, u32, u32, u32) {
let days = unix_secs.div_euclid(86_400);
let secs_of_day = unix_secs.rem_euclid(86_400) as u32;
let z = days + 719_468;
let era = z.div_euclid(146_097);
let doe = z.rem_euclid(146_097) as u64;
let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
let year = if m <= 2 { y + 1 } else { y };
let hh = secs_of_day / 3600;
let mm = (secs_of_day % 3600) / 60;
let ss = secs_of_day % 60;
(year, m, d, hh, mm, ss)
}
#[derive(Clone, Debug)]
struct CueEntry {
track: u64,
time: u64,
cluster_offset: u64,
relative_position: Option<u64>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CuePoint {
pub time: u64,
pub track_positions: Vec<CueTrackPositions>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CueTrackPositions {
pub track: u64,
pub cluster_position: Option<u64>,
pub relative_position: Option<u64>,
pub duration: Option<u64>,
pub block_number: Option<u64>,
pub codec_state: u64,
pub references: Vec<CueReference>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CueReference {
pub ref_time: u64,
pub ref_cluster: Option<u64>,
pub ref_number: Option<u64>,
pub ref_codec_state: Option<u64>,
}
fn parse_cues(
r: &mut dyn ReadSeek,
end: u64,
out: &mut Vec<CueEntry>,
typed: &mut Vec<CuePoint>,
) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CUE_POINT => {
let body_end = r.stream_position()?.saturating_add(e.size);
parse_cue_point(r, body_end, out, typed)?;
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_cue_point(
r: &mut dyn ReadSeek,
end: u64,
out: &mut Vec<CueEntry>,
typed: &mut Vec<CuePoint>,
) -> Result<()> {
let mut time: u64 = 0;
let mut point = CuePoint::default();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CUE_TIME => {
time = read_uint(r, e.size as usize)?;
point.time = time;
}
ids::CUE_TRACK_POSITIONS => {
let body_end = r.stream_position()?.saturating_add(e.size);
parse_cue_track_positions(r, body_end, time, out, &mut point.track_positions)?;
}
_ => skip(r, e.size)?,
}
}
typed.push(point);
Ok(())
}
fn parse_cue_track_positions(
r: &mut dyn ReadSeek,
end: u64,
time: u64,
out: &mut Vec<CueEntry>,
typed: &mut Vec<CueTrackPositions>,
) -> Result<()> {
let mut track: u64 = 0;
let mut cluster_offset: Option<u64> = None;
let mut relative_position: Option<u64> = None;
let mut tp = CueTrackPositions::default();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CUE_TRACK => {
track = read_uint(r, e.size as usize)?;
tp.track = track;
}
ids::CUE_CLUSTER_POSITION => {
let v = read_uint(r, e.size as usize)?;
cluster_offset = Some(v);
tp.cluster_position = Some(v);
}
ids::CUE_RELATIVE_POSITION => {
let v = read_uint(r, e.size as usize)?;
relative_position = Some(v);
tp.relative_position = Some(v);
}
ids::CUE_DURATION => tp.duration = Some(read_uint(r, e.size as usize)?),
ids::CUE_BLOCK_NUMBER => tp.block_number = Some(read_uint(r, e.size as usize)?),
ids::CUE_CODEC_STATE => tp.codec_state = read_uint(r, e.size as usize)?,
ids::CUE_REFERENCE => {
let body_end = r.stream_position()?.saturating_add(e.size);
let reference = parse_cue_reference(r, body_end)?;
tp.references.push(reference);
}
_ => skip(r, e.size)?,
}
}
if let Some(off) = cluster_offset {
out.push(CueEntry {
track,
time,
cluster_offset: off,
relative_position,
});
}
typed.push(tp);
Ok(())
}
fn parse_cue_reference(r: &mut dyn ReadSeek, end: u64) -> Result<CueReference> {
let mut reference = CueReference::default();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CUE_REF_TIME => reference.ref_time = read_uint(r, e.size as usize)?,
ids::CUE_REF_CLUSTER => reference.ref_cluster = Some(read_uint(r, e.size as usize)?),
ids::CUE_REF_NUMBER => reference.ref_number = Some(read_uint(r, e.size as usize)?),
ids::CUE_REF_CODEC_STATE => {
reference.ref_codec_state = Some(read_uint(r, e.size as usize)?)
}
_ => skip(r, e.size)?,
}
}
Ok(reference)
}
fn scan_cues_from(
r: &mut dyn ReadSeek,
start: u64,
end: u64,
out: &mut Vec<CueEntry>,
typed: &mut Vec<CuePoint>,
crc_status: &mut Vec<CrcStatus>,
) -> Result<()> {
r.seek(SeekFrom::Start(start))?;
while r.stream_position()? < end {
let pos = r.stream_position()?;
let e = read_element_header(r)?;
if e.id == ids::CUES {
let body_start = r.stream_position()?;
let body_end = if e.size == VINT_UNKNOWN_SIZE {
end
} else {
body_start.saturating_add(e.size)
};
if body_end > end {
r.seek(SeekFrom::Start(pos))?;
return Ok(());
}
if e.size != VINT_UNKNOWN_SIZE {
if let Some(s) = validate_top_level_crc(r, ids::CUES, body_start, body_end)? {
crc_status.push(s);
}
}
parse_cues(r, body_end, out, typed)?;
return Ok(());
}
if e.size == VINT_UNKNOWN_SIZE {
if e.id == ids::CLUSTER {
if !walk_unknown_cluster(r, end)? {
return Ok(());
}
continue;
}
r.seek(SeekFrom::Start(pos))?;
return Ok(());
}
let body_start = r.stream_position()?;
let body_end = body_start.saturating_add(e.size);
if body_end > end {
r.seek(SeekFrom::Start(pos))?;
return Ok(());
}
r.seek(SeekFrom::Start(body_end))?;
}
Ok(())
}
fn walk_unknown_cluster(r: &mut dyn ReadSeek, end: u64) -> Result<bool> {
while r.stream_position()? < end {
let pos = r.stream_position()?;
let e = match read_element_header(r) {
Ok(v) => v,
Err(_) => return Ok(false),
};
let is_cluster_child = matches!(
e.id,
ids::TIMECODE
| ids::SIMPLE_BLOCK
| ids::BLOCK_GROUP
| ids::BLOCK
| ids::BLOCK_DURATION
| ids::REFERENCE_BLOCK
| ids::VOID
| ids::CRC32
);
if !is_cluster_child {
r.seek(SeekFrom::Start(pos))?;
return Ok(true);
}
if e.size == VINT_UNKNOWN_SIZE {
return Ok(false);
}
let body_end = r.stream_position()?.saturating_add(e.size);
if body_end > end {
return Ok(false);
}
r.seek(SeekFrom::Start(body_end))?;
}
Ok(false)
}
fn parse_tracks(r: &mut dyn ReadSeek, end: u64, out: &mut Vec<TrackEntry>) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TRACK_ENTRY => {
let body_end = r.stream_position()?.saturating_add(e.size);
let mut t = TrackEntry::default();
parse_track_entry(r, body_end, &mut t)?;
out.push(t);
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_track_entry(r: &mut dyn ReadSeek, end: u64, t: &mut TrackEntry) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TRACK_NUMBER => t.number = read_uint(r, e.size as usize)?,
ids::TRACK_UID => t.uid = read_uint(r, e.size as usize)?,
ids::TRACK_TYPE => t.track_type = read_uint(r, e.size as usize)?,
ids::CODEC_ID => t.codec_id_string = read_string(r, e.size as usize)?,
ids::CODEC_PRIVATE => t.codec_private = read_bytes(r, e.size as usize)?,
ids::LANGUAGE => t.language = Some(read_string(r, e.size as usize)?),
ids::AUDIO => {
let body_end = r.stream_position()?.saturating_add(e.size);
parse_audio(r, body_end, t)?;
}
ids::VIDEO => {
let body_end = r.stream_position()?.saturating_add(e.size);
parse_video(r, body_end, t)?;
}
ids::TRACK_OPERATION => {
let body_end = r.stream_position()?.saturating_add(e.size);
let mut op = RawTrackOperation::default();
parse_track_operation(r, body_end, &mut op)?;
t.track_operation = Some(op);
}
ids::CONTENT_ENCODINGS => {
let body_end = r.stream_position()?.saturating_add(e.size);
t.content_encodings = Some(parse_content_encodings(r, body_end)?);
}
ids::BLOCK_ADDITION_MAPPING => {
let body_end = r.stream_position()?.saturating_add(e.size);
let mapping = parse_block_addition_mapping(r, body_end)?;
t.block_addition_mappings.push(mapping);
}
ids::MAX_BLOCK_ADDITION_ID => t.max_block_addition_id = read_uint(r, e.size as usize)?,
ids::DEFAULT_DURATION => {
let v = read_uint(r, e.size as usize)?;
if v != 0 {
t.timing_raw.default_duration = Some(v);
}
}
ids::DEFAULT_DECODED_FIELD_DURATION => {
let v = read_uint(r, e.size as usize)?;
if v != 0 {
t.timing_raw.default_decoded_field_duration = Some(v);
}
}
ids::TRACK_TIMESTAMP_SCALE => {
let v = read_float(r, e.size as usize)?;
if v.is_finite() && v > 0.0 {
t.timing_raw.track_timestamp_scale = Some(v);
}
}
ids::CODEC_DELAY => {
t.codec_timing_raw.0 = Some(read_uint(r, e.size as usize)?);
}
ids::SEEK_PRE_ROLL => {
t.codec_timing_raw.1 = Some(read_uint(r, e.size as usize)?);
}
ids::FLAG_FORCED => t.audience_flags_raw.forced = Some(read_uint(r, e.size as usize)?),
ids::FLAG_HEARING_IMPAIRED => {
t.audience_flags_raw.hearing_impaired = Some(read_uint(r, e.size as usize)?)
}
ids::FLAG_VISUAL_IMPAIRED => {
t.audience_flags_raw.visual_impaired = Some(read_uint(r, e.size as usize)?)
}
ids::FLAG_TEXT_DESCRIPTIONS => {
t.audience_flags_raw.text_descriptions = Some(read_uint(r, e.size as usize)?)
}
ids::FLAG_ORIGINAL => {
t.audience_flags_raw.original = Some(read_uint(r, e.size as usize)?)
}
ids::FLAG_COMMENTARY => {
t.audience_flags_raw.commentary = Some(read_uint(r, e.size as usize)?)
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_content_encodings(r: &mut dyn ReadSeek, end: u64) -> Result<ContentEncodings> {
let mut encodings: Vec<ContentEncoding> = Vec::new();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CONTENT_ENCODING => {
let body_end = r.stream_position()?.saturating_add(e.size);
encodings.push(parse_content_encoding(r, body_end)?);
}
_ => skip(r, e.size)?,
}
}
encodings.sort_by_key(|e| std::cmp::Reverse(e.order));
Ok(ContentEncodings { encodings })
}
fn parse_content_encoding(r: &mut dyn ReadSeek, end: u64) -> Result<ContentEncoding> {
let mut order: u64 = 0; let mut scope: u64 = ids::CONTENT_ENCODING_SCOPE_BLOCK; let mut enc_type: u64 = ids::CONTENT_ENCODING_TYPE_COMPRESSION; let mut comp: Option<(u64, Vec<u8>)> = None;
let mut encr: Option<(u64, Vec<u8>, Option<u64>)> = None;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CONTENT_ENCODING_ORDER => order = read_uint(r, e.size as usize)?,
ids::CONTENT_ENCODING_SCOPE => scope = read_uint(r, e.size as usize)?,
ids::CONTENT_ENCODING_TYPE => enc_type = read_uint(r, e.size as usize)?,
ids::CONTENT_COMPRESSION => {
let body_end = r.stream_position()?.saturating_add(e.size);
comp = Some(parse_content_compression(r, body_end)?);
}
ids::CONTENT_ENCRYPTION => {
let body_end = r.stream_position()?.saturating_add(e.size);
encr = Some(parse_content_encryption(r, body_end)?);
}
_ => skip(r, e.size)?,
}
}
let transform = if enc_type == ids::CONTENT_ENCODING_TYPE_ENCRYPTION {
let (algo, key_id, mode) = encr.unwrap_or((ids::CONTENT_ENC_ALGO_NONE, Vec::new(), None));
ContentEncodingTransform::Encryption {
algo: ContentEncAlgo::from_raw(algo),
key_id,
aes_cipher_mode: mode.map(AesCipherMode::from_raw),
}
} else {
let (algo, settings) = comp.unwrap_or((ids::CONTENT_COMP_ALGO_ZLIB, Vec::new()));
ContentEncodingTransform::Compression {
algo: ContentCompAlgo::from_raw(algo),
settings,
}
};
Ok(ContentEncoding {
order,
scope: ContentEncodingScope(scope),
transform,
})
}
fn parse_content_compression(r: &mut dyn ReadSeek, end: u64) -> Result<(u64, Vec<u8>)> {
let mut algo: u64 = ids::CONTENT_COMP_ALGO_ZLIB; let mut settings: Vec<u8> = Vec::new();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CONTENT_COMP_ALGO => algo = read_uint(r, e.size as usize)?,
ids::CONTENT_COMP_SETTINGS => settings = read_bytes(r, e.size as usize)?,
_ => skip(r, e.size)?,
}
}
Ok((algo, settings))
}
fn parse_content_encryption(r: &mut dyn ReadSeek, end: u64) -> Result<(u64, Vec<u8>, Option<u64>)> {
let mut algo: u64 = ids::CONTENT_ENC_ALGO_NONE; let mut key_id: Vec<u8> = Vec::new();
let mut cipher_mode: Option<u64> = None;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::CONTENT_ENC_ALGO => algo = read_uint(r, e.size as usize)?,
ids::CONTENT_ENC_KEY_ID => key_id = read_bytes(r, e.size as usize)?,
ids::CONTENT_ENC_AES_SETTINGS => {
let body_end = r.stream_position()?.saturating_add(e.size);
cipher_mode = parse_aes_settings(r, body_end)?;
}
_ => skip(r, e.size)?,
}
}
Ok((algo, key_id, cipher_mode))
}
fn parse_aes_settings(r: &mut dyn ReadSeek, end: u64) -> Result<Option<u64>> {
let mut mode: Option<u64> = None;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::AES_SETTINGS_CIPHER_MODE => mode = Some(read_uint(r, e.size as usize)?),
_ => skip(r, e.size)?,
}
}
Ok(mode)
}
fn parse_track_operation(r: &mut dyn ReadSeek, end: u64, op: &mut RawTrackOperation) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TRACK_COMBINE_PLANES => {
let body_end = r.stream_position()?.saturating_add(e.size);
parse_combine_planes(r, body_end, op)?;
}
ids::TRACK_JOIN_BLOCKS => {
let body_end = r.stream_position()?.saturating_add(e.size);
parse_join_blocks(r, body_end, op)?;
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_combine_planes(r: &mut dyn ReadSeek, end: u64, op: &mut RawTrackOperation) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TRACK_PLANE => {
let body_end = r.stream_position()?.saturating_add(e.size);
let mut uid: Option<u64> = None;
let mut plane_type: u64 = ids::TRACK_PLANE_TYPE_LEFT_EYE;
while r.stream_position()? < body_end {
let ce = read_element_header(r)?;
match ce.id {
ids::TRACK_PLANE_UID => uid = Some(read_uint(r, ce.size as usize)?),
ids::TRACK_PLANE_TYPE => plane_type = read_uint(r, ce.size as usize)?,
_ => skip(r, ce.size)?,
}
}
if let Some(u) = uid {
if u != 0 {
op.planes.push((u, plane_type));
}
}
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_join_blocks(r: &mut dyn ReadSeek, end: u64, op: &mut RawTrackOperation) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::TRACK_JOIN_UID => {
let u = read_uint(r, e.size as usize)?;
if u != 0 {
op.join_uids.push(u);
}
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_block_addition_mapping(r: &mut dyn ReadSeek, end: u64) -> Result<BlockAdditionMapping> {
let mut value: Option<u64> = None;
let mut name: Option<String> = None;
let mut addid_type: u64 = 0;
let mut extra_data: Option<Vec<u8>> = None;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::BLOCK_ADD_ID_VALUE => value = Some(read_uint(r, e.size as usize)?),
ids::BLOCK_ADD_ID_NAME => name = Some(read_string(r, e.size as usize)?),
ids::BLOCK_ADD_ID_TYPE => addid_type = read_uint(r, e.size as usize)?,
ids::BLOCK_ADD_ID_EXTRA_DATA => extra_data = Some(read_bytes(r, e.size as usize)?),
_ => skip(r, e.size)?,
}
}
Ok(BlockAdditionMapping {
value,
name,
addid_type,
extra_data,
})
}
fn parse_block_additions(r: &mut dyn ReadSeek, end: u64) -> Result<Vec<BlockAddition>> {
let mut out: Vec<BlockAddition> = Vec::new();
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::BLOCK_MORE => {
let bm_end = r.stream_position()?.saturating_add(e.size);
let mut id: u64 = 1;
let mut data: Option<Vec<u8>> = None;
while r.stream_position()? < bm_end {
let c = read_element_header(r)?;
match c.id {
ids::BLOCK_ADD_ID => id = read_uint(r, c.size as usize)?,
ids::BLOCK_ADDITIONAL => data = Some(read_bytes(r, c.size as usize)?),
_ => skip(r, c.size)?,
}
}
if let Some(data) = data {
if id != 0 && !out.iter().any(|a| a.id == id) {
out.push(BlockAddition { id, data });
}
}
}
_ => skip(r, e.size)?,
}
}
Ok(out)
}
fn parse_audio(r: &mut dyn ReadSeek, end: u64, t: &mut TrackEntry) -> Result<()> {
let raw = t.audio_raw.get_or_insert_with(RawTrackAudio::default);
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::SAMPLING_FREQUENCY => {
let v = read_float(r, e.size as usize)?;
raw.sampling_frequency = Some(v);
t.sample_rate = v;
}
ids::OUTPUT_SAMPLING_FREQUENCY => {
raw.output_sampling_frequency = Some(read_float(r, e.size as usize)?);
}
ids::CHANNELS => {
let v = read_uint(r, e.size as usize)?;
raw.channels = Some(v);
t.channels = v;
}
ids::BIT_DEPTH => {
let v = read_uint(r, e.size as usize)?;
raw.bit_depth = Some(v);
t.bit_depth = v;
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_video(r: &mut dyn ReadSeek, end: u64, t: &mut TrackEntry) -> Result<()> {
let mut flag_interlaced: u64 = ids::FLAG_INTERLACED_UNDETERMINED;
let mut field_order: u64 = ids::FIELD_ORDER_UNDETERMINED;
let mut crop_top: u64 = 0;
let mut crop_bottom: u64 = 0;
let mut crop_left: u64 = 0;
let mut crop_right: u64 = 0;
let mut display_width: u64 = 0;
let mut display_height: u64 = 0;
let mut display_unit: u64 = ids::DISPLAY_UNIT_PIXELS;
let mut stereo_mode: u64 = ids::STEREO_MODE_MONO;
let mut alpha_mode: u64 = ids::ALPHA_MODE_NONE;
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::PIXEL_WIDTH => t.width = read_uint(r, e.size as usize)?,
ids::PIXEL_HEIGHT => t.height = read_uint(r, e.size as usize)?,
ids::FLAG_INTERLACED => flag_interlaced = read_uint(r, e.size as usize)?,
ids::FIELD_ORDER => field_order = read_uint(r, e.size as usize)?,
ids::STEREO_MODE => stereo_mode = read_uint(r, e.size as usize)?,
ids::ALPHA_MODE => alpha_mode = read_uint(r, e.size as usize)?,
ids::ASPECT_RATIO_TYPE => {
t.aspect_ratio_type_raw = Some(read_uint(r, e.size as usize)?)
}
ids::UNCOMPRESSED_FOURCC => {
t.uncompressed_fourcc_raw = Some(read_bytes(r, e.size as usize)?)
}
ids::PIXEL_CROP_TOP => crop_top = read_uint(r, e.size as usize)?,
ids::PIXEL_CROP_BOTTOM => crop_bottom = read_uint(r, e.size as usize)?,
ids::PIXEL_CROP_LEFT => crop_left = read_uint(r, e.size as usize)?,
ids::PIXEL_CROP_RIGHT => crop_right = read_uint(r, e.size as usize)?,
ids::DISPLAY_WIDTH => display_width = read_uint(r, e.size as usize)?,
ids::DISPLAY_HEIGHT => display_height = read_uint(r, e.size as usize)?,
ids::DISPLAY_UNIT => display_unit = read_uint(r, e.size as usize)?,
ids::COLOUR => {
let body_end = r.stream_position()? + e.size;
let mut c = RawColour {
matrix_coefficients: 2,
transfer_characteristics: 2,
primaries: 2,
chroma_siting_horz: ids::CHROMA_SITING_UNSPECIFIED,
chroma_siting_vert: ids::CHROMA_SITING_UNSPECIFIED,
range: ids::COLOUR_RANGE_UNSPECIFIED,
bits_per_channel: 0,
..Default::default()
};
parse_colour(r, body_end, &mut c)?;
t.colour_raw = Some(c);
}
ids::PROJECTION => {
let body_end = r.stream_position()? + e.size;
let mut p = RawProjection {
projection_type_raw: ids::PROJECTION_TYPE_RECTANGULAR,
..Default::default()
};
parse_projection(r, body_end, &mut p)?;
t.projection_raw = Some(p);
}
_ => skip(r, e.size)?,
}
}
t.interlacing_raw = Some((flag_interlaced, field_order));
t.geometry_raw = Some((
crop_top,
crop_bottom,
crop_left,
crop_right,
display_width,
display_height,
display_unit,
));
t.stereo_mode_raw = Some(stereo_mode);
t.alpha_mode_raw = Some(alpha_mode);
Ok(())
}
fn parse_colour(r: &mut dyn ReadSeek, end: u64, c: &mut RawColour) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::MATRIX_COEFFICIENTS => c.matrix_coefficients = read_uint(r, e.size as usize)?,
ids::BITS_PER_CHANNEL => c.bits_per_channel = read_uint(r, e.size as usize)?,
ids::CHROMA_SUBSAMPLING_HORZ => {
c.chroma_subsampling_horz = Some(read_uint(r, e.size as usize)?)
}
ids::CHROMA_SUBSAMPLING_VERT => {
c.chroma_subsampling_vert = Some(read_uint(r, e.size as usize)?)
}
ids::CB_SUBSAMPLING_HORZ => {
c.cb_subsampling_horz = Some(read_uint(r, e.size as usize)?)
}
ids::CB_SUBSAMPLING_VERT => {
c.cb_subsampling_vert = Some(read_uint(r, e.size as usize)?)
}
ids::CHROMA_SITING_HORZ => c.chroma_siting_horz = read_uint(r, e.size as usize)?,
ids::CHROMA_SITING_VERT => c.chroma_siting_vert = read_uint(r, e.size as usize)?,
ids::COLOUR_RANGE => c.range = read_uint(r, e.size as usize)?,
ids::TRANSFER_CHARACTERISTICS => {
c.transfer_characteristics = read_uint(r, e.size as usize)?
}
ids::PRIMARIES => c.primaries = read_uint(r, e.size as usize)?,
ids::MAX_CLL => c.max_cll = Some(read_uint(r, e.size as usize)?),
ids::MAX_FALL => c.max_fall = Some(read_uint(r, e.size as usize)?),
ids::MASTERING_METADATA => {
let body_end = r.stream_position()? + e.size;
let mut m = MasteringMetadata::default();
parse_mastering_metadata(r, body_end, &mut m)?;
c.mastering_metadata = Some(m);
}
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_mastering_metadata(
r: &mut dyn ReadSeek,
end: u64,
m: &mut MasteringMetadata,
) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::PRIMARY_R_CHROMATICITY_X => {
m.primary_r_chromaticity_x = Some(read_float(r, e.size as usize)?)
}
ids::PRIMARY_R_CHROMATICITY_Y => {
m.primary_r_chromaticity_y = Some(read_float(r, e.size as usize)?)
}
ids::PRIMARY_G_CHROMATICITY_X => {
m.primary_g_chromaticity_x = Some(read_float(r, e.size as usize)?)
}
ids::PRIMARY_G_CHROMATICITY_Y => {
m.primary_g_chromaticity_y = Some(read_float(r, e.size as usize)?)
}
ids::PRIMARY_B_CHROMATICITY_X => {
m.primary_b_chromaticity_x = Some(read_float(r, e.size as usize)?)
}
ids::PRIMARY_B_CHROMATICITY_Y => {
m.primary_b_chromaticity_y = Some(read_float(r, e.size as usize)?)
}
ids::WHITE_POINT_CHROMATICITY_X => {
m.white_point_chromaticity_x = Some(read_float(r, e.size as usize)?)
}
ids::WHITE_POINT_CHROMATICITY_Y => {
m.white_point_chromaticity_y = Some(read_float(r, e.size as usize)?)
}
ids::LUMINANCE_MAX => m.luminance_max = Some(read_float(r, e.size as usize)?),
ids::LUMINANCE_MIN => m.luminance_min = Some(read_float(r, e.size as usize)?),
_ => skip(r, e.size)?,
}
}
Ok(())
}
fn parse_projection(r: &mut dyn ReadSeek, end: u64, p: &mut RawProjection) -> Result<()> {
while r.stream_position()? < end {
let e = read_element_header(r)?;
match e.id {
ids::PROJECTION_TYPE => p.projection_type_raw = read_uint(r, e.size as usize)?,
ids::PROJECTION_PRIVATE => p.private = Some(read_bytes(r, e.size as usize)?),
ids::PROJECTION_POSE_YAW => p.pose_yaw = read_float(r, e.size as usize)?,
ids::PROJECTION_POSE_PITCH => p.pose_pitch = read_float(r, e.size as usize)?,
ids::PROJECTION_POSE_ROLL => p.pose_roll = read_float(r, e.size as usize)?,
_ => skip(r, e.size)?,
}
}
Ok(())
}
enum ClusterState {
Idle,
InCluster {
body_start: u64,
body_end: u64,
cluster_timecode: i64,
},
}
pub struct MkvDemuxer {
input: Box<dyn ReadSeek>,
streams: Vec<StreamInfo>,
track_index_by_number: std::collections::HashMap<u64, u32>,
track_number_by_index: Vec<u64>,
segment_data_start: u64,
segment_data_end: u64,
cluster_state: ClusterState,
out_queue: std::collections::VecDeque<(Packet, Option<std::sync::Arc<Vec<BlockAddition>>>)>,
time_base: TimeBase,
metadata: Vec<(String, String)>,
duration_micros: i64,
cues: Vec<CueEntry>,
cue_points: Vec<CuePoint>,
timecode_scale_ns: u64,
tags: Vec<Tag>,
editions: Vec<Edition>,
attachments: Vec<Attachment>,
crc_status: Vec<CrcStatus>,
validated_cluster_starts: std::collections::HashSet<u64>,
track_operations: Vec<Option<TrackOperation>>,
content_encodings: Vec<Option<ContentEncodings>>,
header_strip_prefixes: Vec<Vec<u8>>,
video_interlacings: Vec<Option<VideoInterlacing>>,
video_geometries: Vec<Option<VideoGeometry>>,
video_colours: Vec<Option<VideoColour>>,
video_stereo_modes: Vec<Option<StereoMode>>,
video_projections: Vec<Option<Projection>>,
video_alpha_modes: Vec<Option<AlphaMode>>,
video_aspect_ratio_types: Vec<Option<u64>>,
video_uncompressed_fourccs: Vec<Option<UncompressedFourCC>>,
block_addition_mappings: Vec<Vec<BlockAdditionMapping>>,
max_block_addition_ids: Vec<u64>,
last_block_additions: Option<std::sync::Arc<Vec<BlockAddition>>>,
track_audience_flags: Vec<TrackAudienceFlags>,
track_audio: Vec<Option<TrackAudio>>,
track_timing: Vec<TrackTiming>,
track_codec_timing: Vec<TrackCodecTiming>,
cluster_records: Vec<ClusterRecord>,
cluster_record_by_offset: std::collections::HashMap<u64, usize>,
}
impl Demuxer for MkvDemuxer {
fn format_name(&self) -> &str {
"matroska"
}
fn streams(&self) -> &[StreamInfo] {
&self.streams
}
fn next_packet(&mut self) -> Result<Packet> {
loop {
if let Some((p, additions)) = self.out_queue.pop_front() {
self.last_block_additions = additions;
return Ok(p);
}
self.advance()?;
}
}
fn metadata(&self) -> &[(String, String)] {
&self.metadata
}
fn duration_micros(&self) -> Option<i64> {
if self.duration_micros > 0 {
Some(self.duration_micros)
} else {
None
}
}
fn seek_to(&mut self, stream_index: u32, pts: i64) -> Result<i64> {
if stream_index as usize >= self.streams.len() {
return Err(Error::invalid(format!(
"MKV: stream index {stream_index} out of range"
)));
}
if self.cues.is_empty() {
return Err(Error::unsupported(
"MKV: no Cues index in file — cannot seek",
));
}
let track_number = self.track_number_by_index[stream_index as usize];
let stream_tb = self.streams[stream_index as usize].time_base.as_rational();
let target_ticks_i128: i128 = if stream_tb.num == 0 || stream_tb.den == 0 {
pts as i128
} else {
let numer = pts as i128 * stream_tb.num as i128 * 1_000_000_000i128;
let denom = stream_tb.den as i128 * self.timecode_scale_ns as i128;
if denom == 0 {
pts as i128
} else {
numer / denom
}
};
let target_ticks: u64 = target_ticks_i128.max(0) as u64;
let mut best: Option<&CueEntry> = None;
for c in self.cues.iter().filter(|c| c.track == track_number) {
if c.time <= target_ticks {
best = Some(c);
} else {
break;
}
}
if best.is_none() {
best = self.cues.iter().find(|c| c.track == track_number);
}
let cue = best.ok_or_else(|| {
Error::unsupported(format!(
"MKV: no Cues entries for track {track_number} (stream {stream_index})"
))
})?;
let cue_time = cue.time;
let cue_cluster_offset = cue.cluster_offset;
let relative_position = cue.relative_position;
let abs = self.segment_data_start + cue_cluster_offset;
self.input.seek(SeekFrom::Start(abs))?;
self.cluster_state = ClusterState::Idle;
self.out_queue.clear();
self.last_block_additions = None;
if let Some(rel) = relative_position {
self.apply_cue_relative_position(rel)?;
}
let landed_pts: i64 = if stream_tb.num == 0 || stream_tb.den == 0 {
cue_time as i64
} else {
let numer = cue_time as i128 * stream_tb.den as i128 * self.timecode_scale_ns as i128;
let denom = stream_tb.num as i128 * 1_000_000_000i128;
if denom == 0 {
cue_time as i64
} else {
(numer / denom) as i64
}
};
Ok(landed_pts)
}
}
impl MkvDemuxer {
pub fn tags(&self) -> &[Tag] {
&self.tags
}
pub fn crc_status(&self) -> &[CrcStatus] {
&self.crc_status
}
pub fn cluster_records(&self) -> &[ClusterRecord] {
&self.cluster_records
}
pub fn cue_points(&self) -> &[CuePoint] {
&self.cue_points
}
fn register_cluster_record(&mut self, body_start: u64) {
if self.cluster_record_by_offset.contains_key(&body_start) {
return;
}
let idx = self.cluster_records.len();
self.cluster_records.push(ClusterRecord {
body_offset: body_start,
position: None,
prev_size: None,
});
self.cluster_record_by_offset.insert(body_start, idx);
}
fn set_cluster_position(&mut self, body_start: u64, v: u64) {
if let Some(&idx) = self.cluster_record_by_offset.get(&body_start) {
self.cluster_records[idx].position = Some(v);
}
}
fn set_cluster_prev_size(&mut self, body_start: u64, v: u64) {
if let Some(&idx) = self.cluster_record_by_offset.get(&body_start) {
self.cluster_records[idx].prev_size = Some(v);
}
}
pub fn chapters(&self) -> &[Edition] {
&self.editions
}
pub fn attachments(&self) -> &[Attachment] {
&self.attachments
}
pub fn attachment_data(&mut self, index: u32) -> Result<Vec<u8>> {
if index == 0 {
return Err(Error::invalid(
"MKV: attachment index must be 1-based (got 0)",
));
}
let att = self
.attachments
.iter()
.find(|a| a.index == index)
.ok_or_else(|| Error::invalid(format!("MKV: no attachment with index {index}")))?;
let offset = att.data_offset;
let size = att.data_size;
let saved_pos = self.input.stream_position()?;
self.input.seek(SeekFrom::Start(offset))?;
let mut out = Vec::new();
let n = (&mut *self.input).take(size).read_to_end(&mut out)?;
self.input.seek(SeekFrom::Start(saved_pos))?;
if (n as u64) != size {
return Err(Error::invalid(format!(
"MKV: attachment {index} payload truncated (got {n} of {size} bytes)"
)));
}
Ok(out)
}
pub fn track_operation(&self, stream_index: u32) -> Option<&TrackOperation> {
self.track_operations
.get(stream_index as usize)
.and_then(|o| o.as_ref())
}
pub fn track_operations(&self) -> &[Option<TrackOperation>] {
&self.track_operations
}
pub fn block_addition_mappings(&self, stream_index: u32) -> &[BlockAdditionMapping] {
self.block_addition_mappings
.get(stream_index as usize)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn all_block_addition_mappings(&self) -> &[Vec<BlockAdditionMapping>] {
&self.block_addition_mappings
}
pub fn max_block_addition_id(&self, stream_index: u32) -> Option<u64> {
self.max_block_addition_ids
.get(stream_index as usize)
.copied()
}
pub fn block_additions(&self) -> &[BlockAddition] {
self.last_block_additions
.as_deref()
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn track_audience_flags(&self, stream_index: u32) -> Option<&TrackAudienceFlags> {
self.track_audience_flags.get(stream_index as usize)
}
pub fn all_track_audience_flags(&self) -> &[TrackAudienceFlags] {
&self.track_audience_flags
}
pub fn track_audio(&self, stream_index: u32) -> Option<&TrackAudio> {
self.track_audio
.get(stream_index as usize)
.and_then(|o| o.as_ref())
}
pub fn all_track_audio(&self) -> &[Option<TrackAudio>] {
&self.track_audio
}
pub fn track_timing(&self, stream_index: u32) -> Option<&TrackTiming> {
self.track_timing.get(stream_index as usize)
}
pub fn all_track_timing(&self) -> &[TrackTiming] {
&self.track_timing
}
pub fn track_codec_timing(&self, stream_index: u32) -> Option<&TrackCodecTiming> {
self.track_codec_timing.get(stream_index as usize)
}
pub fn all_track_codec_timing(&self) -> &[TrackCodecTiming] {
&self.track_codec_timing
}
pub fn content_encodings(&self, stream_index: u32) -> Option<&ContentEncodings> {
self.content_encodings
.get(stream_index as usize)
.and_then(|o| o.as_ref())
}
pub fn all_content_encodings(&self) -> &[Option<ContentEncodings>] {
&self.content_encodings
}
pub fn video_interlacing(&self, stream_index: u32) -> Option<&VideoInterlacing> {
self.video_interlacings
.get(stream_index as usize)
.and_then(|v| v.as_ref())
}
pub fn video_interlacings(&self) -> &[Option<VideoInterlacing>] {
&self.video_interlacings
}
pub fn video_geometry(&self, stream_index: u32) -> Option<&VideoGeometry> {
self.video_geometries
.get(stream_index as usize)
.and_then(|v| v.as_ref())
}
pub fn video_geometries(&self) -> &[Option<VideoGeometry>] {
&self.video_geometries
}
pub fn video_colour(&self, stream_index: u32) -> Option<&VideoColour> {
self.video_colours
.get(stream_index as usize)
.and_then(|v| v.as_ref())
}
pub fn video_colours(&self) -> &[Option<VideoColour>] {
&self.video_colours
}
pub fn video_stereo_mode(&self, stream_index: u32) -> Option<StereoMode> {
self.video_stereo_modes
.get(stream_index as usize)
.and_then(|v| *v)
}
pub fn video_stereo_modes(&self) -> &[Option<StereoMode>] {
&self.video_stereo_modes
}
pub fn video_projection(&self, stream_index: u32) -> Option<&Projection> {
self.video_projections
.get(stream_index as usize)
.and_then(|v| v.as_ref())
}
pub fn video_projections(&self) -> &[Option<Projection>] {
&self.video_projections
}
pub fn video_alpha_mode(&self, stream_index: u32) -> Option<AlphaMode> {
self.video_alpha_modes
.get(stream_index as usize)
.and_then(|v| *v)
}
pub fn video_alpha_modes(&self) -> &[Option<AlphaMode>] {
&self.video_alpha_modes
}
pub fn video_aspect_ratio_type(&self, stream_index: u32) -> Option<u64> {
self.video_aspect_ratio_types
.get(stream_index as usize)
.and_then(|v| *v)
}
pub fn video_aspect_ratio_types(&self) -> &[Option<u64>] {
&self.video_aspect_ratio_types
}
pub fn video_uncompressed_fourcc(&self, stream_index: u32) -> Option<&UncompressedFourCC> {
self.video_uncompressed_fourccs
.get(stream_index as usize)
.and_then(|v| v.as_ref())
}
pub fn video_uncompressed_fourccs(&self) -> &[Option<UncompressedFourCC>] {
&self.video_uncompressed_fourccs
}
fn apply_cue_relative_position(&mut self, relative_position: u64) -> Result<()> {
let cluster_head_pos = self.input.stream_position()?;
let e = read_element_header(&mut *self.input)?;
if e.id != ids::CLUSTER {
self.input.seek(SeekFrom::Start(cluster_head_pos))?;
return Ok(());
}
let body_start = self.input.stream_position()?;
let is_unknown_size = e.size == VINT_UNKNOWN_SIZE;
let body_end = if is_unknown_size {
self.segment_data_end
} else {
body_start.saturating_add(e.size)
};
let target = body_start.saturating_add(relative_position);
if target > body_end {
self.input.seek(SeekFrom::Start(cluster_head_pos))?;
return Ok(());
}
self.validate_cluster_crc(body_start, body_end, is_unknown_size)?;
self.input.seek(SeekFrom::Start(body_start))?;
let mut cluster_timecode: i64 = 0;
let mut pos = body_start;
while pos < target {
self.input.seek(SeekFrom::Start(pos))?;
let child = match read_element_header(&mut *self.input) {
Ok(c) => c,
Err(_) => {
self.input.seek(SeekFrom::Start(cluster_head_pos))?;
return Ok(());
}
};
let child_body_start = self.input.stream_position()?;
if child.id == ids::TIMECODE {
cluster_timecode = read_uint(&mut *self.input, child.size as usize)? as i64;
}
let next = child_body_start.saturating_add(child.size);
if next > body_end || next <= pos {
self.input.seek(SeekFrom::Start(cluster_head_pos))?;
return Ok(());
}
pos = next;
}
self.input.seek(SeekFrom::Start(target))?;
self.register_cluster_record(body_start);
self.cluster_state = ClusterState::InCluster {
body_start,
body_end,
cluster_timecode,
};
Ok(())
}
fn validate_cluster_crc(
&mut self,
body_start: u64,
body_end: u64,
is_unknown_size: bool,
) -> Result<()> {
if body_end <= body_start {
return Ok(());
}
if is_unknown_size {
return Ok(());
}
if self.validated_cluster_starts.contains(&body_start) {
return Ok(());
}
let status =
match validate_top_level_crc(&mut *self.input, ids::CLUSTER, body_start, body_end) {
Ok(s) => s,
Err(_) => {
self.input.seek(SeekFrom::Start(body_start))?;
return Ok(());
}
};
self.validated_cluster_starts.insert(body_start);
if let Some(s) = status {
self.crc_status.push(s);
}
Ok(())
}
fn advance(&mut self) -> Result<()> {
match self.cluster_state {
ClusterState::Idle => {
let pos = self.input.stream_position()?;
if pos >= self.segment_data_end {
return Err(Error::Eof);
}
let e = read_element_header(&mut *self.input)?;
match e.id {
ids::CLUSTER => {
let body_start = self.input.stream_position()?;
let is_unknown_size = e.size == VINT_UNKNOWN_SIZE;
let body_end = if is_unknown_size {
self.segment_data_end
} else {
body_start.saturating_add(e.size)
};
self.validate_cluster_crc(body_start, body_end, is_unknown_size)?;
self.register_cluster_record(body_start);
self.cluster_state = ClusterState::InCluster {
body_start,
body_end,
cluster_timecode: 0,
};
Ok(())
}
ids::CUES | ids::ATTACHMENTS | ids::CHAPTERS | ids::TAGS => {
skip(&mut *self.input, e.size)?;
Ok(())
}
_ => {
skip(&mut *self.input, e.size)?;
Ok(())
}
}
}
ClusterState::InCluster {
body_start,
body_end,
cluster_timecode,
} => {
let pos = self.input.stream_position()?;
if pos >= body_end {
self.cluster_state = ClusterState::Idle;
return Ok(());
}
let e = read_element_header(&mut *self.input)?;
match e.id {
ids::TIMECODE => {
let v = read_uint(&mut *self.input, e.size as usize)? as i64;
if let ClusterState::InCluster {
ref mut cluster_timecode,
..
} = self.cluster_state
{
*cluster_timecode = v;
}
}
ids::POSITION => {
let v = read_uint(&mut *self.input, e.size as usize)?;
self.set_cluster_position(body_start, v);
}
ids::PREV_SIZE => {
let v = read_uint(&mut *self.input, e.size as usize)?;
self.set_cluster_prev_size(body_start, v);
}
ids::SIMPLE_BLOCK => {
let bytes = read_bytes(&mut *self.input, e.size as usize)?;
self.queue_block_packets(&bytes, cluster_timecode, false)?;
}
ids::BLOCK_GROUP => {
let bg_end = self.input.stream_position()?.saturating_add(e.size);
self.parse_block_group(bg_end, cluster_timecode)?;
}
ids::CLUSTER
| ids::CUES
| ids::TAGS
| ids::ATTACHMENTS
| ids::CHAPTERS
| ids::SEEK_HEAD
| ids::INFO
| ids::TRACKS => {
self.input.seek(SeekFrom::Start(pos))?;
self.cluster_state = ClusterState::Idle;
}
_ => skip(&mut *self.input, e.size)?,
}
Ok(())
}
}
}
fn parse_block_group(&mut self, end: u64, cluster_timecode: i64) -> Result<()> {
let mut block_bytes: Option<Vec<u8>> = None;
let mut duration: Option<i64> = None;
let mut is_keyframe = true;
let mut additions: Option<std::sync::Arc<Vec<BlockAddition>>> = None;
while self.input.stream_position()? < end {
let e = read_element_header(&mut *self.input)?;
match e.id {
ids::BLOCK => {
block_bytes = Some(read_bytes(&mut *self.input, e.size as usize)?);
}
ids::BLOCK_DURATION => {
duration = Some(read_uint(&mut *self.input, e.size as usize)? as i64);
}
ids::REFERENCE_BLOCK => {
is_keyframe = false;
skip(&mut *self.input, e.size)?;
}
ids::BLOCK_ADDITIONS => {
let ba_end = self.input.stream_position()?.saturating_add(e.size);
let list = parse_block_additions(&mut *self.input, ba_end)?;
if !list.is_empty() {
additions = Some(std::sync::Arc::new(list));
}
}
_ => skip(&mut *self.input, e.size)?,
}
}
if let Some(b) = block_bytes {
self.queue_block_packets_with(&b, cluster_timecode, is_keyframe, duration, additions)?;
}
Ok(())
}
fn queue_block_packets(
&mut self,
bytes: &[u8],
cluster_timecode: i64,
_hint: bool,
) -> Result<()> {
self.queue_block_packets_with(bytes, cluster_timecode, true, None, None)
}
fn queue_block_packets_with(
&mut self,
bytes: &[u8],
cluster_timecode: i64,
default_keyframe: bool,
explicit_duration: Option<i64>,
additions: Option<std::sync::Arc<Vec<BlockAddition>>>,
) -> Result<()> {
let mut cur = std::io::Cursor::new(bytes);
let (track_number, _) = crate::ebml::read_vint(&mut cur, false)?;
let mut tc_buf = [0u8; 2];
cur.read_exact(&mut tc_buf)?;
let timecode_offset = i16::from_be_bytes(tc_buf) as i64;
let mut flags_buf = [0u8; 1];
cur.read_exact(&mut flags_buf)?;
let flags = flags_buf[0];
let lacing = (flags >> 1) & 0x03;
let keyframe_flag = flags & 0x80 != 0;
let stream_idx = match self.track_index_by_number.get(&track_number) {
Some(i) => *i,
None => return Ok(()), };
let body_start = cur.position() as usize;
let body = &bytes[body_start..];
let frames = match lacing {
0 => vec![body.to_vec()],
1 => parse_xiph_lacing(body)?,
2 => parse_fixed_lacing(body)?,
3 => parse_ebml_lacing(body)?,
_ => unreachable!(),
};
let pts_base = cluster_timecode + timecode_offset;
let n_frames = frames.len() as i64;
let per_frame = explicit_duration.map(|d| d / n_frames.max(1));
let strip_prefix = self
.header_strip_prefixes
.get(stream_idx as usize)
.map(Vec::as_slice)
.unwrap_or(&[]);
for (i, f) in frames.into_iter().enumerate() {
let pts = pts_base + per_frame.unwrap_or(0) * i as i64;
let frame_bytes = if strip_prefix.is_empty() {
f
} else {
let mut restored = Vec::with_capacity(strip_prefix.len() + f.len());
restored.extend_from_slice(strip_prefix);
restored.extend_from_slice(&f);
restored
};
let mut pkt = Packet::new(stream_idx, self.time_base, frame_bytes);
pkt.pts = Some(pts);
pkt.dts = Some(pts);
pkt.duration = per_frame;
pkt.flags.keyframe = keyframe_flag || default_keyframe;
self.out_queue.push_back((pkt, additions.clone()));
}
Ok(())
}
}
fn parse_xiph_lacing(body: &[u8]) -> Result<Vec<Vec<u8>>> {
if body.is_empty() {
return Ok(vec![]);
}
let n_frames = body[0] as usize + 1;
let mut sizes = Vec::with_capacity(n_frames);
let mut i = 1;
for _ in 0..n_frames - 1 {
let mut s = 0usize;
loop {
if i >= body.len() {
return Err(Error::invalid("MKV xiph lacing: truncated size"));
}
let b = body[i];
i += 1;
s += b as usize;
if b < 255 {
break;
}
}
sizes.push(s);
}
let used: usize = sizes.iter().sum();
let last_size = (body.len())
.checked_sub(i)
.and_then(|rem| rem.checked_sub(used))
.ok_or_else(|| Error::invalid("MKV xiph lacing: sizes exceed body"))?;
sizes.push(last_size);
let mut frames = Vec::with_capacity(n_frames);
for s in sizes {
if i + s > body.len() {
return Err(Error::invalid("MKV xiph lacing: frame exceeds body"));
}
frames.push(body[i..i + s].to_vec());
i += s;
}
Ok(frames)
}
fn parse_fixed_lacing(body: &[u8]) -> Result<Vec<Vec<u8>>> {
if body.is_empty() {
return Ok(vec![]);
}
let n_frames = body[0] as usize + 1;
let payload = &body[1..];
if payload.len() % n_frames != 0 {
return Err(Error::invalid("MKV fixed lacing: non-divisible payload"));
}
let frame_size = payload.len() / n_frames;
if frame_size == 0 {
return Ok(vec![Vec::new(); n_frames]);
}
let mut frames = Vec::with_capacity(n_frames);
for c in payload.chunks_exact(frame_size) {
frames.push(c.to_vec());
}
Ok(frames)
}
fn parse_ebml_lacing(body: &[u8]) -> Result<Vec<Vec<u8>>> {
if body.is_empty() {
return Ok(vec![]);
}
let mut cur = std::io::Cursor::new(body);
let n_frames = {
let mut buf = [0u8; 1];
cur.read_exact(&mut buf)?;
buf[0] as usize + 1
};
let mut sizes = Vec::with_capacity(n_frames);
let (first, _) = crate::ebml::read_vint(&mut cur, false)?;
sizes.push(first as i64);
let delta_count = n_frames.saturating_sub(2);
for _ in 0..delta_count {
let (raw, w) = crate::ebml::read_vint(&mut cur, false)?;
let bias = ((1i64) << (7 * w as i64 - 1)) - 1;
let signed = (raw as i64) - bias;
let prev = *sizes.last().unwrap();
let next = prev
.checked_add(signed)
.ok_or_else(|| Error::invalid("MKV ebml lacing: size addition overflow"))?;
sizes.push(next);
}
let pos = cur.position() as usize;
let used: i64 = sizes
.iter()
.try_fold(0i64, |acc, s| acc.checked_add(*s))
.ok_or_else(|| Error::invalid("MKV ebml lacing: sizes overflow"))?;
let last = (body.len() as i64)
.checked_sub(pos as i64)
.and_then(|rem| rem.checked_sub(used))
.ok_or_else(|| Error::invalid("MKV ebml lacing: sizes exceed body"))?;
sizes.push(last);
let mut frames = Vec::with_capacity(n_frames);
let mut i = pos;
for s in sizes {
if s < 0 {
return Err(Error::invalid("MKV ebml lacing: negative frame size"));
}
let s_usize = usize::try_from(s)
.map_err(|_| Error::invalid("MKV ebml lacing: frame size overflows usize"))?;
let end = i
.checked_add(s_usize)
.ok_or_else(|| Error::invalid("MKV ebml lacing: frame offset overflows"))?;
if end > body.len() {
return Err(Error::invalid("MKV ebml lacing: invalid frame size"));
}
frames.push(body[i..end].to_vec());
i = end;
}
Ok(frames)
}