From cdf95d7b80bd2b78895671da8f462145bb5db522 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 16 Apr 2025 17:24:08 +0200 Subject: webm and mpeg4 fragments semi fixed --- remuxer/src/lib.rs | 5 ++- remuxer/src/matroska_to_mpeg4.rs | 36 +++++++++++++++++ remuxer/src/matroska_to_webm.rs | 84 ++++++++++++++++++++++++++++++++++++++++ remuxer/src/mpeg4.rs | 34 ---------------- stream/src/fragment.rs | 14 +++++-- web/script/jshelper | 2 +- 6 files changed, 135 insertions(+), 40 deletions(-) create mode 100644 remuxer/src/matroska_to_mpeg4.rs create mode 100644 remuxer/src/matroska_to_webm.rs delete mode 100644 remuxer/src/mpeg4.rs diff --git a/remuxer/src/lib.rs b/remuxer/src/lib.rs index c20197f..931d5e6 100644 --- a/remuxer/src/lib.rs +++ b/remuxer/src/lib.rs @@ -7,16 +7,17 @@ pub mod extract; pub mod fragment; pub mod metadata; -pub mod mpeg4; +pub mod matroska_to_mpeg4; pub mod remux; pub mod seek_index; pub mod segment_extractor; pub mod trim_writer; +pub mod matroska_to_webm; use ebml_struct::matroska::TrackEntry; pub use fragment::write_fragment_into; use jellymatroska::{Master, MatroskaTag}; -pub use mpeg4::matroska_to_mpeg4; +pub use matroska_to_mpeg4::matroska_to_mpeg4; pub use remux::remux_stream_into; pub fn ebml_header(webm: bool) -> MatroskaTag { diff --git a/remuxer/src/matroska_to_mpeg4.rs b/remuxer/src/matroska_to_mpeg4.rs new file mode 100644 index 0000000..e8268e7 --- /dev/null +++ b/remuxer/src/matroska_to_mpeg4.rs @@ -0,0 +1,36 @@ +/* + 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 +*/ +use anyhow::Result; +use std::{ + fs::{remove_file, File}, + io::{copy, Read, Write}, + process::{Command, Stdio}, + random::random, +}; + +pub fn matroska_to_mpeg4( + mut input: impl Read + Send + 'static, + mut output: impl Write, +) -> Result<()> { + let path = format!("/tmp/jellything-tc-hack-{:016x}", random::()); + let args = format!( + "-hide_banner -loglevel warning -f matroska -i pipe:0 -copyts -c copy -f mp4 -movflags frag_keyframe+empty_moov {path}" + ); + let mut child = Command::new("ffmpeg") + .args(args.split(" ")) + .stdin(Stdio::piped()) + .stderr(Stdio::inherit()) + .spawn()?; + + let mut stdin = child.stdin.take().unwrap(); + copy(&mut input, &mut stdin)?; + drop(stdin); + child.wait()?.exit_ok()?; + copy(&mut File::open(&path)?, &mut output)?; + remove_file(path)?; + + Ok(()) +} diff --git a/remuxer/src/matroska_to_webm.rs b/remuxer/src/matroska_to_webm.rs new file mode 100644 index 0000000..b9a1819 --- /dev/null +++ b/remuxer/src/matroska_to_webm.rs @@ -0,0 +1,84 @@ +use crate::ebml_track_entry; +use anyhow::Context; +use ebml_struct::{ + ids::*, + matroska::{Cluster, Ebml, Info, Tracks}, + read::{EbmlReadExt, TagRead}, + write::TagWrite, +}; +use jellymatroska::{read::EbmlReader, write::EbmlWriter, Master, MatroskaTag}; +use log::warn; +use std::io::{BufReader, BufWriter, ErrorKind, Read, Seek, Write}; + +pub fn matroska_to_webm( + input: impl Read + Seek + 'static, + output: impl Write, +) -> anyhow::Result<()> { + let mut output = EbmlWriter::new(BufWriter::new(output), 0); + let mut input = EbmlReader::new(BufReader::new(input)); + + Ebml { + ebml_version: 1, + ebml_read_version: 1, + ebml_max_id_length: 4, + ebml_max_size_length: 8, + doc_type: "webm".to_string(), + doc_type_version: 4, + doc_type_read_version: 2, + doc_type_extensions: vec![], + } + .write(&mut output)?; + output.write_tag(&MatroskaTag::Segment(Master::Start))?; + + let (x, mut ebml) = input.read_tag()?; + assert_eq!(x, EL_EBML); + let ebml = Ebml::read(&mut ebml).unwrap(); + assert!(ebml.doc_type == "matroska" || ebml.doc_type == "webm"); + let (x, mut segment) = input.read_tag()?; + assert_eq!(x, EL_SEGMENT); + + loop { + let (x, mut seg) = match segment.read_tag() { + Ok(o) => o, + Err(e) if e.kind() == ErrorKind::UnexpectedEof => break, + Err(e) => return Err(e.into()), + }; + match x { + EL_INFO => { + let info = Info::read(&mut seg).context("info")?; + output.write_tag(&{ + MatroskaTag::Info(Master::Collected(vec![ + MatroskaTag::TimestampScale(info.timestamp_scale), + MatroskaTag::Duration(info.duration.unwrap_or_default()), + MatroskaTag::Title(info.title.unwrap_or_default()), + MatroskaTag::MuxingApp("jellyremux".to_string()), + MatroskaTag::WritingApp("jellything".to_string()), + ])) + })?; + } + EL_TRACKS => { + let tracks = Tracks::read(&mut seg).context("tracks")?; + output.write_tag(&MatroskaTag::Tracks(Master::Collected( + tracks + .entries + .into_iter() + .map(|t| ebml_track_entry(t.track_number, &t)) + .collect(), + )))?; + } + EL_VOID | EL_CRC32 | EL_CUES | EL_SEEKHEAD | EL_ATTACHMENTS | EL_TAGS => { + seg.consume()?; + } + EL_CLUSTER => { + let cluster = Cluster::read(&mut seg).context("cluster")?; + // TODO mixing both ebml libraries :))) + cluster.write(&mut output)?; + } + id => { + warn!("unknown top-level element {id:x}"); + seg.consume()?; + } + } + } + Ok(()) +} diff --git a/remuxer/src/mpeg4.rs b/remuxer/src/mpeg4.rs deleted file mode 100644 index da66fe2..0000000 --- a/remuxer/src/mpeg4.rs +++ /dev/null @@ -1,34 +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) 2025 metamuffin -*/ -use anyhow::Result; -use std::{ - fs::{remove_file, File}, - io::{copy, Read, Write}, - process::{Command, Stdio}, - random::random, -}; - -pub fn matroska_to_mpeg4( - mut input: impl Read + Send + 'static, - mut output: impl Write, -) -> Result<()> { - let path = format!("/tmp/jellything-tc-hack-{:016x}", random::()); - let args = format!("-f matroska -i pipe:0 -copyts -c copy -f mp4 {path}"); - let mut child = Command::new("ffmpeg") - .args(args.split(" ")) - .stdin(Stdio::piped()) - .stderr(Stdio::inherit()) - .spawn()?; - - let mut stdin = child.stdin.take().unwrap(); - copy(&mut input, &mut stdin)?; - drop(stdin); - child.wait()?.exit_ok()?; - copy(&mut File::open(&path)?, &mut output)?; - remove_file(path)?; - - Ok(()) -} diff --git a/stream/src/fragment.rs b/stream/src/fragment.rs index 2ce3c78..dfe101e 100644 --- a/stream/src/fragment.rs +++ b/stream/src/fragment.rs @@ -6,7 +6,7 @@ use crate::{stream_info, SMediaInfo}; use anyhow::{anyhow, bail, Result}; use jellybase::common::stream::StreamContainer; -use jellyremuxer::matroska_to_mpeg4; +use jellyremuxer::{matroska_to_mpeg4, matroska_to_webm::matroska_to_webm}; use jellytranscoder::fragment::transcode; use log::warn; use std::sync::Arc; @@ -72,10 +72,18 @@ pub async fn fragment_stream( }, ) .await?; - eprintln!("{:?}", location.abs()); + let mut frag = File::open(location.abs()).await?; match container { - StreamContainer::WebM => {} + StreamContainer::WebM => { + tokio::task::spawn_blocking(move || { + if let Err(err) = + matroska_to_webm(SyncIoBridge::new(frag), SyncIoBridge::new(b)) + { + warn!("webm transmux failed: {err}"); + } + }); + } StreamContainer::Matroska => { tokio::task::spawn(async move { if let Err(err) = tokio::io::copy(&mut frag, &mut b).await { diff --git a/web/script/jshelper b/web/script/jshelper index b2bcdcc..ef36d50 160000 --- a/web/script/jshelper +++ b/web/script/jshelper @@ -1 +1 @@ -Subproject commit b2bcdcc99e42015085b4d0d63e7c94b2d4f84e24 +Subproject commit ef36d50d7858a56cbc08bfb4f272bab9476bb977 -- cgit v1.2.3-70-g09d2