From 98b379afb31e455b529d443dcfc5797b8dd6723e Mon Sep 17 00:00:00 2001 From: metamuffin Date: Tue, 16 Jan 2024 21:53:40 +0100 Subject: thumbnail generation --- server/src/routes/mod.rs | 3 +- server/src/routes/stream.rs | 2 +- server/src/routes/ui/assets.rs | 77 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 7 deletions(-) (limited to 'server') diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index c3299d3..47fd6d2 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -27,7 +27,7 @@ use ui::{ r_admin_remove_invite, user::{r_admin_remove_user, r_admin_user, r_admin_user_permission, r_admin_users}, }, - assets::r_item_assets, + assets::{r_item_assets, r_node_thumbnail}, browser::r_all_items_filter, error::{r_api_catch, r_catch}, home::{r_home, r_home_unpriv}, @@ -94,6 +94,7 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket r_assets_js_map, r_stream, r_node_userdata, + r_node_thumbnail, r_player, r_player_progress, r_player_watched, diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs index 8d16ded..e8b14b5 100644 --- a/server/src/routes/stream.rs +++ b/server/src/routes/stream.rs @@ -96,7 +96,7 @@ pub async fn r_stream( }) .await?; - let uri = session.stream( + let uri = session.stream_url( node.public.id.as_ref().unwrap(), &StreamSpec { tracks: vec![remote_index], diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs index f00c416..7df7cf0 100644 --- a/server/src/routes/ui/assets.rs +++ b/server/src/routes/ui/assets.rs @@ -5,12 +5,15 @@ */ use crate::{ database::Database, - routes::ui::{account::session::Session, error::MyError, CacheControlFile}, + routes::ui::{account::session::Session, error::MyResult, CacheControlFile}, }; use anyhow::{anyhow, Context}; -use jellybase::{permission::NodePermissionExt, AssetLocationExt}; -use jellycommon::AssetLocation; +use jellybase::{ + cache::async_cache_file, federation::Federation, permission::NodePermissionExt, + AssetLocationExt, +}; pub use jellycommon::AssetRole; +use jellycommon::{AssetLocation, LocalTrack, SourceTrackKind, TrackSource}; use log::info; use rocket::{get, http::ContentType, State}; use std::{path::PathBuf, str::FromStr}; @@ -23,7 +26,7 @@ pub async fn r_item_assets( id: &str, role: AssetRole, width: Option, -) -> Result<(ContentType, CacheControlFile), MyError> { +) -> MyResult<(ContentType, CacheControlFile)> { let node = db .node .get(&id.to_string())? @@ -45,8 +48,72 @@ pub async fn r_item_assets( let asset = asset.unwrap_or(AssetLocation::Assets( PathBuf::from_str("fallback.avif").unwrap(), )); + Ok(asset_with_res(asset, width).await?) +} + +#[get("/n//thumbnail?&")] +pub async fn r_node_thumbnail( + session: Session, + db: &State, + fed: &State, + id: &str, + t: f64, + width: Option, +) -> MyResult<(ContentType, CacheControlFile)> { + let node = db + .node + .get(&id.to_string())? + .only_if_permitted(&session.user.permissions) + .ok_or(anyhow!("node does not exist"))?; + + let media = node.public.media.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 = node.private.source.ok_or(anyhow!("no source"))?; + let thumb_track_source = &source[thumb_track_index]; + + if t < 0. || t > media.duration { + Err(anyhow!("thumbnail instant not within media duration"))? + } + + let step = 8.; + let t = (t / step).floor() * step; + + let asset = match thumb_track_source { + TrackSource::Local(LocalTrack { path, .. }) => { + // the track selected might be different from thumb_track + jellytranscoder::thumbnail::create_thumbnail(path, t).await? + } + TrackSource::Remote(_) => { + let session = fed + .get_session( + thumb_track + .federated + .last() + .ok_or(anyhow!("federation broken"))?, + ) + .await?; + + async_cache_file(&["fed-thumb", id, &format!("{t}")], |out| { + session.node_thumbnail(out, id, 2048, t) + }) + .await? + } + }; + + Ok(asset_with_res(asset, width).await?) +} + +async fn asset_with_res( + asset: AssetLocation, + width: Option, +) -> MyResult<(ContentType, CacheControlFile)> { // fit the resolution into a finite set so the maximum cache is finite too. - let width = 2usize.pow(width.unwrap_or(2048).clamp(128, 8196).ilog2()); + let width = 2usize.pow(width.unwrap_or(2048).clamp(128, 2048).ilog2()); let path = jellytranscoder::image::transcode(asset, 50., 5, width) .await .context("transcoding asset")?; -- cgit v1.2.3-70-g09d2