use std::collections::VecDeque;
use crate::error::{Container, Error, Result};
use crate::mkv::mux::bytes::refuse;
use crate::mkv::mux::spec::{PacketIn, TrackSpec};
use crate::mkv::mux::timing::{Inexact, rescale};
#[derive(Debug)]
pub(crate) struct Block {
pub data: Vec<u8>,
pub ticks: i64,
pub key: i64,
pub duration: Option<u64>,
pub sync: bool,
pub discardable: bool,
pub discard_padding: Option<i64>,
}
#[derive(Debug)]
pub(crate) struct TrackState {
pub number: u32,
pub uid: u64,
pub spec: TrackSpec,
pub default_ticks: Option<u64>,
pub queue: VecDeque<Block>,
pub end: i64,
pub previous: Option<i64>,
scale: u64,
packets: u64,
key: i64,
last_known: bool,
}
impl TrackState {
pub fn new(number: u32, uid: u64, spec: TrackSpec, scale: u64) -> Self {
let default_ticks = spec
.matroska
.default_duration
.map(|nanoseconds| nanoseconds / scale.max(1));
Self {
number,
uid,
spec,
default_ticks,
queue: VecDeque::new(),
end: 0,
previous: None,
scale,
packets: 0,
key: 0,
last_known: true,
}
}
pub fn is_video(&self) -> bool {
self.spec.matroska.track_type == 1
}
fn invalid(&self, reason: String) -> Error {
Error::InvalidPacket {
container: Container::Matroska,
track_id: self.number,
index: self.packets,
reason,
}
}
fn scaled(&self, value: i128, what: &str) -> Result<i64> {
let timebase = self.spec.timebase;
match rescale(value, timebase, self.scale) {
Ok(ticks) => i64::try_from(ticks).map_err(|_| {
self.invalid(format!(
"the {what} {value} is {ticks} output ticks, outside 64 signed bits. Use a \
coarser `MkvOptions::timestamp_scale`"
))
}),
Err(Inexact::NoNanoseconds) => Err(refuse(format!(
"the {what} {value} of track {} at {}/{} seconds per tick: that time is no whole \
number of nanoseconds, and `TimestampScale` counts whole nanoseconds. To round \
a stream copy to the nearest tick, use `StreamCopy::plan_rounded`",
self.number, timebase.num, timebase.den
))),
Err(Inexact::NotMultiple { nanos, divisor }) => Err(self.invalid(format!(
"the {what} {value} is {nanos} ns, no whole multiple of the `TimestampScale` {}. \
Set `MkvOptions::timestamp_scale` to {divisor}, or to a value \
`StreamCopy::timestamp_scale` gives",
self.scale
))),
}
}
pub fn accept(&self, packet: &PacketIn<'_>) -> Result<Block> {
if packet.pts < 0 {
return Err(refuse(format!(
"packet {} of track {} at pts {}: a Matroska block timestamp is never negative, \
and an MP4 edit list holds a negative start",
self.packets, self.number, packet.pts
)));
}
let ticks = self.scaled(i128::from(packet.pts), "pts")?;
let duration = match packet.duration {
Some(duration) => Some(
self.scaled(i128::from(duration), "duration")?
.unsigned_abs(),
),
None => None,
};
Ok(Block {
data: packet.data.to_vec(),
ticks,
key: self.key.max(ticks),
duration,
sync: packet.sync,
discardable: packet.discardable,
discard_padding: packet.discard_padding,
})
}
pub fn push(&mut self, block: Block) -> usize {
let duration = block.duration.or(self.default_ticks);
let span = i64::try_from(duration.unwrap_or(0)).unwrap_or(i64::MAX);
self.end = self.end.max(block.ticks.saturating_add(span));
self.key = block.key;
self.last_known = duration.is_some();
self.packets += 1;
let bytes = block.data.len();
self.queue.push_back(block);
bytes
}
pub fn written_duration(&self, block: &Block, explicit: bool) -> Option<u64> {
if explicit {
block.duration.or(self.default_ticks)
} else {
block
.duration
.filter(|&duration| Some(duration) != self.default_ticks)
}
}
pub fn reference(&self, block: &Block) -> Option<i64> {
if block.sync {
return None;
}
let back = self
.previous
.map_or(-1, |previous| previous.saturating_sub(block.ticks));
Some(if back == 0 { -1 } else { back })
}
pub fn check_last(&self) -> Result<()> {
match self.packets.checked_sub(1) {
Some(index) if !self.last_known => Err(Error::MissingDuration {
track_id: self.number,
index,
}),
_ => Ok(()),
}
}
}
pub(crate) fn earliest(tracks: &[TrackState]) -> Option<usize> {
tracks
.iter()
.enumerate()
.filter_map(|(slot, track)| track.queue.front().map(|block| (block.key, slot)))
.min()
.map(|(_, slot)| slot)
}