Add relative_sequence_number format option (#206, #241, #246)

This commit is contained in:
bytedream 2023-10-15 20:49:03 +02:00
parent 13335c020b
commit 81385ef6ce
5 changed files with 122 additions and 83 deletions

View file

@ -39,17 +39,19 @@ pub struct Archive {
#[arg(help = "Name of the output file")]
#[arg(long_help = "Name of the output file.\
If you use one of the following pattern they will get replaced:\n \
{title} Title of the video\n \
{series_name} Name of the series\n \
{season_name} Name of the season\n \
{audio} Audio language of the video\n \
{resolution} Resolution of the video\n \
{season_number} Number of the season\n \
{episode_number} Number of the episode\n \
{relative_episode_number} Number of the episode relative to its season\n \
{series_id} ID of the series\n \
{season_id} ID of the season\n \
{episode_id} ID of the episode")]
{title} Title of the video\n \
{series_name} Name of the series\n \
{season_name} Name of the season\n \
{audio} Audio language of the video\n \
{resolution} Resolution of the video\n \
{season_number} Number of the season\n \
{episode_number} Number of the episode\n \
{relative_episode_number} Number of the episode relative to its season\n \
{sequence_number} Like '{episode_number}' but without possible non-number characters\n \
{relative_sequence_number} Like '{relative_episode_number}' but with support for episode 0's and .5's\n \
{series_id} ID of the series\n \
{season_id} ID of the season\n \
{episode_id} ID of the episode")]
#[arg(short, long, default_value = "{title}.mkv")]
pub(crate) output: String,

View file

@ -18,7 +18,7 @@ pub(crate) struct ArchiveFilter {
url_filter: UrlFilter,
archive: Archive,
interactive_input: bool,
season_episode_count: HashMap<String, Vec<String>>,
season_episodes: HashMap<String, Vec<Episode>>,
season_subtitles_missing: Vec<u32>,
season_sorting: Vec<String>,
visited: Visited,
@ -30,7 +30,7 @@ impl ArchiveFilter {
url_filter,
archive,
interactive_input,
season_episode_count: HashMap::new(),
season_episodes: HashMap::new(),
season_subtitles_missing: vec![],
season_sorting: vec![],
visited: Visited::None,
@ -226,12 +226,12 @@ impl Filter for ArchiveFilter {
episodes.extend(eps)
}
if Format::has_relative_episodes_fmt(&self.archive.output) {
if Format::has_relative_fmt(&self.archive.output) {
for episode in episodes.iter() {
self.season_episode_count
self.season_episodes
.entry(episode.season_id.clone())
.or_insert(vec![])
.push(episode.id.clone())
.push(episode.clone())
}
}
@ -299,22 +299,34 @@ impl Filter for ArchiveFilter {
episodes.push((episode.clone(), episode.subtitle_locales.clone()))
}
let relative_episode_number = if Format::has_relative_episodes_fmt(&self.archive.output) {
if self.season_episode_count.get(&episode.season_id).is_none() {
let season_episodes = episode.season().await?.episodes().await?;
self.season_episode_count.insert(
episode.season_id.clone(),
season_episodes.into_iter().map(|e| e.id).collect(),
);
let mut relative_episode_number = None;
let mut relative_sequence_number = None;
// get the relative episode number. only done if the output string has the pattern to include
// the relative episode number as this requires some extra fetching
if Format::has_relative_fmt(&self.archive.output) {
let season_eps = match self.season_episodes.get(&episode.season_id) {
Some(eps) => eps,
None => {
self.season_episodes.insert(
episode.season_id.clone(),
episode.season().await?.episodes().await?,
);
self.season_episodes.get(&episode.season_id).unwrap()
}
};
let mut non_integer_sequence_number_count = 0;
for (i, ep) in season_eps.iter().enumerate() {
if ep.sequence_number.fract() != 0.0 || ep.sequence_number == 0.0 {
non_integer_sequence_number_count += 1;
}
if ep.id == episode.id {
relative_episode_number = Some(i + 1);
relative_sequence_number =
Some((i + 1 - non_integer_sequence_number_count) as f32);
break;
}
}
let relative_episode_number = self
.season_episode_count
.get(&episode.season_id)
.unwrap()
.iter()
.position(|id| id == &episode.id)
.map(|index| index + 1);
if relative_episode_number.is_none() {
if relative_episode_number.is_none() || relative_sequence_number.is_none() {
warn!(
"Failed to get relative episode number for episode {} ({}) of {} season {}",
episode.episode_number,
@ -323,16 +335,18 @@ impl Filter for ArchiveFilter {
episode.season_number,
)
}
relative_episode_number
} else {
None
};
}
Ok(Some(
episodes
.into_iter()
.map(|(e, s)| {
SingleFormat::new_from_episode(e, s, relative_episode_number.map(|n| n as u32))
SingleFormat::new_from_episode(
e,
s,
relative_episode_number.map(|n| n as u32),
relative_sequence_number,
)
})
.collect(),
))

View file

@ -35,17 +35,19 @@ pub struct Download {
#[arg(help = "Name of the output file")]
#[arg(long_help = "Name of the output file.\
If you use one of the following pattern they will get replaced:\n \
{title} Title of the video\n \
{series_name} Name of the series\n \
{season_name} Name of the season\n \
{audio} Audio language of the video\n \
{resolution} Resolution of the video\n \
{season_number} Number of the season\n \
{episode_number} Number of the episode\n \
{relative_episode_number} Number of the episode relative to its season\n \
{series_id} ID of the series\n \
{season_id} ID of the season\n \
{episode_id} ID of the episode")]
{title} Title of the video\n \
{series_name} Name of the series\n \
{season_name} Name of the season\n \
{audio} Audio language of the video\n \
{resolution} Resolution of the video\n \
{season_number} Number of the season\n \
{episode_number} Number of the episode\n \
{relative_episode_number} Number of the episode relative to its season\n \
{sequence_number} Like '{episode_number}' but without possible non-number characters\n \
{relative_sequence_number} Like '{relative_episode_number}' but with support for episode 0's and .5's\n \
{series_id} ID of the series\n \
{season_id} ID of the season\n \
{episode_id} ID of the episode")]
#[arg(short, long, default_value = "{title}.mp4")]
pub(crate) output: String,

View file

@ -12,7 +12,7 @@ pub(crate) struct DownloadFilter {
url_filter: UrlFilter,
download: Download,
interactive_input: bool,
season_episode_count: HashMap<u32, Vec<String>>,
season_episodes: HashMap<u32, Vec<Episode>>,
season_subtitles_missing: Vec<u32>,
season_visited: bool,
}
@ -23,7 +23,7 @@ impl DownloadFilter {
url_filter,
download,
interactive_input,
season_episode_count: HashMap::new(),
season_episodes: HashMap::new(),
season_subtitles_missing: vec![],
season_visited: false,
}
@ -107,12 +107,12 @@ impl Filter for DownloadFilter {
let mut episodes = season.episodes().await?;
if Format::has_relative_episodes_fmt(&self.download.output) {
if Format::has_relative_fmt(&self.download.output) {
for episode in episodes.iter() {
self.season_episode_count
self.season_episodes
.entry(episode.season_number)
.or_insert(vec![])
.push(episode.id.clone())
.push(episode.clone())
}
}
@ -189,28 +189,34 @@ impl Filter for DownloadFilter {
}
}
let mut relative_episode_number = None;
let mut relative_sequence_number = None;
// get the relative episode number. only done if the output string has the pattern to include
// the relative episode number as this requires some extra fetching
let relative_episode_number = if Format::has_relative_episodes_fmt(&self.download.output) {
if self
.season_episode_count
.get(&episode.season_number)
.is_none()
{
let season_episodes = episode.season().await?.episodes().await?;
self.season_episode_count.insert(
episode.season_number,
season_episodes.into_iter().map(|e| e.id).collect(),
);
if Format::has_relative_fmt(&self.download.output) {
let season_eps = match self.season_episodes.get(&episode.season_number) {
Some(eps) => eps,
None => {
self.season_episodes.insert(
episode.season_number,
episode.season().await?.episodes().await?,
);
self.season_episodes.get(&episode.season_number).unwrap()
}
};
let mut non_integer_sequence_number_count = 0;
for (i, ep) in season_eps.iter().enumerate() {
if ep.sequence_number.fract() != 0.0 || ep.sequence_number == 0.0 {
non_integer_sequence_number_count += 1;
}
if ep.id == episode.id {
relative_episode_number = Some(i + 1);
relative_sequence_number =
Some((i + 1 - non_integer_sequence_number_count) as f32);
break;
}
}
let relative_episode_number = self
.season_episode_count
.get(&episode.season_number)
.unwrap()
.iter()
.position(|id| id == &episode.id)
.map(|index| index + 1);
if relative_episode_number.is_none() {
if relative_episode_number.is_none() || relative_sequence_number.is_none() {
warn!(
"Failed to get relative episode number for episode {} ({}) of {} season {}",
episode.episode_number,
@ -219,10 +225,7 @@ impl Filter for DownloadFilter {
episode.season_number,
)
}
relative_episode_number
} else {
None
};
}
Ok(Some(SingleFormat::new_from_episode(
episode.clone(),
@ -234,6 +237,7 @@ impl Filter for DownloadFilter {
}
}),
relative_episode_number.map(|n| n as u32),
relative_sequence_number,
)))
}

View file

@ -29,8 +29,9 @@ pub struct SingleFormat {
pub episode_id: String,
pub episode_number: String,
pub sequence_number: f32,
pub relative_episode_number: Option<u32>,
pub sequence_number: f32,
pub relative_sequence_number: Option<f32>,
pub duration: Duration,
@ -42,6 +43,7 @@ impl SingleFormat {
episode: Episode,
subtitles: Vec<Locale>,
relative_episode_number: Option<u32>,
relative_sequence_number: Option<f32>,
) -> Self {
Self {
identifier: if episode.identifier.is_empty() {
@ -73,6 +75,7 @@ impl SingleFormat {
},
sequence_number: episode.sequence_number,
relative_episode_number,
relative_sequence_number,
duration: episode.duration,
source: episode.into(),
}
@ -92,8 +95,9 @@ impl SingleFormat {
season_number: 1,
episode_id: movie.id.clone(),
episode_number: "1".to_string(),
sequence_number: 1.0,
relative_episode_number: Some(1),
sequence_number: 1.0,
relative_sequence_number: Some(1.0),
duration: movie.duration,
source: movie.into(),
}
@ -113,8 +117,9 @@ impl SingleFormat {
season_number: 1,
episode_id: music_video.id.clone(),
episode_number: "1".to_string(),
sequence_number: 1.0,
relative_episode_number: Some(1),
sequence_number: 1.0,
relative_sequence_number: Some(1.0),
duration: music_video.duration,
source: music_video.into(),
}
@ -134,8 +139,9 @@ impl SingleFormat {
season_number: 1,
episode_id: concert.id.clone(),
episode_number: "1".to_string(),
sequence_number: 1.0,
relative_episode_number: Some(1),
sequence_number: 1.0,
relative_sequence_number: Some(1.0),
duration: concert.duration,
source: concert.into(),
}
@ -328,8 +334,9 @@ pub struct Format {
pub episode_id: String,
pub episode_number: String,
pub sequence_number: f32,
pub relative_episode_number: Option<u32>,
pub sequence_number: f32,
pub relative_sequence_number: Option<f32>,
}
impl Format {
@ -363,8 +370,9 @@ impl Format {
season_number: first_format.season_number,
episode_id: first_format.episode_id,
episode_number: first_format.episode_number,
sequence_number: first_format.sequence_number,
relative_episode_number: first_format.relative_episode_number,
sequence_number: first_format.sequence_number,
relative_sequence_number: first_format.relative_sequence_number,
}
}
@ -401,6 +409,14 @@ impl Format {
.replace(
"{relative_episode_number}",
&self.relative_episode_number.unwrap_or_default().to_string(),
)
.replace("{sequence_number}", &self.sequence_number.to_string())
.replace(
"{relative_sequence_number}",
&self
.relative_sequence_number
.unwrap_or_default()
.to_string(),
);
if sanitize {
@ -447,7 +463,8 @@ impl Format {
tab_info!("FPS: {:.2}", self.fps)
}
pub fn has_relative_episodes_fmt<S: AsRef<str>>(s: S) -> bool {
return s.as_ref().contains("{relative_episode_number}");
pub fn has_relative_fmt<S: AsRef<str>>(s: S) -> bool {
return s.as_ref().contains("{relative_episode_number}")
|| s.as_ref().contains("{relative_sequence_number}");
}
}