/* 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 */ #![feature(iterator_try_collect)] pub mod cues; pub mod dash; mod fragment; mod fragment_index; mod hls; pub mod metadata; mod stream_info; mod webvtt; use anyhow::{Context, Result, anyhow, bail}; use fragment::fragment_stream; use fragment_index::fragment_index_stream; use hls::{hls_multivariant_stream, hls_variant_stream}; use jellycache::Cache; use jellystream_types::{StreamSpec, TrackKind}; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeSet, fs::File, io::{Read, Seek, SeekFrom}, ops::Range, path::PathBuf, sync::Arc, }; use stream_info::{stream_info, write_stream_info}; use crate::{dash::dash, fragment::fragment_init_stream}; #[rustfmt::skip] #[derive(Debug, Deserialize, Serialize, Default)] pub struct Config { #[serde(default)] pub offer_avc: bool, #[serde(default)] pub offer_hevc: bool, #[serde(default)] pub offer_vp8: bool, #[serde(default)] pub offer_vp9: bool, #[serde(default)] pub offer_av1: bool, pub transcoder: jellytranscoder::Config, } pub struct SMediaInfo { pub title: Option, pub files: BTreeSet, pub cache: Arc, pub config: Arc, } pub struct StreamHead { pub content_type: &'static str, pub range_supported: bool, } pub fn stream_head(spec: &StreamSpec) -> StreamHead { let kind = TrackKind::Video; // TODO use StreamSpec::*; let range_supported = matches!(spec, Remux { .. } | Original { .. }); let content_type = match spec { Original { .. } => "video/x-matroska", HlsMultiVariant { .. } => "application/vnd.apple.mpegurl", HlsVariant { .. } => "application/vnd.apple.mpegurl", Info => "application/jellything-stream-info+json", Dash => "application/dash+xml", FragmentIndex { .. } => "application/jellything-frag-index+json", FragmentInit { container, .. } => container.mime_type(kind), Fragment { container, .. } => container.mime_type(kind), Remux { container, .. } => container.mime_type(kind), }; StreamHead { content_type, range_supported, } } pub fn stream( info: Arc, spec: StreamSpec, range: Range, ) -> Result> { match spec { StreamSpec::Original { track } => original_stream(info, track, range), StreamSpec::HlsMultiVariant => hls_multivariant_stream(&info), StreamSpec::HlsVariant { track, format } => hls_variant_stream(&info, track, format), StreamSpec::Info => write_stream_info(&info), StreamSpec::Dash => dash(&info), StreamSpec::FragmentIndex { track } => fragment_index_stream(info, track), StreamSpec::FragmentInit { track, container, format, } => fragment_init_stream(info, track, format, container), StreamSpec::Fragment { track, index, container, format, } => fragment_stream(info, track, index, format, container), _ => bail!("todo"), } } fn original_stream( info: Arc, track: usize, range: Range, ) -> Result> { let (iinfo, _info) = stream_info(&info)?; let (file_index, _) = *iinfo .track_to_file .get(track) .ok_or(anyhow!("unknown track"))?; let mut file = File::open(&iinfo.paths[file_index]).context("opening source")?; file.seek(SeekFrom::Start(range.start)) .context("seek source")?; Ok(Box::new(file.take(range.end - range.start))) }