/* 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 = choose_container(format); let container_mime = container.mime_type(track.kind); 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 = choose_container(format); let container_mime = container.mime_type(track.kind); 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 choose_container(format: &StreamFormatInfo) -> StreamContainer { if format.containers.contains(&StreamContainer::WebM) { StreamContainer::WebM } else if format.containers.contains(&StreamContainer::MP4) { StreamContainer::MP4 } else { StreamContainer::Matroska } } 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 { write!(f, "")?; let mut c = 0; let mut last_t = 0; let mut last_d = 0; let mut r = 0; let mut first = true; for seg in self.0 { let t = (seg.end * 1000.) as i64; let d = t - last_t; if d != last_d && last_d != 0 { match (r, first) { (1, true) => write!(f, r#""#)?, (1, false) => write!(f, r#""#)?, (_, true) => write!(f, r#""#)?, (_, false) => write!(f, r#""#)?, } first = false; c += r; r = 1; } else { r += 1; } last_t = t; last_d = d; } c += r; match (r, first) { (0, _) => (), (1, true) => write!(f, r#""#)?, (1, false) => write!(f, r#""#)?, (_, true) => write!(f, r#""#)?, (_, false) => write!(f, r#""#)?, } assert_eq!(c, self.0.len()); write!(f, "")?; Ok(()) } }