diff options
| author | metamuffin <metamuffin@disroot.org> | 2026-03-05 14:58:26 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2026-03-05 14:58:26 +0100 |
| commit | 8fa153c30c00210ac6512d73268a3adc930c5fbd (patch) | |
| tree | aeaea006953fd1add29c675dab3d3deb2a009ea5 /remuxer/mp4/src | |
| parent | ec2228f6ee9349c0866483abc21124dae31f2b52 (diff) | |
| download | jellything-8fa153c30c00210ac6512d73268a3adc930c5fbd.tar jellything-8fa153c30c00210ac6512d73268a3adc930c5fbd.tar.bz2 jellything-8fa153c30c00210ac6512d73268a3adc930c5fbd.tar.zst | |
add crate for dealing with mp4; convert fragmentmuxer output from writer to vec
Diffstat (limited to 'remuxer/mp4/src')
| -rw-r--r-- | remuxer/mp4/src/boxes.rs | 268 | ||||
| -rw-r--r-- | remuxer/mp4/src/lib.rs | 37 |
2 files changed, 305 insertions, 0 deletions
diff --git a/remuxer/mp4/src/boxes.rs b/remuxer/mp4/src/boxes.rs new file mode 100644 index 0000000..d2c3918 --- /dev/null +++ b/remuxer/mp4/src/boxes.rs @@ -0,0 +1,268 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +use crate::{BoxW, WriteBox}; + +macro_rules! container_box { + ($typ:tt, $iden:literal) => { + pub struct $typ<T: Fn(&mut BoxW)>(pub T); + impl<T: Fn(&mut BoxW)> WriteBox for $typ<T> { + const BOXTYPE: [u8; 4] = *$iden; + fn write(&self, buf: &mut Vec<u8>) { + self.0(&mut BoxW(buf)) + } + } + }; +} + +container_box!(Movie, b"moov"); +container_box!(Track, b"trak"); +container_box!(Edit, b"edts"); +container_box!(Media, b"mdia"); +container_box!(MediaInformation, b"minf"); +container_box!(DataInformation, b"dinf"); +container_box!(SampleTable, b"stbl"); +container_box!(MovieFragment, b"stbl"); +container_box!(TrackFragment, b"stbl"); + +pub struct FileType<'a> { + pub major_brand: [u8; 4], + pub minor_version: u32, + pub compatible_brands: &'a [[u8; 4]], +} +impl WriteBox for FileType<'_> { + const BOXTYPE: [u8; 4] = *b"ftyp"; + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.major_brand); + buf.extend(self.minor_version.to_be_bytes()); + for cb in self.compatible_brands { + buf.extend(cb); + } + } +} + +pub struct MovieHeader { + pub creation_time: u64, + pub modification_time: u64, + pub timescale: u32, + pub duration: u64, + pub rate: u32, + pub volume: u16, + pub matrix: [u32; 9], + pub pre_defined: [u32; 6], + pub next_track_id: u32, +} + +impl WriteBox for MovieHeader { + const BOXTYPE: [u8; 4] = *b"mvhd"; + const VERSION: Option<u8> = Some(1); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.creation_time.to_be_bytes()); + buf.extend(self.modification_time.to_be_bytes()); + buf.extend(self.timescale.to_be_bytes()); + buf.extend(self.duration.to_be_bytes()); + buf.extend(self.rate.to_be_bytes()); + buf.extend(self.volume.to_be_bytes()); + buf.extend([0; 10]); + for e in self.matrix { + buf.extend(e.to_be_bytes()); + } + buf.extend([0; 24]); + buf.extend(self.next_track_id.to_be_bytes()); + } +} +impl Default for MovieHeader { + fn default() -> Self { + Self { + creation_time: 0, + modification_time: 0, + timescale: 0, + duration: 0, + rate: 0x00010000, + volume: 0x0100, + matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x00010000], + pre_defined: [0; 6], + next_track_id: 0, + } + } +} + +#[derive(Default)] +pub struct TrackHeader { + pub creation_time: u64, + pub modification_time: u64, + pub track_id: u32, + pub duration: u64, + pub layer: u16, + pub alternate_group: u16, + pub volume: u16, + pub matrix: [u32; 9], + pub width: u32, + pub height: u32, +} +impl WriteBox for TrackHeader { + const BOXTYPE: [u8; 4] = *b"tkhd"; + const VERSION: Option<u8> = Some(1); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.creation_time.to_be_bytes()); + buf.extend(self.modification_time.to_be_bytes()); + buf.extend(self.track_id.to_be_bytes()); + buf.extend([0; 4]); + buf.extend(self.duration.to_be_bytes()); + buf.extend([0; 8]); + buf.extend(self.layer.to_be_bytes()); + buf.extend(self.alternate_group.to_be_bytes()); + buf.extend(self.volume.to_be_bytes()); + buf.extend([0; 2]); + for e in self.matrix { + buf.extend(e.to_be_bytes()); + } + buf.extend(self.width.to_be_bytes()); + buf.extend(self.height.to_be_bytes()); + } +} + +pub struct EditList<'a> { + pub entries: &'a [EditListEntry], +} +pub struct EditListEntry { + pub edit_duration: u64, + pub media_time: u64, + pub media_rate: u32, +} +impl WriteBox for EditList<'_> { + const BOXTYPE: [u8; 4] = *b"elst"; + fn write(&self, buf: &mut Vec<u8>) { + buf.extend((self.entries.len() as u32).to_be_bytes()); + for e in self.entries { + buf.extend(e.edit_duration.to_be_bytes()); + buf.extend(e.media_time.to_be_bytes()); + buf.extend(e.media_rate.to_be_bytes()); + } + } +} + +#[derive(Default)] +pub struct MediaHeader { + pub creation_time: u64, + pub modification_time: u64, + pub timescale: u32, + pub duration: u64, + pub language: [u8; 3], + pub pre_defined: u16, +} +impl WriteBox for MediaHeader { + const BOXTYPE: [u8; 4] = *b"mdhd"; + const VERSION: Option<u8> = Some(1); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.creation_time.to_be_bytes()); + buf.extend(self.modification_time.to_be_bytes()); + buf.extend(self.timescale.to_be_bytes()); + buf.extend(self.duration.to_be_bytes()); + buf.extend( + ((self.language[0] as u16 & 0b11111 << 10) + | (self.language[1] as u16 & 0b11111 << 5) + | (self.language[2] as u16 & 0b11111 << 0)) + .to_be_bytes(), + ); + buf.extend([0; 2]); + } +} + +pub struct Handler { + pub handler_type: u32, + pub name: String, +} +impl WriteBox for Handler { + const BOXTYPE: [u8; 4] = *b"hdlr"; + const VERSION: Option<u8> = Some(0); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend([0; 4]); + buf.extend(self.handler_type.to_be_bytes()); + buf.extend([0; 12]); + buf.extend(self.name.as_bytes()); + } +} + +#[derive(Default)] +pub struct VideoMediaHandler { + pub graphicsmode: u16, + pub opcolor: [u16; 3], +} +impl WriteBox for VideoMediaHandler { + const BOXTYPE: [u8; 4] = *b"vmhd"; + const VERSION: Option<u8> = Some(0); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.graphicsmode.to_be_bytes()); + buf.extend(self.opcolor[0].to_be_bytes()); + buf.extend(self.opcolor[1].to_be_bytes()); + buf.extend(self.opcolor[2].to_be_bytes()); + } +} + +#[derive(Default)] +pub struct SoundMediaHandler { + pub balance: u16, +} +impl WriteBox for SoundMediaHandler { + const BOXTYPE: [u8; 4] = *b"smhd"; + const VERSION: Option<u8> = Some(0); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.balance.to_be_bytes()); + } +} + +pub struct DataReference<'a> { + pub entries: &'a [()], +} +impl WriteBox for DataReference<'_> { + const BOXTYPE: [u8; 4] = *b"dref"; + const VERSION: Option<u8> = Some(0); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend([0; 4]); + } +} + +pub struct MovieFragmentHeader { + pub sequence_number: u32, +} +impl WriteBox for MovieFragmentHeader { + const BOXTYPE: [u8; 4] = *b"mfhd"; + const VERSION: Option<u8> = Some(0); + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.sequence_number.to_be_bytes()); + } +} + +pub struct TrackFragmentHeader { + pub track_id: u32, +} +impl WriteBox for TrackFragmentHeader { + const BOXTYPE: [u8; 4] = *b"tfhd"; + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.track_id.to_be_bytes()); + } +} + +pub struct TrackFragmentBaseMediaDecodeTime { + pub base_media_decode_time: u64, +} +impl WriteBox for TrackFragmentBaseMediaDecodeTime { + const BOXTYPE: [u8; 4] = *b"tfdt"; + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.base_media_decode_time.to_be_bytes()); + } +} + +pub struct TrackRun { + pub data_offset: i32, +} +impl WriteBox for TrackRun { + const BOXTYPE: [u8; 4] = *b"trun"; + fn write(&self, buf: &mut Vec<u8>) { + buf.extend(self.data_offset.to_be_bytes()); + } +} diff --git a/remuxer/mp4/src/lib.rs b/remuxer/mp4/src/lib.rs new file mode 100644 index 0000000..cb728fa --- /dev/null +++ b/remuxer/mp4/src/lib.rs @@ -0,0 +1,37 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2026 metamuffin <metamuffin.org> +*/ + +mod boxes; +pub use boxes::*; + +pub struct BoxW<'a>(&'a mut Vec<u8>); +impl<'a> BoxW<'a> { + pub fn new(buffer: &'a mut Vec<u8>) -> Self { + Self(buffer) + } + pub fn write<T: WriteBox>(&mut self, b: T) { + let start = self.0.len(); + self.0.extend(0u32.to_be_bytes()); + self.0.extend(T::BOXTYPE); + + if let Some(ver) = T::VERSION { + self.0.push(ver); + self.0.extend(&b.flags().to_be_bytes()[1..]); + } + b.write(self.0); + let size = (self.0.len() - start) as u32; + self.0[start..start + 4].copy_from_slice(&size.to_be_bytes()); + } +} + +pub trait WriteBox { + const BOXTYPE: [u8; 4]; + const VERSION: Option<u8> = None; + fn write(&self, buf: &mut Vec<u8>); + fn flags(&self) -> u32 { + 0 + } +} |