aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-03-05 14:58:26 +0100
committermetamuffin <metamuffin@disroot.org>2026-03-05 14:58:26 +0100
commit8fa153c30c00210ac6512d73268a3adc930c5fbd (patch)
treeaeaea006953fd1add29c675dab3d3deb2a009ea5
parentec2228f6ee9349c0866483abc21124dae31f2b52 (diff)
downloadjellything-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.lock5
-rw-r--r--Cargo.toml1
-rw-r--r--remuxer/Cargo.toml1
-rw-r--r--remuxer/mp4/Cargo.toml6
-rw-r--r--remuxer/mp4/src/boxes.rs268
-rw-r--r--remuxer/mp4/src/lib.rs37
-rw-r--r--remuxer/src/codec_param/mod.rs7
-rw-r--r--remuxer/src/codec_param/vp9.rs78
-rw-r--r--remuxer/src/demuxers/mod.rs2
-rw-r--r--remuxer/src/lib.rs3
-rw-r--r--remuxer/src/muxers/matroska.rs46
-rw-r--r--remuxer/src/muxers/mod.rs19
-rw-r--r--remuxer/src/muxers/mp4.rs63
-rw-r--r--remuxer/src/muxers/mpeg4.rs20
-rw-r--r--stream/src/fragment.rs2
15 files changed, 497 insertions, 61 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 1ee2c06..8cc80bd 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 64da828..794722f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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!(),
}
}