aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2026-03-05 22:42:10 +0100
committermetamuffin <metamuffin@disroot.org>2026-03-05 22:42:10 +0100
commit711f56963c114565a2be8848a6070b28a168c5ff (patch)
tree4dd1441690ff4237b9d7392132c6e3e229bc8075
parent9910c7fd9634a2ce1cd074192a212e8d7e06978e (diff)
downloadjellything-711f56963c114565a2be8848a6070b28a168c5ff.tar
jellything-711f56963c114565a2be8848a6070b28a168c5ff.tar.bz2
jellything-711f56963c114565a2be8848a6070b28a168c5ff.tar.zst
more boxes
-rw-r--r--remuxer/mp4/src/boxes.rs193
-rw-r--r--remuxer/mp4/src/lib.rs11
-rw-r--r--remuxer/src/muxers/mp4.rs107
3 files changed, 262 insertions, 49 deletions
diff --git a/remuxer/mp4/src/boxes.rs b/remuxer/mp4/src/boxes.rs
index 4c14eb1..f695d98 100644
--- a/remuxer/mp4/src/boxes.rs
+++ b/remuxer/mp4/src/boxes.rs
@@ -27,6 +27,7 @@ container_box!(DataInformation, b"dinf");
container_box!(SampleTable, b"stbl");
container_box!(MovieFragment, b"stbl");
container_box!(TrackFragment, b"stbl");
+container_box!(MovieExtends, b"mvex");
pub struct FileType<'a> {
pub major_brand: [u8; 4],
@@ -106,7 +107,6 @@ impl Default for MovieHeader {
}
}
-#[derive(Default)]
pub struct TrackHeader {
pub creation_time: u64,
pub modification_time: u64,
@@ -140,10 +140,24 @@ impl WriteBox for TrackHeader {
buf.extend(self.height.to_be_bytes());
}
}
-
-pub struct EditList<'a> {
- pub entries: &'a [EditListEntry],
+impl Default for TrackHeader {
+ fn default() -> Self {
+ Self {
+ creation_time: 0,
+ modification_time: 0,
+ track_id: 0,
+ duration: 0,
+ layer: 0,
+ alternate_group: 0,
+ volume: 0,
+ matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x00010000],
+ width: 0,
+ height: 0,
+ }
+ }
}
+
+pub struct EditList<'a>(pub &'a [EditListEntry]);
pub struct EditListEntry {
pub edit_duration: u64,
pub media_time: u64,
@@ -153,8 +167,8 @@ impl WriteBox for EditList<'_> {
const BOXTYPE: [u8; 4] = *b"elst";
const VERSION: Option<u8> = Some(1);
fn write(&self, buf: &mut Vec<u8>) {
- buf.extend((self.entries.len() as u32).to_be_bytes());
- for e in self.entries {
+ buf.extend((self.0.len() as u32).to_be_bytes());
+ for e in self.0 {
buf.extend(e.edit_duration.to_be_bytes());
buf.extend(e.media_time.to_be_bytes());
buf.extend(e.media_rate.to_be_bytes());
@@ -189,27 +203,28 @@ impl WriteBox for MediaHeader {
}
}
-pub struct Handler {
- pub handler_type: u32,
- pub name: String,
+pub struct Handler<'a> {
+ pub handler_type: [u8; 4],
+ pub name: &'a str,
}
-impl WriteBox for Handler {
+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(self.handler_type);
buf.extend([0; 12]);
buf.extend(self.name.as_bytes());
+ buf.push(0);
}
}
#[derive(Default)]
-pub struct VideoMediaHandler {
+pub struct VideoMediaHeader {
pub graphicsmode: u16,
pub opcolor: [u16; 3],
}
-impl WriteBox for VideoMediaHandler {
+impl WriteBox for VideoMediaHeader {
const BOXTYPE: [u8; 4] = *b"vmhd";
const VERSION: Option<u8> = Some(0);
fn write(&self, buf: &mut Vec<u8>) {
@@ -221,10 +236,10 @@ impl WriteBox for VideoMediaHandler {
}
#[derive(Default)]
-pub struct SoundMediaHandler {
+pub struct SoundMediaHeader {
pub balance: u16,
}
-impl WriteBox for SoundMediaHandler {
+impl WriteBox for SoundMediaHeader {
const BOXTYPE: [u8; 4] = *b"smhd";
const VERSION: Option<u8> = Some(0);
fn write(&self, buf: &mut Vec<u8>) {
@@ -232,14 +247,29 @@ impl WriteBox for SoundMediaHandler {
}
}
-pub struct DataReference<'a> {
- pub entries: &'a [()],
+pub struct DataReference<'a>(pub &'a [DataReferenceEntry]);
+pub enum DataReferenceEntry {
+ URL(String),
+ URN { name: String, location: String },
+ Imda(u32),
+ SeqNumImda,
}
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]);
+ buf.extend((self.0.len() as u32).to_be_bytes());
+ for e in self.0 {
+ match e {
+ DataReferenceEntry::URL(x) if x.is_empty() => {
+ buf.extend(12u32.to_be_bytes());
+ buf.extend(b"url ");
+ buf.push(0); // version
+ buf.extend([0, 0, 1]); // flags
+ }
+ _ => todo!(),
+ }
+ }
}
}
@@ -353,9 +383,130 @@ impl WriteBox for ChunkOffset<'_> {
}
}
-pub struct SampleDescription;
-impl WriteBox for SampleDescription {
+pub struct SyncSample<'a>(pub &'a [u32]);
+impl WriteBox for SyncSample<'_> {
+ const BOXTYPE: [u8; 4] = *b"stss";
+ const VERSION: Option<u8> = Some(0);
+ fn write(&self, buf: &mut Vec<u8>) {
+ buf.extend((self.0.len() as u32).to_be_bytes());
+ for e in self.0 {
+ buf.extend(e.to_be_bytes());
+ }
+ }
+}
+
+pub struct SampleDescription<T: Fn(&mut BoxW)>(pub T);
+impl<T: Fn(&mut BoxW)> WriteBox for SampleDescription<T> {
const BOXTYPE: [u8; 4] = *b"stsd";
const VERSION: Option<u8> = Some(0);
- fn write(&self, _buf: &mut Vec<u8>) {}
+ fn write(&self, buf: &mut Vec<u8>) {
+ buf.extend(1u32.to_be_bytes());
+ self.0(&mut BoxW(buf))
+ }
+}
+
+pub struct AVCSampleEntry<'a> {
+ pub visual: VisualSampleEntry,
+ pub config: AVCConfiguration<'a>,
+}
+impl WriteBox for AVCSampleEntry<'_> {
+ const BOXTYPE: [u8; 4] = *b"avc1";
+ fn write(&self, buf: &mut Vec<u8>) {
+ self.visual.write(buf);
+ BoxW(buf).write(&self.config);
+ }
+}
+
+pub struct AVCConfiguration<'a>(pub &'a [u8]);
+impl WriteBox for AVCConfiguration<'_> {
+ const BOXTYPE: [u8; 4] = *b"avcC";
+ fn write(&self, buf: &mut Vec<u8>) {
+ buf.extend(self.0);
+ }
+}
+
+#[derive(Default)]
+pub struct SampleEntry {
+ pub data_reference_index: u16,
+}
+impl WriteBox for SampleEntry {
+ const BOXTYPE: [u8; 4] = [0; 4];
+ fn write(&self, buf: &mut Vec<u8>) {
+ buf.extend([0; 6]);
+ buf.extend(self.data_reference_index.to_be_bytes())
+ }
+}
+
+pub struct VisualSampleEntry {
+ pub sample: SampleEntry,
+ pub width: u16,
+ pub height: u16,
+ pub horizresolution: u32,
+ pub vertresolution: u32,
+ pub frame_count: u16,
+ pub compressorname: [u8; 32],
+ pub depth: u16,
+}
+impl WriteBox for VisualSampleEntry {
+ const BOXTYPE: [u8; 4] = [0; 4];
+ fn write(&self, buf: &mut Vec<u8>) {
+ self.sample.write(buf);
+ buf.extend([0; 2]);
+ buf.extend([0; 2]);
+ buf.extend([0; 12]);
+ buf.extend(self.width.to_be_bytes());
+ buf.extend(self.height.to_be_bytes());
+ buf.extend(self.horizresolution.to_be_bytes());
+ buf.extend(self.vertresolution.to_be_bytes());
+ buf.extend([0; 4]);
+ buf.extend(self.frame_count.to_be_bytes());
+ buf.extend(self.compressorname);
+ buf.extend(self.depth.to_be_bytes());
+ buf.extend((-1i16).to_be_bytes());
+ }
+}
+impl Default for VisualSampleEntry {
+ fn default() -> Self {
+ Self {
+ sample: SampleEntry::default(),
+ width: 0,
+ height: 0,
+ horizresolution: 0x00480000,
+ vertresolution: 0x00480000,
+ frame_count: 1,
+ compressorname: [0; 32],
+ depth: 0x0018,
+ }
+ }
+}
+
+pub struct MovieExtendsHeader {
+ pub fragment_duration: u64,
+}
+impl WriteBox for MovieExtendsHeader {
+ const BOXTYPE: [u8; 4] = *b"mehd";
+ const VERSION: Option<u8> = Some(1);
+ fn write(&self, buf: &mut Vec<u8>) {
+ buf.extend(self.fragment_duration.to_be_bytes())
+ }
+}
+
+#[derive(Default)]
+pub struct TrackExtends {
+ pub track_id: u32,
+ pub default_sample_description_index: u32,
+ pub default_sample_duration: u32,
+ pub default_sample_size: u32,
+ pub default_sample_flags: u32,
+}
+impl WriteBox for TrackExtends {
+ const BOXTYPE: [u8; 4] = *b"trex";
+ const VERSION: Option<u8> = Some(0);
+ fn write(&self, buf: &mut Vec<u8>) {
+ buf.extend(self.track_id.to_be_bytes());
+ buf.extend(self.default_sample_description_index.to_be_bytes());
+ buf.extend(self.default_sample_duration.to_be_bytes());
+ buf.extend(self.default_sample_size.to_be_bytes());
+ buf.extend(self.default_sample_flags.to_be_bytes());
+ }
}
diff --git a/remuxer/mp4/src/lib.rs b/remuxer/mp4/src/lib.rs
index cb728fa..7af8417 100644
--- a/remuxer/mp4/src/lib.rs
+++ b/remuxer/mp4/src/lib.rs
@@ -35,3 +35,14 @@ pub trait WriteBox {
0
}
}
+
+impl<T: WriteBox> WriteBox for &T {
+ const BOXTYPE: [u8; 4] = T::BOXTYPE;
+ const VERSION: Option<u8> = T::VERSION;
+ fn write(&self, buf: &mut Vec<u8>) {
+ (**self).write(buf);
+ }
+ fn flags(&self) -> u32 {
+ (**self).flags()
+ }
+}
diff --git a/remuxer/src/muxers/mp4.rs b/remuxer/src/muxers/mp4.rs
index 5975921..bd36789 100644
--- a/remuxer/src/muxers/mp4.rs
+++ b/remuxer/src/muxers/mp4.rs
@@ -6,7 +6,7 @@
use crate::muxers::FragmentMuxer;
use anyhow::Result;
-use winter_matroska::Segment;
+use winter_matroska::{Segment, TrackType};
use winter_mp4::*;
pub struct MP4FragmentMuxer;
@@ -18,42 +18,93 @@ impl FragmentMuxer for MP4FragmentMuxer {
minor_version: 512,
compatible_brands: &[*b"iso5", *b"iso6", *b"mp41"],
});
+
out.write(Movie(|moov| {
moov.write(MovieHeader {
+ timescale: 1_000_000_000,
..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(),
+ for track in &segment.tracks.as_ref().unwrap().entries {
+ moov.write(Track(|trak| {
+ trak.write(TrackHeader {
+ width: track.video.as_ref().map_or(0, |v| v.pixel_width as u32),
+ height: track.video.as_ref().map_or(0, |v| v.pixel_height as u32),
+ volume: track.audio.as_ref().map_or(0, |_| 0x0100),
+ track_id: track.track_number as u32,
+ ..Default::default()
});
- mdia.write(MediaInformation(|minf| {
- minf.write(VideoMediaHandler {
- ..Default::default()
+ // trak.write(Edit(|edts| {
+ // edts.write(EditList(&[]));
+ // }));
+ trak.write(Media(|mdia| {
+ mdia.write(Handler {
+ handler_type: match track.track_type {
+ TrackType::Video => *b"vide",
+ TrackType::Audio => *b"soun",
+ _ => todo!(),
+ },
+ name: &format!("ISO Media file produced by {}", env!("CARGO_PKG_NAME")),
});
- minf.write(DataInformation(|dinf| {
- dinf.write(DataReference { entries: &[] });
- }));
- minf.write(SampleTable(|stbl| {
- stbl.write(SampleDescription);
- stbl.write(TimeToSample(&[]));
- stbl.write(TimeToChunk(&[]));
- stbl.write(ChunkOffset(&[]));
- stbl.write(SampleSize {
- sample_size: 0,
- entries: &[],
- });
+ mdia.write(MediaInformation(|minf| {
+ match track.track_type {
+ TrackType::Video => minf.write(VideoMediaHeader::default()),
+ TrackType::Audio => minf.write(SoundMediaHeader::default()),
+ _ => todo!(),
+ };
+ minf.write(DataInformation(|dinf| {
+ dinf.write(DataReference(&[DataReferenceEntry::URL(
+ String::default(),
+ )]));
+ }));
+ minf.write(SampleTable(|stbl| {
+ stbl.write(SampleDescription(|stsd| {
+ if let Some(video) = &track.video
+ && track.codec_id == "V_MPEG4/ISO/AVC"
+ {
+ stsd.write(AVCSampleEntry {
+ visual: VisualSampleEntry {
+ sample: SampleEntry {
+ data_reference_index: 1,
+ },
+ width: video.pixel_width as u16,
+ height: video.pixel_height as u16,
+ ..Default::default()
+ },
+ config: AVCConfiguration(
+ track.codec_private.as_ref().unwrap(),
+ ),
+ });
+ } else {
+ todo!()
+ }
+ }));
+ stbl.write(TimeToSample(&[]));
+ stbl.write(TimeToChunk(&[]));
+ stbl.write(ChunkOffset(&[]));
+ stbl.write(SyncSample(&[]));
+ stbl.write(SampleSize {
+ sample_size: 0,
+ entries: &[],
+ });
+ }));
}));
}));
}));
- }));
+ moov.write(MovieExtends(|mvex| {
+ mvex.write(MovieExtendsHeader {
+ fragment_duration: (segment.info.duration.unwrap_or_default()
+ * segment.info.timestamp_scale as f64
+ * 1e-9) as u64,
+ });
+ for track in &segment.tracks.as_ref().unwrap().entries {
+ mvex.write(TrackExtends {
+ track_id: track.track_number as u32,
+ default_sample_description_index: 1,
+ ..Default::default()
+ });
+ }
+ }));
+ }
}));
Ok(())