aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/src/routes/mod.rs3
-rw-r--r--server/src/routes/stream.rs47
2 files changed, 47 insertions, 3 deletions
diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs
index e93aac2..5b1c799 100644
--- a/server/src/routes/mod.rs
+++ b/server/src/routes/mod.rs
@@ -47,7 +47,7 @@ use self::{
};
use crate::{
State,
- routes::{admin::r_admin_debug, search::r_search},
+ routes::{admin::r_admin_debug, search::r_search, stream::r_thumbnail},
};
use rocket::{
Build, Config, Rocket, catchers, fairing::AdHoc, fs::FileServer, http::Header, routes,
@@ -120,6 +120,7 @@ pub(super) fn build_rocket(state: Arc<State>) -> Rocket<Build> {
r_image_fallback_person,
r_image,
r_index,
+ r_thumbnail,
r_items,
r_node,
r_player,
diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs
index 45b86a9..0baaca6 100644
--- a/server/src/routes/stream.rs
+++ b/server/src/routes/stream.rs
@@ -3,11 +3,15 @@
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2026 metamuffin <metamuffin.org>
*/
-use crate::{request_info::RequestInfo, routes::error::MyError};
+use crate::{
+ request_info::RequestInfo,
+ routes::error::{MyError, MyResult},
+};
use anyhow::{Result, anyhow};
-use jellycommon::{jellyobject::Path, stream::StreamSpec, *};
+use jellycommon::{jellyobject::Path, routes::u_image, stream::StreamSpec, *};
use jellydb::{Filter, Query};
use jellystream::SMediaInfo;
+use jellytranscoder::thumbnail::create_thumbnail;
use log::{info, warn};
use rocket::{
Either, Request, Response, get, head,
@@ -55,6 +59,7 @@ pub async fn r_stream(
range: Option<RequestRange>,
path: StringPath<'_>,
) -> Result<StreamResponse, MyError> {
+ ri.require_user()?;
let spec = StreamSpec::from_path(&path.0).map_err(|x| anyhow!("media path invalid: {x}"))?;
let mut node = None;
@@ -127,6 +132,44 @@ pub async fn r_stream(
})
}
+#[get("/n/<slug>/thumbnail?<t>&<size>")]
+pub async fn r_thumbnail(
+ ri: RequestInfo<'_>,
+ slug: &str,
+ t: f64,
+ size: usize,
+) -> MyResult<Redirect> {
+ ri.require_user()?;
+ let mut node = None;
+ ri.state.database.transaction(&mut |txn| {
+ if let Some(row) = txn.query_single(Query {
+ filter: Filter::Match(Path(vec![NO_SLUG.0]), slug.into()),
+ ..Default::default()
+ })? {
+ node = txn.get(row)?;
+ }
+ Ok(())
+ })?;
+
+ let Some(node) = node else {
+ Err(anyhow!("node not found"))?
+ };
+
+ let t = (t / 10.).floor() * 10.; // quantize to 10s
+
+ let media_path = node
+ .iter(NO_TRACK)
+ .filter(|t| t.get(TR_KIND) == Some(TRKIND_VIDEO))
+ .filter_map(|t| t.get(TR_SOURCE))
+ .filter_map(|ts| ts.get(TRSOURCE_LOCAL_PATH))
+ .next()
+ .ok_or(anyhow!("node has no suitable video track"))?;
+
+ let thumb_path = create_thumbnail(&ri.state.cache, media_path.as_ref(), t)?;
+
+ Ok(Redirect::temporary(u_image(&thumb_path, size)))
+}
+
pub struct RedirectResponse(String);
#[rocket::async_trait]