aboutsummaryrefslogtreecommitdiff
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
parentbac47e456085ea153ae6ae1b1e28e41868693c9c (diff)
downloadjellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar
jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.bz2
jellything-5db15c323d76dca9ae71b0204d63dcb09fbbcbc5.tar.zst
remove asset token; db json
-rw-r--r--Cargo.lock23
-rw-r--r--Cargo.toml4
-rw-r--r--common/src/helpers.rs11
-rw-r--r--common/src/impl.rs69
-rw-r--r--common/src/lib.rs35
-rw-r--r--database/Cargo.toml3
-rw-r--r--database/src/lib.rs6
-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
-rw-r--r--logic/Cargo.toml1
-rw-r--r--logic/src/assets.rs215
-rw-r--r--server/src/api.rs6
-rw-r--r--server/src/compat/jellyfin/mod.rs10
-rw-r--r--server/src/config.rs2
-rw-r--r--server/src/logic/stream.rs7
-rw-r--r--server/src/routes.rs3
-rw-r--r--server/src/ui/assets.rs93
-rw-r--r--tool/src/add.rs3
-rw-r--r--ui/src/node_page.rs29
25 files changed, 402 insertions, 568 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 5ac8357..b5a1ed3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 1922d6d..b418dbe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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,
})
}