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 | |
| parent | bac47e456085ea153ae6ae1b1e28e41868693c9c (diff) | |
| download | jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.bz2 jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.zst | |
remove asset token; db json
| -rw-r--r-- | Cargo.lock | 23 | ||||
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | common/src/helpers.rs | 11 | ||||
| -rw-r--r-- | common/src/impl.rs | 69 | ||||
| -rw-r--r-- | common/src/lib.rs | 35 | ||||
| -rw-r--r-- | database/Cargo.toml | 3 | ||||
| -rw-r--r-- | database/src/lib.rs | 6 | ||||
| -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 | ||||
| -rw-r--r-- | logic/Cargo.toml | 1 | ||||
| -rw-r--r-- | logic/src/assets.rs | 215 | ||||
| -rw-r--r-- | server/src/api.rs | 6 | ||||
| -rw-r--r-- | server/src/compat/jellyfin/mod.rs | 10 | ||||
| -rw-r--r-- | server/src/config.rs | 2 | ||||
| -rw-r--r-- | server/src/logic/stream.rs | 7 | ||||
| -rw-r--r-- | server/src/routes.rs | 3 | ||||
| -rw-r--r-- | server/src/ui/assets.rs | 93 | ||||
| -rw-r--r-- | tool/src/add.rs | 3 | ||||
| -rw-r--r-- | ui/src/node_page.rs | 29 |
25 files changed, 402 insertions, 568 deletions
@@ -1907,13 +1907,11 @@ dependencies = [ "anyhow", "async-recursion", "base64", - "bincode", "crossbeam-channel", "futures", "jellycache", "jellycommon", "jellydb", - "jellyimport-asset-token", "jellyimport-fallback-generator", "jellyremuxer", "log", @@ -1928,26 +1926,6 @@ dependencies = [ ] [[package]] -name = "jellyimport-asset-token" -version = "0.1.0" -dependencies = [ - "aes-gcm-siv", - "anyhow", - "base64", - "bincode", - "humansize", - "jellycache", - "jellycommon", - "log", - "rand 0.9.2", - "serde", - "serde_json", - "serde_yaml", - "sha2", - "tokio", -] - -[[package]] name = "jellyimport-fallback-generator" version = "0.1.0" dependencies = [ @@ -1970,7 +1948,6 @@ dependencies = [ "jellycommon", "jellydb", "jellyimport", - "jellyimport-asset-token", "jellytranscoder", "log", "rand 0.9.2", @@ -5,7 +5,6 @@ members = [ "database", "ebml_derive", "import", - "import/asset_token", "import/fallback_generator", "logic", "server", @@ -13,7 +12,8 @@ members = [ "transcoder", "stream", "stream/types", - "ui", "remuxer", + "ui", + "remuxer", ] resolver = "3" diff --git a/common/src/helpers.rs b/common/src/helpers.rs index 44e9468..db75ba9 100644 --- a/common/src/helpers.rs +++ b/common/src/helpers.rs @@ -97,6 +97,12 @@ impl Display for IdentifierType { IdentifierType::Tmdb => "tmdb", IdentifierType::Tvdb => "tvdb", IdentifierType::Omdb => "omdb", + IdentifierType::YoutubeVideo => "youtube_video", + IdentifierType::YoutubeChannel => "youtube_channel", + IdentifierType::YoutubeChannelHandle => "youtube_channel_handle", + IdentifierType::Barcode => "barcode", + IdentifierType::AcoustIdTrack => "acoustid_track", + IdentifierType::Bandcamp => "bandcamp", }) } } @@ -114,6 +120,11 @@ impl FromStr for IdentifierType { "tmdb" => IdentifierType::Tmdb, "tvdb" => IdentifierType::Tvdb, "omdb" => IdentifierType::Omdb, + "youtube_video" => IdentifierType::YoutubeVideo, + "youtube_channel" => IdentifierType::YoutubeChannel, + "youtube_channel_handle" => IdentifierType::YoutubeChannelHandle, + "barcode" => IdentifierType::Barcode, + "acoustid_track" => IdentifierType::AcoustIdTrack, _ => return Err(()), }) } diff --git a/common/src/impl.rs b/common/src/impl.rs index ef1a874..585c778 100644 --- a/common/src/impl.rs +++ b/common/src/impl.rs @@ -3,10 +3,7 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ -use crate::{ - IdentifierType, Identifiers, Node, NodeID, NodeIDOrSlug, SourceTrack, SourceTrackKind, - TmdbKind, TraktKind, -}; +use crate::{Node, NodeID, NodeIDOrSlug, SourceTrack, SourceTrackKind}; use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; @@ -44,70 +41,6 @@ impl Display for SourceTrack { } } -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", - }) - } -} - -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", - }) - } -} -impl Display for Identifiers { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(id) = self.0.get(&IdentifierType::Trakt) { - f.write_fmt(format_args!("trakt={}", id))?; - } - if let Some(id) = &self.0.get(&IdentifierType::Tmdb) { - f.write_fmt(format_args!(",tmdb={}", id))?; - } - if let Some(_id) = &self.0.get(&IdentifierType::Imdb) { - f.write_str(",imdb")?; - } - if let Some(_id) = &self.0.get(&IdentifierType::Tvdb) { - f.write_str(",tvdb")?; - } - if let Some(_id) = &self.0.get(&IdentifierType::Omdb) { - f.write_str(",omdb")?; - } - Ok(()) - } -} - impl NodeID { pub fn from_slug(slug: &str) -> Self { let mut h = blake3::Hasher::new(); diff --git a/common/src/lib.rs b/common/src/lib.rs index d09153e..f3d8416 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -70,14 +70,11 @@ pub struct Node { pub ratings: BTreeMap<RatingType, f64>, pub pictures: BTreeMap<PictureSlot, Asset>, pub credits: BTreeMap<CreditCategory, Vec<Appearance>>, - pub identifiers: Identifiers, + pub identifiers: BTreeMap<IdentifierType, String>, pub visibility: Visibility, pub storage_size: u64, } -#[derive(Debug, Clone, Deserialize, Serialize, Default)] -pub struct Identifiers(pub BTreeMap<IdentifierType, String>); - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] pub enum PictureSlot { @@ -87,14 +84,20 @@ pub enum PictureSlot { Headshot, } -#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] pub enum IdentifierType { MusicbrainzRecording, MusicbrainzArtist, MusicbrainzRelease, MusicbrainzReleaseGroup, + AcoustIdTrack, + YoutubeVideo, + YoutubeChannel, + YoutubeChannelHandle, + Bandcamp, Isrc, + Barcode, Trakt, Imdb, Tmdb, @@ -103,7 +106,7 @@ pub enum IdentifierType { } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct Asset(pub String); +pub struct Asset(pub PathBuf); #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Appearance { @@ -165,7 +168,7 @@ pub enum NodeKind { #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum TrackSource { - Local(Asset), + Local(PathBuf, u64), Remote(usize), } @@ -231,21 +234,3 @@ pub enum SourceTrackKind { }, Subtitle, } - -// TODO move this somewhere else -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Hash, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum TraktKind { - Movie, - Show, - Season, - Episode, - Person, - User, -} - -#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)] -pub enum TmdbKind { - Tv, - Movie, -} diff --git a/database/Cargo.toml b/database/Cargo.toml index c5bfc9d..19f632a 100644 --- a/database/Cargo.toml +++ b/database/Cargo.toml @@ -11,6 +11,3 @@ log = { workspace = true } serde_json = "1.0.145" redb = "3.1.0" anyhow = "1.0.100" - -[features] -db_json = [] diff --git a/database/src/lib.rs b/database/src/lib.rs index fe43ad4..90f596e 100644 --- a/database/src/lib.rs +++ b/database/src/lib.rs @@ -13,11 +13,9 @@ use jellycommon::{ use log::info; use redb::{Durability, ReadableDatabase, ReadableTable, StorageError, TableDefinition}; use search::NodeTextSearchIndex; -#[cfg(not(feature = "db_json"))] use serde::{Serialize, de::DeserializeOwned}; use std::{ fs::create_dir_all, - hash::{DefaultHasher, Hasher}, path::{Path, PathBuf}, str::FromStr, sync::Arc, @@ -197,7 +195,7 @@ impl Database { for parent in &node.parents { t_node_children.insert((parent.0, id.0), ())?; } - for (pl, eid) in &node.identifiers.0 { + for (pl, eid) in &node.identifiers { t_node_external_id.insert((pl.to_string().as_str(), eid.as_str()), id.0)?; } for tag in &node.tags { @@ -468,9 +466,7 @@ impl Database { } #[derive(Debug)] -#[cfg(not(feature = "db_json"))] pub struct Ser<T>(pub T); -#[cfg(not(feature = "db_json"))] impl<T: DeserializeOwned + Serialize + std::fmt::Debug> redb::Value for Ser<T> { type SelfType<'a> = Ser<T> 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", + }) + } +} diff --git a/logic/Cargo.toml b/logic/Cargo.toml index 3c3854c..0c144f2 100644 --- a/logic/Cargo.toml +++ b/logic/Cargo.toml @@ -4,7 +4,6 @@ version = "0.1.0" edition = "2024" [dependencies] -jellyimport-asset-token = { path = "../import/asset_token" } jellyimport = { path = "../import" } jellycommon = { path = "../common" } jellydb = { path = "../database" } diff --git a/logic/src/assets.rs b/logic/src/assets.rs index 02d0c2e..068576b 100644 --- a/logic/src/assets.rs +++ b/logic/src/assets.rs @@ -6,124 +6,125 @@ use crate::{DATABASE, session::Session}; use anyhow::{Result, anyhow}; -use jellycommon::{Asset, LocalTrack, NodeID, PeopleGroup, SourceTrackKind, TrackSource}; -use jellyimport_asset_token::AssetInner; +use jellycommon::{Asset, LocalTrack, NodeID, SourceTrackKind, TrackSource}; -pub fn get_node_backdrop(_session: &Session, id: NodeID) -> Result<Asset> { - // TODO perm - let node = DATABASE - .get_node(id)? - .ok_or(anyhow!("node does not exist"))?; +// pub fn get_node_backdrop(_session: &Session, id: NodeID) -> Result<Asset> { +// // TODO perm +// let node = DATABASE +// .get_node(id)? +// .ok_or(anyhow!("node does not exist"))?; - let mut asset = node.backdrop.clone(); - if asset.is_none() - && let Some(parent) = node.parents.last().copied() { - let parent = DATABASE - .get_node(parent)? - .ok_or(anyhow!("node does not exist"))?; - asset = parent.backdrop.clone(); - }; - Ok(asset.unwrap_or_else(|| { - AssetInner::Assets(format!("fallback-{:?}.avif", node.kind).into()).ser() - })) -} -pub fn get_node_poster(_session: &Session, id: NodeID) -> Result<Asset> { - // TODO perm - let node = DATABASE - .get_node(id)? - .ok_or(anyhow!("node does not exist"))?; +// let mut asset = node.backdrop.clone(); +// if asset.is_none() +// && let Some(parent) = node.parents.last().copied() +// { +// let parent = DATABASE +// .get_node(parent)? +// .ok_or(anyhow!("node does not exist"))?; +// asset = parent.backdrop.clone(); +// }; +// Ok(asset.unwrap_or_else(|| { +// AssetInner::Assets(format!("fallback-{:?}.avif", node.kind).into()).ser() +// })) +// } +// pub fn get_node_poster(_session: &Session, id: NodeID) -> Result<Asset> { +// // TODO perm +// let node = DATABASE +// .get_node(id)? +// .ok_or(anyhow!("node does not exist"))?; - let mut asset = node.poster.clone(); - if asset.is_none() - && let Some(parent) = node.parents.last().copied() { - let parent = DATABASE - .get_node(parent)? - .ok_or(anyhow!("node does not exist"))?; - asset = parent.poster.clone(); - }; - Ok(asset.unwrap_or_else(|| { - AssetInner::Assets(format!("fallback-{:?}.avif", node.kind).into()).ser() - })) -} +// let mut asset = node.poster.clone(); +// if asset.is_none() +// && let Some(parent) = node.parents.last().copied() +// { +// let parent = DATABASE +// .get_node(parent)? +// .ok_or(anyhow!("node does not exist"))?; +// asset = parent.poster.clone(); +// }; +// Ok(asset.unwrap_or_else(|| { +// AssetInner::Assets(format!("fallback-{:?}.avif", node.kind).into()).ser() +// })) +// } -pub fn get_node_person_asset( - _session: &Session, - id: NodeID, - group: PeopleGroup, - index: usize, -) -> Result<Asset> { - // TODO perm +// pub fn get_node_person_asset( +// _session: &Session, +// id: NodeID, +// group: PeopleGroup, +// index: usize, +// ) -> Result<Asset> { +// // TODO perm - let node = DATABASE - .get_node(id)? - .ok_or(anyhow!("node does not exist"))?; - let app = node - .credits - .get(&group) - .ok_or(anyhow!("group has no members"))? - .get(index) - .ok_or(anyhow!("person does not exist"))?; +// let node = DATABASE +// .get_node(id)? +// .ok_or(anyhow!("node does not exist"))?; +// let app = node +// .credits +// .get(&group) +// .ok_or(anyhow!("group has no members"))? +// .get(index) +// .ok_or(anyhow!("person does not exist"))?; - let asset = app - .person - .headshot - .to_owned() - .unwrap_or(AssetInner::Assets("fallback-Person.avif".into()).ser()); +// let asset = app +// .person +// .headshot +// .to_owned() +// .unwrap_or(AssetInner::Assets("fallback-Person.avif".into()).ser()); - Ok(asset) -} +// Ok(asset) +// } -pub async fn get_node_thumbnail(_session: &Session, id: NodeID, t: f64) -> Result<Asset> { - let node = DATABASE - .get_node(id)? - .ok_or(anyhow!("node does not exist"))?; +// pub async fn get_node_thumbnail(_session: &Session, id: NodeID, t: f64) -> Result<Asset> { +// let node = DATABASE +// .get_node(id)? +// .ok_or(anyhow!("node does not exist"))?; - let media = node.media.as_ref().ok_or(anyhow!("no media"))?; - let (thumb_track_index, _thumb_track) = media - .tracks - .iter() - .enumerate() - .find(|(_i, t)| matches!(t.kind, SourceTrackKind::Video { .. })) - .ok_or(anyhow!("no video track to create a thumbnail of"))?; - let source = media - .tracks - .get(thumb_track_index) - .ok_or(anyhow!("no source"))?; - let thumb_track_source = source.source.clone(); +// let media = node.media.as_ref().ok_or(anyhow!("no media"))?; +// let (thumb_track_index, _thumb_track) = media +// .tracks +// .iter() +// .enumerate() +// .find(|(_i, t)| matches!(t.kind, SourceTrackKind::Video { .. })) +// .ok_or(anyhow!("no video track to create a thumbnail of"))?; +// let source = media +// .tracks +// .get(thumb_track_index) +// .ok_or(anyhow!("no source"))?; +// let thumb_track_source = source.source.clone(); - if t < 0. || t > media.duration { - Err(anyhow!("thumbnail instant not within media duration"))? - } +// if t < 0. || t > media.duration { +// Err(anyhow!("thumbnail instant not within media duration"))? +// } - let step = 8.; - let t = (t / step).floor() * step; +// let step = 8.; +// let t = (t / step).floor() * step; - let asset = match thumb_track_source { - TrackSource::Local(a) => { - let AssetInner::LocalTrack(LocalTrack { path, .. }) = AssetInner::deser(&a.0)? else { - return Err(anyhow!("track set to wrong asset type")); - }; - // the track selected might be different from thumb_track - jellytranscoder::thumbnail::create_thumbnail(&path, t).await? - } - TrackSource::Remote(_) => { - // // TODO in the new system this is preferrably a property of node ext for regular fed - // let session = fed - // .get_session( - // thumb_track - // .federated - // .last() - // .ok_or(anyhow!("federation broken"))?, - // ) - // .await?; +// let asset = match thumb_track_source { +// TrackSource::Local(a) => { +// let AssetInner::LocalTrack(LocalTrack { path, .. }) = AssetInner::deser(&a.0)? else { +// return Err(anyhow!("track set to wrong asset type")); +// }; +// // the track selected might be different from thumb_track +// jellytranscoder::thumbnail::create_thumbnail(&path, t).await? +// } +// TrackSource::Remote(_) => { +// // // TODO in the new system this is preferrably a property of node ext for regular fed +// // let session = fed +// // .get_session( +// // thumb_track +// // .federated +// // .last() +// // .ok_or(anyhow!("federation broken"))?, +// // ) +// // .await?; - // async_cache_file("fed-thumb", (id.0, t as i64), |out| { - // session.node_thumbnail(out, id.0.into(), 2048, t) - // }) - // .await? - todo!() - } - }; +// // async_cache_file("fed-thumb", (id.0, t as i64), |out| { +// // session.node_thumbnail(out, id.0.into(), 2048, t) +// // }) +// // .await? +// todo!() +// } +// }; - Ok(AssetInner::Cache(asset).ser()) -} +// Ok(AssetInner::Cache(asset).ser()) +// } diff --git a/server/src/api.rs b/server/src/api.rs index 4fecfb6..217cd9f 100644 --- a/server/src/api.rs +++ b/server/src/api.rs @@ -6,7 +6,6 @@ use super::ui::error::MyResult; use crate::helper::{accept::AcceptJson, language::AcceptLanguage, A}; use jellycommon::{user::CreateSessionParams, NodeID}; -use jellyimport::asset_token::AssetInner; use jellylogic::{login::login_logic, node::get_nodes_modified_since, session::Session}; use jellyui::locale::get_translation_table; use rocket::{get, post, response::Redirect, serde::json::Json, Either}; @@ -55,11 +54,6 @@ pub fn r_api_account_login(data: Json<CreateSessionParams>) -> MyResult<Value> { Ok(json!(token)) } -#[get("/api/asset_token_raw/<token>")] -pub fn r_api_asset_token_raw(session: A<Session>, token: &str) -> MyResult<Json<AssetInner>> { - session.0.assert_admin()?; - Ok(Json(AssetInner::deser(token)?)) -} #[get("/nodes_modified?<since>")] pub fn r_nodes_modified_since(session: A<Session>, since: u64) -> MyResult<Json<Vec<NodeID>>> { diff --git a/server/src/compat/jellyfin/mod.rs b/server/src/compat/jellyfin/mod.rs index 3f04705..27df1aa 100644 --- a/server/src/compat/jellyfin/mod.rs +++ b/server/src/compat/jellyfin/mod.rs @@ -739,9 +739,13 @@ fn item_object(node: &Node, userdata: &NodeUserData) -> JellyfinItem { .iter() .flat_map(|(_pg, ps)| { ps.iter().map(|p| JellyfinPerson { - name: p.person.name.clone(), - id: p.person.ids.tmdb.unwrap_or_default().to_string(), - primary_image_tag: p.person.headshot.clone().map(|a| a.0).unwrap_or_default(), + // TODO + id: String::new(), + name: String::new(), + primary_image_tag: String::new(), + // name: p..name.clone(), + // id: p..ids.tmdb.unwrap_or_default().to_string(), + // primary_image_tag: p..headshot.clone().map(|a| a.0).unwrap_or_default(), role: p.characters.join(","), r#type: JellyfinPersonType::Actor, }) diff --git a/server/src/config.rs b/server/src/config.rs index 28fcf90..b663c78 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -17,7 +17,6 @@ struct Config { stream: jellystream::Config, cache: jellycache::Config, server: crate::Config, - base: jellyimport::asset_token::Config, logic: jellylogic::Config, import: jellyimport::Config, } @@ -37,7 +36,6 @@ pub async fn load_config() -> Result<()> { *jellytranscoder::CONF_PRELOAD.lock().unwrap() = Some(config.transcoder); *jellycache::CONF_PRELOAD.lock().unwrap() = Some(config.cache); *jellylogic::CONF_PRELOAD.lock().unwrap() = Some(config.logic); - *jellyimport::asset_token::CONF_PRELOAD.lock().unwrap() = Some(config.base); *jellyimport::CONF_PRELOAD.lock().unwrap() = Some(config.import); *crate::CONF_PRELOAD.lock().unwrap() = Some(config.server); *jellyui::CONF_PRELOAD.lock().unwrap() = Some(config.ui); diff --git a/server/src/logic/stream.rs b/server/src/logic/stream.rs index d239c92..782cbcc 100644 --- a/server/src/logic/stream.rs +++ b/server/src/logic/stream.rs @@ -6,7 +6,6 @@ use crate::{helper::A, ui::error::MyError}; use anyhow::{anyhow, Result}; use jellycommon::{api::NodeFilterSort, stream::StreamSpec, NodeID, TrackSource}; -use jellyimport::asset_token::AssetInner; use jellylogic::{node::get_node, session::Session}; use jellystream::SMediaInfo; use log::{info, warn}; @@ -136,10 +135,8 @@ pub async fn r_stream( let mut sources = BTreeSet::new(); for t in &media.tracks { - if let TrackSource::Local(x) = &t.source { - if let AssetInner::LocalTrack(m) = AssetInner::deser(&x.0)? { - sources.insert(m.path); - } + if let TrackSource::Local(path, _) = &t.source { + sources.insert(path.to_owned()); } } let media = Arc::new(SMediaInfo { diff --git a/server/src/routes.rs b/server/src/routes.rs index 3f3518b..9a35105 100644 --- a/server/src/routes.rs +++ b/server/src/routes.rs @@ -30,7 +30,7 @@ use crate::ui::{ use crate::CONF; use crate::{ api::{ - r_api_account_login, r_api_asset_token_raw, r_api_root, r_nodes_modified_since, + r_api_account_login, r_api_root, r_nodes_modified_since, r_translations, r_version, }, compat::{ @@ -160,7 +160,6 @@ pub fn build_rocket() -> Rocket<Build> { r_stream, // API r_api_account_login, - r_api_asset_token_raw, r_nodes_modified_since, r_api_root, r_version, diff --git a/server/src/ui/assets.rs b/server/src/ui/assets.rs index 97fd9c7..969f3ed 100644 --- a/server/src/ui/assets.rs +++ b/server/src/ui/assets.rs @@ -9,12 +9,8 @@ use crate::{ CONF, }; use anyhow::{anyhow, bail, Context}; -use jellycommon::{NodeID, PeopleGroup}; -use jellyimport::asset_token::AssetInner; -use jellylogic::{ - assets::{get_node_backdrop, get_node_person_asset, get_node_poster, get_node_thumbnail}, - session::Session, -}; +use jellycommon::NodeID; +use jellylogic::session::Session; use log::info; use rocket::{get, http::ContentType, response::Redirect}; use std::path::PathBuf; @@ -28,42 +24,43 @@ pub async fn r_asset( token: &str, width: Option<usize>, ) -> MyResult<(ContentType, CacheControlFile)> { - let width = width.unwrap_or(2048); - let asset = AssetInner::deser(token)?; + // let width = width.unwrap_or(2048); + // let asset = AssetInner::deser(token)?; - // if let AssetInner::Federated { host, asset } = asset { - // let session = fed.get_session(&host).await?; + // // if let AssetInner::Federated { host, asset } = asset { + // // let session = fed.get_session(&host).await?; - // let asset = base64::engine::general_purpose::URL_SAFE.encode(asset); - // async_cache_file("fed-asset", &asset, |out| async { - // session.asset(out, &asset, width).await - // }) - // .await? - // } else - let path = { - let source = resolve_asset(asset).await.context("resolving asset")?; + // // let asset = base64::engine::general_purpose::URL_SAFE.encode(asset); + // // async_cache_file("fed-asset", &asset, |out| async { + // // session.asset(out, &asset, width).await + // // }) + // // .await? + // // } else + // let path = { + // let source = resolve_asset(asset).await.context("resolving asset")?; - // fit the resolution into a finite set so the maximum cache is finite too. - let width = 2usize.pow(width.clamp(128, 2048).ilog2()); - jellytranscoder::image::transcode(&source, AVIF_QUALITY, AVIF_SPEED, width) - .await - .context("transcoding asset")? - }; - info!("loading asset from {path:?}"); - Ok(( - ContentType::AVIF, - CacheControlFile::new_cachekey(&path.abs()).await?, - )) + // // fit the resolution into a finite set so the maximum cache is finite too. + // let width = 2usize.pow(width.clamp(128, 2048).ilog2()); + // jellytranscoder::image::transcode(&source, AVIF_QUALITY, AVIF_SPEED, width) + // .await + // .context("transcoding asset")? + // }; + // info!("loading asset from {path:?}"); + // Ok(( + // ContentType::AVIF, + // CacheControlFile::new_cachekey(&path.abs()).await?, + // )) + todo!() } -pub async fn resolve_asset(asset: AssetInner) -> anyhow::Result<PathBuf> { - match asset { - AssetInner::Cache(c) => Ok(c.abs()), - AssetInner::Assets(c) => Ok(CONF.asset_path.join(c)), - AssetInner::Media(c) => Ok(c), - _ => bail!("wrong asset type"), - } -} +// pub async fn resolve_asset(asset: AssetInner) -> anyhow::Result<PathBuf> { +// match asset { +// AssetInner::Cache(c) => Ok(c.abs()), +// AssetInner::Assets(c) => Ok(CONF.asset_path.join(c)), +// AssetInner::Media(c) => Ok(c), +// _ => bail!("wrong asset type"), +// } +// } #[get("/n/<id>/poster?<width>")] pub async fn r_item_poster( @@ -71,8 +68,9 @@ pub async fn r_item_poster( id: A<NodeID>, width: Option<usize>, ) -> MyResult<Redirect> { - let asset = get_node_poster(&session.0, id.0)?; - Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) + // let asset = get_node_poster(&session.0, id.0)?; + // Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) + Err(anyhow!("a").into()) } #[get("/n/<id>/backdrop?<width>")] @@ -81,8 +79,9 @@ pub async fn r_item_backdrop( id: A<NodeID>, width: Option<usize>, ) -> MyResult<Redirect> { - let asset = get_node_backdrop(&session.0, id.0)?; - Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) + // let asset = get_node_backdrop(&session.0, id.0)?; + // Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) + Err(anyhow!("a").into()) } #[get("/n/<id>/person/<index>/asset?<group>&<width>")] @@ -93,9 +92,10 @@ pub async fn r_person_asset( group: String, width: Option<usize>, ) -> MyResult<Redirect> { - let group = PeopleGroup::from_str_opt(&group).ok_or(anyhow!("unknown people group"))?; - let asset = get_node_person_asset(&session.0, id.0, group, index)?; - Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) + // let group = PeopleGroup::from_str_opt(&group).ok_or(anyhow!("unknown people group"))?; + // let asset = get_node_person_asset(&session.0, id.0, group, index)?; + // Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width)))) + Err(anyhow!("a").into()) } #[get("/n/<id>/thumbnail?<t>&<width>")] @@ -105,6 +105,7 @@ pub async fn r_node_thumbnail( t: f64, width: Option<usize>, ) -> MyResult<Redirect> { - let asset = get_node_thumbnail(&session.0, id.0, t).await?; - Ok(Redirect::temporary(rocket::uri!(r_asset(asset.0, width)))) + // let asset = get_node_thumbnail(&session.0, id.0, t).await?; + // Ok(Redirect::temporary(rocket::uri!(r_asset(asset.0, width)))) + Err(anyhow!("a").into()) } diff --git a/tool/src/add.rs b/tool/src/add.rs index d5e7180..fc43b76 100644 --- a/tool/src/add.rs +++ b/tool/src/add.rs @@ -5,8 +5,7 @@ */ use crate::cli::Action; use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect, Input}; -use jellycommon::TraktKind; -use jellyimport::get_trakt; +use jellyimport::{get_trakt, trakt::TraktKind}; use log::warn; use std::{ fmt::Display, diff --git a/ui/src/node_page.rs b/ui/src/node_page.rs index 1e029ae..e7e7b1d 100644 --- a/ui/src/node_page.rs +++ b/ui/src/node_page.rs @@ -13,7 +13,7 @@ use crate::{ props::Props, }; use jellycommon::{ - Chapter, Node, NodeKind, PeopleGroup, + Chapter, CreditCategory, IdentifierType, Node, NodeKind, api::NodeFilterSort, routes::{ u_node_slug, u_node_slug_backdrop, u_node_slug_person_asset, u_node_slug_player, @@ -119,7 +119,7 @@ markup::define! { @if !node.credits.is_empty() { h2 { @trs(lang, "node.people") } @for (group, people) in &node.credits { - details[open=group==&PeopleGroup::Cast] { + details[open=group==&CreditCategory::Cast] { summary { h3 { @format!("{}", group) } } ul.children.hlist { @for (i, pe) in people.iter().enumerate() { li { .card."aspect-port" { @@ -129,7 +129,7 @@ markup::define! { } } .title { - span { @pe.person.name } br; + // TODO span { @pe.person.name } br; @if let Some(c) = pe.characters.first() { span.subtitle { @c } } @@ -156,7 +156,7 @@ markup::define! { @for (key, value) in &node.identifiers { tr { tr { td { @trs(lang, &format!("eid.{}", key)) } - @if let Some(url) = external_id_url(key, value) { + @if let Some(url) = external_id_url(*key, value) { td { a[href=url] { pre { @value } } } } else { td { pre { @value } } @@ -216,16 +216,19 @@ pub fn aspect_class(kind: NodeKind) -> &'static str { } } -fn external_id_url(key: &str, value: &str) -> Option<String> { +fn external_id_url(key: IdentifierType, value: &str) -> Option<String> { Some(match key { - "youtube.video" => format!("https://youtube.com/watch?v={value}"), - "youtube.channel" => format!("https://youtube.com/channel/{value}"), - "youtube.channelname" => format!("https://youtube.com/channel/@{value}"), - "musicbrainz.release" => format!("https://musicbrainz.org/release/{value}"), - "musicbrainz.albumartist" => format!("https://musicbrainz.org/artist/{value}"), - "musicbrainz.artist" => format!("https://musicbrainz.org/artist/{value}"), - "musicbrainz.releasegroup" => format!("https://musicbrainz.org/release-group/{value}"), - "musicbrainz.recording" => format!("https://musicbrainz.org/recording/{value}"), + IdentifierType::YoutubeVideo => format!("https://youtube.com/watch?v={value}"), + IdentifierType::YoutubeChannel => format!("https://youtube.com/channel/{value}"), + IdentifierType::YoutubeChannelHandle => format!("https://youtube.com/channel/@{value}"), + IdentifierType::MusicbrainzRelease => format!("https://musicbrainz.org/release/{value}"), + IdentifierType::MusicbrainzArtist => format!("https://musicbrainz.org/artist/{value}"), + IdentifierType::MusicbrainzReleaseGroup => { + format!("https://musicbrainz.org/release-group/{value}") + } + IdentifierType::MusicbrainzRecording => { + format!("https://musicbrainz.org/recording/{value}") + } _ => return None, }) } |