diff options
Diffstat (limited to 'import')
-rw-r--r-- | import/Cargo.toml | 4 | ||||
-rw-r--r-- | import/asset_token/Cargo.toml | 20 | ||||
-rw-r--r-- | import/asset_token/src/lib.rs | 105 | ||||
-rw-r--r-- | import/src/infojson.rs | 2 | ||||
-rw-r--r-- | import/src/lib.rs | 16 | ||||
-rw-r--r-- | import/src/tmdb.rs | 4 | ||||
-rw-r--r-- | import/src/trakt.rs | 2 |
7 files changed, 140 insertions, 13 deletions
diff --git a/import/Cargo.toml b/import/Cargo.toml index 112df40..d0b16b4 100644 --- a/import/Cargo.toml +++ b/import/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] -jellybase = { path = "../base" } jellyremuxer = { path = "../remuxer" } jellycache = { path = "../cache" } +jellycommon = { path = "../common" } +jellydb = { path = "../database" } jellyimport-fallback-generator = { path = "fallback_generator" } +jellyimport-asset-token = { path = "asset_token" } rayon = "1.10.0" crossbeam-channel = "0.5.14" diff --git a/import/asset_token/Cargo.toml b/import/asset_token/Cargo.toml new file mode 100644 index 0000000..95615ce --- /dev/null +++ b/import/asset_token/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "jellyimport-asset-token" +version = "0.1.0" +edition = "2021" + +[dependencies] +jellycommon = { path = "../../common" } +jellycache = { path = "../../cache" } +serde = { version = "1.0.217", features = ["derive"] } +serde_yaml = "0.9.34" +log = { workspace = true } +sha2 = "0.10.8" +base64 = "0.22.1" +tokio = { workspace = true } +anyhow = "1.0.95" +bincode = "2.0.0-rc.3" +rand = "0.9.0" +serde_json = "1.0.138" +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 new file mode 100644 index 0000000..87ea261 --- /dev/null +++ b/import/asset_token/src/lib.rs @@ -0,0 +1,105 @@ +/* + 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> +*/ +pub mod assetfed; + +use aes_gcm_siv::{ + aead::{generic_array::GenericArray, Aead}, + Aes256GcmSiv, KeyInit, +}; +use anyhow::{anyhow, bail, Context}; +use base64::Engine; +use bincode::{Decode, Encode}; +use jellycache::CachePath; +pub use jellycommon as common; +use jellycommon::{Asset, LocalTrack}; +use log::warn; +use serde::{Deserialize, Serialize}; +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, Encode, Decode, Serialize)] +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 { .. }) + } +} diff --git a/import/src/infojson.rs b/import/src/infojson.rs index 1efbae9..e0ebc43 100644 --- a/import/src/infojson.rs +++ b/import/src/infojson.rs @@ -5,7 +5,7 @@ */ use anyhow::Context; use bincode::{Decode, Encode}; -use jellybase::common::chrono::{format::Parsed, Utc}; +use jellycommon::chrono::{format::Parsed, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/import/src/lib.rs b/import/src/lib.rs index 784b717..426a96a 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -14,18 +14,18 @@ pub mod vgmdb; pub mod wikidata; pub mod wikimedia_commons; +use jellydb::Database; +pub use jellyimport_asset_token as asset_token; +use jellyimport_asset_token::AssetInner; + use acoustid::{acoustid_fingerprint, AcoustID}; use anyhow::{anyhow, bail, Context, Result}; use infojson::YVideo; -use jellybase::{ - assetfed::AssetInner, - common::{ - Appearance, Chapter, LocalTrack, MediaInfo, Node, NodeID, NodeKind, ObjectIds, PeopleGroup, - Person, Rating, SourceTrack, SourceTrackKind, TmdbKind, TrackSource, TraktKind, Visibility, - }, - database::Database, -}; use jellycache::cache_file; +use jellycommon::{ + Appearance, Chapter, LocalTrack, MediaInfo, Node, NodeID, NodeKind, ObjectIds, PeopleGroup, + Person, Rating, SourceTrack, SourceTrackKind, TmdbKind, TrackSource, TraktKind, Visibility, +}; use jellyimport_fallback_generator::generate_fallback; use jellyremuxer::metadata::checked_matroska_metadata; use log::info; diff --git a/import/src/tmdb.rs b/import/src/tmdb.rs index dff0e95..ceb1650 100644 --- a/import/src/tmdb.rs +++ b/import/src/tmdb.rs @@ -6,11 +6,11 @@ use crate::USER_AGENT; use anyhow::{anyhow, bail, Context}; use bincode::{Decode, Encode}; -use jellybase::common::{ +use jellycache::{async_cache_file, async_cache_memory, CachePath}; +use jellycommon::{ chrono::{format::Parsed, Utc}, TmdbKind, }; -use jellycache::{async_cache_file, async_cache_memory, CachePath}; use log::info; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, diff --git a/import/src/trakt.rs b/import/src/trakt.rs index 434a3a0..f25fa9e 100644 --- a/import/src/trakt.rs +++ b/import/src/trakt.rs @@ -6,8 +6,8 @@ use crate::USER_AGENT; use anyhow::Context; use bincode::{Decode, Encode}; -use jellybase::common::{Appearance, ObjectIds, PeopleGroup, Person, TraktKind}; use jellycache::async_cache_memory; +use jellycommon::{Appearance, ObjectIds, PeopleGroup, Person, TraktKind}; use log::info; use reqwest::{ header::{HeaderMap, HeaderName, HeaderValue}, |