From 4c70753ee7311f644401669e6fde7b4a6cd32992 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Tue, 3 Mar 2026 22:36:42 +0100 Subject: dash --- stream/src/dash.rs | 190 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 stream/src/dash.rs (limited to 'stream/src/dash.rs') diff --git a/stream/src/dash.rs b/stream/src/dash.rs new file mode 100644 index 0000000..17fe43e --- /dev/null +++ b/stream/src/dash.rs @@ -0,0 +1,190 @@ +/* + 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 +*/ + +use crate::{SMediaInfo, fragment_index::fragment_index, stream_info}; +use anyhow::Result; +use jellystream_types::{StreamContainer, StreamFormatInfo, TrackKind}; +use std::{ + fmt::{Display, Write}, + io::{Cursor, Read}, + ops::Range, +}; + +pub fn dash(sinfo: &SMediaInfo) -> Result> { + let (_iinfo, info) = stream_info(&sinfo)?; + + let mut out = String::new(); + + writeln!( + out, + " \ + ", + Time(info.duration) + )?; + + writeln!(out, "")?; + writeln!(out, "")?; + writeln!(out, r#""#)?; + for (as_id, track) in info.tracks.iter().enumerate() { + let frags = fragment_index(&sinfo, as_id)?; + match track.kind { + TrackKind::Video => { + let max_width = track + .formats + .iter() + .flat_map(|f| f.width) + .max() + .unwrap_or_default(); + let max_height = track + .formats + .iter() + .flat_map(|f| f.height) + .max() + .unwrap_or_default(); + let framerate = "2997/1000"; + let par = "16:9"; // TODO + writeln!( + out, + "" + )?; + for (repr_id, format) in track.formats.iter().enumerate() { + let StreamFormatInfo { + width: Some(width), + height: Some(height), + bitrate, + .. + } = format + else { + unreachable!() + }; + let container = StreamContainer::WebM; + let container_mime = container.mime_type(); + let codec_param = &format.codec_param; + writeln!( + out, + "" + )?; + write_segment_template(&mut out, as_id, container, &frags)?; + writeln!(out, "")?; + } + writeln!(out, "")?; + } + TrackKind::Audio => { + writeln!( + out, + "" + )?; + for (repr_id, format) in track.formats.iter().enumerate() { + let StreamFormatInfo { + bitrate, + samplerate: Some(samplerate), + .. + } = format + else { + unreachable!() + }; + let container = StreamContainer::WebM; + let container_mime = container.mime_type(); + let codec_param = &format.codec_param; + writeln!( + out, + "" + )?; + write_segment_template(&mut out, as_id, container, &frags)?; + writeln!(out, "")?; + } + writeln!(out, "")?; + } + TrackKind::Subtitle => (), + } + } + writeln!(out, r#""#)?; + + writeln!(out, r#""#)?; + + Ok(Box::new(Cursor::new(out))) +} + +fn write_segment_template( + out: &mut String, + as_id: usize, + container: StreamContainer, + frags: &[Range], +) -> Result<()> { + writeln!( + out, + "" + )?; + writeln!(out, "{}", Timeline(&frags))?; + writeln!(out, "")?; + Ok(()) +} + +struct Time(f64); +impl Display for Time { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "PT{:.01}S", self.0) + } +} +struct Timeline<'a>(&'a [Range]); +impl Display for Timeline<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "")?; + let mut last_t = 0; + for (i, r) in self.0.iter().enumerate() { + let t = (r.start * 1000.) as i64; + let d = t - last_t; + last_t = t; + if i == 0 { + writeln!(f, r#""#)?; + } else { + writeln!(f, r#""#)?; + } + } + writeln!(f, "")?; + Ok(()) + } +} -- cgit v1.3