aboutsummaryrefslogtreecommitdiff
path: root/server/src/ui/assets.rs
blob: 97fd9c794bcb3e6078b1fa3bbb3af68f52ca0479 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/*
    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::error::MyResult;
use crate::{
    helper::{cache::CacheControlFile, A},
    CONF,
};
use anyhow::{anyhow, bail, Context};
use jellycommon::{NodeID, PeopleGroup};
use jellyimport::asset_token::AssetInner;
use jellylogic::{
    assets::{get_node_backdrop, get_node_person_asset, get_node_poster, get_node_thumbnail},
    session::Session,
};
use log::info;
use rocket::{get, http::ContentType, response::Redirect};
use std::path::PathBuf;

pub const AVIF_QUALITY: f32 = 50.;
pub const AVIF_SPEED: u8 = 5;

#[get("/asset/<token>?<width>")]
pub async fn r_asset(
    _session: A<Session>,
    token: &str,
    width: Option<usize>,
) -> MyResult<(ContentType, CacheControlFile)> {
    let width = width.unwrap_or(2048);
    let asset = AssetInner::deser(token)?;

    // if let AssetInner::Federated { host, asset } = asset {
    //     let session = fed.get_session(&host).await?;

    //     let asset = base64::engine::general_purpose::URL_SAFE.encode(asset);
    //     async_cache_file("fed-asset", &asset, |out| async {
    //         session.asset(out, &asset, width).await
    //     })
    //     .await?
    // } else
    let path = {
        let source = resolve_asset(asset).await.context("resolving asset")?;

        // fit the resolution into a finite set so the maximum cache is finite too.
        let width = 2usize.pow(width.clamp(128, 2048).ilog2());
        jellytranscoder::image::transcode(&source, AVIF_QUALITY, AVIF_SPEED, width)
            .await
            .context("transcoding asset")?
    };
    info!("loading asset from {path:?}");
    Ok((
        ContentType::AVIF,
        CacheControlFile::new_cachekey(&path.abs()).await?,
    ))
}

pub async fn resolve_asset(asset: AssetInner) -> anyhow::Result<PathBuf> {
    match asset {
        AssetInner::Cache(c) => Ok(c.abs()),
        AssetInner::Assets(c) => Ok(CONF.asset_path.join(c)),
        AssetInner::Media(c) => Ok(c),
        _ => bail!("wrong asset type"),
    }
}

#[get("/n/<id>/poster?<width>")]
pub async fn r_item_poster(
    session: A<Session>,
    id: A<NodeID>,
    width: Option<usize>,
) -> MyResult<Redirect> {
    let asset = get_node_poster(&session.0, id.0)?;
    Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width))))
}

#[get("/n/<id>/backdrop?<width>")]
pub async fn r_item_backdrop(
    session: A<Session>,
    id: A<NodeID>,
    width: Option<usize>,
) -> MyResult<Redirect> {
    let asset = get_node_backdrop(&session.0, id.0)?;
    Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width))))
}

#[get("/n/<id>/person/<index>/asset?<group>&<width>")]
pub async fn r_person_asset(
    session: A<Session>,
    id: A<NodeID>,
    index: usize,
    group: String,
    width: Option<usize>,
) -> MyResult<Redirect> {
    let group = PeopleGroup::from_str_opt(&group).ok_or(anyhow!("unknown people group"))?;
    let asset = get_node_person_asset(&session.0, id.0, group, index)?;
    Ok(Redirect::permanent(rocket::uri!(r_asset(asset.0, width))))
}

#[get("/n/<id>/thumbnail?<t>&<width>")]
pub async fn r_node_thumbnail(
    session: A<Session>,
    id: A<NodeID>,
    t: f64,
    width: Option<usize>,
) -> MyResult<Redirect> {
    let asset = get_node_thumbnail(&session.0, id.0, t).await?;
    Ok(Redirect::temporary(rocket::uri!(r_asset(asset.0, width))))
}