diff options
author | metamuffin <metamuffin@disroot.org> | 2023-07-31 19:53:01 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-07-31 19:53:01 +0200 |
commit | aeafba7847e189313df3025e6d6f291999b57350 (patch) | |
tree | bf7affdca28208695648bc9b18856cbb7049d1e8 /server/src/routes | |
parent | 0c651f11920350a4aa96aa24f8fe15b28390aed2 (diff) | |
download | jellything-aeafba7847e189313df3025e6d6f291999b57350.tar jellything-aeafba7847e189313df3025e6d6f291999b57350.tar.bz2 jellything-aeafba7847e189313df3025e6d6f291999b57350.tar.zst |
update server to new schema
Diffstat (limited to 'server/src/routes')
-rw-r--r-- | server/src/routes/api/mod.rs | 73 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 19 | ||||
-rw-r--r-- | server/src/routes/stream.rs | 44 | ||||
-rw-r--r-- | server/src/routes/ui/assets.rs | 44 | ||||
-rw-r--r-- | server/src/routes/ui/browser.rs | 31 | ||||
-rw-r--r-- | server/src/routes/ui/home.rs | 8 | ||||
-rw-r--r-- | server/src/routes/ui/mod.rs | 1 | ||||
-rw-r--r-- | server/src/routes/ui/node.rs | 172 | ||||
-rw-r--r-- | server/src/routes/ui/player.rs | 49 |
9 files changed, 194 insertions, 247 deletions
diff --git a/server/src/routes/api/mod.rs b/server/src/routes/api/mod.rs index af795f1..d49ecec 100644 --- a/server/src/routes/api/mod.rs +++ b/server/src/routes/api/mod.rs @@ -5,31 +5,10 @@ */ pub mod error; -use std::path::PathBuf; - -use super::ui::{ - account::{login_logic, LoginForm}, - node::AssetRole, - CacheControlFile, -}; -use crate::{ - database::Database, - library::{Library, Node}, - routes::{api::error::ApiResult, ui::account::session::Session}, -}; -use anyhow::Context; -use jellycommon::api::ApiNode; -use log::info; -use rocket::{ - get, - http::{ContentType, CookieJar}, - post, - response::Redirect, - serde::json::Json, - State, -}; +use super::ui::account::{login_logic, LoginForm}; +use crate::{database::Database, routes::api::error::ApiResult}; +use rocket::{get, http::CookieJar, post, response::Redirect, serde::json::Json, State}; use serde_json::{json, Value}; -use tokio::fs::File; #[get("/api")] pub fn r_api_root() -> Redirect { @@ -50,49 +29,3 @@ pub fn r_api_account_login( login_logic(jar, database, &data.username, &data.password)?; Ok(json!({ "ok": true })) } - -#[get("/api/assets/node/<path..>?<role>")] -pub async fn r_api_assets_node( - _sess: Session, - path: PathBuf, - role: AssetRole, - library: &State<Library>, -) -> ApiResult<(ContentType, CacheControlFile)> { - let node = library - .nested_path(&path) - .context("retrieving library node")?; - let path = node.get_asset(library, role); - info!("loading asset from {path:?}"); - let ext = path.extension().unwrap().to_str().unwrap(); - Ok(( - ContentType::from_extension(ext).unwrap(), - CacheControlFile::new(File::open(path).await?).await, - )) -} - -#[get("/api/node/<path..>")] -pub fn r_api_node( - _sess: Session, - path: PathBuf, - library: &State<Library>, -) -> ApiResult<Json<ApiNode>> { - let node = library - .nested_path(&path) - .context("retrieving library node")?; - - Ok(Json(match node.as_ref() { - Node::Directory(d) => ApiNode::Directory { - identifier: d.identifier.clone(), - info: d.info.clone(), - children: d - .children - .iter() - .map(|c| c.identifier().to_string()) - .collect::<Vec<_>>(), - }, - Node::Item(i) => ApiNode::Item { - identifier: i.identifier.clone(), - info: i.info.clone(), - }, - })) -} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 8d50c2e..52918d9 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -3,11 +3,8 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin <metamuffin.org> */ -use crate::{database::Database, library::Library, routes::ui::error::MyResult, CONF}; -use api::{ - error::r_api_catch, r_api_account_login, r_api_assets_node, r_api_node, r_api_root, - r_api_version, -}; +use crate::{database::Database, routes::ui::error::MyResult, CONF}; +use api::{error::r_api_catch, r_api_account_login, r_api_root, r_api_version}; use jellyremuxer::RemuxerContext; use rocket::{ catchers, config::SecretKey, fairing::AdHoc, fs::FileServer, get, http::Header, routes, Build, @@ -25,10 +22,11 @@ use ui::{ r_account_register, r_account_register_post, settings::{r_account_settings, r_account_settings_post}, }, + assets::r_item_assets, browser::r_all_items, error::r_catch, home::{r_home, r_home_unpriv}, - node::{r_item_assets, r_library_node}, + node::r_library_node, player::r_player, style::{r_assets_font, r_assets_js, r_assets_style}, }; @@ -44,18 +42,13 @@ macro_rules! uri { }; } -pub fn build_rocket( - remuxer: RemuxerContext, - library: Library, - database: Database, -) -> Rocket<Build> { +pub fn build_rocket(remuxer: RemuxerContext, database: Database) -> Rocket<Build> { rocket::build() .configure(Config { secret_key: SecretKey::derive_from(CONF.cookie_key.as_bytes()), ..Default::default() }) .manage(remuxer) - .manage(library) .manage(database) .attach(AdHoc::on_response("set server header", |_req, res| { res.set_header(Header::new("server", "jellything")); @@ -92,8 +85,6 @@ pub fn build_rocket( r_account_settings_post, r_api_version, r_api_account_login, - r_api_node, - r_api_assets_node, r_api_root, ], ) diff --git a/server/src/routes/stream.rs b/server/src/routes/stream.rs index b2b708b..2583cb1 100644 --- a/server/src/routes/stream.rs +++ b/server/src/routes/stream.rs @@ -4,8 +4,9 @@ Copyright (C) 2023 metamuffin <metamuffin.org> */ use super::ui::{account::session::Session, error::MyError}; -use crate::library::Library; -use anyhow::{anyhow, Context, Result}; +use crate::{database::Database, CONF}; +use anyhow::{anyhow, Result}; +use jellycommon::MediaSource; use jellyremuxer::RemuxerContext; use log::{debug, info, warn}; use rocket::{ @@ -15,17 +16,14 @@ use rocket::{ response::{self, Responder}, Request, Response, State, }; -use std::{ - ops::{Deref, Range}, - path::{Path, PathBuf}, -}; +use std::ops::{Deref, Range}; use tokio::io::{duplex, DuplexStream}; use tokio_util::io::SyncIoBridge; -pub fn stream_uri(path: &Path, tracks: &[u64], webm: bool) -> String { +pub fn stream_uri(id: &str, tracks: &[u64], webm: bool) -> String { format!( "/stream/{}?tracks={}&webm={}", - path.to_str().unwrap(), + id, tracks .iter() .map(|v| format!("{v}")) @@ -35,16 +33,27 @@ pub fn stream_uri(path: &Path, tracks: &[u64], webm: bool) -> String { ) } -#[get("/stream/<path..>?<tracks>&<webm>")] +#[get("/stream/<id>?<tracks>&<webm>")] pub fn r_stream( _sess: Session, - path: PathBuf, + id: String, webm: Option<bool>, tracks: String, remuxer: &State<RemuxerContext>, - library: &State<Library>, + db: &State<Database>, range: Option<RequestRange>, ) -> Result<StreamResponse, MyError> { + let node = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?; + let source = node + .private + .source + .ok_or(anyhow!("item does not contain media"))?; + + let source_tracks = match source { + MediaSource::Local { tracks } => tracks, + _ => Err(anyhow!("todo"))?, + }; + info!( "stream request (range={})", range @@ -52,12 +61,9 @@ pub fn r_stream( .map(|r| r.to_cr_hv()) .unwrap_or(format!("none")) ); + let (a, b) = duplex(4096); - let path = path.to_str().unwrap().to_string(); - let item = library - .nested(&path) - .context("retrieving library node")? - .get_item()?; + let remuxer = remuxer.deref().clone(); let tracks = tracks .split(',') @@ -74,13 +80,13 @@ pub fn r_stream( None => 0..(isize::MAX as usize), }; - let path_base = library.root_path.clone(); tokio::task::spawn_blocking(move || { if let Err(e) = remuxer.generate_into( b, urange, - path_base, - item.info.clone(), + CONF.library_path.clone(), + node.public, + source_tracks, tracks, webm.unwrap_or(false), ) { diff --git a/server/src/routes/ui/assets.rs b/server/src/routes/ui/assets.rs new file mode 100644 index 0000000..2c0b85a --- /dev/null +++ b/server/src/routes/ui/assets.rs @@ -0,0 +1,44 @@ +/* + 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 <metamuffin.org> +*/ +use crate::{ + database::Database, + routes::ui::{account::session::Session, error::MyError, CacheControlFile}, + CONF, +}; +use anyhow::anyhow; +use log::info; +use rocket::{get, http::ContentType, FromFormField, State, UriDisplayQuery}; +use std::{path::PathBuf, str::FromStr}; +use tokio::fs::File; + +#[derive(FromFormField, UriDisplayQuery)] +pub enum AssetRole { + Poster, + Backdrop, +} + +#[get("/item_assets/<id>?<role>")] +pub async fn r_item_assets( + _sess: Session, + id: String, + role: AssetRole, + db: &State<Database>, +) -> Result<(ContentType, CacheControlFile), MyError> { + let node = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?; + let path = CONF.asset_path.join( + match role { + AssetRole::Backdrop => node.private.backdrop, + AssetRole::Poster => node.private.poster, + } + .unwrap_or_else(|| PathBuf::from_str("fallback.jpeg").unwrap()), + ); + info!("loading asset from {path:?}"); + let ext = path.extension().unwrap().to_str().unwrap(); + Ok(( + ContentType::from_extension(ext).unwrap(), + CacheControlFile::new(File::open(path).await?).await, + )) +} diff --git a/server/src/routes/ui/browser.rs b/server/src/routes/ui/browser.rs index 30eb3f2..767e411 100644 --- a/server/src/routes/ui/browser.rs +++ b/server/src/routes/ui/browser.rs @@ -3,35 +3,24 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2023 metamuffin <metamuffin.org> */ -use super::{account::session::Session, error::MyError, layout::DynLayoutPage, node::PosterCard}; -use crate::library::{Library, Node}; +use super::{account::session::Session, error::MyError, layout::DynLayoutPage}; +use crate::database::Database; use rocket::{get, State}; -use std::collections::VecDeque; #[get("/items")] -pub fn r_all_items(_sess: Session, library: &State<Library>) -> Result<DynLayoutPage<'_>, MyError> { - let mut dirs = VecDeque::from_iter(Some(library.root.get_directory().unwrap())); - let mut items = Vec::new(); - while let Some(d) = dirs.pop_front() { - for e in &d.children { - match e.as_ref() { - Node::Directory(d) => dirs.push_back(d.clone()), - Node::Item(i) => items.push(i.clone()), - } - } - } +pub fn r_all_items(_sess: Session, db: &State<Database>) -> Result<DynLayoutPage<'_>, MyError> { Ok(super::layout::LayoutPage { title: "All Items".to_owned(), content: markup::new! { .page.dir { h1 { "All Items" } - ul.directorylisting { @for item in &items { - li {@PosterCard { - wide: false, dir: false, - path: item.lib_path.clone(), - title: &item.info.title - }} - }} + // ul.directorylisting { @for item in &items { + // li {@PosterCard { + // wide: false, dir: false, + // path: item.lib_path.clone(), + // title: &item.info.title + // }} + // }} } }, ..Default::default() diff --git a/server/src/routes/ui/home.rs b/server/src/routes/ui/home.rs index cdde478..0b85e89 100644 --- a/server/src/routes/ui/home.rs +++ b/server/src/routes/ui/home.rs @@ -5,20 +5,20 @@ */ use super::{account::session::Session, layout::LayoutPage}; use crate::{ - library::Library, - routes::ui::{error::MyResult, layout::DynLayoutPage, node::NodePage}, + database::Database, + routes::ui::{error::MyResult, layout::DynLayoutPage}, CONF, }; use rocket::{get, State}; use tokio::fs::read_to_string; #[get("/")] -pub fn r_home(_sess: Session, library: &State<Library>) -> DynLayoutPage { +pub fn r_home(_sess: Session, _db: &State<Database>) -> DynLayoutPage { LayoutPage { title: "Home".to_string(), content: markup::new! { p { "Welcome to " @CONF.brand } - @NodePage { node: &library.root } + // @NodePage { node: &db } }, ..Default::default() } diff --git a/server/src/routes/ui/mod.rs b/server/src/routes/ui/mod.rs index f566f6d..5fad2b7 100644 --- a/server/src/routes/ui/mod.rs +++ b/server/src/routes/ui/mod.rs @@ -29,6 +29,7 @@ pub mod layout; pub mod node; pub mod player; pub mod style; +pub mod assets; pub struct HtmlTemplate<'a>(pub markup::DynRender<'a>); diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs index 4d599dc..0ae0d9e 100644 --- a/server/src/routes/ui/node.rs +++ b/server/src/routes/ui/node.rs @@ -1,163 +1,141 @@ /* -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 <metamuffin.org> + 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 <metamuffin.org> */ -use super::error::MyError; -use super::player::player_uri; -use super::CacheControlFile; -use crate::uri; +use super::{assets::rocket_uri_macro_r_item_assets, error::MyError, player::player_uri}; use crate::{ - library::{Directory, Item, Library, Node}, + database::Database, routes::ui::{ account::session::Session, + assets::AssetRole, layout::{DynLayoutPage, LayoutPage}, }, + uri, }; -use anyhow::Context; -use jellycommon::DirectoryKind; -use log::info; -use rocket::{get, http::ContentType, State}; -use rocket::{FromFormField, UriDisplayQuery}; -use std::{path::PathBuf, sync::Arc}; -use tokio::fs::File; +use anyhow::{anyhow, Context}; +use jellycommon::{Node, NodeKind}; +use rocket::{get, State}; -#[get("/library/<path..>")] +#[get("/library/<id>")] pub async fn r_library_node( _sess: Session, - path: PathBuf, - library: &State<Library>, + id: String, + db: &State<Database>, ) -> Result<DynLayoutPage<'_>, MyError> { - let node = library - .nested_path(&path) - .context("retrieving library node")?; + let node = db + .node + .get(&id) + .context("retrieving library node")? + .ok_or(anyhow!("node does not exist"))?; + + let children = node + .public + .children + .iter() + .map(|c| { + Ok(( + c.to_owned(), + db.node.get(c)?.ok_or(anyhow!("child does not exist"))?, + )) + }) + .collect::<anyhow::Result<Vec<_>>>()? + .into_iter() + .collect(); + Ok(LayoutPage { - title: node.common().title.to_string(), - show_back: node.get_item().is_ok(), + title: node.public.title.to_string(), + show_back: matches!(node.public.kind, NodeKind::Movie | NodeKind::Episode), content: markup::new! { - @NodePage { node: &node } + @NodePage { node: &node, id: &id, children: &children } }, ..Default::default() }) } markup::define! { - NodePage<'a>(node: &'a Arc<Node>) { - @match node.as_ref() { - Node::Directory(dir) => { @match dir.info.kind { - DirectoryKind::Series => { @SeriesPage { dir } } - _ => { @DirectoryPage { dir } } - } } - Node::Item(item) => { @ItemPage { item } } + NodePage<'a>(id: &'a str, node: &'a Node, children: &'a Vec<(String,Node)>) { + @match node.public.kind { + NodeKind::Collection | NodeKind::Show | NodeKind::Season => { @DirectoryPage { node, id, children } } + NodeKind::Series => { @SeriesPage { node, children, id } } + NodeKind::Movie | NodeKind::Episode => { @ItemPage { node, id } } } } - NodeCard<'a>(node: &'a Arc<Node>) { - @match node.as_ref() { - Node::Directory(dir) => {@PosterCard { - wide: !matches!(dir.info.kind, DirectoryKind::Series | DirectoryKind::Season), - dir: true, - path: dir.lib_path.clone(), - title: &dir.info.title - }} - Node::Item(item) => {@PosterCard { - wide: false, dir: false, - path: item.lib_path.clone(), - title: &item.info.title - }} + NodeCard<'a>(id: &'a str, node: &'a Node) { + @PosterCard { + wide: matches!(node.public.kind, NodeKind::Collection), + dir: !matches!(node.public.kind, NodeKind::Movie | NodeKind::Episode), + id, + title: &node.public.title } } - DirectoryPage<'a>(dir: &'a Arc<Directory>) { + DirectoryPage<'a>(id: &'a str, node: &'a Node, children: &'a Vec<(String,Node)>) { div.page.dir { - h1 { @dir.info.title } - @if let Some(parent) = dir.lib_path.parent() { - a.dirup[href=uri!(r_library_node(&parent))] { "Go up" } - } + h1 { @node.public.title } + // @if let Some(parent) = node.lib_path.parent() { + // a.dirup[href=uri!(r_library_node(&parent))] { "Go up" } + // } ul.directorylisting { - @for node in &dir.children { - li { @NodeCard { node } } + @for (id, node) in children.iter() { + li { @NodeCard { id, node } } } } } } - PosterCard<'a>(path: PathBuf, title: &'a str, wide: bool, dir: bool) { + PosterCard<'a>(id: &'a str, title: &'a str, wide: bool, dir: bool) { div[class=if *wide {"card wide poster"} else {"card poster"}] { div.banner { - a[href=uri!(r_library_node(path))] { - img[src=uri!(r_item_assets(path, AssetRole::Poster))]; + a[href=uri!(r_library_node(id))] { + img[src=uri!(r_item_assets(id, AssetRole::Poster))]; } @if *dir { - div.hoverdir { a[href=&uri!(r_library_node(path))] { "Open" } } + div.hoverdir { a[href=&uri!(r_library_node(id))] { "Open" } } } else { - div.hoveritem { a[href=&player_uri(path)] { "▶" } } + div.hoveritem { a[href=&player_uri(id)] { "▶" } } } } p.title { - a[href=uri!(r_library_node(path))] { + a[href=uri!(r_library_node(id))] { @title } } } } - ItemPage<'a>(item: &'a Arc<Item>) { + ItemPage<'a>(id: &'a str, node: &'a Node) { // TODO different image here - img.backdrop[src=uri!(r_item_assets(&item.lib_path, AssetRole::Backdrop))]; + img.backdrop[src=uri!(r_item_assets(id, AssetRole::Backdrop))]; div.page.item { div.banner { - img[src=uri!(r_item_assets(&item.lib_path, AssetRole::Poster))]; + img[src=uri!(r_item_assets(id, AssetRole::Poster))]; } div.title { - h1 { @item.info.title } + h1 { @node.public.title } // TODO release date, duration, ratings - a.play[href=&player_uri(&item.lib_path)] { "Watch now" } + a.play[href=&player_uri(id)] { "Watch now" } } div.details { - h3 { @item.info.tagline } - p { @item.info.description } + h3 { @node.public.tagline } + p { @node.public.description } } } } - SeriesPage<'a>(dir: &'a Arc<Directory>) { + SeriesPage<'a>(id: &'a str, node: &'a Node, children: &'a Vec<(String,Node)>) { // TODO different image here - img.backdrop[src=uri!(r_item_assets(&dir.lib_path, AssetRole::Backdrop))]; + img.backdrop[src=uri!(r_item_assets(id, AssetRole::Backdrop))]; div.page.item { div.banner { - img[src=uri!(r_item_assets(&dir.lib_path, AssetRole::Poster))]; + img[src=uri!(r_item_assets(id, AssetRole::Poster))]; } div.title { - h1 { @dir.info.title } + h1 { @node.public.title } } div.details { - h3 { @dir.info.tagline } - p { @dir.info.description } + h3 { @node.public.tagline } + p { @node.public.description } } - ol { @for ep in &dir.children { - li { a[href=uri!(r_library_node(ep.lib_path()))] { @ep.common().title } } + ol { @for (id, c) in children.iter() { + li { a[href=uri!(r_library_node(id))] { @c.public.title } } } } } } } - -#[derive(FromFormField, UriDisplayQuery)] -pub enum AssetRole { - Poster, - Backdrop, -} - -#[get("/item_assets/<path..>?<role>")] -pub async fn r_item_assets( - _sess: Session, - path: PathBuf, - role: AssetRole, - library: &State<Library>, -) -> Result<(ContentType, CacheControlFile), MyError> { - let node = library - .nested_path(&path) - .context("retrieving library node")?; - let path = node.get_asset(library, role); - info!("loading asset from {path:?}"); - let ext = path.extension().unwrap().to_str().unwrap(); - Ok(( - ContentType::from_extension(ext).unwrap(), - CacheControlFile::new(File::open(path).await?).await, - )) -} diff --git a/server/src/routes/ui/player.rs b/server/src/routes/ui/player.rs index d5cb685..0e87749 100644 --- a/server/src/routes/ui/player.rs +++ b/server/src/routes/ui/player.rs @@ -5,27 +5,24 @@ */ use super::{account::session::Session, layout::LayoutPage}; use crate::{ - library::{Item, Library}, + database::Database, routes::{ stream::stream_uri, ui::{ + assets::{rocket_uri_macro_r_item_assets, AssetRole}, error::MyResult, layout::DynLayoutPage, - node::{rocket_uri_macro_r_item_assets, AssetRole}, }, }, uri, }; -use jellycommon::SourceTrackKind; +use anyhow::anyhow; +use jellycommon::{Node, SourceTrackKind}; use markup::DynRender; use rocket::{get, FromForm, State}; -use std::{ - path::{Path, PathBuf}, - sync::Arc, -}; -pub fn player_uri(path: &Path) -> String { - format!("/player/{}", path.to_str().unwrap()) +pub fn player_uri(id: &str) -> String { + format!("/player/{}", id) } #[derive(FromForm, Default, Clone, Debug)] @@ -36,14 +33,14 @@ pub struct PlayerConfig { pub webm: bool, } -#[get("/player/<path..>?<conf..>", rank = 4)] +#[get("/player/<id>?<conf..>", rank = 4)] pub fn r_player( _sess: Session, - library: &State<Library>, - path: PathBuf, + db: &State<Database>, + id: String, conf: PlayerConfig, ) -> MyResult<DynLayoutPage<'_>> { - let item = library.nested_path(&path)?.get_item()?; + let item = db.node.get(&id)?.ok_or(anyhow!("node does not exist"))?; let tracks = None .into_iter() .chain(conf.v.into_iter()) @@ -51,27 +48,35 @@ pub fn r_player( .chain(conf.s.into_iter()) .collect::<Vec<_>>(); + let conf = player_conf(item.clone(), !tracks.is_empty())?; Ok(LayoutPage { - title: item.info.title.to_owned(), + title: item.public.title.to_owned(), class: Some("player"), content: markup::new! { @if tracks.is_empty() { - img.backdrop[src=uri!(r_item_assets(&item.lib_path, AssetRole::Backdrop)).to_string()]; + img.backdrop[src=uri!(r_item_assets(&id, AssetRole::Backdrop)).to_string()]; } else { - video[src=stream_uri(&item.lib_path, &tracks, true), controls]{} + video[src=stream_uri(&id, &tracks, true), controls]{} } - @player_conf(item.clone(), !tracks.is_empty()) + @conf }, show_back: true, ..Default::default() }) } -pub fn player_conf<'a>(item: Arc<Item>, playing: bool) -> DynRender<'a> { +pub fn player_conf<'a>(item: Node, playing: bool) -> anyhow::Result<DynRender<'a>> { let mut audio_tracks = vec![]; let mut video_tracks = vec![]; let mut sub_tracks = vec![]; - for (tid, track) in item.info.tracks.clone() { + let tracks = item + .public + .media + .clone() + .ok_or(anyhow!("node does not have media"))? + .tracks + .clone(); + for (tid, track) in tracks.into_iter().enumerate() { match &track.kind { SourceTrackKind::Audio { .. } => audio_tracks.push((tid, track)), SourceTrackKind::Video { .. } => video_tracks.push((tid, track)), @@ -79,9 +84,9 @@ pub fn player_conf<'a>(item: Arc<Item>, playing: bool) -> DynRender<'a> { } } - markup::new! { + Ok(markup::new! { form.playerconf[method = "GET", action = ""] { - h2 { "Select tracks for " @item.info.title } + h2 { "Select tracks for " @item.public.title } fieldset.video { legend { "Video" } @@ -115,5 +120,5 @@ pub fn player_conf<'a>(item: Arc<Item>, playing: bool) -> DynRender<'a> { input[type="submit", value=if playing { "Change tracks" } else { "Start playback" }]; } - } + }) } |