use std::fmt;
use crate::codecs::{CodecParameters, audio, subtitle, video};
use crate::common::FourCc;
use crate::errors::Result;
use crate::io::MediaSourceStream;
use crate::meta::{ChapterGroup, Metadata, MetadataLog};
use crate::packet::Packet;
use crate::units::{Duration, Time, TimeBase, Timestamp};
use bitflags::bitflags;
pub mod prelude {
pub use crate::meta::{Chapter, ChapterGroup, ChapterGroupItem};
pub use crate::packet::{Packet, PacketBuilder};
pub use crate::units::{Duration, TimeBase, Timestamp};
pub use super::{
Attachment, FileAttachment, FormatId, FormatInfo, FormatOptions, FormatReader, MediaInfo,
SeekMode, SeekTo, SeekedTo, Track, VendorDataAttachment,
};
}
pub mod probe;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FormatId(u32);
impl FormatId {
pub const fn new(cc: FourCc) -> FormatId {
Self(0x8000_0000 | u32::from_be_bytes(cc.get()))
}
}
impl From<FourCc> for FormatId {
fn from(value: FourCc) -> Self {
FormatId::new(value)
}
}
impl fmt::Display for FormatId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#x}", self.0)
}
}
pub const FORMAT_ID_NULL: FormatId = FormatId(0x0);
#[derive(Copy, Clone)]
pub struct FormatInfo {
pub format: FormatId,
pub short_name: &'static str,
pub long_name: &'static str,
}
pub enum SeekTo {
Time {
time: Time,
track_id: Option<u32>,
},
Timestamp {
ts: Timestamp,
track_id: u32,
},
}
#[derive(Copy, Clone, Debug)]
pub struct SeekedTo {
pub track_id: u32,
pub required_ts: Timestamp,
pub actual_ts: Timestamp,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SeekMode {
Coarse,
Accurate,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct FormatOptions {
pub prebuild_seek_index: bool,
pub seek_index_fill_period_ms: u16,
pub external_data: ExternalFormatData,
}
#[derive(Clone, Debug, Default)]
pub struct ExternalFormatData {
pub metadata: Option<MetadataLog>,
pub chapters: Option<ChapterGroup>,
}
impl Default for FormatOptions {
fn default() -> Self {
FormatOptions {
prebuild_seek_index: false,
seek_index_fill_period_ms: 1000,
external_data: Default::default(),
}
}
}
impl FormatOptions {
pub fn prebuild_seek_index(mut self, prebuild: bool) -> Self {
self.prebuild_seek_index = prebuild;
self
}
pub fn seek_index_fill_period_ms(mut self, period: u16) -> Self {
self.seek_index_fill_period_ms = period;
self
}
}
bitflags! {
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct TrackFlags: u32 {
const DEFAULT = 1 << 0;
const FORCED = 1 << 1;
const ORIGINAL_LANGUAGE = 1 << 2;
const COMMENTARY = 1 << 3;
const HEARING_IMPAIRED = 1 << 4;
const VISUALLY_IMPAIRED = 1 << 5;
const TEXT_DESCRIPTIONS = 1 << 6;
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum TrackType {
Audio,
Video,
Subtitle,
}
#[derive(Clone, Debug)]
pub struct Track {
pub id: u32,
pub codec_params: Option<CodecParameters>,
pub language: Option<String>,
pub time_base: Option<TimeBase>,
pub num_frames: Option<u64>,
pub duration: Option<Duration>,
pub start_ts: Timestamp,
pub delay: Option<u32>,
pub padding: Option<u32>,
pub flags: TrackFlags,
}
impl Track {
pub fn new(id: u32) -> Self {
Track {
id,
codec_params: None,
language: None,
time_base: None,
num_frames: None,
duration: None,
start_ts: Timestamp::new(0),
delay: None,
padding: None,
flags: TrackFlags::empty(),
}
}
pub fn with_codec_params(&mut self, codec_params: CodecParameters) -> &mut Self {
if self.time_base.is_none() {
self.time_base = match &codec_params {
CodecParameters::Audio(params) => {
params.sample_rate.and_then(TimeBase::try_from_recip)
}
_ => None,
};
}
self.codec_params = Some(codec_params);
self
}
pub fn with_language(&mut self, language: &str) -> &mut Self {
self.language = Some(language.to_string());
self
}
pub fn with_time_base(&mut self, time_base: TimeBase) -> &mut Self {
self.time_base = Some(time_base);
self
}
pub fn with_num_frames(&mut self, num_frames: u64) -> &mut Self {
self.num_frames = Some(num_frames);
self
}
pub fn with_duration(&mut self, duration: Duration) -> &mut Self {
self.duration = Some(duration);
self
}
pub fn with_start_ts(&mut self, start_ts: Timestamp) -> &mut Self {
self.start_ts = start_ts;
self
}
pub fn with_delay(&mut self, delay: u32) -> &mut Self {
self.delay = Some(delay);
self
}
pub fn with_padding(&mut self, padding: u32) -> &mut Self {
self.padding = Some(padding);
self
}
pub fn with_flags(&mut self, flags: TrackFlags) -> &mut Self {
self.flags |= flags;
self
}
pub fn track_type(&self) -> Option<TrackType> {
match self.codec_params {
Some(CodecParameters::Audio(_)) => Some(TrackType::Audio),
Some(CodecParameters::Video(_)) => Some(TrackType::Video),
Some(CodecParameters::Subtitle(_)) => Some(TrackType::Subtitle),
None => None,
}
}
}
pub enum Attachment {
File(FileAttachment),
VendorData(VendorDataAttachment),
}
pub struct FileAttachment {
pub name: String,
pub description: Option<String>,
pub media_type: Option<String>,
pub data: Box<[u8]>,
}
#[derive(Clone, Debug)]
pub struct VendorDataAttachment {
pub ident: String,
pub data: Box<[u8]>,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Default)]
pub struct MediaInfo {
pub time_base: Option<TimeBase>,
pub duration: Option<Duration>,
pub start_ts: Timestamp,
}
impl MediaInfo {
pub fn from_track(track: &Track) -> Self {
MediaInfo { time_base: track.time_base, duration: track.duration, start_ts: track.start_ts }
}
pub fn from_tracks(tracks: &[Track]) -> Self {
match tracks {
[] => Default::default(),
[track] => Self::from_track(track),
tracks => {
let mut media_info: MediaInfo = Default::default();
if let Some(track) = tracks.iter().min_by_key(|t| t.start_ts) {
media_info.start_ts = track.start_ts;
}
if let Some(track) = tracks
.iter()
.filter_map(|t| {
let tb = t.time_base?;
let dur = t.duration?;
let dur_as_ts =
dur.timestamp_from(Timestamp::ZERO).unwrap_or(Timestamp::MAX);
let dur_time = tb.calc_time_saturating(dur_as_ts);
Some((dur_time, t))
})
.max_by_key(|(dur_time, _)| *dur_time)
.map(|(_, t)| t)
{
media_info.time_base = track.time_base;
media_info.duration = track.duration;
}
media_info
}
}
}
pub fn new() -> Self {
Default::default()
}
pub fn with_time_base(&mut self, time_base: TimeBase) -> &mut Self {
self.time_base = Some(time_base);
self
}
pub fn with_duration(&mut self, duration: Duration) -> &mut Self {
self.duration = Some(duration);
self
}
pub fn with_start_ts(&mut self, start_ts: Timestamp) -> &mut Self {
self.start_ts = start_ts;
self
}
}
pub trait FormatReader: Send + Sync {
fn format_info(&self) -> &FormatInfo;
fn media_info(&self) -> &MediaInfo;
fn attachments(&self) -> &[Attachment] {
&[]
}
fn chapters(&self) -> Option<&ChapterGroup> {
None
}
fn metadata(&mut self) -> Metadata<'_>;
fn seek(&mut self, mode: SeekMode, to: SeekTo) -> Result<SeekedTo>;
fn tracks(&self) -> &[Track];
fn first_track(&self, track_type: TrackType) -> Option<&Track> {
self.tracks().iter().find(|track| matches_track_type(track, track_type))
}
fn first_track_known_codec(&self, track_type: TrackType) -> Option<&Track> {
self.tracks().iter().find(|track| match &track.codec_params {
Some(CodecParameters::Audio(params)) if track_type == TrackType::Audio => {
params.codec != audio::CODEC_ID_NULL_AUDIO
}
Some(CodecParameters::Video(params)) if track_type == TrackType::Video => {
params.codec != video::CODEC_ID_NULL_VIDEO
}
Some(CodecParameters::Subtitle(params)) if track_type == TrackType::Subtitle => {
params.codec != subtitle::CODEC_ID_NULL_SUBTITLE
}
_ => false,
})
}
fn default_track(&self, track_type: TrackType) -> Option<&Track> {
self.tracks()
.iter()
.filter(|track| track.flags.contains(TrackFlags::DEFAULT))
.find(|track| matches_track_type(track, track_type))
.or_else(|| self.first_track_known_codec(track_type))
}
fn next_packet(&mut self) -> Result<Option<Packet>>;
fn into_inner<'s>(self: Box<Self>) -> MediaSourceStream<'s>
where
Self: 's;
}
fn matches_track_type(track: &Track, track_type: TrackType) -> bool {
match track.codec_params {
Some(CodecParameters::Audio(_)) if track_type == TrackType::Audio => true,
Some(CodecParameters::Video(_)) if track_type == TrackType::Video => true,
Some(CodecParameters::Subtitle(_)) if track_type == TrackType::Subtitle => true,
_ => false,
}
}
pub mod util {
use crate::units::Timestamp;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct SeekPoint {
pub frame_ts: Timestamp,
pub byte_offset: u64,
pub n_frames: u32,
}
impl SeekPoint {
fn new(frame_ts: Timestamp, byte_offset: u64, n_frames: u32) -> Self {
SeekPoint { frame_ts, byte_offset, n_frames }
}
}
#[derive(Default)]
pub struct SeekIndex {
points: Vec<SeekPoint>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SeekSearchResult {
Stream,
Upper(SeekPoint),
Lower(SeekPoint),
Range(SeekPoint, SeekPoint),
}
impl SeekIndex {
pub fn new() -> SeekIndex {
SeekIndex { points: Vec::new() }
}
pub fn insert(&mut self, ts: Timestamp, byte_offset: u64, n_frames: u32) {
let seek_point = SeekPoint::new(ts, byte_offset, n_frames);
let (last_ts, last_offset) =
self.points.last().map_or((Timestamp::MIN, 0), |p| (p.frame_ts, p.byte_offset));
if ts > last_ts && byte_offset >= last_offset {
self.points.push(seek_point)
}
else if ts < last_ts {
let i = self
.points
.partition_point(|p| ts > p.frame_ts && byte_offset >= p.byte_offset);
if i < self.points.len() || i == 0 {
self.points.insert(i, seek_point);
}
}
}
pub fn search(&self, frame_ts: Timestamp) -> SeekSearchResult {
if !self.points.is_empty() {
let mut lower = 0;
let mut upper = self.points.len() - 1;
if frame_ts < self.points[lower].frame_ts {
return SeekSearchResult::Upper(self.points[lower]);
}
else if frame_ts >= self.points[upper].frame_ts {
return SeekSearchResult::Lower(self.points[upper]);
}
while upper - lower > 1 {
let mid = (lower + upper) / 2;
let mid_ts = self.points[mid].frame_ts;
if frame_ts < mid_ts {
upper = mid;
}
else {
lower = mid;
}
}
return SeekSearchResult::Range(self.points[lower], self.points[upper]);
}
SeekSearchResult::Stream
}
}
#[cfg(test)]
mod tests {
use crate::units::Timestamp;
use super::{SeekIndex, SeekPoint, SeekSearchResult};
#[test]
fn verify_seek_index_search() {
let mut index = SeekIndex::new();
index.insert(Timestamp::new(479232), 706812, 1152);
index.insert(Timestamp::new(959616), 1421536, 1152);
index.insert(Timestamp::new(1919232), 2833241, 1152);
index.insert(Timestamp::new(2399616), 3546987, 1152);
index.insert(Timestamp::new(2880000), 4259455, 1152);
assert_eq!(
index.search(Timestamp::new(0)),
SeekSearchResult::Upper(SeekPoint::new(Timestamp::new(479232), 706812, 1152))
);
assert_eq!(
index.search(Timestamp::new(3000000)),
SeekSearchResult::Lower(SeekPoint::new(Timestamp::new(2880000), 4259455, 1152))
);
assert_eq!(
index.search(Timestamp::new(959616)),
SeekSearchResult::Range(
SeekPoint::new(Timestamp::new(959616), 1421536, 1152),
SeekPoint::new(Timestamp::new(1919232), 2833241, 1152)
)
);
index.insert(Timestamp::new(1440000), 2132419, 1152);
index.insert(Timestamp::new(-78000), 20, 1152);
index.insert(Timestamp::new(-50000), 45000, 1152);
assert_eq!(
index.search(Timestamp::MIN),
SeekSearchResult::Upper(SeekPoint::new(Timestamp::new(-78000), 20, 1152))
);
assert_eq!(
index.search(Timestamp::new(-69000)),
SeekSearchResult::Range(
SeekPoint::new(Timestamp::new(-78000), 20, 1152),
SeekPoint::new(Timestamp::new(-50000), 45000, 1152)
)
);
assert_eq!(
index.search(Timestamp::new(0)),
SeekSearchResult::Range(
SeekPoint::new(Timestamp::new(-50000), 45000, 1152),
SeekPoint::new(Timestamp::new(479232), 706812, 1152)
)
);
assert_eq!(
index.search(Timestamp::new(1000000)),
SeekSearchResult::Range(
SeekPoint::new(Timestamp::new(959616), 1421536, 1152),
SeekPoint::new(Timestamp::new(1440000), 2132419, 1152)
)
);
index.insert(Timestamp::new(3359232), 0, 0);
assert_eq!(
index.search(Timestamp::new(3359232)),
SeekSearchResult::Lower(SeekPoint::new(Timestamp::new(2880000), 4259455, 1152))
);
}
}
}
pub mod well_known {
use super::FormatId;
pub const FORMAT_ID_WAVE: FormatId = FormatId(0x100);
pub const FORMAT_ID_AIFF: FormatId = FormatId(0x101);
pub const FORMAT_ID_AVI: FormatId = FormatId(0x102);
pub const FORMAT_ID_CAF: FormatId = FormatId(0x103);
pub const FORMAT_ID_MP1: FormatId = FormatId(0x104);
pub const FORMAT_ID_MP2: FormatId = FormatId(0x105);
pub const FORMAT_ID_MP3: FormatId = FormatId(0x106);
pub const FORMAT_ID_ADTS: FormatId = FormatId(0x107);
pub const FORMAT_ID_OGG: FormatId = FormatId(0x108);
pub const FORMAT_ID_FLAC: FormatId = FormatId(0x109);
pub const FORMAT_ID_WAVPACK: FormatId = FormatId(0x10a);
pub const FORMAT_ID_ISOMP4: FormatId = FormatId(0x10b);
pub const FORMAT_ID_MKV: FormatId = FormatId(0x10c);
pub const FORMAT_ID_FLV: FormatId = FormatId(0x10d);
}