/* 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) 2026 metamuffin */ use super::error::MyResult; use crate::helper::{A, cache::CacheControlImage}; use anyhow::{Context, anyhow}; use jellycommon::{Asset, NodeID, PictureSlot, api::NodeFilterSort}; use jellylogic::{assets::get_node_thumbnail, node::get_node, session::Session}; use rocket::{get, http::ContentType, response::Redirect}; use std::str::FromStr; pub const AVIF_QUALITY: u32 = 70; pub const AVIF_SPEED: u8 = 5; #[get("/image/?")] pub async fn r_image( _session: A, key: A, size: Option, ) -> MyResult<(ContentType, CacheControlImage)> { let size = size.unwrap_or(2048); if !key.0.0.ends_with(".image") { Err(anyhow!("request to non-image"))? } // fit the resolution into a finite set so the maximum cache is finite too. let width = 2usize.pow(size.clamp(128, 2048).ilog2()); let encoded = jellytranscoder::image::transcode(&key.0.0, AVIF_QUALITY, AVIF_SPEED, width) .context("transcoding asset")?; Ok((ContentType::AVIF, CacheControlImage(encoded))) } #[get("/n//image/?")] pub async fn r_item_poster( session: A, id: A, slot: &str, size: Option, ) -> MyResult { let slot = PictureSlot::from_str(slot).map_err(|_| anyhow!("slot invalid"))?; let node = get_node(&session.0, id.0, false, false, NodeFilterSort::default())?; let picture = node .node .pictures .get(&slot) .cloned() .ok_or(anyhow!("no pic todo"))?; Ok(Redirect::permanent(rocket::uri!(r_image(picture, size)))) } #[get("/n//thumbnail?&")] pub async fn r_node_thumbnail( session: A, id: A, t: f64, size: Option, ) -> MyResult { let picture = get_node_thumbnail(&session.0, id.0, t).await?; Ok(Redirect::permanent(rocket::uri!(r_image(picture, size)))) }