aboutsummaryrefslogtreecommitdiff
path: root/remuxer
diff options
context:
space:
mode:
Diffstat (limited to 'remuxer')
-rw-r--r--remuxer/Cargo.toml5
-rw-r--r--remuxer/src/bin/average_cluster_duration.rs38
-rw-r--r--remuxer/src/demuxers/flac.rs13
-rw-r--r--remuxer/src/demuxers/matroska.rs15
-rw-r--r--remuxer/src/demuxers/mod.rs14
-rw-r--r--remuxer/src/lib.rs3
-rw-r--r--remuxer/src/muxers/matroska.rs63
-rw-r--r--remuxer/src/muxers/mod.rs40
8 files changed, 180 insertions, 11 deletions
diff --git a/remuxer/Cargo.toml b/remuxer/Cargo.toml
index 24cd9ab..a8fd18f 100644
--- a/remuxer/Cargo.toml
+++ b/remuxer/Cargo.toml
@@ -4,7 +4,6 @@ version = "0.1.0"
edition = "2024"
[dependencies]
-jellycache = { path = "../cache" }
hex = "0.4.3"
anyhow = "1.0.95"
@@ -15,4 +14,6 @@ serde = { version = "1.0.217", features = ["derive"] }
bincode = { version = "2.0.0-rc.3", features = ["serde"] }
winter-ebml = { git = "https://codeberg.org/metamuffin/ebml-rs", package = "ebml" }
-winter-matroska = { git = "https://codeberg.org/metamuffin/ebml-rs", package = "matroska" }
+winter-matroska = { git = "https://codeberg.org/metamuffin/ebml-rs", package = "matroska", features = [
+ "serde",
+] }
diff --git a/remuxer/src/bin/average_cluster_duration.rs b/remuxer/src/bin/average_cluster_duration.rs
new file mode 100644
index 0000000..69bb79c
--- /dev/null
+++ b/remuxer/src/bin/average_cluster_duration.rs
@@ -0,0 +1,38 @@
+/*
+ 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) 2025 metamuffin <metamuffin.org>
+*/
+
+use anyhow::{Result, anyhow};
+use jellyremuxer::demuxers::{Demuxer, DemuxerNew, matroska::MatroskaDemuxer};
+use std::{env::args, fs::File};
+
+fn main() -> Result<()> {
+ env_logger::init_from_env("LOG");
+ let path = args().nth(1).ok_or(anyhow!("first arg is input path"))?;
+ let file = File::open(path)?;
+ let mut reader = MatroskaDemuxer::new(Box::new(file));
+
+ let info = reader.info()?;
+
+ reader.seek_cluster(None)?;
+ let mut num_clusters = 0;
+ let mut last_ts = 0;
+ let mut total_size = 0;
+ while let Some((_, cluster)) = reader.read_cluster()? {
+ last_ts = cluster.timestamp * info.timestamp_scale;
+ num_clusters += 1;
+ total_size += cluster
+ .simple_blocks
+ .iter()
+ .map(|b| b.data.len())
+ .sum::<usize>() as u64
+ }
+
+ let average_duration = (last_ts / num_clusters) as f64 / 1_000_000_000.;
+ let average_size = (total_size / num_clusters) as f64 / 1_000_000.;
+ println!("{average_duration:>6.02}s {average_size:>6.02}MB");
+
+ Ok(())
+}
diff --git a/remuxer/src/demuxers/flac.rs b/remuxer/src/demuxers/flac.rs
index 04d15e0..b397a8c 100644
--- a/remuxer/src/demuxers/flac.rs
+++ b/remuxer/src/demuxers/flac.rs
@@ -7,7 +7,7 @@
use crate::demuxers::{Demuxer, DemuxerNew, ReadSeek};
use anyhow::{Result, anyhow, bail};
use std::io::{BufReader, Read, Seek, SeekFrom};
-use winter_matroska::{Audio, Cluster, TrackEntry, TrackType, Tracks};
+use winter_matroska::{Audio, Cluster, Info, TrackEntry, TrackType, Tracks};
pub struct FlacDemuxer {
reader: BufReader<Box<dyn ReadSeek>>,
@@ -28,6 +28,7 @@ struct MetadataBlock {
r#type: u8,
data: Vec<u8>,
}
+#[allow(unused)]
impl MetadataBlock {
const TY_STREAMINFO: u8 = 0;
const TY_PADDING: u8 = 1;
@@ -38,6 +39,7 @@ impl MetadataBlock {
const TY_PICTURE: u8 = 6;
}
+#[allow(unused)]
struct StreamInfo {
min_block_size: u16,
max_block_size: u16,
@@ -109,6 +111,13 @@ impl FlacDemuxer {
}
}
impl Demuxer for FlacDemuxer {
+ fn info(&mut self) -> Result<Info> {
+ Ok(Info {
+ duration: Some(120000.), // TODO
+ timestamp_scale: 1_000_000,
+ ..Default::default()
+ })
+ }
fn tracks(&mut self) -> Result<Option<Tracks>> {
let si = self.stream_info()?;
let mut buf = Vec::new();
@@ -243,8 +252,6 @@ impl Demuxer for FlacDemuxer {
let mut crc_buf = [0u8; 1];
self.reader.read_exact(&mut crc_buf)?;
-
-
Ok(None)
}
}
diff --git a/remuxer/src/demuxers/matroska.rs b/remuxer/src/demuxers/matroska.rs
index 6301f15..b70054d 100644
--- a/remuxer/src/demuxers/matroska.rs
+++ b/remuxer/src/demuxers/matroska.rs
@@ -7,7 +7,7 @@
use crate::demuxers::{Demuxer, DemuxerNew, ReadSeek};
use anyhow::{Context, Result, anyhow, bail};
use log::debug;
-use std::io::{BufReader, Read, Seek, SeekFrom};
+use std::io::{BufReader, ErrorKind, Read, Seek, SeekFrom};
use winter_ebml::{Ebml, EbmlHeader, VintReadExt, read_vint_slice};
use winter_matroska::{
Attachments, Chapters, Cluster, Cues, Info, MatroskaFile, SeekHead, Segment, Tags, Tracks,
@@ -47,6 +47,7 @@ impl MatroskaDemuxer {
if !matches!(header.doc_type.as_str(), "matroska" | "webm") {
bail!("file is {:?} but not matroska/webm", header.doc_type)
}
+ eprintln!("{header:?}");
if header.ebml_max_id_length != 4 {
bail!(
"file has invalid EBMLMaxIDLength of {}",
@@ -156,8 +157,10 @@ impl MatroskaDemuxer {
}
}
impl Demuxer for MatroskaDemuxer {
- fn info(&mut self) -> Result<Option<Info>> {
- self.read_segment_tag("Info", Segment::TAG_INFO)
+ fn info(&mut self) -> Result<Info> {
+ Ok(self
+ .read_segment_tag("Info", Segment::TAG_INFO)?
+ .ok_or(anyhow!("info missing"))?)
}
fn tracks(&mut self) -> Result<Option<Tracks>> {
self.read_segment_tag("Tracks", Segment::TAG_TRACKS)
@@ -187,7 +190,11 @@ impl Demuxer for MatroskaDemuxer {
loop {
let position = self.reader.stream_position()?;
// TODO handle eof
- let tag = self.reader.read_vint()?;
+ let tag = match self.reader.read_vint() {
+ Ok(val) => val,
+ Err(e) if e.kind() == ErrorKind::UnexpectedEof => return Ok(None),
+ Err(e) => return Err(e.into()),
+ };
let size = self.reader.read_vint()?;
if tag != Segment::TAG_CLUSTERS {
self.reader.seek_relative(size as i64)?;
diff --git a/remuxer/src/demuxers/mod.rs b/remuxer/src/demuxers/mod.rs
index 8940ca5..f001250 100644
--- a/remuxer/src/demuxers/mod.rs
+++ b/remuxer/src/demuxers/mod.rs
@@ -10,9 +10,10 @@ pub mod matroska;
use crate::{
ContainerFormat,
demuxers::{flac::FlacDemuxer, matroska::MatroskaDemuxer},
+ magic::detect_container_format,
};
use anyhow::Result;
-use std::io::{Read, Seek};
+use std::io::{Read, Seek, SeekFrom};
use winter_matroska::{Attachments, Chapters, Cluster, Cues, Info, Tags, Tracks};
pub trait ReadSeek: Read + Seek {}
@@ -24,7 +25,7 @@ pub trait DemuxerNew: Demuxer + Sized {
#[rustfmt::skip]
pub trait Demuxer {
- fn info(&mut self) -> Result<Option<Info>> { Ok(None) }
+ fn info(&mut self) -> Result<Info>;
fn tracks(&mut self) -> Result<Option<Tracks>> { Ok(None) }
fn chapters(&mut self) -> Result<Option<Chapters>> { Ok(None) }
fn attachments(&mut self) -> Result<Option<Attachments>> { Ok(None) }
@@ -41,3 +42,12 @@ pub fn create_demuxer(container: ContainerFormat, reader: Box<dyn ReadSeek>) ->
ContainerFormat::Flac => Box::new(FlacDemuxer::new(reader)),
}
}
+pub fn create_demuxer_autodetect(
+ mut reader: Box<dyn ReadSeek>,
+) -> Result<Option<Box<dyn Demuxer>>> {
+ let Some(container) = detect_container_format(&mut reader)? else {
+ return Ok(None);
+ };
+ reader.seek(SeekFrom::Start(0))?;
+ Ok(Some(create_demuxer(container, reader)))
+}
diff --git a/remuxer/src/lib.rs b/remuxer/src/lib.rs
index 049c12f..13ae06f 100644
--- a/remuxer/src/lib.rs
+++ b/remuxer/src/lib.rs
@@ -6,6 +6,9 @@
pub mod demuxers;
pub mod magic;
+pub mod muxers;
+
+pub use winter_matroska as matroska;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ContainerFormat {
diff --git a/remuxer/src/muxers/matroska.rs b/remuxer/src/muxers/matroska.rs
new file mode 100644
index 0000000..47210c9
--- /dev/null
+++ b/remuxer/src/muxers/matroska.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) 2025 metamuffin <metamuffin.org>
+*/
+
+use crate::muxers::FragmentMuxer;
+use anyhow::Result;
+use std::io::Write;
+use winter_ebml::{EbmlHeader, EbmlToVec};
+use winter_matroska::{Cluster, Info, MatroskaFile, Segment, Tracks};
+
+fn write_fragment_shared(
+ out: &mut dyn Write,
+ info: Info,
+ tracks: Tracks,
+ cluster: Cluster,
+ webm: bool,
+) -> Result<()> {
+ let file = MatroskaFile {
+ ebml_header: EbmlHeader {
+ ebml_version: 1,
+ ebml_read_version: 1,
+ ebml_max_id_length: 4,
+ ebml_max_size_length: 8,
+ doc_type: if webm { "webm" } else { "matroska" }.to_string(),
+ doc_type_version: 4,
+ doc_type_read_version: 2,
+ ..Default::default()
+ },
+ segment: Segment {
+ info,
+ tracks: Some(tracks),
+ clusters: vec![cluster],
+ ..Default::default()
+ },
+ };
+ out.write_all(&file.to_vec())?;
+ Ok(())
+}
+
+pub struct MatroskaFragmentMuxer;
+impl FragmentMuxer for MatroskaFragmentMuxer {
+ fn write_fragment(
+ out: &mut dyn Write,
+ info: Info,
+ tracks: Tracks,
+ cluster: Cluster,
+ ) -> Result<()> {
+ write_fragment_shared(out, info, tracks, cluster, false)
+ }
+}
+pub struct WebmFragmentMuxer;
+impl FragmentMuxer for WebmFragmentMuxer {
+ fn write_fragment(
+ out: &mut dyn Write,
+ info: Info,
+ tracks: Tracks,
+ cluster: Cluster,
+ ) -> Result<()> {
+ write_fragment_shared(out, info, tracks, cluster, true)
+ }
+}
diff --git a/remuxer/src/muxers/mod.rs b/remuxer/src/muxers/mod.rs
new file mode 100644
index 0000000..8752373
--- /dev/null
+++ b/remuxer/src/muxers/mod.rs
@@ -0,0 +1,40 @@
+/*
+ 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) 2025 metamuffin <metamuffin.org>
+*/
+
+pub mod matroska;
+
+use crate::{
+ ContainerFormat,
+ muxers::matroska::{MatroskaFragmentMuxer, WebmFragmentMuxer},
+};
+use anyhow::Result;
+use std::io::Write;
+use winter_matroska::{Cluster, Info, Tracks};
+
+pub trait FragmentMuxer {
+ fn write_fragment(
+ out: &mut dyn Write,
+ info: Info,
+ tracks: Tracks,
+ cluster: Cluster,
+ ) -> Result<()>;
+}
+
+pub fn write_fragment(
+ container: ContainerFormat,
+ out: &mut dyn Write,
+ info: Info,
+ tracks: Tracks,
+ cluster: Cluster,
+) -> Result<()> {
+ match container {
+ ContainerFormat::Matroska => {
+ MatroskaFragmentMuxer::write_fragment(out, info, tracks, cluster)
+ }
+ ContainerFormat::Webm => WebmFragmentMuxer::write_fragment(out, info, tracks, cluster),
+ _ => unimplemented!(),
+ }
+}