diff options
Diffstat (limited to 'remuxer')
-rw-r--r-- | remuxer/src/snippet.rs | 94 |
1 files changed, 93 insertions, 1 deletions
diff --git a/remuxer/src/snippet.rs b/remuxer/src/snippet.rs index 65d63ff..186be19 100644 --- a/remuxer/src/snippet.rs +++ b/remuxer/src/snippet.rs @@ -2,4 +2,96 @@ 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> -*/
\ No newline at end of file +*/ + +use crate::ebml_track_entry; +use crate::segment_extractor::SegmentExtractIter; +use crate::{ebml_header, ebml_segment_info}; +use anyhow::anyhow; +use anyhow::Context; +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, path::PathBuf}; + +const SNIPPET_LENGTH: f64 = 2.; + +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- {n} {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; + info!("average keyframe interval: {average_kf_interval}"); + info!(" => 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::new(); + 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(()) +} |