#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TrackFlags {
bits: u32,
}
impl TrackFlags {
pub const ENABLED: u32 = 0x0000_0001;
pub const IN_MOVIE: u32 = 0x0000_0002;
pub const IN_PREVIEW: u32 = 0x0000_0004;
pub const SIZE_IS_ASPECT_RATIO: u32 = 0x0000_0008;
#[must_use]
pub const fn from_bits(bits: u32) -> Self {
Self {
bits: bits & 0x00FF_FFFF,
}
}
#[must_use]
pub const fn default_flags() -> Self {
Self {
bits: Self::ENABLED | Self::IN_MOVIE,
}
}
#[must_use]
pub const fn bits(self) -> u32 {
self.bits
}
#[must_use]
pub const fn is_enabled(self) -> bool {
self.bits & Self::ENABLED != 0
}
#[must_use]
pub const fn is_in_movie(self) -> bool {
self.bits & Self::IN_MOVIE != 0
}
#[must_use]
pub const fn is_in_preview(self) -> bool {
self.bits & Self::IN_PREVIEW != 0
}
#[must_use]
pub const fn set_enabled(mut self, enabled: bool) -> Self {
if enabled {
self.bits |= Self::ENABLED;
} else {
self.bits &= !Self::ENABLED;
}
self
}
}
impl Default for TrackFlags {
fn default() -> Self {
Self::default_flags()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransformMatrix {
pub values: [f64; 9],
}
impl TransformMatrix {
pub const IDENTITY: Self = Self {
values: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0],
};
#[must_use]
pub const fn new(values: [f64; 9]) -> Self {
Self { values }
}
#[must_use]
pub fn is_identity(&self) -> bool {
let id = Self::IDENTITY.values;
self.values
.iter()
.zip(id.iter())
.all(|(a, b)| (a - b).abs() < 1e-9)
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn rotation(degrees: f64) -> Self {
let rad = degrees * std::f64::consts::PI / 180.0;
let cos = rad.cos();
let sin = rad.sin();
Self {
values: [cos, sin, 0.0, -sin, cos, 0.0, 0.0, 0.0, 1.0],
}
}
#[must_use]
pub fn scale(sx: f64, sy: f64) -> Self {
Self {
values: [sx, 0.0, 0.0, 0.0, sy, 0.0, 0.0, 0.0, 1.0],
}
}
#[must_use]
pub fn translation(tx: f64, ty: f64) -> Self {
Self {
values: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, tx, ty, 1.0],
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn rotation_degrees(&self) -> f64 {
self.values[1].atan2(self.values[0]) * 180.0 / std::f64::consts::PI
}
#[must_use]
pub fn translation_xy(&self) -> (f64, f64) {
(self.values[6], self.values[7])
}
}
impl Default for TransformMatrix {
fn default() -> Self {
Self::IDENTITY
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TrackType {
Video,
Audio,
Subtitle,
Hint,
Metadata,
Unknown,
}
impl TrackType {
#[must_use]
pub fn is_media(self) -> bool {
matches!(self, Self::Video | Self::Audio)
}
}
#[derive(Debug, Clone)]
pub struct TrackHeader {
pub version: u8,
pub flags: TrackFlags,
pub track_id: u32,
pub duration: u64,
pub layer: i16,
pub alternate_group: u16,
pub volume: f32,
pub width: f64,
pub height: f64,
pub matrix: TransformMatrix,
pub track_type: TrackType,
}
impl TrackHeader {
#[must_use]
pub fn video(track_id: u32, width: f64, height: f64, duration: u64) -> Self {
Self {
version: 0,
flags: TrackFlags::default(),
track_id,
duration,
layer: 0,
alternate_group: 0,
volume: 0.0,
width,
height,
matrix: TransformMatrix::IDENTITY,
track_type: TrackType::Video,
}
}
#[must_use]
pub fn audio(track_id: u32, duration: u64) -> Self {
Self {
version: 0,
flags: TrackFlags::default(),
track_id,
duration,
layer: 0,
alternate_group: 0,
volume: 1.0,
width: 0.0,
height: 0.0,
matrix: TransformMatrix::IDENTITY,
track_type: TrackType::Audio,
}
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn aspect_ratio(&self) -> Option<f64> {
if self.height.abs() < f64::EPSILON {
return None;
}
Some(self.width / self.height)
}
#[must_use]
pub fn is_video(&self) -> bool {
self.track_type == TrackType::Video
}
#[must_use]
pub fn is_audio(&self) -> bool {
self.track_type == TrackType::Audio
}
#[must_use]
pub fn is_enabled(&self) -> bool {
self.flags.is_enabled()
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn dimensions(&self) -> (u32, u32) {
(self.width as u32, self.height as u32)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_track_flags_default() {
let f = TrackFlags::default();
assert!(f.is_enabled());
assert!(f.is_in_movie());
assert!(!f.is_in_preview());
}
#[test]
fn test_track_flags_from_bits() {
let f = TrackFlags::from_bits(0x07);
assert!(f.is_enabled());
assert!(f.is_in_movie());
assert!(f.is_in_preview());
}
#[test]
fn test_track_flags_set_enabled() {
let f = TrackFlags::default().set_enabled(false);
assert!(!f.is_enabled());
assert!(f.is_in_movie());
let f2 = f.set_enabled(true);
assert!(f2.is_enabled());
}
#[test]
fn test_transform_identity() {
let m = TransformMatrix::IDENTITY;
assert!(m.is_identity());
}
#[test]
fn test_transform_rotation() {
let m = TransformMatrix::rotation(90.0);
assert!(!m.is_identity());
assert!((m.rotation_degrees() - 90.0).abs() < 0.01);
}
#[test]
fn test_transform_scale() {
let m = TransformMatrix::scale(2.0, 3.0);
assert!((m.values[0] - 2.0).abs() < f64::EPSILON);
assert!((m.values[4] - 3.0).abs() < f64::EPSILON);
}
#[test]
fn test_transform_translation() {
let m = TransformMatrix::translation(10.0, 20.0);
let (tx, ty) = m.translation_xy();
assert!((tx - 10.0).abs() < f64::EPSILON);
assert!((ty - 20.0).abs() < f64::EPSILON);
}
#[test]
fn test_track_type_is_media() {
assert!(TrackType::Video.is_media());
assert!(TrackType::Audio.is_media());
assert!(!TrackType::Subtitle.is_media());
assert!(!TrackType::Hint.is_media());
}
#[test]
fn test_video_header() {
let h = TrackHeader::video(1, 1920.0, 1080.0, 90000);
assert!(h.is_video());
assert!(!h.is_audio());
assert!(h.is_enabled());
assert_eq!(h.dimensions(), (1920, 1080));
}
#[test]
fn test_audio_header() {
let h = TrackHeader::audio(2, 48000);
assert!(h.is_audio());
assert!(!h.is_video());
assert!((h.volume - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_aspect_ratio() {
let h = TrackHeader::video(1, 1920.0, 1080.0, 0);
let ratio = h.aspect_ratio().expect("operation should succeed");
assert!((ratio - 16.0 / 9.0).abs() < 0.01);
}
#[test]
fn test_aspect_ratio_zero_height() {
let h = TrackHeader::video(1, 1920.0, 0.0, 0);
assert!(h.aspect_ratio().is_none());
}
#[test]
fn test_flags_bits_masking() {
let f = TrackFlags::from_bits(0xFF000003);
assert_eq!(f.bits(), 0x000003);
}
}