mirror of
https://github.com/crunchy-labs/crunchy-cli.git
synced 2026-01-21 12:12:00 -06:00
Add --special-output and --skip-specials flag
This commit is contained in:
parent
5a3a304443
commit
787d8ab02c
5 changed files with 106 additions and 10 deletions
|
|
@ -54,6 +54,11 @@ pub struct Archive {
|
||||||
{episode_id} → ID of the episode")]
|
{episode_id} → ID of the episode")]
|
||||||
#[arg(short, long, default_value = "{title}.mkv")]
|
#[arg(short, long, default_value = "{title}.mkv")]
|
||||||
pub(crate) output: String,
|
pub(crate) output: String,
|
||||||
|
#[arg(help = "Name of the output file if the episode is a special")]
|
||||||
|
#[arg(long_help = "Name of the output file if the episode is a special. \
|
||||||
|
If not set, the '-o'/'--output' flag will be used as name template")]
|
||||||
|
#[arg(long)]
|
||||||
|
pub(crate) special_output: Option<String>,
|
||||||
|
|
||||||
#[arg(help = "Video resolution")]
|
#[arg(help = "Video resolution")]
|
||||||
#[arg(long_help = "The video resolution.\
|
#[arg(long_help = "The video resolution.\
|
||||||
|
|
@ -95,6 +100,9 @@ pub struct Archive {
|
||||||
#[arg(help = "Skip files which are already existing")]
|
#[arg(help = "Skip files which are already existing")]
|
||||||
#[arg(long, default_value_t = false)]
|
#[arg(long, default_value_t = false)]
|
||||||
pub(crate) skip_existing: bool,
|
pub(crate) skip_existing: bool,
|
||||||
|
#[arg(help = "Skip special episodes")]
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub(crate) skip_specials: bool,
|
||||||
|
|
||||||
#[arg(help = "Skip any interactive input")]
|
#[arg(help = "Skip any interactive input")]
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
|
@ -123,6 +131,17 @@ impl Execute for Archive {
|
||||||
&& self.output != "-"
|
&& self.output != "-"
|
||||||
{
|
{
|
||||||
bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported")
|
bail!("File extension is not '.mkv'. Currently only matroska / '.mkv' files are supported")
|
||||||
|
} else if let Some(special_output) = &self.special_output {
|
||||||
|
if PathBuf::from(special_output)
|
||||||
|
.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy()
|
||||||
|
!= "mkv"
|
||||||
|
&& !is_special_file(special_output)
|
||||||
|
&& special_output != "-"
|
||||||
|
{
|
||||||
|
bail!("File extension for special episodes is not '.mkv'. Currently only matroska / '.mkv' files are supported")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.audio = all_locale_in_locales(self.audio.clone());
|
self.audio = all_locale_in_locales(self.audio.clone());
|
||||||
|
|
@ -147,9 +166,10 @@ impl Execute for Archive {
|
||||||
|
|
||||||
for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() {
|
for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() {
|
||||||
let progress_handler = progress!("Fetching series details");
|
let progress_handler = progress!("Fetching series details");
|
||||||
let single_format_collection = ArchiveFilter::new(url_filter, self.clone(), !self.yes)
|
let single_format_collection =
|
||||||
.visit(media_collection)
|
ArchiveFilter::new(url_filter, self.clone(), !self.yes, self.skip_specials)
|
||||||
.await?;
|
.visit(media_collection)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if single_format_collection.is_empty() {
|
if single_format_collection.is_empty() {
|
||||||
progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1));
|
progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1));
|
||||||
|
|
@ -175,7 +195,15 @@ impl Execute for Archive {
|
||||||
downloader.add_format(download_format)
|
downloader.add_format(download_format)
|
||||||
}
|
}
|
||||||
|
|
||||||
let formatted_path = format.format_path((&self.output).into());
|
let formatted_path = if format.is_special() {
|
||||||
|
format.format_path(
|
||||||
|
self.special_output
|
||||||
|
.as_ref()
|
||||||
|
.map_or((&self.output).into(), |so| so.into()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format.format_path((&self.output).into())
|
||||||
|
};
|
||||||
let (path, changed) = free_file(formatted_path.clone());
|
let (path, changed) = free_file(formatted_path.clone());
|
||||||
|
|
||||||
if changed && self.skip_existing {
|
if changed && self.skip_existing {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ pub(crate) struct ArchiveFilter {
|
||||||
url_filter: UrlFilter,
|
url_filter: UrlFilter,
|
||||||
archive: Archive,
|
archive: Archive,
|
||||||
interactive_input: bool,
|
interactive_input: bool,
|
||||||
|
skip_special: bool,
|
||||||
season_episodes: HashMap<String, Vec<Episode>>,
|
season_episodes: HashMap<String, Vec<Episode>>,
|
||||||
season_subtitles_missing: Vec<u32>,
|
season_subtitles_missing: Vec<u32>,
|
||||||
season_sorting: Vec<String>,
|
season_sorting: Vec<String>,
|
||||||
|
|
@ -25,11 +26,17 @@ pub(crate) struct ArchiveFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArchiveFilter {
|
impl ArchiveFilter {
|
||||||
pub(crate) fn new(url_filter: UrlFilter, archive: Archive, interactive_input: bool) -> Self {
|
pub(crate) fn new(
|
||||||
|
url_filter: UrlFilter,
|
||||||
|
archive: Archive,
|
||||||
|
interactive_input: bool,
|
||||||
|
skip_special: bool,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
url_filter,
|
url_filter,
|
||||||
archive,
|
archive,
|
||||||
interactive_input,
|
interactive_input,
|
||||||
|
skip_special,
|
||||||
season_episodes: HashMap::new(),
|
season_episodes: HashMap::new(),
|
||||||
season_subtitles_missing: vec![],
|
season_subtitles_missing: vec![],
|
||||||
season_sorting: vec![],
|
season_sorting: vec![],
|
||||||
|
|
@ -246,6 +253,13 @@ impl Filter for ArchiveFilter {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip the episode if it's a special
|
||||||
|
if self.skip_special
|
||||||
|
&& (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
let mut episodes = vec![];
|
let mut episodes = vec![];
|
||||||
if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) {
|
if !matches!(self.visited, Visited::Series) && !matches!(self.visited, Visited::Season) {
|
||||||
if self.archive.audio.contains(&episode.audio_locale) {
|
if self.archive.audio.contains(&episode.audio_locale) {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,11 @@ pub struct Download {
|
||||||
{episode_id} → ID of the episode")]
|
{episode_id} → ID of the episode")]
|
||||||
#[arg(short, long, default_value = "{title}.mp4")]
|
#[arg(short, long, default_value = "{title}.mp4")]
|
||||||
pub(crate) output: String,
|
pub(crate) output: String,
|
||||||
|
#[arg(help = "Name of the output file if the episode is a special")]
|
||||||
|
#[arg(long_help = "Name of the output file if the episode is a special. \
|
||||||
|
If not set, the '-o'/'--output' flag will be used as name template")]
|
||||||
|
#[arg(long)]
|
||||||
|
pub(crate) special_output: Option<String>,
|
||||||
|
|
||||||
#[arg(help = "Video resolution")]
|
#[arg(help = "Video resolution")]
|
||||||
#[arg(long_help = "The video resolution.\
|
#[arg(long_help = "The video resolution.\
|
||||||
|
|
@ -73,6 +78,9 @@ pub struct Download {
|
||||||
#[arg(help = "Skip files which are already existing")]
|
#[arg(help = "Skip files which are already existing")]
|
||||||
#[arg(long, default_value_t = false)]
|
#[arg(long, default_value_t = false)]
|
||||||
pub(crate) skip_existing: bool,
|
pub(crate) skip_existing: bool,
|
||||||
|
#[arg(help = "Skip special episodes")]
|
||||||
|
#[arg(long, default_value_t = false)]
|
||||||
|
pub(crate) skip_specials: bool,
|
||||||
|
|
||||||
#[arg(help = "Skip any interactive input")]
|
#[arg(help = "Skip any interactive input")]
|
||||||
#[arg(short, long, default_value_t = false)]
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
|
@ -116,6 +124,25 @@ impl Execute for Download {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(special_output) = &self.special_output {
|
||||||
|
if Path::new(special_output)
|
||||||
|
.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.is_empty()
|
||||||
|
&& !is_special_file(special_output)
|
||||||
|
&& special_output != "-"
|
||||||
|
{
|
||||||
|
bail!("No file extension found. Please specify a file extension (via `--special-output`) for the output file")
|
||||||
|
}
|
||||||
|
if let Some(ext) = Path::new(special_output).extension() {
|
||||||
|
if self.force_hardsub {
|
||||||
|
warn!("Hardsubs are forced for special episodes. Adding subtitles may take a while")
|
||||||
|
} else if !["mkv", "mov", "mp4"].contains(&ext.to_string_lossy().as_ref()) {
|
||||||
|
warn!("Detected a container which does not support softsubs. Adding subtitles for special episodes may take a while")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,9 +162,10 @@ impl Execute for Download {
|
||||||
|
|
||||||
for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() {
|
for (i, (media_collection, url_filter)) in parsed_urls.into_iter().enumerate() {
|
||||||
let progress_handler = progress!("Fetching series details");
|
let progress_handler = progress!("Fetching series details");
|
||||||
let single_format_collection = DownloadFilter::new(url_filter, self.clone(), !self.yes)
|
let single_format_collection =
|
||||||
.visit(media_collection)
|
DownloadFilter::new(url_filter, self.clone(), !self.yes, self.skip_specials)
|
||||||
.await?;
|
.visit(media_collection)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if single_format_collection.is_empty() {
|
if single_format_collection.is_empty() {
|
||||||
progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1));
|
progress_handler.stop(format!("Skipping url {} (no matching videos found)", i + 1));
|
||||||
|
|
@ -167,7 +195,15 @@ impl Execute for Download {
|
||||||
let mut downloader = download_builder.clone().build();
|
let mut downloader = download_builder.clone().build();
|
||||||
downloader.add_format(download_format);
|
downloader.add_format(download_format);
|
||||||
|
|
||||||
let formatted_path = format.format_path((&self.output).into());
|
let formatted_path = if format.is_special() {
|
||||||
|
format.format_path(
|
||||||
|
self.special_output
|
||||||
|
.as_ref()
|
||||||
|
.map_or((&self.output).into(), |so| so.into()),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format.format_path((&self.output).into())
|
||||||
|
};
|
||||||
let (path, changed) = free_file(formatted_path.clone());
|
let (path, changed) = free_file(formatted_path.clone());
|
||||||
|
|
||||||
if changed && self.skip_existing {
|
if changed && self.skip_existing {
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,24 @@ pub(crate) struct DownloadFilter {
|
||||||
url_filter: UrlFilter,
|
url_filter: UrlFilter,
|
||||||
download: Download,
|
download: Download,
|
||||||
interactive_input: bool,
|
interactive_input: bool,
|
||||||
|
skip_special: bool,
|
||||||
season_episodes: HashMap<u32, Vec<Episode>>,
|
season_episodes: HashMap<u32, Vec<Episode>>,
|
||||||
season_subtitles_missing: Vec<u32>,
|
season_subtitles_missing: Vec<u32>,
|
||||||
season_visited: bool,
|
season_visited: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DownloadFilter {
|
impl DownloadFilter {
|
||||||
pub(crate) fn new(url_filter: UrlFilter, download: Download, interactive_input: bool) -> Self {
|
pub(crate) fn new(
|
||||||
|
url_filter: UrlFilter,
|
||||||
|
download: Download,
|
||||||
|
interactive_input: bool,
|
||||||
|
skip_special: bool,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
url_filter,
|
url_filter,
|
||||||
download,
|
download,
|
||||||
interactive_input,
|
interactive_input,
|
||||||
|
skip_special,
|
||||||
season_episodes: HashMap::new(),
|
season_episodes: HashMap::new(),
|
||||||
season_subtitles_missing: vec![],
|
season_subtitles_missing: vec![],
|
||||||
season_visited: false,
|
season_visited: false,
|
||||||
|
|
@ -132,6 +139,13 @@ impl Filter for DownloadFilter {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skip the episode if it's a special
|
||||||
|
if self.skip_special
|
||||||
|
&& (episode.sequence_number == 0.0 || episode.sequence_number.fract() != 0.0)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
// check if the audio locale is correct.
|
// check if the audio locale is correct.
|
||||||
// should only be incorrect if the console input was a episode url. otherwise
|
// should only be incorrect if the console input was a episode url. otherwise
|
||||||
// `DownloadFilter::visit_season` returns the correct episodes with matching audio
|
// `DownloadFilter::visit_season` returns the correct episodes with matching audio
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,10 @@ impl Format {
|
||||||
tab_info!("FPS: {:.2}", self.fps)
|
tab_info!("FPS: {:.2}", self.fps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_special(&self) -> bool {
|
||||||
|
self.sequence_number == 0.0 || self.sequence_number.fract() != 0.0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_relative_fmt<S: AsRef<str>>(s: S) -> bool {
|
pub fn has_relative_fmt<S: AsRef<str>>(s: S) -> bool {
|
||||||
return s.as_ref().contains("{relative_episode_number}")
|
return s.as_ref().contains("{relative_episode_number}")
|
||||||
|| s.as_ref().contains("{relative_sequence_number}");
|
|| s.as_ref().contains("{relative_sequence_number}");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue