/* 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) 2023 metamuffin */ use crate::{ database::DataAcid, routes::ui::{account::session::Session, error::MyResult, CacheControlFile}, }; use anyhow::{anyhow, Context}; use jellybase::{ cache::async_cache_file, database::{TableExt, T_NODE, T_NODE_EXTENDED}, federation::Federation, permission::NodePermissionExt, AssetLocationExt, }; pub use jellycommon::AssetRole; use jellycommon::{AssetLocation, LocalTrack, PeopleGroup, SourceTrackKind, TrackSource}; use log::info; use rocket::{get, http::ContentType, State}; #[get("/n//asset?&")] pub async fn r_item_assets( session: Session, db: &State, id: &str, role: AssetRole, width: Option, ) -> MyResult<(ContentType, CacheControlFile)> { let node = T_NODE .get(&db, id)? .only_if_permitted(&session.user.permissions) .ok_or(anyhow!("node does not exist"))?; let mut asset = match role { AssetRole::Backdrop => node.private.backdrop, AssetRole::Poster => node.private.poster, }; if let None = asset { if let Some(parent) = &node.public.path.last() { let parent = T_NODE .get(&db, parent.as_str())? .ok_or(anyhow!("node does not exist"))?; asset = match role { AssetRole::Backdrop => parent.private.backdrop, AssetRole::Poster => parent.private.poster, }; } }; let asset = asset.unwrap_or(AssetLocation::Assets( format!("fallback-{:?}.avif", node.public.kind.unwrap_or_default()).into(), )); Ok(asset_with_res(asset, width).await?) } #[get("/n//person//asset?&")] pub async fn r_person_asset( session: Session, db: &State, id: &str, index: usize, group: PeopleGroup, width: Option, ) -> MyResult<(ContentType, CacheControlFile)> { T_NODE .get(&db, id)? .only_if_permitted(&session.user.permissions) .ok_or(anyhow!("node does not exist"))?; let ext = T_NODE_EXTENDED.get(db, id)?.unwrap_or_default(); let app = ext .people .get(&group) .ok_or(anyhow!("group has no members"))? .get(index) .ok_or(anyhow!("person does not exist"))?; let asset = app.person.asset.to_owned().unwrap_or(AssetLocation::Assets( format!("fallback-Person.avif").into(), )); Ok(asset_with_res(asset, width).await?) } // TODO this can create "federation recursion" because track selection cannot be relied on. #[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 = T_NODE .get(&db, id)? .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, 2048).ilog2()); let path = jellytranscoder::image::transcode(asset, 50., 5, width) .await .context("transcoding asset")?; info!("loading asset from {path:?}"); Ok(( ContentType::AVIF, CacheControlFile::new_cachekey(&path.path()).await?, )) }