aboutsummaryrefslogtreecommitdiff
path: root/stream/src/fragment.rs
blob: 4cdc55e53789c7d505fbfff1bf7126805fcd1f15 (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
122
123
124
/*
    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 <metamuffin.org>
*/
use crate::{
    cues::{generate_cues, GeneratedCue},
    stream_info, SMediaInfo,
};
use anyhow::{anyhow, Result};
use jellyremuxer::{
    demuxers::create_demuxer_autodetect,
    matroska::{self, Segment},
    muxers::write_fragment,
    ContainerFormat,
};
use jellystream_types::{FormatNum, IndexNum, StreamContainer, TrackNum};
use jellytranscoder::fragment::transcode;
use std::{
    fs::File,
    io::{Cursor, Read},
    sync::Arc,
};

pub fn fragment_stream(
    info: Arc<SMediaInfo>,
    track: TrackNum,
    index: IndexNum,
    format_num: FormatNum,
    container: StreamContainer,
) -> Result<Box<dyn Read + Send + Sync>> {
    let (iinfo, info) = stream_info(info)?;

    let (file_index, track_num) = *iinfo
        .track_to_file
        .get(track)
        .ok_or(anyhow!("track not found"))?;
    let media_path = iinfo.paths[file_index].clone();
    let track = info.tracks.get(track).ok_or(anyhow!("track not found"))?;
    let format = track
        .formats
        .get(format_num)
        .ok_or(anyhow!("format not found"))?;

    let mk_track = iinfo.metadata[file_index]
        .tracks
        .as_ref()
        .unwrap()
        .entries
        .iter()
        .find(|t| t.track_number == track_num)
        .unwrap();

    let timestamp_scale = iinfo.metadata[file_index].info.timestamp_scale;
    let total_duration = iinfo.metadata[file_index].info.duration;
    let cue_stat = generate_cues(&media_path)?;
    let start_cue = cue_stat
        .cues
        .get(index)
        .ok_or(anyhow!("fragment index out of range"))?;
    let end_cue = cue_stat
        .cues
        .get(index + 1)
        .copied()
        .unwrap_or(GeneratedCue {
            position: 0,
            time: total_duration.unwrap_or_default() as u64 * timestamp_scale, // TODO rounding?
        });
    let cluster_offset = start_cue.position;
    let duration = (end_cue.time - start_cue.time) as f64 / timestamp_scale as f64;

    let mk_info = matroska::Info {
        duration: Some(duration),
        timestamp_scale,
        ..Default::default()
    };
    let mk_tracks = matroska::Tracks {
        entries: vec![mk_track.to_owned()],
    };

    let mut cluster = {
        let media_file = File::open(&media_path)?;
        let mut media = create_demuxer_autodetect(Box::new(media_file))?
            .ok_or(anyhow!("media container unknown"))?;
        media.seek_cluster(Some(cluster_offset))?;
        media
            .read_cluster()?
            .ok_or(anyhow!("cluster unexpectedly missing"))?
            .1
    };

    cluster.simple_blocks.retain(|b| b.track == track_num);
    cluster.block_groups.retain(|b| b.block.track == track_num);

    let jr_container = match container {
        StreamContainer::WebM => ContainerFormat::Webm,
        StreamContainer::Matroska => ContainerFormat::Matroska,
        StreamContainer::WebVTT => todo!(),
        StreamContainer::MPEG4 => ContainerFormat::Mpeg4,
        StreamContainer::JVTT => todo!(),
    };

    let mut segment = Segment {
        info: mk_info,
        tracks: Some(mk_tracks),
        clusters: vec![cluster],
        ..Default::default()
    };
    segment.info.writing_app =
        concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")).to_string();

    if !format.remux {
        segment = transcode(
            track.kind,
            &format!("{media_path:?} {track_num} {index}"),
            format,
            segment,
        )?;
    }

    let mut out = Vec::new();
    write_fragment(jr_container, &mut out, segment)?;
    Ok(Box::new(Cursor::new(out)))
}