aboutsummaryrefslogtreecommitdiff
path: root/remuxer/src/snippet.rs
blob: cd965bae91e5521e042764f452bccb0001fb369d (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
125
126
127
128
/*
    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) 2023 metamuffin <metamuffin.org>
*/

use crate::{
    ebml_header, ebml_segment_info, ebml_track_entry, segment_extractor::SegmentExtractIter,
};
use anyhow::{anyhow, Context, Result};
use jellycommon::{seek_index::SeekIndex, LocalTrack, NodePublic};
use jellymatroska::{read::EbmlReader, write::EbmlWriter, Master, MatroskaTag};
use log::{debug, info};
use std::{fs::File, io::Write, ops::Range, path::PathBuf};

const SNIPPET_LENGTH: f64 = 2.;

pub fn snippet_index(
    path_base: PathBuf,
    item: NodePublic,
    track_sources: &Vec<LocalTrack>,
    track: usize,
) -> Result<Vec<Range<f64>>> {
    let media_info = item.media.as_ref().unwrap();
    let private = &track_sources[track];
    let source_path = path_base.join(&private.path);
    let mut index = File::open(source_path.with_extension(format!("si.{}", private.track)))
        .context("opening seek index file")?;
    let index =
        bincode::decode_from_std_read::<SeekIndex, _, _>(&mut index, bincode::config::standard())?;
    let average_kf_interval = media_info.duration / index.keyframes.len() as f64;
    let kf_per_snip = (SNIPPET_LENGTH / average_kf_interval).ceil() as usize;
    debug!("average keyframe interval: {average_kf_interval}");
    debug!(" => keyframes per snippet {kf_per_snip}");

    let n_snips = index.keyframes.len().div_ceil(kf_per_snip);
    Ok((0..n_snips)
        .map(|i| {
            let start = index.blocks[index.keyframes[i * kf_per_snip]].pts as f64 / 1000.;
            let end = index
                .keyframes
                .get((i + 1) * kf_per_snip)
                .map(|i| index.blocks[*i].pts as f64 / 1000.)
                .unwrap_or(media_info.duration);
            start..end
        })
        .collect())
}

pub fn write_snippet_into(
    writer: impl Write,
    path_base: PathBuf,
    item: NodePublic,
    track_sources: Vec<LocalTrack>,
    track: usize,
    webm: bool,
    n: usize,
) -> anyhow::Result<()> {
    info!("writing snippet {n} of {:?} (track #{track})", item.title);
    let mut output = EbmlWriter::new(writer, 0);

    let media_info = item.media.as_ref().unwrap();
    let info = media_info
        .tracks
        .get(track)
        .ok_or(anyhow!("track not available"))?
        .to_owned();
    let private = &track_sources[track];
    let source_path = path_base.join(&private.path);
    let mapped = 1;
    info!("\t- {track} {source_path:?} ({} => {mapped})", private.track);
    info!("\t    {}", info);
    let file = File::open(&source_path).context("opening source file")?;
    let mut index = File::open(source_path.with_extension(format!("si.{}", private.track)))
        .context("opening seek index file")?;
    let index =
        bincode::decode_from_std_read::<SeekIndex, _, _>(&mut index, bincode::config::standard())?;
    debug!("\t    seek index: {} blocks loaded", index.blocks.len());
    let mut reader = EbmlReader::new(file);

    // TODO maybe refactor this to approx. contant time per snippet
    let average_kf_interval = media_info.duration / index.keyframes.len() as f64;
    let kf_per_snip = (SNIPPET_LENGTH / average_kf_interval).ceil() as usize;
    debug!("average keyframe interval: {average_kf_interval}");
    debug!(" => keyframes per snippet {kf_per_snip}");

    let start_block_index = *index
        .keyframes
        .get(n * kf_per_snip)
        .ok_or(anyhow!("snippet index out of range"))?;
    let end_block_index = *index
        .keyframes
        .get((n + 1) * kf_per_snip)
        .unwrap_or(&index.blocks.len());
    let start_block = &index.blocks[start_block_index];
    let last_block = &index.blocks[end_block_index - 1];

    reader.seek(start_block.source_off, MatroskaTag::Cluster(Master::Start))?;

    output.write_tag(&ebml_header(webm))?;
    output.write_tag(&MatroskaTag::Segment(Master::Start))?;
    output.write_tag(&ebml_segment_info(
        format!("{} (snippet {n})", item.title),
        (last_block.pts - start_block.pts) as f64 / 1000.,
    ))?;
    output.write_tag(&MatroskaTag::Tags(Master::Collected(vec![])))?;
    output.write_tag(&MatroskaTag::Tracks(Master::Collected(vec![
        ebml_track_entry(mapped, &info, private.codec_private.clone()),
    ])))?;

    let mut reader = SegmentExtractIter::new(&mut reader, private.track as u64);

    let mut blocks = vec![MatroskaTag::Timestamp(0)];
    for i in start_block_index..end_block_index {
        let index_block = &index.blocks[i];
        let mut block = reader.next()?;

        assert_eq!(index_block.size, block.data.len(), "seek index is wrong");

        block.track = 1;
        block.timestamp_off = (index_block.pts - start_block.pts).try_into().unwrap();
        blocks.push(MatroskaTag::SimpleBlock(block.dump()))
    }
    output.write_tag(&MatroskaTag::Cluster(Master::Collected(blocks)))?;

    output.write_tag(&MatroskaTag::Segment(Master::End))?;
    Ok(())
}