diff options
Diffstat (limited to 'import/src/lib.rs')
-rw-r--r-- | import/src/lib.rs | 102 |
1 files changed, 89 insertions, 13 deletions
diff --git a/import/src/lib.rs b/import/src/lib.rs index 0d8a10a..0990ba1 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -18,6 +18,7 @@ use jellyclient::{Appearance, PeopleGroup, TmdbKind, TraktKind, Visibility}; use log::info; use matroska::matroska_metadata; use rayon::iter::{ParallelBridge, ParallelIterator}; +use regex::Regex; use std::{ collections::{BTreeMap, HashMap}, fs::{read_to_string, File}, @@ -42,8 +43,8 @@ pub mod trakt; static IMPORT_SEM: LazyLock<Semaphore> = LazyLock::new(|| Semaphore::new(1)); pub static IMPORT_ERRORS: RwLock<Vec<String>> = RwLock::const_new(Vec::new()); -// static RE_EPISODE_FILENAME: LazyLock<Regex> = -// LazyLock::new(|| Regex::new(r#"([sS](\d+))?([eE](\d+))( (.+))?"#).unwrap()); +static RE_EPISODE_FILENAME: LazyLock<Regex> = + LazyLock::new(|| Regex::new(r#"([sS](?<season>\d+))?([eE](?<episode>\d+))( (.+))?"#).unwrap()); struct Apis { trakt: Option<Trakt>, @@ -296,11 +297,20 @@ fn import_media_file( }) .unwrap_or_default(); - let filename = path - .file_name() - .ok_or(anyhow!("no file stem"))? - .to_string_lossy() - .to_string(); + let filename = path.file_name().unwrap().to_string_lossy().to_string(); + + let mut episode_index = None; + if let Some(cap) = RE_EPISODE_FILENAME.captures(&filename) { + if let Some(episode) = cap.name("episode").map(|m| m.as_str()) { + let season = cap.name("season").map(|m| m.as_str()); + let episode = episode.parse::<usize>().context("parse episode num")?; + let season = season + .unwrap_or("1") + .parse::<usize>() + .context("parse season num")?; + episode_index = Some((season, episode)) + } + } let mut filename_toks = filename.split("."); let filepath_stem = filename_toks.next().unwrap(); @@ -309,17 +319,36 @@ fn import_media_file( .infojson .as_ref() .map(|ij| format!("youtube-{}", ij.id)) - .unwrap_or(make_kebab(&filepath_stem)); + .unwrap_or_else(|| { + if let Some((s, e)) = episode_index { + format!( + "{}-s{s}e{e}", + make_kebab( + &path + .parent() + .unwrap() + .file_name() + .unwrap_or_default() + .to_string_lossy() + ) + ) + } else { + make_kebab(&filepath_stem) + } + }); let node = NodeID::from_slug(&slug); db.update_node_init(node, |node| { node.slug = slug; - node.title = info.title; + node.title = info.title.or(node.title.clone()); node.visibility = visibility; node.poster = m.cover.clone(); - node.description = tags.remove("DESCRIPTION").or(tags.remove("SYNOPSIS")); - node.tagline = tags.remove("COMMENT"); + node.description = tags + .remove("DESCRIPTION") + .or(tags.remove("SYNOPSIS")) + .or(node.description.clone()); + node.tagline = tags.remove("COMMENT").or(node.tagline.clone()); node.parents.insert(parent); if let Some(ct) = tags.get("CONTENT_TYPE") { @@ -440,6 +469,53 @@ fn import_media_file( Ok(()) })?; + if let Some((season, episode)) = episode_index { + let mut trakt_id = None; + let flagspath = path.parent().unwrap().join("flags"); + if flagspath.exists() { + for flag in read_to_string(flagspath)?.lines() { + if let Some(value) = flag.strip_prefix("trakt-").or(flag.strip_prefix("trakt=")) { + let (kind, id) = value.split_once(":").unwrap_or(("", value)); + if kind == "show" { + trakt_id = Some(id.parse::<u64>()?); + } + } + } + } + if let Some(trakt_id) = trakt_id { + let trakt = apis.trakt.as_ref().ok_or(anyhow!("trakt required"))?; + let seasons = rthandle.block_on(trakt.show_seasons(trakt_id))?; + if seasons.iter().any(|x| x.number == season) { + let episodes = rthandle.block_on(trakt.show_season_episodes(trakt_id, season))?; + let mut poster = None; + if let Some(tmdb) = &apis.tmdb { + let trakt_details = + rthandle.block_on(trakt.lookup(TraktKind::Show, trakt_id))?; + if let Some(tmdb_id) = trakt_details.ids.tmdb { + let tmdb_details = + rthandle.block_on(tmdb.episode_details(tmdb_id, season, episode))?; + if let Some(still) = &tmdb_details.still_path { + poster = Some( + AssetInner::Cache(rthandle.block_on(tmdb.image(&still))?).ser(), + ) + } + } + } + if let Some(episode) = episodes.get(episode.saturating_sub(1)) { + db.update_node_init(node, |node| { + node.kind = NodeKind::Episode; + node.index = Some(episode.number); + node.title = Some(episode.title.clone()); + node.poster = poster.or(node.poster.clone()); + node.description = episode.overview.clone().or(node.description.clone()); + node.ratings.insert(Rating::Trakt, episode.rating); + Ok(()) + })? + } + } + } + } + for tok in filename_toks { apply_node_flag(db, rthandle, apis, node, tok)?; } @@ -510,10 +586,10 @@ fn apply_trakt_tmdb( let trakt_id: u64 = trakt_id.parse().context("parse trakt id")?; if let (Some(trakt), Some(tmdb)) = (&apis.trakt, &apis.tmdb) { let data = rthandle - .block_on(trakt.lookup(trakt_kind, trakt_id, true)) + .block_on(trakt.lookup(trakt_kind, trakt_id)) .context("trakt lookup")?; let people = rthandle - .block_on(trakt.people(trakt_kind, trakt_id, true)) + .block_on(trakt.people(trakt_kind, trakt_id)) .context("trakt people lookup")?; let mut people_map = BTreeMap::<PeopleGroup, Vec<Appearance>>::new(); |