use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use serde::Serialize; use std::io::{Read, Seek, Write}; use crate::mp4box::*; pub enum TrackFlag { TrackEnabled = 0x000001, // TrackInMovie = 0x000002, // TrackInPreview = 0x000004, } #[derive(Debug, Clone, PartialEq, Serialize)] pub struct TkhdBox { pub version: u8, pub flags: u32, pub creation_time: u64, pub modification_time: u64, pub track_id: u32, pub duration: u64, pub layer: u16, pub alternate_group: u16, #[serde(with = "value_u8")] pub volume: FixedPointU8, pub matrix: Matrix, #[serde(with = "value_u32")] pub width: FixedPointU16, #[serde(with = "value_u32")] pub height: FixedPointU16, } impl Default for TkhdBox { fn default() -> Self { TkhdBox { version: 0, flags: TrackFlag::TrackEnabled as u32, creation_time: 0, modification_time: 0, track_id: 0, duration: 0, layer: 0, alternate_group: 0, volume: FixedPointU8::new(1), matrix: Matrix::default(), width: FixedPointU16::new(0), height: FixedPointU16::new(0), } } } #[derive(Debug, Clone, PartialEq, Serialize)] pub struct Matrix { pub a: i32, pub b: i32, pub u: i32, pub c: i32, pub d: i32, pub v: i32, pub x: i32, pub y: i32, pub w: i32, } impl std::fmt::Display for Matrix { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x}", self.a, self.b, self.u, self.c, self.d, self.v, self.x, self.y, self.w ) } } impl Default for Matrix { fn default() -> Self { Self { // unity matrix according to ISO/IEC 14496-12:2005(E) a: 0x00010000, b: 0, u: 0, c: 0, d: 0x00010000, v: 0, x: 0, y: 0, w: 0x40000000, } } } impl TkhdBox { pub fn get_type(&self) -> BoxType { BoxType::TkhdBox } pub fn get_size(&self) -> u64 { let mut size = HEADER_SIZE + HEADER_EXT_SIZE; if self.version == 1 { size += 32; } else if self.version == 0 { size += 20; } size += 60; size } pub fn set_width(&mut self, width: u16) { self.width = FixedPointU16::new(width); } pub fn set_height(&mut self, height: u16) { self.height = FixedPointU16::new(height); } } impl Mp4Box for TkhdBox { fn box_type(&self) -> BoxType { self.get_type() } fn box_size(&self) -> u64 { self.get_size() } fn to_json(&self) -> Result { Ok(serde_json::to_string(&self).unwrap()) } fn summary(&self) -> Result { let s = format!( "creation_time={} track_id={} duration={} layer={} volume={} matrix={} width={} height={}", self.creation_time, self.track_id, self.duration, self.layer, self.volume.value(), self.matrix, self.width.value(), self.height.value() ); Ok(s) } } impl ReadBox<&mut R> for TkhdBox { fn read_box(reader: &mut R, size: u64) -> Result { let start = box_start(reader)?; let (version, flags) = read_box_header_ext(reader)?; let (creation_time, modification_time, track_id, _, duration) = if version == 1 { ( reader.read_u64::()?, reader.read_u64::()?, reader.read_u32::()?, reader.read_u32::()?, reader.read_u64::()?, ) } else if version == 0 { ( reader.read_u32::()? as u64, reader.read_u32::()? as u64, reader.read_u32::()?, reader.read_u32::()?, reader.read_u32::()? as u64, ) } else { return Err(Error::InvalidData("version must be 0 or 1")); }; reader.read_u64::()?; // reserved let layer = reader.read_u16::()?; let alternate_group = reader.read_u16::()?; let volume = FixedPointU8::new_raw(reader.read_u16::()?); reader.read_u16::()?; // reserved let matrix = Matrix { a: reader.read_i32::()?, b: reader.read_i32::()?, u: reader.read_i32::()?, c: reader.read_i32::()?, d: reader.read_i32::()?, v: reader.read_i32::()?, x: reader.read_i32::()?, y: reader.read_i32::()?, w: reader.read_i32::()?, }; let width = FixedPointU16::new_raw(reader.read_u32::()?); let height = FixedPointU16::new_raw(reader.read_u32::()?); skip_bytes_to(reader, start + size)?; Ok(TkhdBox { version, flags, creation_time, modification_time, track_id, duration, layer, alternate_group, volume, matrix, width, height, }) } } impl WriteBox<&mut W> for TkhdBox { fn write_box(&self, writer: &mut W) -> Result { let size = self.box_size(); BoxHeader::new(self.box_type(), size).write(writer)?; write_box_header_ext(writer, self.version, self.flags)?; if self.version == 1 { writer.write_u64::(self.creation_time)?; writer.write_u64::(self.modification_time)?; writer.write_u32::(self.track_id)?; writer.write_u32::(0)?; // reserved writer.write_u64::(self.duration)?; } else if self.version == 0 { writer.write_u32::(self.creation_time as u32)?; writer.write_u32::(self.modification_time as u32)?; writer.write_u32::(self.track_id)?; writer.write_u32::(0)?; // reserved writer.write_u32::(self.duration as u32)?; } else { return Err(Error::InvalidData("version must be 0 or 1")); } writer.write_u64::(0)?; // reserved writer.write_u16::(self.layer)?; writer.write_u16::(self.alternate_group)?; writer.write_u16::(self.volume.raw_value())?; writer.write_u16::(0)?; // reserved writer.write_i32::(self.matrix.a)?; writer.write_i32::(self.matrix.b)?; writer.write_i32::(self.matrix.u)?; writer.write_i32::(self.matrix.c)?; writer.write_i32::(self.matrix.d)?; writer.write_i32::(self.matrix.v)?; writer.write_i32::(self.matrix.x)?; writer.write_i32::(self.matrix.y)?; writer.write_i32::(self.matrix.w)?; writer.write_u32::(self.width.raw_value())?; writer.write_u32::(self.height.raw_value())?; Ok(size) } } #[cfg(test)] mod tests { use super::*; use crate::mp4box::BoxHeader; use std::io::Cursor; #[test] fn test_tkhd32() { let src_box = TkhdBox { version: 0, flags: TrackFlag::TrackEnabled as u32, creation_time: 100, modification_time: 200, track_id: 1, duration: 634634, layer: 0, alternate_group: 0, volume: FixedPointU8::new(1), matrix: Matrix::default(), width: FixedPointU16::new(512), height: FixedPointU16::new(288), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); let mut reader = Cursor::new(&buf); let header = BoxHeader::read(&mut reader).unwrap(); assert_eq!(header.name, BoxType::TkhdBox); assert_eq!(src_box.box_size(), header.size); let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap(); assert_eq!(src_box, dst_box); } #[test] fn test_tkhd64() { let src_box = TkhdBox { version: 1, flags: TrackFlag::TrackEnabled as u32, creation_time: 100, modification_time: 200, track_id: 1, duration: 634634, layer: 0, alternate_group: 0, volume: FixedPointU8::new(1), matrix: Matrix::default(), width: FixedPointU16::new(512), height: FixedPointU16::new(288), }; let mut buf = Vec::new(); src_box.write_box(&mut buf).unwrap(); assert_eq!(buf.len(), src_box.box_size() as usize); let mut reader = Cursor::new(&buf); let header = BoxHeader::read(&mut reader).unwrap(); assert_eq!(header.name, BoxType::TkhdBox); assert_eq!(src_box.box_size(), header.size); let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap(); assert_eq!(src_box, dst_box); } }