diff options
author | metamuffin <metamuffin@disroot.org> | 2025-02-02 16:02:42 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-02-02 16:02:42 +0100 |
commit | 4d3ec68b9cbac493ee76981527cb0e780fac9432 (patch) | |
tree | 3f0df02f4c1a119e70663e0e3b4a485d81bc92b3 /import/src | |
parent | 64c962b50d4fbd4605087fc97eac1a032bb826ce (diff) | |
download | jellything-4d3ec68b9cbac493ee76981527cb0e780fac9432.tar jellything-4d3ec68b9cbac493ee76981527cb0e780fac9432.tar.bz2 jellything-4d3ec68b9cbac493ee76981527cb0e780fac9432.tar.zst |
trakt metadata source
Diffstat (limited to 'import/src')
-rw-r--r-- | import/src/lib.rs | 174 |
1 files changed, 122 insertions, 52 deletions
diff --git a/import/src/lib.rs b/import/src/lib.rs index c70d357..4d39565 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -14,12 +14,12 @@ use jellybase::{ database::Database, CONF, SECRETS, }; -use jellyclient::Visibility; +use jellyclient::{Appearance, PeopleGroup, TmdbKind, TraktKind, Visibility}; use log::warn; use matroska::matroska_metadata; use rayon::iter::{ParallelBridge, ParallelIterator}; use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, fs::{read_to_string, File}, io::BufReader, path::Path, @@ -28,6 +28,7 @@ use std::{ }; use tmdb::Tmdb; use tokio::{ + runtime::Handle, sync::{RwLock, Semaphore}, task::spawn_blocking, }; @@ -73,11 +74,14 @@ fn import(db: &Database, incremental: bool) -> Result<()> { trakt: SECRETS.api.trakt.as_ref().map(|key| Trakt::new(key)), tmdb: SECRETS.api.tmdb.as_ref().map(|key| Tmdb::new(key)), }; - drop((apis.tmdb, apis.trakt)); + + let rthandle = Handle::current(); import_traverse( &CONF.media_path, db, + &apis, + &rthandle, incremental, NodeID::MIN, "", @@ -90,6 +94,8 @@ fn import(db: &Database, incremental: bool) -> Result<()> { fn import_traverse( path: &Path, db: &Database, + apis: &Apis, + rthandle: &Handle, incremental: bool, parent: NodeID, parent_slug_fragment: &str, @@ -127,8 +133,16 @@ fn import_traverse( path.read_dir()?.par_bridge().try_for_each(|e| { let path = e?.path(); - if let Err(e) = import_traverse(&path, db, incremental, id, &slug_fragment, visibility) - { + if let Err(e) = import_traverse( + &path, + db, + apis, + rthandle, + incremental, + id, + &slug_fragment, + visibility, + ) { IMPORT_ERRORS.blocking_write().push(format!("{e:#}")); } Ok::<_, anyhow::Error>(()) @@ -147,13 +161,20 @@ fn import_traverse( } } - import_file(&db, &path, parent, visibility).context(anyhow!("{path:?}"))?; + import_file(&db, apis, rthandle, &path, parent, visibility).context(anyhow!("{path:?}"))?; db.set_import_file_mtime(&path, mtime)?; } return Ok(()); } -fn import_file(db: &Database, path: &Path, parent: NodeID, visibility: Visibility) -> Result<()> { +fn import_file( + db: &Database, + apis: &Apis, + rthandle: &Handle, + path: &Path, + parent: NodeID, + visibility: Visibility, +) -> Result<()> { let filename = path.file_name().unwrap().to_string_lossy(); match filename.as_ref() { "poster.jpeg" | "poster.webp" | "poster.png" => { @@ -215,7 +236,9 @@ fn import_file(db: &Database, path: &Path, parent: NodeID, visibility: Visibilit Ok(()) })?; } - _ => import_media_file(db, path, parent, visibility).context("media file")?, + _ => { + import_media_file(db, apis, rthandle, path, parent, visibility).context("media file")? + } } Ok(()) @@ -223,6 +246,8 @@ fn import_file(db: &Database, path: &Path, parent: NodeID, visibility: Visibilit fn import_media_file( db: &Database, + apis: &Apis, + rthandle: &Handle, path: &Path, parent: NodeID, visibility: Visibility, @@ -245,12 +270,74 @@ fn import_media_file( }) .unwrap_or_default(); - let filepath_stem = path - .file_stem() + let filename = path + .file_name() .ok_or(anyhow!("no file stem"))? .to_string_lossy() .to_string(); + let mut backdrop = None; + let mut poster = None; + let mut trakt_data = None; + let mut tmdb_data = None; + + let mut filename_toks = filename.split("."); + let filepath_stem = filename_toks.next().unwrap(); + for tok in filename_toks { + if let Some(trakt_id) = tok.strip_prefix("trakt-") { + 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(TraktKind::Movie, trakt_id, true)) + .context("trakt lookup")?; + let people = rthandle + .block_on(trakt.people(TraktKind::Movie, trakt_id, true)) + .context("trakt people lookup")?; + + let mut people_map = BTreeMap::<PeopleGroup, Vec<Appearance>>::new(); + for p in people.cast.iter() { + people_map.entry(PeopleGroup::Cast).or_default().push(p.a()) + } + for (group, people) in people.crew.iter() { + for p in people { + people_map.entry(group.a()).or_default().push(p.a()) + } + } + + if let Some(tmdb_id) = data.ids.tmdb { + let data = rthandle + .block_on(tmdb.details(TmdbKind::Movie, tmdb_id)) + .context("tmdb details")?; + tmdb_data = Some(data.clone()); + + if let Some(path) = &data.backdrop_path { + let im = rthandle + .block_on(tmdb.image(path)) + .context("tmdb backdrop image")?; + backdrop = Some(AssetInner::Cache(im).ser()); + } + if let Some(path) = &data.poster_path { + let im = rthandle + .block_on(tmdb.image(path)) + .context("tmdb poster image")?; + poster = Some(AssetInner::Cache(im).ser()); + } + + for p in people_map.values_mut().flatten() { + if let Some(id) = p.person.ids.tmdb { + let k = rthandle.block_on(tmdb.person_image(id))?; + if let Some(prof) = k.profiles.first() { + let im = rthandle.block_on(tmdb.image(&prof.file_path))?; + p.person.headshot = Some(AssetInner::Cache(im).ser()); + } + } + } + } + trakt_data = Some((data.clone(), people_map)); + } + } + } + let slug = m .infojson .as_ref() @@ -261,11 +348,35 @@ fn import_media_file( node.slug = slug; node.title = info.title; node.visibility = visibility; - node.poster = m.cover.clone(); + node.poster = m.cover.clone().or(poster); + node.backdrop = backdrop; node.description = tags.remove("DESCRIPTION"); node.tagline = tags.remove("COMMENT"); node.parents.insert(parent); + if let Some(data) = tmdb_data { + node.title = data.title.clone(); + node.tagline = data.tagline.clone(); + node.description = Some(data.overview.clone()); + node.ratings.insert(Rating::Tmdb, data.vote_average); + if let Some(date) = data.release_date.clone() { + node.release_date = tmdb::parse_release_date(&date)?; + } + } + if let Some((data, people)) = trakt_data { + node.title = Some(data.title.clone()); + if let Some(overview) = &data.overview { + node.description = Some(overview.clone()) + } + if let Some(tagline) = &data.tagline { + node.tagline = Some(tagline.clone()) + } + if let Some(rating) = &data.rating { + node.ratings.insert(Rating::Trakt, *rating); + } + node.people.extend(people); + } + let tracks = tracks .entries .into_iter() @@ -303,47 +414,6 @@ fn import_media_file( }) .collect::<Vec<_>>(); - if let Some(infojson) = m.infojson { - node.kind = if !tracks - .iter() - .any(|t| matches!(t.kind, SourceTrackKind::Video { .. })) - { - NodeKind::Music - } else if infojson.duration.unwrap_or(0.) < 600. - && infojson.aspect_ratio.unwrap_or(2.) < 1. - { - NodeKind::ShortFormVideo - } else { - NodeKind::Video - }; - node.title = Some(infojson.title); - if let Some(desc) = infojson.description { - node.description = Some(desc) - } - node.tagline = Some(infojson.webpage_url); - if let Some(date) = &infojson.upload_date { - node.release_date = - Some(infojson::parse_upload_date(date).context("parsing upload date")?); - } - match infojson.extractor.as_str() { - "youtube" => { - node.external_ids - .insert("youtube:video".to_string(), infojson.id); - node.ratings.insert( - Rating::YoutubeViews, - infojson.view_count.unwrap_or_default() as f64, - ); - if let Some(lc) = infojson.like_count { - node.ratings.insert(Rating::YoutubeLikes, lc as f64); - } - } - "Bandcamp" => drop( - node.external_ids - .insert("bandcamp".to_string(), infojson.id), - ), - _ => (), - } - } node.media = Some(MediaInfo { chapters: m .chapters |