diff options
author | metamuffin <metamuffin@disroot.org> | 2025-09-25 04:00:51 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-09-25 04:00:51 +0200 |
commit | f8d4c438d10450ead56c0082b037e466ef5f9f24 (patch) | |
tree | 703612747f06b91f063dcd9e8a778675e6aa1303 /remuxer | |
parent | 0fe48987c336b9b50cba09355aa3c1cf11740edc (diff) | |
download | jellything-f8d4c438d10450ead56c0082b037e466ef5f9f24.tar jellything-f8d4c438d10450ead56c0082b037e466ef5f9f24.tar.bz2 jellything-f8d4c438d10450ead56c0082b037e466ef5f9f24.tar.zst |
start media processing refactor
Diffstat (limited to 'remuxer')
-rw-r--r-- | remuxer/Cargo.toml | 5 | ||||
-rw-r--r-- | remuxer/src/bin/average_cluster_duration.rs | 38 | ||||
-rw-r--r-- | remuxer/src/demuxers/flac.rs | 13 | ||||
-rw-r--r-- | remuxer/src/demuxers/matroska.rs | 15 | ||||
-rw-r--r-- | remuxer/src/demuxers/mod.rs | 14 | ||||
-rw-r--r-- | remuxer/src/lib.rs | 3 | ||||
-rw-r--r-- | remuxer/src/muxers/matroska.rs | 63 | ||||
-rw-r--r-- | remuxer/src/muxers/mod.rs | 40 |
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!(), + } +} |