aboutsummaryrefslogtreecommitdiff
path: root/stream/src/lib.rs
blob: 801f29cbc764259dc10fd058563db4afb76bbe9d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/*
    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 <metamuffin.org>
*/
#![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<String>,
    pub files: BTreeSet<PathBuf>,
    pub cache: Arc<Cache>,
    pub config: Arc<Config>,
}

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<SMediaInfo>,
    spec: StreamSpec,
    range: Range<u64>,
) -> Result<Box<dyn Read + Send + Sync>> {
    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<SMediaInfo>,
    track: usize,
    range: Range<u64>,
) -> Result<Box<dyn Read + Send + Sync>> {
    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)))
}