aboutsummaryrefslogtreecommitdiff
path: root/import
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-11-29 13:32:52 +0100
committermetamuffin <metamuffin@disroot.org>2025-11-29 13:32:52 +0100
commit5db15c323d76dca9ae71b0204d63dcb09fbbcbc5 (patch)
tree4f69e58c9b6825b7b602712893950673abf9c286 /import
parentbac47e456085ea153ae6ae1b1e28e41868693c9c (diff)
downloadjellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar
jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.bz2
jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.zst
remove asset token; db json
Diffstat (limited to 'import')
-rw-r--r--import/Cargo.toml2
-rw-r--r--import/asset_token/Cargo.toml20
-rw-r--r--import/asset_token/src/lib.rs111
-rw-r--r--import/src/acoustid.rs7
-rw-r--r--import/src/infojson.rs15
-rw-r--r--import/src/lib.rs149
-rw-r--r--import/src/tmdb.rs22
-rw-r--r--import/src/trakt.rs124
8 files changed, 195 insertions, 255 deletions
diff --git a/import/Cargo.toml b/import/Cargo.toml
index 4c638f4..4276768 100644
--- a/import/Cargo.toml
+++ b/import/Cargo.toml
@@ -9,7 +9,6 @@ jellycache = { path = "../cache" }
jellycommon = { path = "../common" }
jellydb = { path = "../database" }
jellyimport-fallback-generator = { path = "fallback_generator" }
-jellyimport-asset-token = { path = "asset_token" }
rayon = "1.11.0"
crossbeam-channel = "0.5.15"
@@ -19,7 +18,6 @@ anyhow = "1.0.100"
reqwest = { workspace = true }
urlencoding = "2.1.3"
-bincode = { version = "2.0.1", features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.145"
serde_yaml = "0.9.34"
diff --git a/import/asset_token/Cargo.toml b/import/asset_token/Cargo.toml
deleted file mode 100644
index a85fcd2..0000000
--- a/import/asset_token/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-name = "jellyimport-asset-token"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-jellycommon = { path = "../../common" }
-jellycache = { path = "../../cache" }
-serde = { version = "1.0.228", features = ["derive"] }
-serde_yaml = "0.9.34"
-log = { workspace = true }
-sha2 = "0.10.9"
-base64 = "0.22.1"
-tokio = { workspace = true }
-anyhow = "1.0.100"
-bincode = { version = "2.0.1", features = ["serde"] }
-rand = "0.9.2"
-serde_json = "1.0.145"
-aes-gcm-siv = "0.11.1"
-humansize = "2.1.3"
diff --git a/import/asset_token/src/lib.rs b/import/asset_token/src/lib.rs
deleted file mode 100644
index ef1850e..0000000
--- a/import/asset_token/src/lib.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- This file is part of jellything (https://codeberg.org/metamuffin/jellything)
- which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
- Copyright (C) 2025 metamuffin <metamuffin.org>
-*/
-use aes_gcm_siv::{aead::Aead, Aes256GcmSiv, KeyInit};
-use anyhow::{anyhow, bail, Context};
-use base64::Engine;
-use jellycache::CachePath;
-pub use jellycommon as common;
-use jellycommon::{Asset, LocalTrack};
-use log::warn;
-use serde::{Deserialize, Serialize};
-use sha2::digest::generic_array::GenericArray;
-use std::sync::Mutex;
-use std::{path::PathBuf, sync::LazyLock};
-
-#[rustfmt::skip]
-#[derive(Debug, Deserialize, Serialize, Default)]
-pub struct Config {
- asset_key: Option<String>,
-}
-
-pub static CONF_PRELOAD: Mutex<Option<Config>> = Mutex::new(None);
-static CONF: LazyLock<Config> = LazyLock::new(|| {
- CONF_PRELOAD
- .lock()
- .unwrap()
- .take()
- .expect("cache config not preloaded. logic error")
-});
-
-const VERSION: u32 = 3;
-
-static ASSET_KEY: LazyLock<Aes256GcmSiv> = LazyLock::new(|| {
- if let Some(sk) = &CONF.asset_key {
- let r = base64::engine::general_purpose::STANDARD
- .decode(sk)
- .expect("key invalid; should be valid base64");
- aes_gcm_siv::Aes256GcmSiv::new_from_slice(&r)
- .expect("key has the wrong length; should be 32 bytes")
- } else {
- warn!("session_key not configured; generating a random one.");
- aes_gcm_siv::Aes256GcmSiv::new_from_slice(&[(); 32].map(|_| rand::random())).unwrap()
- }
-});
-
-#[derive(Debug, Serialize, PartialEq, Eq)]
-pub enum AssetInner {
- Federated { host: String, asset: Vec<u8> },
- Cache(CachePath),
- Assets(PathBuf),
- Media(PathBuf),
- LocalTrack(LocalTrack),
-}
-
-impl AssetInner {
- pub fn ser(&self) -> Asset {
- let mut plaintext = Vec::new();
- plaintext.extend(u32::to_le_bytes(VERSION));
- plaintext.extend(bincode::encode_to_vec(self, bincode::config::standard()).unwrap());
-
- while plaintext.len() % 16 == 0 {
- plaintext.push(0);
- }
-
- let nonce = [(); 12].map(|_| rand::random());
- let mut ciphertext = ASSET_KEY
- .encrypt(&GenericArray::from(nonce), plaintext.as_slice())
- .unwrap();
- ciphertext.extend(nonce);
-
- Asset(base64::engine::general_purpose::URL_SAFE.encode(&ciphertext))
- }
- pub fn deser(s: &str) -> anyhow::Result<Self> {
- let ciphertext = base64::engine::general_purpose::URL_SAFE.decode(s)?;
- let (ciphertext, nonce) = ciphertext.split_at(ciphertext.len() - 12);
- let plaintext = ASSET_KEY
- .decrypt(nonce.into(), ciphertext)
- .map_err(|_| anyhow!("asset token decrypt failed"))?;
-
- let version = u32::from_le_bytes(plaintext[0..4].try_into().unwrap());
- if version != VERSION {
- bail!("asset token version mismatch");
- }
-
- let (data, _): (AssetInner, _) =
- bincode::decode_from_slice(&plaintext[4..], bincode::config::standard())
- .context("asset token has invalid format")?;
- Ok(data)
- }
-
- /// Returns `true` if the asset inner is [`Federated`].
- ///
- /// [`Federated`]: AssetInner::Federated
- #[must_use]
- pub fn is_federated(&self) -> bool {
- matches!(self, Self::Federated { .. })
- }
-}
-
-#[test]
-fn test_identities() {
- *CONF_PRELOAD.lock().unwrap() = Some(Config { asset_key: None });
-
- let a = AssetInner::Assets(PathBuf::new());
- let b = a.ser();
- let c = AssetInner::deser(&b.0).unwrap();
-
- assert_eq!(a, c)
-}
diff --git a/import/src/acoustid.rs b/import/src/acoustid.rs
index a328146..c35708d 100644
--- a/import/src/acoustid.rs
+++ b/import/src/acoustid.rs
@@ -5,7 +5,6 @@
*/
use crate::USER_AGENT;
use anyhow::{Context, Result};
-use bincode::{Decode, Encode};
use jellycache::async_cache_memory;
use log::info;
use reqwest::{
@@ -39,18 +38,18 @@ pub(crate) struct FpCalcOutput {
fingerprint: String,
}
-#[derive(Serialize, Deserialize, Encode, Decode)]
+#[derive(Serialize, Deserialize)]
pub(crate) struct AcoustIDLookupResultRecording {
id: String,
}
-#[derive(Serialize, Deserialize, Encode, Decode)]
+#[derive(Serialize, Deserialize)]
pub(crate) struct AcoustIDLookupResult {
id: String,
score: f32,
#[serde(default)]
recordings: Vec<AcoustIDLookupResultRecording>,
}
-#[derive(Serialize, Deserialize, Encode, Decode)]
+#[derive(Serialize, Deserialize)]
pub(crate) struct AcoustIDLookupResponse {
status: String,
results: Vec<AcoustIDLookupResult>,
diff --git a/import/src/infojson.rs b/import/src/infojson.rs
index e0ebc43..ada6c3a 100644
--- a/import/src/infojson.rs
+++ b/import/src/infojson.rs
@@ -4,12 +4,11 @@
Copyright (C) 2025 metamuffin <metamuffin.org>
*/
use anyhow::Context;
-use bincode::{Decode, Encode};
use jellycommon::chrono::{format::Parsed, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YVideo {
pub id: String,
pub title: String,
@@ -64,7 +63,7 @@ pub struct YVideo {
pub epoch: usize,
}
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YCaption {
pub url: Option<String>,
pub ext: String, //"vtt" | "json3" | "srv1" | "srv2" | "srv3" | "ttml",
@@ -72,7 +71,7 @@ pub struct YCaption {
pub name: Option<String>,
}
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YFormat {
pub format_id: String,
pub format_note: Option<String>,
@@ -97,13 +96,13 @@ pub struct YFormat {
pub format: String,
}
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YFragment {
pub url: Option<String>,
pub duration: Option<f64>,
}
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YThumbnail {
pub url: String,
pub preference: Option<i32>,
@@ -113,14 +112,14 @@ pub struct YThumbnail {
pub resolution: Option<String>,
}
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YChapter {
pub start_time: f64,
pub end_time: f64,
pub title: String,
}
-#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct YHeatmapSample {
pub start_time: f64,
pub end_time: f64,
diff --git a/import/src/lib.rs b/import/src/lib.rs
index 1a1d5ad..d12913f 100644
--- a/import/src/lib.rs
+++ b/import/src/lib.rs
@@ -15,16 +15,16 @@ pub mod wikidata;
pub mod wikimedia_commons;
use jellydb::Database;
-pub use jellyimport_asset_token as asset_token;
-use jellyimport_asset_token::AssetInner;
+use crate::{tmdb::TmdbKind, trakt::TraktKind};
use acoustid::{acoustid_fingerprint, AcoustID};
use anyhow::{anyhow, bail, Context, Result};
use infojson::YVideo;
use jellycache::{cache_file, cache_memory};
use jellycommon::{
- Appearance, Chapter, LocalTrack, MediaInfo, Node, NodeID, NodeKind, ObjectIds, PeopleGroup,
- Person, RatingType, SourceTrack, SourceTrackKind, TmdbKind, TrackSource, TraktKind, Visibility,
+ Appearance, Asset, Chapter, CreditCategory, IdentifierType, LocalTrack, MediaInfo, Node,
+ NodeID, NodeKind, PictureSlot, RatingType, SourceTrack, SourceTrackKind, TrackSource,
+ Visibility,
};
use jellyimport_fallback_generator::generate_fallback;
use jellyremuxer::{
@@ -39,7 +39,7 @@ use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap},
fs::{read_to_string, File},
- io::{BufReader, Write},
+ io::{self, BufReader, Write},
path::{Path, PathBuf},
sync::{Arc, LazyLock, Mutex},
time::UNIX_EPOCH,
@@ -262,15 +262,23 @@ fn import_file(
match filename.as_ref() {
"poster.jpeg" | "poster.webp" | "poster.png" => {
info!("import poster at {path:?}");
+ let path = cache_file("picture-file", path, |mut f| {
+ io::copy(&mut File::open(path)?, &mut f)?;
+ Ok(())
+ })?;
db.update_node_init(parent, |node| {
- node.poster = Some(AssetInner::Media(path.to_owned()).ser());
+ node.pictures.insert(PictureSlot::Poster, Asset(path.0));
Ok(())
})?;
}
"backdrop.jpeg" | "backdrop.webp" | "backdrop.png" => {
info!("import backdrop at {path:?}");
+ let path = cache_file("picture-file", path, |mut f| {
+ io::copy(&mut File::open(path)?, &mut f)?;
+ Ok(())
+ })?;
db.update_node_init(parent, |node| {
- node.backdrop = Some(AssetInner::Media(path.to_owned()).ser());
+ node.pictures.insert(PictureSlot::Backdrop, Asset(path.0));
Ok(())
})?;
}
@@ -291,7 +299,6 @@ fn import_file(
merge_option(&mut node.description, data.description);
merge_option(&mut node.index, data.index);
merge_option(&mut node.release_date, data.release_date);
- node.identifiers.extend(data.identifiers);
Ok(())
})?;
@@ -323,11 +330,11 @@ fn import_file(
node.kind = NodeKind::Channel;
node.title = Some(clean_uploader_name(&data.title).to_owned());
if let Some(cid) = data.channel_id {
- node.identifiers.insert("youtube.channel".to_string(), cid);
+ node.identifiers.insert(IdentifierType::YoutubeChannel, cid);
}
if let Some(uid) = data.uploader_id {
node.identifiers
- .insert("youtube.channelname".to_string(), uid);
+ .insert(IdentifierType::YoutubeChannelHandle, uid);
}
if let Some(desc) = data.description {
node.description = Some(desc);
@@ -455,27 +462,27 @@ fn import_media_file(
let node = NodeID::from_slug(&slug);
let meta = path.metadata()?;
- let mut eids = BTreeMap::new();
+ let mut eids = BTreeMap::<IdentifierType, String>::new();
for (key, value) in &tags {
match key.as_str() {
"MUSICBRAINZ_TRACKID" => {
- eids.insert("musicbrainz.recording".to_string(), value.to_owned())
+ eids.insert(IdentifierType::MusicbrainzRecording, value.to_owned())
}
"MUSICBRAINZ_ARTISTID" => {
- eids.insert("musicbrainz.artist".to_string(), value.to_owned())
+ eids.insert(IdentifierType::MusicbrainzArtist, value.to_owned())
}
"MUSICBRAINZ_ALBUMID" => {
- eids.insert("musicbrainz.release".to_string(), value.to_owned())
+ eids.insert(IdentifierType::MusicbrainzRelease, value.to_owned())
}
"MUSICBRAINZ_ALBUMARTISTID" => {
- eids.insert("musicbrainz.albumartist".to_string(), value.to_owned())
+ None //? ignore this?
}
"MUSICBRAINZ_RELEASEGROUPID" => {
- eids.insert("musicbrainz.releasegroup".to_string(), value.to_owned())
+ eids.insert(IdentifierType::MusicbrainzReleaseGroup, value.to_owned())
}
- "ISRC" => eids.insert("isrc".to_string(), value.to_owned()),
- "BARCODE" => eids.insert("barcode".to_string(), value.to_owned()),
+ "ISRC" => eids.insert(IdentifierType::Isrc, value.to_owned()),
+ "BARCODE" => eids.insert(IdentifierType::Barcode, value.to_owned()),
_ => None,
};
}
@@ -488,20 +495,18 @@ fn import_media_file(
.ok_or(anyhow!("need acoustid"))?
.get_atid_mbid(&fp),
)? {
- eids.insert("acoustid.track".to_string(), atid);
- eids.insert("musicbrainz.recording".to_string(), mbid);
+ eids.insert(IdentifierType::AcoustIdTrack, atid);
+ eids.insert(IdentifierType::MusicbrainzRecording, mbid);
};
}
- let mbrec = eids.get("musicbrainz.recording").cloned();
+ let mbrec = eids.get(&IdentifierType::MusicbrainzRecording).cloned();
db.update_node_init(node, |node| {
node.slug = slug;
node.title = m.info.title.clone().or(node.title.clone());
node.visibility = iflags.visibility;
- node.poster = cover
- .map(|a| AssetInner::Cache(a).ser())
- .or(node.poster.clone());
+
node.description = tags
.remove("DESCRIPTION")
.or(tags.remove("SYNOPSIS"))
@@ -511,6 +516,10 @@ fn import_media_file(
node.identifiers.extend(eids);
+ if let Some(cover) = cover {
+ node.pictures.insert(PictureSlot::Cover, Asset(cover.0));
+ }
+
if let Some(ct) = tags.get("CONTENT_TYPE") {
node.kind = match ct.to_lowercase().trim() {
"movie" | "documentary" | "film" => NodeKind::Movie,
@@ -545,13 +554,7 @@ fn import_media_file(
} else {
SourceTrackKind::Subtitle
},
- source: TrackSource::Local(
- AssetInner::LocalTrack(LocalTrack {
- path: path.to_owned(),
- track: track.track_number as usize,
- })
- .ser(),
- ),
+ source: TrackSource::Local(path.to_owned(), track.track_number),
})
.collect::<Vec<_>>();
@@ -593,7 +596,7 @@ fn import_media_file(
match infojson.extractor.as_str() {
"youtube" => {
node.identifiers
- .insert("youtube.video".to_string(), infojson.id);
+ .insert(IdentifierType::YoutubeVideo, infojson.id);
node.ratings.insert(
RatingType::YoutubeViews,
infojson.view_count.unwrap_or_default() as f64,
@@ -602,7 +605,10 @@ fn import_media_file(
node.ratings.insert(RatingType::YoutubeLikes, lc as f64);
}
}
- "Bandcamp" => drop(node.identifiers.insert("bandcamp".to_string(), infojson.id)),
+ "Bandcamp" => drop(
+ node.identifiers
+ .insert(IdentifierType::Bandcamp, infojson.id),
+ ),
_ => (),
}
}
@@ -669,8 +675,7 @@ fn import_media_file(
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())
+ poster = Some(Asset(rthandle.block_on(tmdb.image(still))?.0))
}
}
}
@@ -679,7 +684,9 @@ fn import_media_file(
node.kind = NodeKind::Episode;
node.index = Some(episode.number);
node.title = Some(episode.title.clone());
- node.poster = poster.or(node.poster.clone());
+ if let Some(poster) = poster {
+ node.pictures.insert(PictureSlot::Poster, poster);
+ }
node.description = episode.overview.clone().or(node.description.clone());
node.ratings.insert(RatingType::Trakt, episode.rating);
Ok(())
@@ -766,11 +773,11 @@ fn apply_musicbrainz_recording(
db.update_node_init(node, |node| {
node.title = Some(rec.title.clone());
node.identifiers
- .insert("musicbrainz.recording".to_string(), rec.id.to_string());
+ .insert(IdentifierType::MusicbrainzRecording, rec.id.to_string());
if let Some(a) = rec.artist_credit.first() {
node.subtitle = Some(a.artist.name.clone());
node.identifiers
- .insert("musicbrainz.artist".to_string(), a.artist.id.to_string());
+ .insert(IdentifierType::MusicbrainzArtist, a.artist.id.to_string());
}
// // TODO proper dedup
@@ -779,12 +786,14 @@ fn apply_musicbrainz_recording(
for rel in &rec.relations {
use musicbrainz::reltypes::*;
let a = match rel.type_id.as_str() {
- INSTRUMENT => Some(("", PeopleGroup::Instrument)),
- VOCAL => Some(("", PeopleGroup::Vocal)),
- PRODUCER => Some(("", PeopleGroup::Producer)),
- MIX => Some(("mix ", PeopleGroup::Engineer)),
- PHONOGRAPHIC_COPYRIGHT => Some(("phonographic copyright ", PeopleGroup::Engineer)),
- PROGRAMMING => Some(("programming ", PeopleGroup::Engineer)),
+ INSTRUMENT => Some(("", CreditCategory::Instrument)),
+ VOCAL => Some(("", CreditCategory::Vocal)),
+ PRODUCER => Some(("", CreditCategory::Producer)),
+ MIX => Some(("mix ", CreditCategory::Engineer)),
+ PHONOGRAPHIC_COPYRIGHT => {
+ Some(("phonographic copyright ", CreditCategory::Engineer))
+ }
+ PROGRAMMING => Some(("programming ", CreditCategory::Engineer)),
_ => None,
};
@@ -808,7 +817,7 @@ fn apply_musicbrainz_recording(
let path = rthandle.block_on(
apis.wikimedia_commons.image_by_filename(filename),
)?;
- image_1 = Some(AssetInner::Cache(path).ser());
+ image_1 = Some(Asset(path.0));
}
}
}
@@ -819,7 +828,7 @@ fn apply_musicbrainz_recording(
if let Some(path) =
rthandle.block_on(apis.vgmdb.get_artist_image(id))?
{
- image_2 = Some(AssetInner::Cache(path).ser());
+ image_2 = Some(Asset(path.0));
}
}
}
@@ -834,27 +843,26 @@ fn apply_musicbrainz_recording(
let headshot = match image_1.or(image_2) {
Some(x) => x,
- None => AssetInner::Cache(cache_file(
- "person-headshot-fallback",
- &artist.sort_name,
- |mut file| {
+ None => Asset(
+ cache_file("person-headshot-fallback", &artist.sort_name, |mut file| {
generate_fallback(&artist.sort_name, &mut file)?;
Ok(())
- },
- )?)
- .ser(),
+ })?
+ .0,
+ ),
};
node.credits.entry(group).or_default().push(Appearance {
jobs,
characters: vec![],
+ node: NodeID([0; 32]), // TODO
});
}
}
for isrc in &rec.isrcs {
node.identifiers
- .insert("isrc".to_string(), isrc.to_string());
+ .insert(IdentifierType::Isrc, isrc.to_string());
}
Ok(())
})?;
@@ -878,9 +886,12 @@ fn apply_trakt_tmdb(
.block_on(trakt.people(trakt_kind, trakt_id))
.context("trakt people lookup")?;
- let mut people_map = BTreeMap::<PeopleGroup, Vec<Appearance>>::new();
+ let mut people_map = BTreeMap::<CreditCategory, Vec<Appearance>>::new();
for p in people.cast.iter() {
- people_map.entry(PeopleGroup::Cast).or_default().push(p.a())
+ people_map
+ .entry(CreditCategory::Cast)
+ .or_default()
+ .push(p.a())
}
for (group, people) in people.crew.iter() {
for p in people {
@@ -908,24 +919,24 @@ fn apply_trakt_tmdb(
let im = rthandle
.block_on(tmdb.image(path))
.context("tmdb backdrop image")?;
- backdrop = Some(AssetInner::Cache(im).ser());
+ backdrop = Some(Asset(im.0));
}
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());
+ poster = Some(Asset(im.0));
}
- 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());
- }
- }
- }
+ // 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());
+ // }
+ // }
+ // }
}
db.update_node_init(node, |node| {
@@ -949,10 +960,10 @@ fn apply_trakt_tmdb(
node.ratings.insert(RatingType::Trakt, *rating);
}
if let Some(poster) = poster {
- node.poster = Some(poster);
+ node.pictures.insert(PictureSlot::Poster, poster);
}
if let Some(backdrop) = backdrop {
- node.backdrop = Some(backdrop);
+ node.pictures.insert(PictureSlot::Backdrop, backdrop);
}
if let Some(data) = tmdb_data {
node.title = data.title.clone().or(node.title.clone());
diff --git a/import/src/tmdb.rs b/import/src/tmdb.rs
index ad99fde..414058f 100644
--- a/import/src/tmdb.rs
+++ b/import/src/tmdb.rs
@@ -6,17 +6,14 @@
use crate::USER_AGENT;
use anyhow::{anyhow, bail, Context};
use jellycache::{async_cache_file, async_cache_memory, CachePath};
-use jellycommon::{
- chrono::{format::Parsed, Utc},
- TmdbKind,
-};
+use jellycommon::chrono::{format::Parsed, Utc};
use log::info;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
Client, ClientBuilder,
};
use serde::{Deserialize, Serialize};
-use std::sync::Arc;
+use std::{fmt::Display, sync::Arc};
use tokio::io::AsyncWriteExt;
pub struct Tmdb {
@@ -164,6 +161,15 @@ pub fn parse_release_date(d: &str) -> anyhow::Result<Option<i64>> {
Ok(Some(p.to_datetime_with_timezone(&Utc)?.timestamp_millis()))
}
+impl Display for TmdbKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(match self {
+ TmdbKind::Tv => "tv",
+ TmdbKind::Movie => "movie",
+ })
+ }
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TmdbEpisode {
pub air_date: String,
@@ -176,6 +182,12 @@ pub struct TmdbEpisode {
pub vote_count: usize,
}
+#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
+pub enum TmdbKind {
+ Tv,
+ Movie,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TmdbPersonImage {
pub id: u64,
diff --git a/import/src/trakt.rs b/import/src/trakt.rs
index f25fa9e..3f0ad47 100644
--- a/import/src/trakt.rs
+++ b/import/src/trakt.rs
@@ -5,9 +5,8 @@
*/
use crate::USER_AGENT;
use anyhow::Context;
-use bincode::{Decode, Encode};
use jellycache::async_cache_memory;
-use jellycommon::{Appearance, ObjectIds, PeopleGroup, Person, TraktKind};
+use jellycommon::{Appearance, CreditCategory, NodeID};
use log::info;
use reqwest::{
header::{HeaderMap, HeaderName, HeaderValue},
@@ -120,10 +119,10 @@ impl Trakt {
}
}
-#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct TraktSeason {
pub number: usize,
- pub ids: ObjectIds,
+ pub ids: TraktIds,
pub rating: f64,
pub votes: usize,
pub episode_count: usize,
@@ -133,12 +132,12 @@ pub struct TraktSeason {
pub network: String,
}
-#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct TraktEpisode {
pub season: Option<usize>,
pub number: usize,
pub number_abs: Option<usize>,
- pub ids: ObjectIds,
+ pub ids: TraktIds,
pub rating: f64,
pub votes: usize,
pub title: String,
@@ -149,7 +148,7 @@ pub struct TraktEpisode {
pub episode_type: String,
}
-#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct TraktPeople {
#[serde(default)]
pub cast: Vec<TraktAppearance>,
@@ -157,7 +156,7 @@ pub struct TraktPeople {
pub crew: BTreeMap<TraktPeopleGroup, Vec<TraktAppearance>>,
}
-#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct TraktAppearance {
#[serde(default)]
pub jobs: Vec<String>,
@@ -166,13 +165,13 @@ pub struct TraktAppearance {
pub person: TraktPerson,
}
-#[derive(Debug, Clone, Deserialize, Serialize, Default, Encode, Decode)]
+#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct TraktPerson {
pub name: String,
- pub ids: ObjectIds,
+ pub ids: TraktIds,
}
-#[derive(Debug, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Serialize, Deserialize)]
pub struct TraktSearchResult {
pub r#type: TraktKind,
pub score: f64,
@@ -180,7 +179,7 @@ pub struct TraktSearchResult {
pub inner: TraktKindObject,
}
-#[derive(Debug, Serialize, Deserialize, Encode, Decode)]
+#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TraktKindObject {
Movie(TraktMediaObject),
@@ -204,9 +203,7 @@ impl TraktKindObject {
}
}
-#[derive(
- Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Encode, Decode, Clone, Copy,
-)]
+#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum TraktPeopleGroup {
#[serde(rename = "production")]
Production,
@@ -234,20 +231,20 @@ pub enum TraktPeopleGroup {
CreatedBy,
}
impl TraktPeopleGroup {
- pub fn a(self) -> PeopleGroup {
+ pub fn a(self) -> CreditCategory {
match self {
- TraktPeopleGroup::Production => PeopleGroup::Production,
- TraktPeopleGroup::Art => PeopleGroup::Art,
- TraktPeopleGroup::Crew => PeopleGroup::Crew,
- TraktPeopleGroup::CostumeMakeup => PeopleGroup::CostumeMakeup,
- TraktPeopleGroup::Directing => PeopleGroup::Directing,
- TraktPeopleGroup::Writing => PeopleGroup::Writing,
- TraktPeopleGroup::Sound => PeopleGroup::Sound,
- TraktPeopleGroup::Camera => PeopleGroup::Camera,
- TraktPeopleGroup::VisualEffects => PeopleGroup::Vfx,
- TraktPeopleGroup::Lighting => PeopleGroup::Lighting,
- TraktPeopleGroup::Editing => PeopleGroup::Editing,
- TraktPeopleGroup::CreatedBy => PeopleGroup::CreatedBy,
+ TraktPeopleGroup::Production => CreditCategory::Production,
+ TraktPeopleGroup::Art => CreditCategory::Art,
+ TraktPeopleGroup::Crew => CreditCategory::Crew,
+ TraktPeopleGroup::CostumeMakeup => CreditCategory::CostumeMakeup,
+ TraktPeopleGroup::Directing => CreditCategory::Directing,
+ TraktPeopleGroup::Writing => CreditCategory::Writing,
+ TraktPeopleGroup::Sound => CreditCategory::Sound,
+ TraktPeopleGroup::Camera => CreditCategory::Camera,
+ TraktPeopleGroup::VisualEffects => CreditCategory::Vfx,
+ TraktPeopleGroup::Lighting => CreditCategory::Lighting,
+ TraktPeopleGroup::Editing => CreditCategory::Editing,
+ TraktPeopleGroup::CreatedBy => CreditCategory::CreatedBy,
}
}
}
@@ -256,20 +253,20 @@ impl TraktAppearance {
Appearance {
jobs: self.jobs.to_owned(),
characters: self.characters.to_owned(),
- person: Person {
- name: self.person.name.to_owned(),
- headshot: None,
- ids: self.person.ids.to_owned(),
- },
+ node: NodeID([0; 32]), // person: Person {
+ // name: self.person.name.to_owned(),
+ // headshot: None,
+ // ids: self.person.ids.to_owned(),
+ // },
}
}
}
-#[derive(Debug, Serialize, Deserialize, Encode, Decode, Clone)]
+#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct TraktMediaObject {
pub title: String,
pub year: Option<u32>,
- pub ids: ObjectIds,
+ pub ids: TraktIds,
pub tagline: Option<String>,
pub overview: Option<String>,
@@ -287,10 +284,19 @@ pub struct TraktMediaObject {
pub genres: Option<Vec<String>>,
}
+#[derive(Debug, Serialize, Deserialize, Clone, Default)]
+pub struct TraktIds {
+ pub trakt: Option<u64>,
+ pub slug: Option<String>,
+ pub tvdb: Option<u64>,
+ pub imdb: Option<String>,
+ pub tmdb: Option<u64>,
+}
+
impl Display for TraktSearchResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
- "{} ({}) \x1b[2m{} [{}]\x1b[0m",
+ "{} ({}) \x1b[2m{} [{:?}]\x1b[0m",
self.inner.inner().title,
self.inner.inner().year.unwrap_or(0),
self.r#type,
@@ -298,3 +304,49 @@ impl Display for TraktSearchResult {
))
}
}
+
+#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq)]
+#[serde(rename_all = "snake_case")]
+pub enum TraktKind {
+ Movie,
+ Show,
+ Season,
+ Episode,
+ Person,
+ User,
+}
+
+impl TraktKind {
+ pub fn singular(self) -> &'static str {
+ match self {
+ TraktKind::Movie => "movie",
+ TraktKind::Show => "show",
+ TraktKind::Season => "season",
+ TraktKind::Episode => "episode",
+ TraktKind::Person => "person",
+ TraktKind::User => "user",
+ }
+ }
+ pub fn plural(self) -> &'static str {
+ match self {
+ TraktKind::Movie => "movies",
+ TraktKind::Show => "shows",
+ TraktKind::Season => "seasons",
+ TraktKind::Episode => "episodes",
+ TraktKind::Person => "people",
+ TraktKind::User => "users", // //! not used in API
+ }
+ }
+}
+impl Display for TraktKind {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(match self {
+ TraktKind::Movie => "Movie",
+ TraktKind::Show => "Show",
+ TraktKind::Season => "Season",
+ TraktKind::Episode => "Episode",
+ TraktKind::Person => "Person",
+ TraktKind::User => "User",
+ })
+ }
+}