1 Commits

Author SHA1 Message Date
davidskrundz 2b348851d7 Implement multiple episode numbers 2025-05-20 00:20:43 -06:00
11 changed files with 154 additions and 49 deletions
Generated
+40 -11
View File
@@ -214,7 +214,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "flix"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"chrono",
"flix-tmdb",
@@ -224,12 +224,13 @@ dependencies = [
[[package]]
name = "flix-cli"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"anyhow",
"clap",
"flix",
"flix-tmdb",
"futures",
"home",
"serde",
"tokio",
@@ -238,7 +239,7 @@ dependencies = [
[[package]]
name = "flix-tmdb"
version = "0.0.5"
version = "0.0.6"
dependencies = [
"chrono",
"reqwest",
@@ -263,6 +264,20 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -270,6 +285,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@@ -278,6 +294,18 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
@@ -291,6 +319,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
@@ -429,9 +458,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.11"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710"
dependencies = [
"bytes",
"futures-channel",
@@ -496,9 +525,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
[[package]]
name = "icu_properties"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a"
checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
dependencies = [
"displaydoc",
"icu_collections",
@@ -512,9 +541,9 @@ dependencies = [
[[package]]
name = "icu_properties_data"
version = "2.0.0"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04"
checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
[[package]]
name = "icu_provider"
@@ -1445,9 +1474,9 @@ dependencies = [
[[package]]
name = "windows-result"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
+3 -2
View File
@@ -34,12 +34,13 @@ overflow-checks = true
strip = "debuginfo"
[workspace.dependencies]
flix = { path = "crates/flix", version = "=0.0.5", default-features = false }
flix-tmdb = { path = "crates/tmdb", version = "=0.0.5", default-features = false }
flix = { path = "crates/flix", version = "=0.0.6", default-features = false }
flix-tmdb = { path = "crates/tmdb", version = "=0.0.6", default-features = false }
anyhow = { version = "^1", default-features = false }
chrono = { version = "^0.4", default-features = false }
clap = { version = "^4", default-features = false, features = ["std"] }
futures = { version = "^0.3", default-features = false }
home = { version = "^0.5", default-features = false }
reqwest = { version = "^0.12", default-features = false }
serde = { version = "^1", default-features = false }
+2 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-cli"
version = "0.0.5"
version = "0.0.6"
categories = ["command-line-utilities"]
description = "CLI for interacting with flix media"
@@ -48,5 +48,6 @@ clap = { workspace = true, features = [
] }
home = { workspace = true }
serde = { workspace = true, features = ["derive"] }
futures = { workspace = true }
tokio = { workspace = true, features = ["rt", "fs", "macros"] }
toml = { workspace = true, features = ["display", "parse"] }
+2 -2
View File
@@ -23,8 +23,8 @@ impl Cli {
}
}
pub fn command(&self) -> &Command {
&self.command
pub fn command(self) -> Command {
self.command
}
}
+3
View File
@@ -25,6 +25,7 @@ pub enum Command {
season: u32,
},
/// Process a TMDB episode
#[command(trailing_var_arg = true)]
Episode {
#[arg(value_name = "TMDB_ID")]
id: u32,
@@ -32,5 +33,7 @@ pub enum Command {
season: u32,
#[arg(value_name = "EPISODE_NUM")]
episode: u32,
#[arg(value_name = "...")]
episodes: Vec<u32>,
},
}
+7 -6
View File
@@ -33,14 +33,14 @@ async fn main() -> Result<()> {
force,
output,
command,
} => exec_write(client, *force, output, command).await?,
Command::Update { output } => exec_update(client, output).await?,
} => exec_write(client, force, &output, command).await?,
Command::Update { output } => exec_update(client, &output).await?,
}
Ok(())
}
async fn exec_print(client: Client, command: &BackendCommand) -> Result<()> {
async fn exec_print(client: Client, command: BackendCommand) -> Result<()> {
match command {
BackendCommand::Tmdb { command } => {
let object = run::tmdb::TmdbObject::fetch(&client, command).await?;
@@ -54,7 +54,7 @@ async fn exec_write(
client: Client,
force: bool,
output: &Path,
command: &BackendCommand,
command: BackendCommand,
) -> Result<()> {
match command {
BackendCommand::Tmdb { command } => {
@@ -83,8 +83,9 @@ async fn exec_update(client: Client, output: &Path) -> Result<()> {
let content = fs::read_to_string(output)
.await
.with_context(|| format!("failed to read file at path: {}", output.display()))?;
let object: FlixObject = toml::from_str(&content).context("failed to deserialize flix file")?;
let object: FlixObject = toml::from_str(&content)
.with_context(|| format!("failed to deserialize flix file: {}", output.display()))?;
let command = object.backend_command()?;
exec_write(client, true, output, &command).await
exec_write(client, true, output, command).await
}
+9 -2
View File
@@ -1,6 +1,6 @@
use flix::model::{Collection, Episode, Movie, Season, Show, Verse};
use anyhow::{Result, bail};
use anyhow::{Result, anyhow, bail};
use crate::cli::BackendCommand;
use crate::cli::tmdb;
@@ -56,7 +56,14 @@ impl FlixObject {
tmdb::Command::Episode {
id: tmdb.show_id.into(),
season: episode.episode.season,
episode: episode.episode.number,
episode: episode
.episode
.number
.primary_episode_number()
.ok_or_else(|| {
anyhow!("the episode does not have a primary episode number")
})?,
episodes: episode.episode.number.additional_episode_numbers(),
}
.into()
}
+40 -12
View File
@@ -1,6 +1,7 @@
use flix::model::{
Collection, Episode, GenericCollection, GenericEpisode, GenericMovie, GenericSeason,
GenericShow, Movie, Season, Show, TmdbCollection, TmdbEpisode, TmdbMovie, TmdbSeason, TmdbShow,
Collection, Episode, EpisodeNumber, GenericCollection, GenericEpisode, GenericMovie,
GenericSeason, GenericShow, Movie, Season, Show, TmdbCollection, TmdbEpisode, TmdbMovie,
TmdbSeason, TmdbShow,
};
use flix_tmdb::Client;
use flix_tmdb::model::{
@@ -9,6 +10,7 @@ use flix_tmdb::model::{
};
use anyhow::{Context, Result};
use futures::{StreamExt, TryStreamExt, stream};
use crate::cli::tmdb::Command;
@@ -17,7 +19,7 @@ pub enum TmdbObject {
Movie(TMovie),
Show(TShow),
Season(TSeason, ShowId),
Episode(TEpisode, u32, ShowId),
Episode(TEpisode, Vec<u32>, u32, ShowId),
}
impl TmdbObject {
@@ -63,23 +65,34 @@ impl TmdbObject {
},
tmdb: Some(TmdbSeason { show_id }),
})?,
TmdbObject::Episode(tmdb, season_number, show_id) => toml::to_string(&Episode {
TmdbObject::Episode(tmdb, mut episode_numbers, season_number, show_id) => {
toml::to_string(&Episode {
episode: GenericEpisode {
number: if episode_numbers.is_empty() {
EpisodeNumber::Single {
number: tmdb.episode_number,
}
} else {
episode_numbers.insert(0, tmdb.episode_number);
EpisodeNumber::Multiple {
numbers: episode_numbers,
}
},
season: season_number,
title: tmdb.title,
overview: tmdb.overview,
air_date: tmdb.air_date,
},
tmdb: Some(TmdbEpisode { show_id }),
})?,
})?
}
})
}
}
impl TmdbObject {
pub async fn fetch(client: &Client, command: &Command) -> Result<Self> {
Ok(match *command {
pub async fn fetch(client: &Client, command: Command) -> Result<Self> {
Ok(match command {
Command::Collection { id } => Self::Collection(
client
.collections()
@@ -113,17 +126,32 @@ impl TmdbObject {
id,
season,
episode,
} => Self::Episode(
episodes,
} => {
let mut episode = client
.episodes()
.get_details(id, season, episode, None)
.await
.with_context(|| {
format!("could not get show details for '{id}' S{season}E{episode}")
})?;
let title = stream::once(async { Ok(episode.title) })
.chain(stream::iter(episodes.clone()).then(|episode| async move {
client
.episodes()
.get_details(id, season, episode, None)
.await
.with_context(|| {
format!("could not get show details for '{id}' S{season}E{episode}")
})?,
season,
id.into(),
),
})
.map(|episode| episode.title)
}))
.try_collect::<Vec<_>>()
.await?
.join(" + ");
episode.title = title;
Self::Episode(episode, episodes, season, id.into())
}
})
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix"
version = "0.0.5"
version = "0.0.6"
categories = []
description = "Types for storing persistent data about media"
+37 -2
View File
@@ -14,11 +14,46 @@ pub struct Episode {
pub tmdb: Option<TmdbEpisode>,
}
/// A wrapper for handling single and multi-episode entries
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum EpisodeNumber {
/// The entry contains a single episode
Single {
/// The episode's number
number: u32,
},
/// The entry contains multiple episodes
Multiple {
/// The list of episode numbers
numbers: Vec<u32>,
},
}
impl EpisodeNumber {
/// Get the primary episode number of this episode
pub fn primary_episode_number(&self) -> Option<u32> {
match self {
EpisodeNumber::Single { number } => Some(*number),
EpisodeNumber::Multiple { numbers } => numbers.first().copied(),
}
}
/// Get additional episode numbers of this episode
pub fn additional_episode_numbers(&self) -> Vec<u32> {
match self {
EpisodeNumber::Single { number: _ } => vec![],
EpisodeNumber::Multiple { numbers } => numbers.iter().skip(1).copied().collect(),
}
}
}
/// The generic episode data
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct GenericEpisode {
/// The episode's number
pub number: u32,
/// The episode's number(s)
#[serde(flatten)]
pub number: EpisodeNumber,
/// The episode's season's number
pub season: u32,
/// The episode's title
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "flix-tmdb"
version = "0.0.5"
version = "0.0.6"
categories = []
description = "Clients and models for fetching data from TMDB"