Archive subtitles of all versions of an episode

This commit is contained in:
Hannes Braun 2023-01-04 22:09:31 +01:00
parent 892407d1f0
commit 06fd9a7a98
No known key found for this signature in database
GPG key ID: 7B6557E1DFD685BE
3 changed files with 82 additions and 25 deletions

View file

@ -6,6 +6,7 @@ use crate::utils::log::progress;
use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile}; use crate::utils::os::{free_file, has_ffmpeg, is_special_file, tempfile};
use crate::utils::parse::{parse_url, UrlFilter}; use crate::utils::parse::{parse_url, UrlFilter};
use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number}; use crate::utils::sort::{sort_formats_after_seasons, sort_seasons_after_number};
use crate::utils::subtitle::Subtitle;
use crate::Execute; use crate::Execute;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use chrono::NaiveTime; use chrono::NaiveTime;
@ -232,7 +233,7 @@ impl Execute for Archive {
} }
} }
for (formats, subtitles) in archive_formats { for (formats, mut subtitles) in archive_formats {
let (primary, additionally) = formats.split_first().unwrap(); let (primary, additionally) = formats.split_first().unwrap();
let mut path = PathBuf::from(&self.output); let mut path = PathBuf::from(&self.output);
@ -276,13 +277,14 @@ impl Execute for Archive {
"Subtitle: {}", "Subtitle: {}",
subtitles subtitles
.iter() .iter()
.filter(|s| s.primary) // Don't print subtitles of non-primary streams. They might get removed depending on the merge behavior.
.map(|s| { .map(|s| {
if let Some(default) = &self.default_subtitle { if let Some(default) = &self.default_subtitle {
if default == &s.locale { if default == &s.stream_subtitle.locale {
return format!("{} (primary)", default); return format!("{} (primary)", default);
} }
} }
s.locale.to_string() s.stream_subtitle.locale.to_string()
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", ") .join(", ")
@ -309,13 +311,23 @@ impl Execute for Archive {
} else { } else {
video_paths.push((path, additional)) video_paths.push((path, additional))
} }
// Remove subtitles of deleted video
if only_audio {
subtitles.retain(|s| s.episode_id != additional.id);
}
} }
let (primary_video, _) = video_paths.get(0).unwrap(); let (primary_video, _) = video_paths.get(0).unwrap();
let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap(); let primary_video_length = get_video_length(primary_video.to_path_buf()).unwrap();
for subtitle in subtitles { for subtitle in subtitles {
subtitle_paths.push(( subtitle_paths.push((
download_subtitle(&self, subtitle.clone(), primary_video_length).await?, download_subtitle(
&self,
subtitle.stream_subtitle.clone(),
primary_video_length,
)
.await?,
subtitle, subtitle,
)) ))
} }
@ -334,7 +346,7 @@ async fn formats_from_series(
archive: &Archive, archive: &Archive,
series: Media<Series>, series: Media<Series>,
url_filter: &UrlFilter, url_filter: &UrlFilter,
) -> Result<Vec<(Vec<Format>, Vec<StreamSubtitle>)>> { ) -> Result<Vec<(Vec<Format>, Vec<Subtitle>)>> {
let mut seasons = series.seasons().await?; let mut seasons = series.seasons().await?;
// filter any season out which does not contain the specified audio languages // filter any season out which does not contain the specified audio languages
@ -367,8 +379,8 @@ async fn formats_from_series(
} }
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<StreamSubtitle>)>> = let mut result: BTreeMap<u32, BTreeMap<u32, (Vec<Format>, Vec<Subtitle>)>> = BTreeMap::new();
BTreeMap::new(); let mut primary_season = true;
for season in series.seasons().await? { for season in series.seasons().await? {
if !url_filter.is_season_valid(season.metadata.season_number) if !url_filter.is_season_valid(season.metadata.season_number)
|| !archive || !archive
@ -402,20 +414,26 @@ async fn formats_from_series(
) )
}; };
let (ref mut formats, _) = result let (ref mut formats, subtitles) = result
.entry(season.metadata.season_number) .entry(season.metadata.season_number)
.or_insert_with(BTreeMap::new) .or_insert_with(BTreeMap::new)
.entry(episode.metadata.episode_number) .entry(episode.metadata.episode_number)
.or_insert_with(|| { .or_insert_with(|| (vec![], vec![]));
let subtitles: Vec<StreamSubtitle> = archive subtitles.extend(archive.subtitle.iter().filter_map(|l| {
.subtitle let stream_subtitle = streams.subtitles.get(l).cloned()?;
.iter() let subtitle = Subtitle {
.filter_map(|l| streams.subtitles.get(l).cloned()) stream_subtitle,
.collect(); audio_locale: episode.metadata.audio_locale.clone(),
(vec![], subtitles) episode_id: episode.id.clone(),
}); forced: !episode.metadata.is_subbed,
primary: primary_season,
};
Some(subtitle)
}));
formats.push(Format::new_from_episode(episode, stream)); formats.push(Format::new_from_episode(episode, stream));
} }
primary_season = false;
} }
Ok(result.into_values().flat_map(|v| v.into_values()).collect()) Ok(result.into_values().flat_map(|v| v.into_values()).collect())
@ -562,11 +580,12 @@ fn generate_mkv(
target: PathBuf, target: PathBuf,
video_paths: Vec<(TempPath, &Format)>, video_paths: Vec<(TempPath, &Format)>,
audio_paths: Vec<(TempPath, &Format)>, audio_paths: Vec<(TempPath, &Format)>,
subtitle_paths: Vec<(TempPath, StreamSubtitle)>, subtitle_paths: Vec<(TempPath, Subtitle)>,
) -> Result<()> { ) -> Result<()> {
let mut input = vec![]; let mut input = vec![];
let mut maps = vec![]; let mut maps = vec![];
let mut metadata = vec![]; let mut metadata = vec![];
let mut dispositions = vec![vec![]; subtitle_paths.len()];
for (i, (video_path, format)) in video_paths.iter().enumerate() { for (i, (video_path, format)) in video_paths.iter().enumerate() {
input.extend(["-i".to_string(), video_path.to_string_lossy().to_string()]); input.extend(["-i".to_string(), video_path.to_string_lossy().to_string()]);
@ -611,12 +630,26 @@ fn generate_mkv(
]); ]);
metadata.extend([ metadata.extend([
format!("-metadata:s:s:{}", i), format!("-metadata:s:s:{}", i),
format!("language={}", subtitle.locale), format!("language={}", subtitle.stream_subtitle.locale),
]); ]);
metadata.extend([ metadata.extend([
format!("-metadata:s:s:{}", i), format!("-metadata:s:s:{}", i),
format!("title={}", subtitle.locale.to_human_readable()), format!(
"title={}",
subtitle.stream_subtitle.locale.to_human_readable()
+ if !subtitle.primary {
format!(" [Video: {}]", subtitle.audio_locale.to_human_readable())
} else {
"".to_string()
}
.as_str()
),
]); ]);
// mark forced subtitles
if subtitle.forced {
dispositions[i].push("forced");
}
} }
let (input_presets, output_presets) = let (input_presets, output_presets) =
@ -633,16 +666,28 @@ fn generate_mkv(
// if `--default_subtitle <locale>` is given set the default subtitle to the given locale // if `--default_subtitle <locale>` is given set the default subtitle to the given locale
if let Some(position) = subtitle_paths if let Some(position) = subtitle_paths
.iter() .iter()
.position(|(_, subtitle)| &subtitle.locale == default_subtitle) .position(|(_, subtitle)| &subtitle.stream_subtitle.locale == default_subtitle)
{ {
command_args.extend([format!("-disposition:s:{}", position), "default".to_string()]) dispositions[position].push("default");
} else {
command_args.extend(["-disposition:s:0".to_string(), "0".to_string()])
} }
} else {
command_args.extend(["-disposition:s:0".to_string(), "0".to_string()])
} }
let disposition_args: Vec<String> = dispositions
.iter()
.enumerate()
.flat_map(|(i, d)| {
vec![
format!("-disposition:s:{}", i),
if !d.is_empty() {
d.join("+")
} else {
"0".to_string()
},
]
})
.collect();
command_args.extend(disposition_args);
command_args.extend(output_presets); command_args.extend(output_presets);
command_args.extend([ command_args.extend([
"-f".to_string(), "-f".to_string(),

View file

@ -6,3 +6,4 @@ pub mod log;
pub mod os; pub mod os;
pub mod parse; pub mod parse;
pub mod sort; pub mod sort;
pub mod subtitle;

View file

@ -0,0 +1,11 @@
use crunchyroll_rs::media::StreamSubtitle;
use crunchyroll_rs::Locale;
#[derive(Clone)]
pub struct Subtitle {
pub stream_subtitle: StreamSubtitle,
pub audio_locale: Locale,
pub episode_id: String,
pub forced: bool,
pub primary: bool,
}