diff options
author | metamuffin <metamuffin@disroot.org> | 2025-01-30 16:45:06 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-01-30 16:45:06 +0100 |
commit | bfc5552a8eba07897c2ed626b49c085d97fdfa0d (patch) | |
tree | 2eaff957b4a744e55710502c81f7ce38a3f86294 | |
parent | 32a05b5ec244d4d8143993b082f8d3f86a0a4ecd (diff) | |
download | jellything-bfc5552a8eba07897c2ed626b49c085d97fdfa0d.tar jellything-bfc5552a8eba07897c2ed626b49c085d97fdfa0d.tar.bz2 jellything-bfc5552a8eba07897c2ed626b49c085d97fdfa0d.tar.zst |
external ids and urls
-rw-r--r-- | base/src/database.rs | 22 | ||||
-rw-r--r-- | import/src/lib.rs | 23 | ||||
-rw-r--r-- | import/src/matroska.rs | 14 | ||||
-rw-r--r-- | server/src/routes/external_compat.rs | 41 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 4 |
5 files changed, 82 insertions, 22 deletions
diff --git a/base/src/database.rs b/base/src/database.rs index cd8b2bc..28cafaa 100644 --- a/base/src/database.rs +++ b/base/src/database.rs @@ -56,11 +56,13 @@ impl Database { { // this creates all tables such that read operations on them do not fail. let txn = r.inner.begin_write()?; - drop(txn.open_table(T_INVITE)?); - drop(txn.open_table(T_USER)?); - drop(txn.open_table(T_USER_NODE)?); - drop(txn.open_table(T_NODE)?); - drop(txn.open_table(T_IMPORT_FILE_MTIME)?); + txn.open_table(T_INVITE)?; + txn.open_table(T_USER)?; + txn.open_table(T_USER_NODE)?; + txn.open_table(T_NODE)?; + txn.open_table(T_NODE_CHILDREN)?; + txn.open_table(T_NODE_EXTERNAL_ID)?; + txn.open_table(T_IMPORT_FILE_MTIME)?; txn.commit()?; } @@ -81,7 +83,15 @@ impl Database { Ok(None) } } - + pub fn get_node_external_id(&self, platform: &str, eid: &str) -> Result<Option<NodeID>> { + let txn = self.inner.begin_read()?; + let t_node_external_id = txn.open_table(T_NODE_EXTERNAL_ID)?; + if let Some(id) = t_node_external_id.get((platform, eid))? { + Ok(Some(NodeID(id.value()))) + } else { + Ok(None) + } + } pub fn get_node_children(&self, id: NodeID) -> Result<Vec<NodeID>> { let txn = self.inner.begin_read()?; let t_node_children = txn.open_table(T_NODE_CHILDREN)?; diff --git a/import/src/lib.rs b/import/src/lib.rs index a22551e..c841294 100644 --- a/import/src/lib.rs +++ b/import/src/lib.rs @@ -10,13 +10,12 @@ use jellycommon::{ Chapter, LocalTrack, MediaInfo, Node, NodeID, NodeKind, Rating, SourceTrack, SourceTrackKind, TrackSource, }; -use log::info; use matroska::matroska_metadata; use rayon::iter::{ParallelDrainRange, ParallelIterator}; use std::{ collections::HashMap, fs::File, - io::{BufReader, Read}, + io::BufReader, mem::swap, path::{Path, PathBuf}, sync::LazyLock, @@ -170,9 +169,10 @@ fn import_file(db: &Database, path: &Path) -> Result<()> { .to_owned(), ); node.external_ids - .insert("youtube".to_string(), data.channel_id); + .insert("youtube:channel".to_string(), data.channel_id); if let Some(uid) = data.uploader_id { - node.external_ids.insert("youtube".to_string(), uid); + node.external_ids + .insert("youtube:channel-name".to_string(), uid); } node.description = Some(data.description); if let Some(followers) = data.channel_follower_count { @@ -185,19 +185,15 @@ fn import_file(db: &Database, path: &Path) -> Result<()> { _ => (), } - let mut magic = [0; 4]; - File::open(path)?.read_exact(&mut magic).ok(); - if matches!(magic, [0x1A, 0x45, 0xDF, 0xA3]) { - import_media_file(db, path, parent).context("media file")?; - } + import_media_file(db, path, parent).context("media file")?; Ok(()) } fn import_media_file(db: &Database, path: &Path, parent: NodeID) -> Result<()> { - info!("reading media file {path:?}"); - - let m = (*matroska_metadata(path)?).to_owned(); + let Some(m) = (*matroska_metadata(path)?).to_owned() else { + return Ok(()); + }; let info = m.info.ok_or(anyhow!("no info"))?; let tracks = m.tracks.ok_or(anyhow!("no tracks"))?; @@ -251,7 +247,8 @@ fn import_media_file(db: &Database, path: &Path, parent: NodeID) -> Result<()> { node.release_date = Some(infojson::parse_upload_date(date).context("parsing upload date")?); } - node.external_ids.insert("youtube".to_string(), infojson.id); + node.external_ids + .insert("youtube:video".to_string(), infojson.id); node.ratings.insert( Rating::YoutubeViews, infojson.view_count.unwrap_or_default() as f64, diff --git a/import/src/matroska.rs b/import/src/matroska.rs index bb8d927..6a33420 100644 --- a/import/src/matroska.rs +++ b/import/src/matroska.rs @@ -16,6 +16,7 @@ use jellybase::{ cache::{cache_file, cache_memory}, }; use jellycommon::Asset; +use log::info; use std::{ fs::File, io::{BufReader, ErrorKind, Read, Write}, @@ -32,8 +33,15 @@ pub(crate) struct MatroskaMetadata { pub tags: Option<Tags>, pub infojson: Option<YVideo>, } -pub(crate) fn matroska_metadata(path: &Path) -> Result<Arc<MatroskaMetadata>> { +pub(crate) fn matroska_metadata(path: &Path) -> Result<Arc<Option<MatroskaMetadata>>> { cache_memory(&["mkmeta-v1", path.to_string_lossy().as_ref()], || { + let mut magic = [0; 4]; + File::open(path)?.read_exact(&mut magic).ok(); + if !matches!(magic, [0x1A, 0x45, 0xDF, 0xA3]) { + return Ok(None); + } + + info!("reading media file {path:?}"); let mut file = BufReader::new(File::open(path)?); let mut file = file.by_ref().take(u64::MAX); @@ -100,13 +108,13 @@ pub(crate) fn matroska_metadata(path: &Path) -> Result<Arc<MatroskaMetadata>> { } } } - Ok(MatroskaMetadata { + Ok(Some(MatroskaMetadata { chapters, cover, info, infojson, tags, tracks, - }) + })) }) } diff --git a/server/src/routes/external_compat.rs b/server/src/routes/external_compat.rs new file mode 100644 index 0000000..7babfa5 --- /dev/null +++ b/server/src/routes/external_compat.rs @@ -0,0 +1,41 @@ +/* + 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 super::ui::{account::session::Session, error::MyResult}; +use crate::routes::ui::node::rocket_uri_macro_r_library_node; +use anyhow::anyhow; +use jellybase::database::Database; +use rocket::{get, response::Redirect, State}; + +#[get("/watch?<v>")] +pub fn r_ext_youtube_watch(_session: Session, db: &State<Database>, v: &str) -> MyResult<Redirect> { + if v.len() != 11 { + Err(anyhow!("video id length incorrect"))? + } + let Some(id) = db.get_node_external_id("youtube:video", v)? else { + Err(anyhow!("element not found"))? + }; + let node = db.get_node(id)?.ok_or(anyhow!("node missing"))?; + Ok(Redirect::to(rocket::uri!(r_library_node(&node.slug)))) +} + +#[get("/channel/<id>")] +pub fn r_ext_youtube_channel( + _session: Session, + db: &State<Database>, + id: &str, +) -> MyResult<Redirect> { + let Some(id) = (if id.starts_with("UC") { + db.get_node_external_id("youtube:channel", id)? + } else if id.starts_with("@") { + db.get_node_external_id("youtube:channel-name", id)? + } else { + Err(anyhow!("unknown channel id format"))? + }) else { + Err(anyhow!("channel not found"))? + }; + let node = db.get_node(id)?.ok_or(anyhow!("node missing"))?; + Ok(Redirect::to(rocket::uri!(r_library_node(&node.slug)))) +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 600d544..5fb9b26 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -7,6 +7,7 @@ use self::playersync::{r_streamsync, PlayersyncChannels}; use crate::{database::Database, routes::ui::error::MyResult}; use api::{r_api_account_login, r_api_asset_token_raw, r_api_root, r_api_version}; use base64::Engine; +use external_compat::{r_ext_youtube_channel, r_ext_youtube_watch}; use jellybase::{federation::Federation, CONF, SECRETS}; use log::warn; use rand::random; @@ -48,6 +49,7 @@ use userdata::{ }; pub mod api; +pub mod external_compat; pub mod playersync; pub mod stream; pub mod ui; @@ -140,6 +142,8 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build> r_api_account_login, r_api_root, r_api_asset_token_raw, + r_ext_youtube_watch, + r_ext_youtube_channel, ], ) } |