Add --include-chapters flag to archive and download (#301)

This commit is contained in:
bytedream 2024-03-10 01:58:35 +01:00
parent 9c44fa7dae
commit 56f0ed1795
6 changed files with 272 additions and 191 deletions

View file

@ -4,7 +4,7 @@ use crate::utils::os::{cache_dir, is_special_file, temp_directory, temp_named_pi
use crate::utils::rate_limit::RateLimiterService;
use anyhow::{bail, Result};
use chrono::NaiveTime;
use crunchyroll_rs::media::{Subtitle, VariantData, VariantSegment};
use crunchyroll_rs::media::{SkipEvents, SkipEventsEvent, Subtitle, VariantData, VariantSegment};
use crunchyroll_rs::Locale;
use indicatif::{ProgressBar, ProgressDrawTarget, ProgressFinish, ProgressStyle};
use log::{debug, warn, LevelFilter};
@ -113,6 +113,11 @@ pub struct DownloadFormat {
pub video: (VariantData, Locale),
pub audios: Vec<(VariantData, Locale)>,
pub subtitles: Vec<(Subtitle, bool)>,
pub metadata: DownloadFormatMetadata,
}
pub struct DownloadFormatMetadata {
pub skip_events: Option<SkipEvents>,
}
pub struct Downloader {
@ -205,6 +210,8 @@ impl Downloader {
let mut audios = vec![];
let mut subtitles = vec![];
let mut fonts = vec![];
let mut chapters = None;
let mut max_len = NaiveTime::MIN;
let mut max_frames = 0f64;
let fmt_space = self
.formats
@ -243,6 +250,9 @@ impl Downloader {
}
let (len, fps) = get_video_stats(&video_path)?;
if max_len < len {
max_len = len
}
let frames = len.signed_duration_since(NaiveTime::MIN).num_seconds() as f64 * fps;
if frames > max_frames {
max_frames = frames;
@ -322,6 +332,22 @@ impl Downloader {
format!("#{}", i + 1)
},
});
if let Some(skip_events) = &format.metadata.skip_events {
let (file, path) = tempfile(".chapter")?.into_parts();
chapters = Some((
(file, path),
[
skip_events.recap.as_ref().map(|e| ("Recap", e)),
skip_events.intro.as_ref().map(|e| ("Intro", e)),
skip_events.credits.as_ref().map(|e| ("Credits", e)),
skip_events.preview.as_ref().map(|e| ("Preview", e)),
]
.into_iter()
.flatten()
.collect::<Vec<(&str, &SkipEventsEvent)>>(),
));
}
}
if self.download_fonts
@ -440,6 +466,20 @@ impl Downloader {
}
}
if let Some(((file, path), chapters)) = chapters.as_mut() {
write_ffmpeg_chapters(file, max_len, chapters)?;
input.extend(["-i".to_string(), path.to_string_lossy().to_string()]);
maps.extend([
"-map_metadata".to_string(),
(videos.len()
+ audios.len()
+ container_supports_softsubs
.then_some(subtitles.len())
.unwrap_or_default())
.to_string(),
])
}
let preset_custom = matches!(self.ffmpeg_preset, FFmpegPreset::Custom(_));
let (input_presets, mut output_presets) = self.ffmpeg_preset.into_input_output_args();
let fifo = temp_named_pipe()?;
@ -1156,6 +1196,54 @@ fn fix_subtitles(raw: &mut Vec<u8>, max_length: NaiveTime) {
*raw = as_lines.join("\n").into_bytes()
}
fn write_ffmpeg_chapters(
file: &mut fs::File,
video_len: NaiveTime,
events: &mut Vec<(&str, &SkipEventsEvent)>,
) -> Result<()> {
let video_len = video_len
.signed_duration_since(NaiveTime::MIN)
.num_seconds() as u32;
events.sort_by(|(_, event_a), (_, event_b)| event_a.start.cmp(&event_b.start));
writeln!(file, ";FFMETADATA1")?;
let mut last_end_time = 0;
for (name, event) in events {
// include an extra 'Episode' chapter if the start of the current chapter is more than 10
// seconds later than the end of the last chapter.
// this is done before writing the actual chapter of this loop to keep the chapter
// chronologically in order
if event.start as i32 - last_end_time as i32 > 10 {
writeln!(file, "[CHAPTER]")?;
writeln!(file, "TIMEBASE=1/1")?;
writeln!(file, "START={}", last_end_time)?;
writeln!(file, "END={}", event.start)?;
writeln!(file, "title=Episode")?;
}
writeln!(file, "[CHAPTER]")?;
writeln!(file, "TIMEBASE=1/1")?;
writeln!(file, "START={}", event.start)?;
writeln!(file, "END={}", event.end)?;
writeln!(file, "title={}", name)?;
last_end_time = event.end;
}
// only add a traling chapter if the gab between the end of the last chapter and the total video
// length is greater than 10 seconds
if video_len as i32 - last_end_time as i32 > 10 {
writeln!(file, "[CHAPTER]")?;
writeln!(file, "TIMEBASE=1/1")?;
writeln!(file, "START={}", last_end_time)?;
writeln!(file, "END={}", video_len)?;
writeln!(file, "title=Episode")?;
}
Ok(())
}
async fn ffmpeg_progress<R: AsyncReadExt + Unpin>(
total_frames: u64,
stats: R,