use oximedia_core::CodecId;
use super::matroska_v4::BlockAdditionMapping;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum DocType {
#[default]
Matroska,
WebM,
}
impl DocType {
#[must_use]
pub const fn is_webm(&self) -> bool {
matches!(self, Self::WebM)
}
#[must_use]
pub const fn as_str(&self) -> &'static str {
match self {
Self::Matroska => "matroska",
Self::WebM => "webm",
}
}
}
#[derive(Clone, Debug)]
pub struct EbmlHeader {
pub version: u64,
pub read_version: u64,
pub max_id_length: u64,
pub max_size_length: u64,
pub doc_type: DocType,
pub doc_type_version: u64,
pub doc_type_read_version: u64,
}
impl Default for EbmlHeader {
fn default() -> Self {
Self {
version: 1,
read_version: 1,
max_id_length: 4,
max_size_length: 8,
doc_type: DocType::Matroska,
doc_type_version: 4,
doc_type_read_version: 2,
}
}
}
#[derive(Clone, Debug)]
pub struct SegmentInfo {
pub timecode_scale: u64,
pub duration: Option<f64>,
pub title: Option<String>,
pub muxing_app: Option<String>,
pub writing_app: Option<String>,
pub date_utc: Option<i64>,
pub segment_uid: Option<Vec<u8>>,
}
impl Default for SegmentInfo {
fn default() -> Self {
Self {
timecode_scale: 1_000_000, duration: None,
title: None,
muxing_app: None,
writing_app: None,
date_utc: None,
segment_uid: None,
}
}
}
impl SegmentInfo {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration_seconds(&self) -> Option<f64> {
self.duration
.map(|d| d * self.timecode_scale as f64 / 1_000_000_000.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum TrackType {
Video = 1,
Audio = 2,
Complex = 3,
Logo = 0x10,
Subtitle = 0x11,
Buttons = 0x12,
Control = 0x20,
Metadata = 0x21,
}
impl TryFrom<u64> for TrackType {
type Error = ();
fn try_from(value: u64) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::Video),
2 => Ok(Self::Audio),
3 => Ok(Self::Complex),
0x10 => Ok(Self::Logo),
0x11 => Ok(Self::Subtitle),
0x12 => Ok(Self::Buttons),
0x20 => Ok(Self::Control),
0x21 => Ok(Self::Metadata),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct VideoSettings {
pub pixel_width: u32,
pub pixel_height: u32,
pub display_width: Option<u32>,
pub display_height: Option<u32>,
pub display_unit: u8,
pub interlaced: bool,
pub field_order: Option<u8>,
pub stereo_mode: Option<u8>,
pub alpha_mode: Option<u8>,
pub pixel_crop_bottom: u32,
pub pixel_crop_top: u32,
pub pixel_crop_left: u32,
pub pixel_crop_right: u32,
pub colour: Option<ColourSettings>,
}
impl VideoSettings {
#[must_use]
pub fn effective_display_width(&self) -> u32 {
self.display_width.unwrap_or(self.pixel_width)
}
#[must_use]
pub fn effective_display_height(&self) -> u32 {
self.display_height.unwrap_or(self.pixel_height)
}
}
#[derive(Clone, Debug, Default)]
pub struct ColourSettings {
pub matrix_coefficients: Option<u8>,
pub bits_per_channel: Option<u8>,
pub chroma_subsampling_horz: Option<u8>,
pub chroma_subsampling_vert: Option<u8>,
pub cb_subsampling_horz: Option<u8>,
pub cb_subsampling_vert: Option<u8>,
pub chroma_siting_horz: Option<u8>,
pub chroma_siting_vert: Option<u8>,
pub range: Option<u8>,
pub transfer_characteristics: Option<u8>,
pub primaries: Option<u8>,
pub max_cll: Option<u64>,
pub max_fall: Option<u64>,
pub mastering_metadata: Option<MasteringMetadata>,
}
#[derive(Clone, Debug, Default)]
pub struct MasteringMetadata {
pub primary_r_chromaticity_x: Option<f64>,
pub primary_r_chromaticity_y: Option<f64>,
pub primary_g_chromaticity_x: Option<f64>,
pub primary_g_chromaticity_y: Option<f64>,
pub primary_b_chromaticity_x: Option<f64>,
pub primary_b_chromaticity_y: Option<f64>,
pub white_point_chromaticity_x: Option<f64>,
pub white_point_chromaticity_y: Option<f64>,
pub luminance_max: Option<f64>,
pub luminance_min: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct AudioSettings {
pub sampling_frequency: f64,
pub output_sampling_frequency: Option<f64>,
pub channels: u8,
pub bit_depth: Option<u8>,
}
impl Default for AudioSettings {
fn default() -> Self {
Self {
sampling_frequency: 8000.0,
output_sampling_frequency: None,
channels: 1,
bit_depth: None,
}
}
}
impl AudioSettings {
#[must_use]
pub fn effective_sample_rate(&self) -> f64 {
self.output_sampling_frequency
.unwrap_or(self.sampling_frequency)
}
}
#[derive(Clone, Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct TrackEntry {
pub number: u64,
pub uid: u64,
pub track_type: TrackType,
pub enabled: bool,
pub default: bool,
pub forced: bool,
pub lacing: bool,
pub min_cache: u64,
pub max_cache: Option<u64>,
pub default_duration: Option<u64>,
pub track_timecode_scale: f64,
pub name: Option<String>,
pub language: String,
pub language_ietf: Option<String>,
pub codec_id: String,
pub codec_private: Option<Vec<u8>>,
pub codec_name: Option<String>,
pub codec_delay: Option<u64>,
pub seek_pre_roll: Option<u64>,
pub video: Option<VideoSettings>,
pub audio: Option<AudioSettings>,
pub block_addition_mappings: Vec<BlockAdditionMapping>,
pub oxi_codec: Option<CodecId>,
}
impl Default for TrackEntry {
fn default() -> Self {
Self {
number: 0,
uid: 0,
track_type: TrackType::Video,
enabled: true,
default: true,
forced: false,
lacing: true,
min_cache: 0,
max_cache: None,
default_duration: None,
track_timecode_scale: 1.0,
name: None,
language: "eng".to_string(),
language_ietf: None,
codec_id: String::new(),
codec_private: None,
codec_name: None,
codec_delay: None,
seek_pre_roll: None,
video: None,
audio: None,
block_addition_mappings: Vec::new(),
oxi_codec: None,
}
}
}
impl TrackEntry {
#[must_use]
pub const fn is_video(&self) -> bool {
matches!(self.track_type, TrackType::Video)
}
#[must_use]
pub const fn is_audio(&self) -> bool {
matches!(self.track_type, TrackType::Audio)
}
#[must_use]
pub const fn is_subtitle(&self) -> bool {
matches!(self.track_type, TrackType::Subtitle)
}
}
#[derive(Clone, Debug, Default)]
pub struct CuePoint {
pub time: u64,
pub track_positions: Vec<CueTrackPosition>,
}
#[derive(Clone, Debug, Default)]
pub struct CueTrackPosition {
pub track: u64,
pub cluster_position: u64,
pub relative_position: Option<u64>,
pub duration: Option<u64>,
pub block_number: Option<u64>,
}
#[derive(Clone, Debug, Default)]
pub struct Edition {
pub uid: Option<u64>,
pub hidden: bool,
pub default: bool,
pub ordered: bool,
pub chapters: Vec<Chapter>,
}
#[derive(Clone, Debug, Default)]
pub struct Chapter {
pub uid: u64,
pub string_uid: Option<String>,
pub time_start: u64,
pub time_end: Option<u64>,
pub hidden: bool,
pub enabled: bool,
pub display: Vec<ChapterDisplay>,
pub children: Vec<Chapter>,
}
#[derive(Clone, Debug, Default)]
pub struct ChapterDisplay {
pub string: String,
pub language: String,
pub language_ietf: Option<String>,
pub country: Option<String>,
}
#[derive(Clone, Debug, Default)]
pub struct Tag {
pub targets: TagTargets,
pub simple_tags: Vec<SimpleTag>,
}
#[derive(Clone, Debug, Default)]
pub struct TagTargets {
pub target_type_value: Option<u64>,
pub target_type: Option<String>,
pub track_uid: Vec<u64>,
pub edition_uid: Vec<u64>,
pub chapter_uid: Vec<u64>,
pub attachment_uid: Vec<u64>,
}
#[derive(Clone, Debug, Default)]
pub struct SimpleTag {
pub name: String,
pub language: String,
pub language_ietf: Option<String>,
pub default: bool,
pub string: Option<String>,
pub binary: Option<Vec<u8>>,
pub children: Vec<SimpleTag>,
}
#[derive(Clone, Debug, Default)]
pub struct AttachedFile {
pub description: Option<String>,
pub name: String,
pub mime_type: String,
pub data: Vec<u8>,
pub uid: u64,
}
#[derive(Clone, Debug, Default)]
pub struct SeekEntry {
pub id: u32,
pub position: u64,
}
#[derive(Clone, Debug, Default)]
pub struct ClusterState {
pub timecode: u64,
pub position: u64,
pub size: Option<u64>,
pub data_position: u64,
}
#[derive(Clone, Debug, Default)]
pub struct BlockHeader {
pub track_number: u64,
pub timecode: i16,
pub keyframe: bool,
pub invisible: bool,
pub lacing: LacingType,
pub discardable: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum LacingType {
#[default]
None,
Xiph,
Ebml,
FixedSize,
}
impl TryFrom<u8> for LacingType {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::None),
1 => Ok(Self::Xiph),
2 => Ok(Self::FixedSize),
3 => Ok(Self::Ebml),
_ => Err(()),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Block {
pub header: BlockHeader,
pub frames: Vec<Vec<u8>>,
pub duration: Option<u64>,
pub references: Vec<i64>,
pub discard_padding: Option<i64>,
}
impl Block {
#[must_use]
pub fn is_keyframe(&self) -> bool {
self.header.keyframe || self.references.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_doc_type() {
assert!(!DocType::Matroska.is_webm());
assert!(DocType::WebM.is_webm());
assert_eq!(DocType::Matroska.as_str(), "matroska");
assert_eq!(DocType::WebM.as_str(), "webm");
}
#[test]
fn test_ebml_header_default() {
let header = EbmlHeader::default();
assert_eq!(header.version, 1);
assert_eq!(header.max_id_length, 4);
assert_eq!(header.max_size_length, 8);
assert_eq!(header.doc_type, DocType::Matroska);
}
#[test]
fn test_segment_info_duration() {
let mut info = SegmentInfo::default();
info.duration = Some(60000.0);
let secs = info.duration_seconds().expect("operation should succeed");
assert!((secs - 60.0).abs() < 0.001);
}
#[test]
fn test_track_type_conversion() {
assert_eq!(TrackType::try_from(1u64), Ok(TrackType::Video));
assert_eq!(TrackType::try_from(2u64), Ok(TrackType::Audio));
assert_eq!(TrackType::try_from(0x11u64), Ok(TrackType::Subtitle));
assert!(TrackType::try_from(255u64).is_err());
}
#[test]
fn test_video_settings_effective_dimensions() {
let mut settings = VideoSettings::default();
settings.pixel_width = 1920;
settings.pixel_height = 1080;
assert_eq!(settings.effective_display_width(), 1920);
assert_eq!(settings.effective_display_height(), 1080);
settings.display_width = Some(1920);
settings.display_height = Some(800);
assert_eq!(settings.effective_display_width(), 1920);
assert_eq!(settings.effective_display_height(), 800);
}
#[test]
fn test_audio_settings_effective_sample_rate() {
let mut settings = AudioSettings::default();
settings.sampling_frequency = 44100.0;
assert!((settings.effective_sample_rate() - 44100.0).abs() < f64::EPSILON);
settings.output_sampling_frequency = Some(48000.0);
assert!((settings.effective_sample_rate() - 48000.0).abs() < f64::EPSILON);
}
#[test]
fn test_track_entry_type_checks() {
let mut track = TrackEntry::default();
track.track_type = TrackType::Video;
assert!(track.is_video());
assert!(!track.is_audio());
assert!(!track.is_subtitle());
track.track_type = TrackType::Audio;
assert!(!track.is_video());
assert!(track.is_audio());
assert!(!track.is_subtitle());
track.track_type = TrackType::Subtitle;
assert!(!track.is_video());
assert!(!track.is_audio());
assert!(track.is_subtitle());
}
#[test]
fn test_lacing_type_conversion() {
assert_eq!(LacingType::try_from(0u8), Ok(LacingType::None));
assert_eq!(LacingType::try_from(1u8), Ok(LacingType::Xiph));
assert_eq!(LacingType::try_from(2u8), Ok(LacingType::FixedSize));
assert_eq!(LacingType::try_from(3u8), Ok(LacingType::Ebml));
assert!(LacingType::try_from(4u8).is_err());
}
#[test]
fn test_block_is_keyframe() {
let mut block = Block::default();
assert!(block.is_keyframe());
block.header.keyframe = true;
assert!(block.is_keyframe());
block.header.keyframe = false;
block.references.push(-33);
assert!(!block.is_keyframe());
}
}