Manually burn-in subtitles only if no pre-burned video is available (#268)

This commit is contained in:
bytedream 2023-11-19 19:24:15 +01:00
parent 14e71c05b8
commit 2c37093959
5 changed files with 88 additions and 14 deletions

View file

@ -248,7 +248,8 @@ async fn get_format(
for single_format in single_formats { for single_format in single_formats {
let stream = single_format.stream().await?; let stream = single_format.stream().await?;
let Some((video, audio)) = variant_data_from_stream(&stream, &archive.resolution).await? let Some((video, audio, _)) =
variant_data_from_stream(&stream, &archive.resolution, None).await?
else { else {
if single_format.is_episode() { if single_format.is_episode() {
bail!( bail!(

View file

@ -1,7 +1,7 @@
use crate::download::filter::DownloadFilter; use crate::download::filter::DownloadFilter;
use crate::utils::context::Context; use crate::utils::context::Context;
use crate::utils::download::{DownloadBuilder, DownloadFormat}; use crate::utils::download::{DownloadBuilder, DownloadFormat};
use crate::utils::ffmpeg::FFmpegPreset; use crate::utils::ffmpeg::{FFmpegPreset, SOFTSUB_CONTAINERS};
use crate::utils::filter::Filter; use crate::utils::filter::Filter;
use crate::utils::format::{Format, SingleFormat}; use crate::utils::format::{Format, SingleFormat};
use crate::utils::log::progress; use crate::utils::log::progress;
@ -149,6 +149,25 @@ impl Execute for Download {
async fn execute(self, ctx: Context) -> Result<()> { async fn execute(self, ctx: Context) -> Result<()> {
let mut parsed_urls = vec![]; let mut parsed_urls = vec![];
let output_supports_softsubs = SOFTSUB_CONTAINERS.contains(
&Path::new(&self.output)
.extension()
.unwrap_or_default()
.to_string_lossy()
.as_ref(),
);
let special_output_supports_softsubs = if let Some(so) = &self.output_specials {
SOFTSUB_CONTAINERS.contains(
&Path::new(so)
.extension()
.unwrap_or_default()
.to_string_lossy()
.as_ref(),
)
} else {
output_supports_softsubs
};
for (i, url) in self.urls.clone().into_iter().enumerate() { for (i, url) in self.urls.clone().into_iter().enumerate() {
let progress_handler = progress!("Parsing url {}", i + 1); let progress_handler = progress!("Parsing url {}", i + 1);
match parse_url(&ctx.crunchy, url.clone(), true).await { match parse_url(&ctx.crunchy, url.clone(), true).await {
@ -190,7 +209,18 @@ impl Execute for Download {
// the vec contains always only one item // the vec contains always only one item
let single_format = single_formats.remove(0); let single_format = single_formats.remove(0);
let (download_format, format) = get_format(&self, &single_format).await?; let (download_format, format) = get_format(
&self,
&single_format,
if self.force_hardsub {
true
} else if single_format.is_special() {
!special_output_supports_softsubs
} else {
!output_supports_softsubs
},
)
.await?;
let mut downloader = download_builder.clone().build(); let mut downloader = download_builder.clone().build();
downloader.add_format(download_format); downloader.add_format(download_format);
@ -227,9 +257,19 @@ impl Execute for Download {
async fn get_format( async fn get_format(
download: &Download, download: &Download,
single_format: &SingleFormat, single_format: &SingleFormat,
try_peer_hardsubs: bool,
) -> Result<(DownloadFormat, Format)> { ) -> Result<(DownloadFormat, Format)> {
let stream = single_format.stream().await?; let stream = single_format.stream().await?;
let Some((video, audio)) = variant_data_from_stream(&stream, &download.resolution).await? let Some((video, audio, contains_hardsub)) = variant_data_from_stream(
&stream,
&download.resolution,
if try_peer_hardsubs {
download.subtitle.clone()
} else {
None
},
)
.await?
else { else {
if single_format.is_episode() { if single_format.is_episode() {
bail!( bail!(
@ -250,7 +290,9 @@ async fn get_format(
} }
}; };
let subtitle = if let Some(subtitle_locale) = &download.subtitle { let subtitle = if contains_hardsub {
None
} else if let Some(subtitle_locale) = &download.subtitle {
stream.subtitles.get(subtitle_locale).cloned() stream.subtitles.get(subtitle_locale).cloned()
} else { } else {
None None
@ -266,7 +308,7 @@ async fn get_format(
)] )]
}), }),
}; };
let format = Format::from_single_formats(vec![( let mut format = Format::from_single_formats(vec![(
single_format.clone(), single_format.clone(),
video, video,
subtitle.map_or(vec![], |s| { subtitle.map_or(vec![], |s| {
@ -276,6 +318,10 @@ async fn get_format(
)] )]
}), }),
)]); )]);
if contains_hardsub {
let (_, subs) = format.locales.get_mut(0).unwrap();
subs.push(download.subtitle.clone().unwrap())
}
Ok((download_format, format)) Ok((download_format, format))
} }

View file

@ -2,6 +2,8 @@ use lazy_static::lazy_static;
use regex::Regex; use regex::Regex;
use std::str::FromStr; use std::str::FromStr;
pub const SOFTSUB_CONTAINERS: [&str; 3] = ["mkv", "mov", "mp4"];
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub enum FFmpegPreset { pub enum FFmpegPreset {
Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality), Predefined(FFmpegCodec, Option<FFmpegHwAccel>, FFmpegQuality),

View file

@ -172,6 +172,10 @@ impl SingleFormat {
pub fn is_episode(&self) -> bool { pub fn is_episode(&self) -> bool {
matches!(self.source, MediaCollection::Episode(_)) matches!(self.source, MediaCollection::Episode(_))
} }
pub fn is_special(&self) -> bool {
self.sequence_number == 0.0 || self.sequence_number.fract() != 0.0
}
} }
struct SingleFormatCollectionEpisodeKey(f32); struct SingleFormatCollectionEpisodeKey(f32);

View file

@ -1,32 +1,47 @@
use anyhow::Result; use anyhow::{bail, Result};
use crunchyroll_rs::media::{Resolution, Stream, VariantData}; use crunchyroll_rs::media::{Resolution, Stream, VariantData};
use crunchyroll_rs::Locale; use crunchyroll_rs::Locale;
pub async fn variant_data_from_stream( pub async fn variant_data_from_stream(
stream: &Stream, stream: &Stream,
resolution: &Resolution, resolution: &Resolution,
) -> Result<Option<(VariantData, VariantData)>> { subtitle: Option<Locale>,
) -> Result<Option<(VariantData, VariantData, bool)>> {
// sometimes Crunchyroll marks episodes without real subtitles that they have subtitles and // sometimes Crunchyroll marks episodes without real subtitles that they have subtitles and
// reports that only hardsub episode are existing. the following lines are trying to prevent // reports that only hardsub episode are existing. the following lines are trying to prevent
// potential errors which might get caused by this incorrect reporting // potential errors which might get caused by this incorrect reporting
// (https://github.com/crunchy-labs/crunchy-cli/issues/231) // (https://github.com/crunchy-labs/crunchy-cli/issues/231)
let mut hardsub_locales = stream.streaming_hardsub_locales(); let mut hardsub_locales = stream.streaming_hardsub_locales();
let hardsub_locale = if !hardsub_locales.contains(&Locale::Custom("".to_string())) let (hardsub_locale, mut contains_hardsub) = if !hardsub_locales
.contains(&Locale::Custom("".to_string()))
&& !hardsub_locales.contains(&Locale::Custom(":".to_string())) && !hardsub_locales.contains(&Locale::Custom(":".to_string()))
{ {
// if only one hardsub locale exists, assume that this stream doesn't really contains hardsubs // if only one hardsub locale exists, assume that this stream doesn't really contains hardsubs
if hardsub_locales.len() == 1 { if hardsub_locales.len() == 1 {
Some(hardsub_locales.remove(0)) (Some(hardsub_locales.remove(0)), false)
} else { } else {
// fallback to `None`. this should trigger an error message in `stream.dash_streaming_data` // fallback to `None`. this should trigger an error message in `stream.dash_streaming_data`
// that the requested stream is not available // that the requested stream is not available
None (None, false)
} }
} else { } else {
None let hardsubs_requested = subtitle.is_some();
(subtitle, hardsubs_requested)
}; };
let mut streaming_data = stream.dash_streaming_data(hardsub_locale).await?; let mut streaming_data = match stream.dash_streaming_data(hardsub_locale).await {
Ok(data) => data,
Err(e) => {
// the error variant is only `crunchyroll_rs::error::Error::Input` when the requested
// hardsub is not available
if let crunchyroll_rs::error::Error::Input { .. } = e {
contains_hardsub = false;
stream.dash_streaming_data(None).await?
} else {
bail!(e)
}
}
};
streaming_data streaming_data
.0 .0
.sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse()); .sort_by(|a, b| a.bandwidth.cmp(&b.bandwidth).reverse());
@ -42,5 +57,11 @@ pub async fn variant_data_from_stream(
.into_iter() .into_iter()
.find(|v| resolution.height == v.resolution.height), .find(|v| resolution.height == v.resolution.height),
}; };
Ok(video_variant.map(|v| (v, streaming_data.1.first().unwrap().clone()))) Ok(video_variant.map(|v| {
(
v,
streaming_data.1.first().unwrap().clone(),
contains_hardsub,
)
}))
} }