mirror of
https://github.com/crunchy-labs/crunchy-cli.git
synced 2026-01-21 04:02:00 -06:00
Manually burn-in subtitles only if no pre-burned video is available (#268)
This commit is contained in:
parent
14e71c05b8
commit
2c37093959
5 changed files with 88 additions and 14 deletions
|
|
@ -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!(
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue