diff options
author | metamuffin <metamuffin@disroot.org> | 2023-06-14 22:04:31 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2023-06-14 22:04:31 +0200 |
commit | 50be406bbf448d93a2c669419b494797cdde675e (patch) | |
tree | c35e228e906249240f11fabe5216f38feda2a7d5 | |
parent | 16202a62e64a615d488f5bc376466029c31b00ad (diff) | |
download | jellything-50be406bbf448d93a2c669419b494797cdde675e.tar jellything-50be406bbf448d93a2c669419b494797cdde675e.tar.bz2 jellything-50be406bbf448d93a2c669419b494797cdde675e.tar.zst |
show series as poster
-rw-r--r-- | common/src/lib.rs | 4 | ||||
-rw-r--r-- | server/src/routes/ui/browser.rs | 8 | ||||
-rw-r--r-- | server/src/routes/ui/node.rs | 47 | ||||
-rw-r--r-- | server/src/routes/ui/style/directorypage.css | 24 | ||||
-rw-r--r-- | tools/src/bin/import.rs | 106 | ||||
-rw-r--r-- | tools/src/tmdb.rs | 32 |
6 files changed, 144 insertions, 77 deletions
diff --git a/common/src/lib.rs b/common/src/lib.rs index 2e80744..fd69d8f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -20,6 +20,8 @@ pub struct CommmonInfo { pub poster: Option<PathBuf>, #[serde(default)] pub backdrop: Option<PathBuf>, + #[serde(default)] + pub index: Option<usize>, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -38,8 +40,6 @@ pub struct ItemInfo { pub tracks: BTreeMap<usize, SourceTrack>, } - - #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum DirectoryKind { diff --git a/server/src/routes/ui/browser.rs b/server/src/routes/ui/browser.rs index d0c09b1..30eb3f2 100644 --- a/server/src/routes/ui/browser.rs +++ b/server/src/routes/ui/browser.rs @@ -3,7 +3,7 @@ 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::ItemCard}; +use super::{account::session::Session, error::MyError, layout::DynLayoutPage, node::PosterCard}; use crate::library::{Library, Node}; use rocket::{get, State}; use std::collections::VecDeque; @@ -26,7 +26,11 @@ pub fn r_all_items(_sess: Session, library: &State<Library>) -> Result<DynLayout .page.dir { h1 { "All Items" } ul.directorylisting { @for item in &items { - li { @ItemCard { item: &item } } + li {@PosterCard { + wide: false, dir: false, + path: item.lib_path.clone(), + title: &item.info.title + }} }} } }, diff --git a/server/src/routes/ui/node.rs b/server/src/routes/ui/node.rs index d5e52ff..b9e9871 100644 --- a/server/src/routes/ui/node.rs +++ b/server/src/routes/ui/node.rs @@ -15,6 +15,7 @@ use crate::{ CONF, }; use anyhow::Context; +use jellycommon::DirectoryKind; use log::info; use rocket::{get, http::ContentType, State}; use rocket::{FromFormField, UriDisplayQuery}; @@ -49,23 +50,17 @@ markup::define! { } NodeCard<'a>(node: &'a Arc<Node>) { @match node.as_ref() { - Node::Directory(dir) => { @DirectoryCard { dir } } - Node::Item(item) => { @ItemCard { item } } - } - } - DirectoryCard<'a>(dir: &'a Arc<Directory>) { - div.card.dir { - div.banner { - a[href=uri!(r_library_node(&dir.lib_path))] { - img[src=uri!(r_item_assets(&dir.lib_path, AssetRole::Poster))]; - } - div.hover { a[href=uri!(r_library_node(&dir.lib_path))] { "Open" } } - } - p.title { - a[href=uri!(r_library_node(&dir.lib_path))] { - @dir.info.title - } - } + 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 + }} } } DirectoryPage<'a>(dir: &'a Arc<Directory>) { @@ -81,17 +76,21 @@ markup::define! { } } } - ItemCard<'a>(item: &'a Arc<Item>) { - div.card.item { + PosterCard<'a>(path: PathBuf, 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(&item.lib_path))] { - img[src=uri!(r_item_assets(&item.lib_path, AssetRole::Poster))]; + a[href=uri!(r_library_node(path))] { + img[src=uri!(r_item_assets(path, AssetRole::Poster))]; + } + @if *dir { + div.hoverdir { a[href=&uri!(r_library_node(path))] { "Open" } } + } else { + div.hoveritem { a[href=&player_uri(path)] { "▶" } } } - div.hover { a[href=&player_uri(&item.lib_path)] { "▶" } } } p.title { - a[href=uri!(r_library_node(&item.lib_path))] { - @item.info.title + a[href=uri!(r_library_node(path))] { + @title } } } diff --git a/server/src/routes/ui/style/directorypage.css b/server/src/routes/ui/style/directorypage.css index e81dce7..0d18c82 100644 --- a/server/src/routes/ui/style/directorypage.css +++ b/server/src/routes/ui/style/directorypage.css @@ -37,10 +37,10 @@ padding: 1em; height: var(--card-size); } -.card.item { +.card.poster { width: calc(var(--card-size) / var(--item-banner-aspect)); } -.card.dir { +.card.poster.wide { width: calc(var(--card-size) / var(--dir-banner-aspect)); } @@ -56,11 +56,11 @@ grid-area: 1 / 1; } -.card.item .banner img { +.card.poster .banner img { width: calc(var(--card-size) / var(--item-banner-aspect)); height: var(--card-size); } -.card.dir .banner img { +.card.poster.wide .banner img { width: calc(var(--card-size) / var(--dir-banner-aspect)); height: var(--card-size); } @@ -70,7 +70,7 @@ } -.card.dir .banner .hover { +.card.poster .banner .hoverdir { transition: opacity 0.3s, backdrop-filter 0.3s; opacity: 0; display: flex; @@ -79,12 +79,12 @@ height: 5em; margin-top: -5em; } -.card.dir .banner:hover .hover { +.card.poster .banner:hover .hoverdir { opacity: 1; background-color: #0004; backdrop-filter: blur(3px); } -.card.dir .banner .hover a { +.card.poster .banner .hoverdir a { text-decoration: none; width: 100%; height: 1.7em; @@ -97,11 +97,11 @@ margin: 0.6em; transition: background-color 0.2s; } -.card.dir .banner .hover a:hover { +.card.poster .banner .hoverdir a:hover { background-color: #0008; } -.card.item .banner .hover { +.card.poster .banner .hoveritem { pointer-events: none; grid-area: 1 / 1; transition: opacity 0.3s, backdrop-filter 0.3s; @@ -110,12 +110,12 @@ justify-content: center; align-items: center; } -.card.item .banner:hover .hover { +.card.poster .banner:hover .hoveritem { opacity: 1; background-color: #0004; backdrop-filter: blur(3px); } -.card.item .banner .hover a { +.card.poster .banner .hoveritem a { text-decoration: none; font-stretch: 200%; width: 1em; @@ -129,7 +129,7 @@ background-color: #0005; transition: background-color 0.2s, font-size 0.2s; } -.card.item .banner .hover a:hover { +.card.poster .banner .hoveritem a:hover { background-color: #0008; font-size: 2.4em; } diff --git a/tools/src/bin/import.rs b/tools/src/bin/import.rs index c771450..3050f71 100644 --- a/tools/src/bin/import.rs +++ b/tools/src/bin/import.rs @@ -5,7 +5,7 @@ Copyright (C) 2023 metamuffin <metamuffin.org> */ use anyhow::Context; use clap::{Parser, Subcommand}; -use jellycommon::{CommmonInfo, ItemInfo}; +use jellycommon::{CommmonInfo, DirectoryInfo, ItemInfo}; use jellymatroska::read::EbmlReader; use jellyremuxer::import::import_read; use jellytools::tmdb::{tmdb_details, tmdb_image, tmdb_search}; @@ -13,7 +13,7 @@ use log::{info, warn}; use std::{ fs::File, io::{stdin, Write}, - path::PathBuf, + path::{Path, PathBuf}, process::exit, }; @@ -32,7 +32,15 @@ enum Action { title: Option<String>, #[arg(short = 'T', long)] tmdb: Option<String>, - #[arg(short = 'i', long)] + #[arg(short, long)] + input: Option<PathBuf>, + #[arg(short, long)] + series: bool, + }, + Episode { + path: PathBuf, + index: usize, + title: String, input: PathBuf, }, Set { @@ -66,23 +74,26 @@ fn main() -> anyhow::Result<()> { title, tmdb: id, input, + series, } => { + assert!(series || input.is_some(), "series or input required"); + let kind = if series { "tv" } else { "movie" }; let key = std::env::var("TMDB_API_KEY").context("tmdb api key required")?; let id = if let Some(id) = id { id.parse().unwrap() } else { let title = title.unwrap(); - let results = tmdb_search(&title, &key)?; + let results = tmdb_search(kind, &title, &key)?; info!("results:"); for (i, r) in results.results.iter().enumerate() { info!( "\t[{i}] {}: {} ({})", r.id, - r.title, + r.title.as_ref().or(r.name.as_ref()).unwrap(), r.overview.chars().take(100).collect::<String>() ); } - let res_index = if results.results.len() != 1 { + let res_index = if results.results.len() > 1 { stdin() .lines() .next() @@ -96,8 +107,8 @@ fn main() -> anyhow::Result<()> { results.results[res_index].id }; - let details = tmdb_details(id, &key).context("fetching details")?; - let ident = make_ident(&details.title); + let details = tmdb_details(kind, id, &key).context("fetching details")?; + let ident = make_ident(details.title.as_ref().or(details.name.as_ref()).unwrap()); let path = path.join(&ident); std::fs::create_dir_all(&path)?; @@ -125,36 +136,82 @@ fn main() -> anyhow::Result<()> { backdrop, description: Some(details.overview), tagline: details.tagline, - title: details.title, + title: details.title.clone().or(details.name.clone()).unwrap(), + index: None, }; - let mut iteminfo = ItemInfo { - common, - duration: Default::default(), - tracks: Default::default(), - }; - info!("{iteminfo:#?}"); info!("is this correct? [y/n]"); if stdin().lines().next().unwrap().unwrap() != "y" { exit(0) } + let k = if let Some(input) = input { + let mut iteminfo = ItemInfo { + common, + duration: Default::default(), + tracks: Default::default(), + }; + info!("{iteminfo:#?}"); + let source_path = path.join(format!("source.mkv")); + // std::fs::rename(&input, &source_path)?; + // std::os::unix::fs::symlink(&input, &source_path)?; + std::fs::copy(&input, &source_path)?; + import_source(&mut iteminfo, &source_path)?; + serde_json::to_string_pretty(&iteminfo)? + } else { + serde_json::to_string_pretty(&DirectoryInfo { + common, + kind: jellycommon::DirectoryKind::Series, + })? + }; + + if args.dry { + println!("{k}") + } else { + let mut f = File::create(path.join(if series { + "directory.json".to_string() + } else { + format!("{ident}.jelly",) + }))?; + f.write_all(k.as_bytes())?; + } + + Ok(()) + } + Action::Episode { + path, + index, + title, + input, + } => { + let ident = make_ident(&title); + let common = CommmonInfo { + poster: None, + backdrop: None, + description: None, + tagline: None, + title, + index: Some(index), + }; + let mut iteminfo = ItemInfo { + common, + duration: Default::default(), + tracks: Default::default(), + }; + let path = path.join(&ident); + std::fs::create_dir_all(&path)?; let source_path = path.join(format!("source.mkv")); // std::fs::rename(&input, &source_path)?; // std::os::unix::fs::symlink(&input, &source_path)?; std::fs::copy(&input, &source_path)?; - let input = File::open(&source_path).unwrap(); - let mut input = EbmlReader::new(input); - import_read(&source_path, &mut input, &mut iteminfo)?; - + import_source(&mut iteminfo, &source_path)?; let k = serde_json::to_string_pretty(&iteminfo)?; if args.dry { println!("{k}") } else { - let mut f = File::create(path.join(format!("{ident}.jelly")))?; + let mut f = File::create(path.join(format!("{ident}.jelly",)))?; f.write_all(k.as_bytes())?; } - Ok(()) } Action::Set { @@ -178,6 +235,7 @@ fn main() -> anyhow::Result<()> { tagline: None, description: None, title: item.to_str().unwrap().to_string(), + index: None, }, duration: 0.0, tracks: Default::default(), @@ -231,3 +289,9 @@ fn make_ident(s: &str) -> String { } out } +fn import_source(iteminfo: &mut ItemInfo, source_path: &Path) -> anyhow::Result<()> { + let input = File::open(&source_path).unwrap(); + let mut input = EbmlReader::new(input); + import_read(&source_path.to_path_buf(), &mut input, iteminfo)?; + Ok(()) +} diff --git a/tools/src/tmdb.rs b/tools/src/tmdb.rs index 6f8c341..5f21afd 100644 --- a/tools/src/tmdb.rs +++ b/tools/src/tmdb.rs @@ -21,14 +21,14 @@ pub struct TmdbQueryResult { pub backdrop_path: Option<String>, pub genre_ids: Vec<u64>, pub id: u64, - pub original_language: String, - pub original_title: String, + pub original_language: Option<String>, + pub original_title: Option<String>, pub overview: String, pub popularity: f64, pub poster_path: Option<String>, - pub release_date: String, - pub title: String, - pub video: bool, + pub release_date: Option<String>, + pub title: Option<String>, + pub name: Option<String>, pub vote_average: f64, pub vote_count: usize, } @@ -39,21 +39,21 @@ pub struct TmdbDetails { pub backdrop_path: Option<String>, pub genres: Vec<TmdbGenre>, pub id: u64, - pub original_language: String, - pub original_title: String, + pub original_language: Option<String>, + pub original_title: Option<String>, pub overview: String, pub popularity: f64, pub poster_path: Option<String>, - pub release_date: String, - pub title: String, - pub video: bool, + pub release_date: Option<String>, + pub title: Option<String>, + pub name: Option<String>, pub vote_average: f64, pub vote_count: usize, - pub budget: usize, + pub budget: Option<usize>, pub homepage: Option<String>, pub imdb_id: Option<String>, pub production_companies: Vec<TmdbProductionCompany>, - pub revenue: usize, + pub revenue: Option<usize>, pub tagline: Option<String>, } @@ -70,19 +70,19 @@ pub struct TmdbProductionCompany { pub logo_path: Option<String>, } -pub fn tmdb_search(query: &str, key: &str) -> anyhow::Result<TmdbQuery> { +pub fn tmdb_search(kind: &str, query: &str, key: &str) -> anyhow::Result<TmdbQuery> { info!("searching tmdb: {query:?}"); Ok(reqwest::blocking::get(&format!( - "https://api.themoviedb.org/3/search/movie?query={}&api_key={key}", + "https://api.themoviedb.org/3/search/{kind}?query={}&api_key={key}", query.replace(" ", "+") ))? .json::<TmdbQuery>()?) } -pub fn tmdb_details(id: u64, key: &str) -> anyhow::Result<TmdbDetails> { +pub fn tmdb_details(kind: &str, id: u64, key: &str) -> anyhow::Result<TmdbDetails> { info!("fetching details: {id:?}"); Ok(reqwest::blocking::get(&format!( - "https://api.themoviedb.org/3/movie/{id}?api_key={key}" + "https://api.themoviedb.org/3/{kind}/{id}?api_key={key}" ))? .json()?) } |