aboutsummaryrefslogtreecommitdiff
path: root/import
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-02-02 16:02:42 +0100
committermetamuffin <metamuffin@disroot.org>2025-02-02 16:02:42 +0100
commit4d3ec68b9cbac493ee76981527cb0e780fac9432 (patch)
tree3f0df02f4c1a119e70663e0e3b4a485d81bc92b3 /import
parent64c962b50d4fbd4605087fc97eac1a032bb826ce (diff)
downloadjellything-4d3ec68b9cbac493ee76981527cb0e780fac9432.tar
jellything-4d3ec68b9cbac493ee76981527cb0e780fac9432.tar.bz2
jellything-4d3ec68b9cbac493ee76981527cb0e780fac9432.tar.zst
trakt metadata source
Diffstat (limited to 'import')
-rw-r--r--import/src/lib.rs174
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