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 | |
| 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
| -rw-r--r-- | Cargo.lock | 5 | ||||
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | remuxer/Cargo.toml | 1 | ||||
| -rw-r--r-- | remuxer/mp4/Cargo.toml | 6 | ||||
| -rw-r--r-- | remuxer/mp4/src/boxes.rs | 268 | ||||
| -rw-r--r-- | remuxer/mp4/src/lib.rs | 37 | ||||
| -rw-r--r-- | remuxer/src/codec_param/mod.rs | 7 | ||||
| -rw-r--r-- | remuxer/src/codec_param/vp9.rs | 78 | ||||
| -rw-r--r-- | remuxer/src/demuxers/mod.rs | 2 | ||||
| -rw-r--r-- | remuxer/src/lib.rs | 3 | ||||
| -rw-r--r-- | remuxer/src/muxers/matroska.rs | 46 | ||||
| -rw-r--r-- | remuxer/src/muxers/mod.rs | 19 | ||||
| -rw-r--r-- | remuxer/src/muxers/mp4.rs | 63 | ||||
| -rw-r--r-- | remuxer/src/muxers/mpeg4.rs | 20 | ||||
| -rw-r--r-- | stream/src/fragment.rs | 2 |
15 files changed, 497 insertions, 61 deletions
@@ -2036,6 +2036,7 @@ dependencies = [ "log", "matroska", "serde", + "winter-mp4", ] [[package]] @@ -5133,6 +5134,10 @@ dependencies = [ ] [[package]] +name = "winter-mp4" +version = "0.1.0" + +[[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -10,6 +10,7 @@ members = [ "import/fallback_generator", "kv", "remuxer", + "remuxer/mp4", "server", "stream", "stream/types", diff --git a/remuxer/Cargo.toml b/remuxer/Cargo.toml index 18db7ad..efdcc0a 100644 --- a/remuxer/Cargo.toml +++ b/remuxer/Cargo.toml @@ -15,3 +15,4 @@ winter-ebml = { git = "https://codeberg.org/metamuffin/ebml-rs", package = "ebml winter-matroska = { git = "https://codeberg.org/metamuffin/ebml-rs", package = "matroska", features = [ "serde", ] } +winter-mp4 = { path = "mp4" } diff --git a/remuxer/mp4/Cargo.toml b/remuxer/mp4/Cargo.toml new file mode 100644 index 0000000..0931b10 --- /dev/null +++ b/remuxer/mp4/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "winter-mp4" +version = "0.1.0" +edition = "2024" + +[dependencies] 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 + } +} diff --git a/remuxer/src/codec_param/mod.rs b/remuxer/src/codec_param/mod.rs index 147fa0d..d5d9cae 100644 --- a/remuxer/src/codec_param/mod.rs +++ b/remuxer/src/codec_param/mod.rs @@ -4,11 +4,12 @@ Copyright (C) 2026 metamuffin <metamuffin.org> */ -use crate::codec_param::{av1::av1_codec_param, hevc::hevc_codec_param}; +use crate::codec_param::{av1::av1_codec_param, hevc::hevc_codec_param, vp9::vp9_codec_param}; use winter_matroska::TrackEntry; mod av1; mod hevc; +mod vp9; pub fn codec_param(te: &TrackEntry) -> String { let empty_cp = vec![]; @@ -22,7 +23,9 @@ pub fn codec_param(te: &TrackEntry) -> String { "V_AV1" => av1_codec_param(cp), "V_MPEG4/ISO/AVC" => format!("avc1.{:02x}{:02x}{:02x}", cp[1], cp[2], cp[3]), "V_MPEGH/ISO/HEVC" => hevc_codec_param(cp), - "V_VP9" => "vp09.00.50.08".to_string(), // TODO + "V_VP9" => vp9_codec_param(te), + + "D_WEBVTT/SUBTITLES" => "webvtt".to_string(), x => todo!("{x:?}"), } diff --git a/remuxer/src/codec_param/vp9.rs b/remuxer/src/codec_param/vp9.rs new file mode 100644 index 0000000..b3ed04c --- /dev/null +++ b/remuxer/src/codec_param/vp9.rs @@ -0,0 +1,78 @@ +use winter_matroska::TrackEntry; + +enum VPLevel { + Level1 = 10, + Level1_1 = 11, + Level2 = 20, + Level2_1 = 21, + Level3 = 30, + Level3_1 = 31, + Level4 = 40, + Level4_1 = 41, + Level5 = 50, + Level5_1 = 51, + Level5_2 = 52, + Level6 = 60, + Level6_1 = 61, + Level6_2 = 62, +} + +struct VPLevelDef { + level: VPLevel, + max_luma_sample_rate: u64, + max_luma_picture_size: u32, + max_avg_bitrate: f64, + max_cpb_size: f64, + min_compression_ratio: f64, + max_num_column_tiles: u8, + min_altref_distance: u32, + max_ref_frame_buffers: u8, +} + +static LEVELS: &[VPLevelDef] = { + const fn level( + a: VPLevel, + b: u64, + c: u32, + d: f64, + e: f64, + f: f64, + g: u8, + h: u32, + i: u8, + ) -> VPLevelDef { + VPLevelDef { + level: a, + max_luma_sample_rate: b, + max_luma_picture_size: c, + max_avg_bitrate: d, + max_cpb_size: e, + min_compression_ratio: f, + max_num_column_tiles: g, + min_altref_distance: h, + max_ref_frame_buffers: i, + } + } + use VPLevel::*; + #[rustfmt::skip] + &[ + level(Level1, 829440, 36864, 200., 400., 2., 1, 4, 8), + level(Level1_1, 2764800, 73728, 800., 1000., 2., 1, 4, 8), + level(Level2, 4608000, 122880, 1800., 1500., 2., 1, 4, 8), + level(Level2_1, 9216000, 245760, 3600., 2800., 2., 2, 4, 8), + level(Level3, 20736000, 552960, 7200., 6000., 2., 4, 4, 8), + level(Level3_1, 36864000, 983040, 12000., 10000., 2., 4, 4, 8), + level(Level4, 83558400, 2228224, 18000., 16000., 4., 4, 4, 8), + level(Level4_1, 160432128, 2228224, 30000., 18000., 4., 4, 5, 6), + level(Level5, 311951360, 8912896, 60000., 36000., 6., 8, 6, 4), + level(Level5_1, 588251136, 8912896, 120000., 46000., 8., 8, 10, 4), + level(Level5_2, 1176502272, 8912896, 180000., 90000., 8., 8, 10, 4), + level(Level6, 1176502272, 35651584, 180000., 90000., 8., 16, 10, 4), + level(Level6_1, 2353004544, 35651584, 240000., 180000., 8., 16, 10, 4), + level(Level6_2, 4706009088, 35651584, 480000., 360000., 8., 16, 10, 4), + ] +}; + +pub fn vp9_codec_param(_te: &TrackEntry) -> String { + format!("vp09.00.50.08") +} diff --git a/remuxer/src/demuxers/mod.rs b/remuxer/src/demuxers/mod.rs index 3107476..e9ad140 100644 --- a/remuxer/src/demuxers/mod.rs +++ b/remuxer/src/demuxers/mod.rs @@ -40,7 +40,7 @@ pub fn create_demuxer(container: ContainerFormat, reader: Box<dyn ReadSeek>) -> match container { ContainerFormat::Matroska | ContainerFormat::Webm => Box::new(MatroskaDemuxer::new(reader)), ContainerFormat::Flac => Box::new(FlacDemuxer::new(reader)), - ContainerFormat::Mpeg4 => todo!(), + ContainerFormat::MP4 => todo!(), } } pub fn create_demuxer_autodetect( diff --git a/remuxer/src/lib.rs b/remuxer/src/lib.rs index ad5de53..b4f6600 100644 --- a/remuxer/src/lib.rs +++ b/remuxer/src/lib.rs @@ -3,6 +3,7 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2026 metamuffin <metamuffin.org> */ +#![feature(stmt_expr_attributes)] pub mod demuxers; pub mod magic; @@ -17,5 +18,5 @@ pub enum ContainerFormat { Matroska, Webm, Flac, - Mpeg4, + MP4, } diff --git a/remuxer/src/muxers/matroska.rs b/remuxer/src/muxers/matroska.rs index 319ab21..5a2d024 100644 --- a/remuxer/src/muxers/matroska.rs +++ b/remuxer/src/muxers/matroska.rs @@ -6,11 +6,10 @@ use crate::muxers::FragmentMuxer; use anyhow::Result; -use std::io::Write; use winter_ebml::{Ebml, EbmlHeader, EbmlToVec, write_vint_slice}; use winter_matroska::{MatroskaFile, Segment}; -fn write_init_shared(out: &mut dyn Write, mut segment: Segment, webm: bool) -> Result<()> { +fn write_init_shared(out: &mut Vec<u8>, mut segment: Segment, webm: bool) -> Result<()> { segment.info.muxing_app = concat!(env!("CARGO_PKG_NAME"), "-", env!("CARGO_PKG_VERSION")).to_string(); if webm && let Some(tracks) = &mut segment.tracks { @@ -36,54 +35,49 @@ fn write_init_shared(out: &mut dyn Write, mut segment: Segment, webm: bool) -> R ..Default::default() }; - let mut buf = Vec::new(); - write_vint_slice(&mut buf, MatroskaFile::TAG_EBML_HEADER); - write_vint_slice(&mut buf, header.size() as u64); - header.write(&mut buf); + write_vint_slice(out, MatroskaFile::TAG_EBML_HEADER); + write_vint_slice(out, header.size() as u64); + header.write(out); - write_vint_slice(&mut buf, MatroskaFile::TAG_SEGMENT); - write_vint_slice(&mut buf, 0x00ff_ffff_ffff_ffff); + write_vint_slice(out, MatroskaFile::TAG_SEGMENT); + write_vint_slice(out, 0x00ff_ffff_ffff_ffff); - write_vint_slice(&mut buf, Segment::TAG_INFO); - write_vint_slice(&mut buf, segment.info.size() as u64); - segment.info.write(&mut buf); + write_vint_slice(out, Segment::TAG_INFO); + write_vint_slice(out, segment.info.size() as u64); + segment.info.write(out); if let Some(tracks) = segment.tracks { - write_vint_slice(&mut buf, Segment::TAG_TRACKS); - write_vint_slice(&mut buf, tracks.size() as u64); - tracks.write(&mut buf); + write_vint_slice(out, Segment::TAG_TRACKS); + write_vint_slice(out, tracks.size() as u64); + tracks.write(out); } - - out.write_all(&buf)?; Ok(()) } -fn write_frag_shared(out: &mut dyn Write, segment: Segment, _webm: bool) -> Result<()> { +fn write_frag_shared(out: &mut Vec<u8>, segment: Segment, _webm: bool) -> Result<()> { for cluster in segment.clusters { let cluster = cluster.to_vec(); - let mut buf = Vec::new(); - write_vint_slice(&mut buf, Segment::TAG_CLUSTERS); - write_vint_slice(&mut buf, cluster.len() as u64); - out.write_all(&buf)?; - out.write_all(&cluster)?; + write_vint_slice(out, Segment::TAG_CLUSTERS); + write_vint_slice(out, cluster.len() as u64); + out.extend(cluster); } Ok(()) } pub struct MatroskaFragmentMuxer; impl FragmentMuxer for MatroskaFragmentMuxer { - fn write_init(out: &mut dyn Write, segment: Segment) -> Result<()> { + fn write_init(out: &mut Vec<u8>, segment: Segment) -> Result<()> { write_init_shared(out, segment, false) } - fn write_frag(out: &mut dyn Write, segment: Segment) -> Result<()> { + fn write_frag(out: &mut Vec<u8>, segment: Segment) -> Result<()> { write_frag_shared(out, segment, false) } } pub struct WebmFragmentMuxer; impl FragmentMuxer for WebmFragmentMuxer { - fn write_init(out: &mut dyn Write, segment: Segment) -> Result<()> { + fn write_init(out: &mut Vec<u8>, segment: Segment) -> Result<()> { write_init_shared(out, segment, true) } - fn write_frag(out: &mut dyn Write, segment: Segment) -> Result<()> { + fn write_frag(out: &mut Vec<u8>, segment: Segment) -> Result<()> { write_frag_shared(out, segment, true) } } diff --git a/remuxer/src/muxers/mod.rs b/remuxer/src/muxers/mod.rs index 9349b77..d668ab2 100644 --- a/remuxer/src/muxers/mod.rs +++ b/remuxer/src/muxers/mod.rs @@ -5,43 +5,42 @@ */ pub mod matroska; -pub mod mpeg4; +pub mod mp4; use crate::{ ContainerFormat, muxers::{ matroska::{MatroskaFragmentMuxer, WebmFragmentMuxer}, - mpeg4::Mpeg4FragmentMuxer, + mp4::MP4FragmentMuxer, }, }; use anyhow::Result; -use std::io::Write; use winter_matroska::Segment; pub trait FragmentMuxer { - fn write_init(out: &mut dyn Write, segment: Segment) -> Result<()>; - fn write_frag(out: &mut dyn Write, segment: Segment) -> Result<()>; + fn write_init(out: &mut Vec<u8>, segment: Segment) -> Result<()>; + fn write_frag(out: &mut Vec<u8>, segment: Segment) -> Result<()>; } -pub fn write_init(container: ContainerFormat, out: &mut dyn Write, segment: Segment) -> Result<()> { +pub fn write_init(container: ContainerFormat, out: &mut Vec<u8>, segment: Segment) -> Result<()> { match container { ContainerFormat::Matroska => MatroskaFragmentMuxer::write_init(out, segment), ContainerFormat::Webm => WebmFragmentMuxer::write_init(out, segment), - ContainerFormat::Mpeg4 => Mpeg4FragmentMuxer::write_init(out, segment), + ContainerFormat::MP4 => MP4FragmentMuxer::write_init(out, segment), _ => unimplemented!(), } } -pub fn write_frag(container: ContainerFormat, out: &mut dyn Write, segment: Segment) -> Result<()> { +pub fn write_frag(container: ContainerFormat, out: &mut Vec<u8>, segment: Segment) -> Result<()> { match container { ContainerFormat::Matroska => MatroskaFragmentMuxer::write_frag(out, segment), ContainerFormat::Webm => WebmFragmentMuxer::write_frag(out, segment), - ContainerFormat::Mpeg4 => Mpeg4FragmentMuxer::write_frag(out, segment), + ContainerFormat::MP4 => MP4FragmentMuxer::write_frag(out, segment), _ => unimplemented!(), } } pub fn write_init_frag( container: ContainerFormat, - out: &mut dyn Write, + out: &mut Vec<u8>, segment: Segment, ) -> Result<()> { write_init( diff --git a/remuxer/src/muxers/mp4.rs b/remuxer/src/muxers/mp4.rs new file mode 100644 index 0000000..8d33f7f --- /dev/null +++ b/remuxer/src/muxers/mp4.rs @@ -0,0 +1,63 @@ +/* + 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::muxers::FragmentMuxer; +use anyhow::Result; +use winter_matroska::Segment; +use winter_mp4::*; + +pub struct MP4FragmentMuxer; +impl FragmentMuxer for MP4FragmentMuxer { + fn write_init(out: &mut Vec<u8>, segment: Segment) -> Result<()> { + let mut out = BoxW::new(out); + out.write(FileType { + major_brand: *b"mp41", + minor_version: 0, + compatible_brands: &[], + }); + out.write(Movie(|moov| { + moov.write(MovieHeader { + ..Default::default() + }); + moov.write(Track(|trak| { + trak.write(TrackHeader { + ..Default::default() + }); + trak.write(Edit(|edts| { + edts.write(EditList { entries: &[] }); + })); + trak.write(Media(|mdia| { + mdia.write(Handler { + handler_type: 0, + name: "blub".to_string(), + }); + mdia.write(MediaInformation(|minf| { + minf.write(DataInformation(|dinf| {})); + minf.write(SampleTable(|stbl| {})); + })); + })); + })); + })); + + Ok(()) + } + fn write_frag(out: &mut Vec<u8>, segment: Segment) -> Result<()> { + let mut out = BoxW::new(out); + + out.write(MovieFragment(|moof| { + moof.write(MovieFragmentHeader { sequence_number: 0 }); + moof.write(TrackFragment(|traf| { + traf.write(TrackFragmentHeader { track_id: 0 }); + traf.write(TrackFragmentBaseMediaDecodeTime { + base_media_decode_time: 0, + }); + traf.write(TrackRun { data_offset: 0 }); + })); + })); + + Ok(()) + } +} diff --git a/remuxer/src/muxers/mpeg4.rs b/remuxer/src/muxers/mpeg4.rs deleted file mode 100644 index b4c8fa8..0000000 --- a/remuxer/src/muxers/mpeg4.rs +++ /dev/null @@ -1,20 +0,0 @@ -/* - 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::muxers::FragmentMuxer; -use anyhow::Result; -use std::io::Write; -use winter_matroska::Segment; - -pub struct Mpeg4FragmentMuxer; -impl FragmentMuxer for Mpeg4FragmentMuxer { - fn write_init(out: &mut dyn Write, segment: Segment) -> Result<()> { - Ok(()) - } - fn write_frag(out: &mut dyn Write, segment: Segment) -> Result<()> { - Ok(()) - } -} diff --git a/stream/src/fragment.rs b/stream/src/fragment.rs index de7cd11..57561b2 100644 --- a/stream/src/fragment.rs +++ b/stream/src/fragment.rs @@ -220,7 +220,7 @@ fn map_container(container: StreamContainer) -> ContainerFormat { StreamContainer::WebM => ContainerFormat::Webm, StreamContainer::Matroska => ContainerFormat::Matroska, StreamContainer::WebVTT => todo!(), - StreamContainer::MPEG4 => ContainerFormat::Mpeg4, + StreamContainer::MPEG4 => ContainerFormat::MP4, StreamContainer::JVTT => todo!(), } } |