diff options
| author | metamuffin <metamuffin@disroot.org> | 2025-11-29 13:32:52 +0100 |
|---|---|---|
| committer | metamuffin <metamuffin@disroot.org> | 2025-11-29 13:32:52 +0100 |
| commit | 5db15c323d76dca9ae71b0204d63dcb09fbbcbc5 (patch) | |
| tree | 4f69e58c9b6825b7b602712893950673abf9c286 /import | |
| parent | bac47e456085ea153ae6ae1b1e28e41868693c9c (diff) | |
| download | jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.bz2 jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.zst | |
remove asset token; db json
Diffstat (limited to 'import')
| -rw-r--r-- | import/Cargo.toml | 2 | ||||
| -rw-r--r-- | import/asset_token/Cargo.toml | 20 | ||||
| -rw-r--r-- | import/asset_token/src/lib.rs | 111 | ||||
| -rw-r--r-- | import/src/acoustid.rs | 7 | ||||
| -rw-r--r-- | import/src/infojson.rs | 15 | ||||
| -rw-r--r-- | import/src/lib.rs | 149 | ||||
| -rw-r--r-- | import/src/tmdb.rs | 22 | ||||
| -rw-r--r-- | import/src/trakt.rs | 124 |
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", + }) + } +} |