/* 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 */ use crate::{ Node, NodeID, NodeIDOrSlug, ObjectIds, PeopleGroup, SourceTrack, SourceTrackKind, TmdbKind, TraktKind, }; use hex::FromHexError; use serde::{Deserialize, Serialize}; use std::{fmt::Display, str::FromStr}; impl SourceTrackKind { pub fn letter(&self) -> char { match self { SourceTrackKind::Video { .. } => 'v', SourceTrackKind::Audio { .. } => 'a', SourceTrackKind::Subtitles => 's', } } } impl Display for SourceTrack { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let kspec = match &self.kind { SourceTrackKind::Video { width, height, fps, .. } => { format!("Video: {width}x{height} {}fps ", fps.unwrap_or(0.)) } SourceTrackKind::Audio { channels, sample_rate, bit_depth, } => format!( "Audio: {channels}ch {sample_rate}Hz {}bits ", bit_depth.unwrap_or(0) ), SourceTrackKind::Subtitles => "Subtitles: ".to_string(), }; f.write_fmt(format_args!( "{} {:?} {} ({})", kspec, self.name, self.language, self.codec )) } } impl Display for TmdbKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { TmdbKind::Tv => "tv", TmdbKind::Movie => "movie", }) } } impl TraktKind { pub fn singular(self) -> &'static str { match self { TraktKind::Movie => "movie", TraktKind::Show => "show", TraktKind::Season => "season", TraktKind::Episode => "episode", TraktKind::Person => "person", TraktKind::User => "user", } } pub fn plural(self) -> &'static str { match self { TraktKind::Movie => "movies", TraktKind::Show => "shows", TraktKind::Season => "seasons", TraktKind::Episode => "episodes", TraktKind::Person => "people", TraktKind::User => "users", // //! not used in API } } } impl Display for TraktKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { TraktKind::Movie => "Movie", TraktKind::Show => "Show", TraktKind::Season => "Season", TraktKind::Episode => "Episode", TraktKind::Person => "Person", TraktKind::User => "User", }) } } impl Display for ObjectIds { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(id) = self.trakt { f.write_fmt(format_args!("trakt={}", id))?; } if let Some(_id) = &self.slug { f.write_str(",slug")?; } if let Some(id) = self.tmdb { f.write_fmt(format_args!(",tmdb={}", id))?; } if let Some(_id) = &self.imdb { f.write_str(",imdb")?; } if let Some(_id) = &self.tvdb { f.write_str(",tvdb")?; } if let Some(_id) = &self.omdb { f.write_str(",omdb")?; } Ok(()) } } impl Display for PeopleGroup { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(match self { PeopleGroup::Cast => "Cast", PeopleGroup::Writing => "Writing", PeopleGroup::Directing => "Directing", PeopleGroup::Art => "Art", PeopleGroup::Sound => "Sound", PeopleGroup::Camera => "Camera", PeopleGroup::Lighting => "Lighting", PeopleGroup::Crew => "Crew", PeopleGroup::Editing => "Editing", PeopleGroup::Production => "Production", PeopleGroup::Vfx => "Visual Effects", PeopleGroup::CostumeMakeup => "Costume & Makeup", PeopleGroup::CreatedBy => "Created by:", PeopleGroup::Performance => "Performance", PeopleGroup::Instrument => "Instrument", PeopleGroup::Vocal => "Vocal", PeopleGroup::Arranger => "Arranger", PeopleGroup::Producer => "Producer", PeopleGroup::Engineer => "Engineer", }) } } impl FromStr for PeopleGroup { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "Cast" => PeopleGroup::Cast, "Writing" => PeopleGroup::Writing, "Directing" => PeopleGroup::Directing, "Art" => PeopleGroup::Art, "Sound" => PeopleGroup::Sound, "Camera" => PeopleGroup::Camera, "Lighting" => PeopleGroup::Lighting, "Crew" => PeopleGroup::Crew, "Editing" => PeopleGroup::Editing, "Production" => PeopleGroup::Production, "Visual Effects" => PeopleGroup::Vfx, "Costume & Makeup" => PeopleGroup::CostumeMakeup, "Created by:" => PeopleGroup::CreatedBy, _ => return Err(()), }) } } impl NodeID { pub fn from_slug(slug: &str) -> Self { let mut h = blake3::Hasher::new(); h.update(slug.as_bytes()); Self(*h.finalize().as_bytes()) } #[inline] pub fn from_node(node: &Node) -> Self { Self::from_slug(&node.slug) } } impl Node { #[inline] pub fn id(&self) -> NodeID { NodeID::from_node(self) } } impl NodeID { pub const MIN: NodeID = NodeID([0; 32]); pub const MAX: NodeID = NodeID([255; 32]); } #[cfg(feature = "rocket")] impl<'a> rocket::request::FromParam<'a> for NodeID { type Error = FromHexError; fn from_param(param: &'a str) -> Result { if let Some(id) = param.strip_prefix("+") { let mut k = [0; 32]; hex::decode_to_slice(id, &mut k)?; Ok(NodeID(k)) } else { Ok(NodeID::from_slug(param)) } } } #[cfg(feature = "rocket")] impl rocket::http::uri::fmt::FromUriParam for NodeID { type Target = NodeID; fn from_uri_param(param: NodeID) -> Self::Target { param } } #[cfg(feature = "rocket")] impl<'a> rocket::http::uri::fmt::FromUriParam for NodeID { type Target = &'a str; fn from_uri_param(param: &'a String) -> Self::Target { param.as_str() } } #[cfg(feature = "rocket")] impl<'a> rocket::http::uri::fmt::FromUriParam for NodeID { type Target = &'a str; fn from_uri_param(param: &'a str) -> Self::Target { param } } #[cfg(feature = "rocket")] impl rocket::http::uri::fmt::UriDisplay for NodeID { fn fmt( &self, f: &mut rocket::http::uri::fmt::Formatter<'_, rocket::http::uri::fmt::Path>, ) -> std::fmt::Result { f.write_value(format!("+{}", hex::encode(self.0)))?; Ok(()) } } impl Display for NodeID { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("+")?; f.write_str(&hex::encode(self.0))?; Ok(()) } } impl Display for NodeIDOrSlug { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { NodeIDOrSlug::ID(x) => x.fmt(f), NodeIDOrSlug::Slug(x) => x.fmt(f), } } } impl From for NodeIDOrSlug { fn from(value: NodeID) -> Self { Self::ID(value) } } impl From for NodeIDOrSlug { fn from(value: String) -> Self { Self::Slug(value) } } impl Serialize for NodeID { fn serialize(&self, serializer: S) -> Result { serializer.serialize_str(&hex::encode(self.0)) } } impl<'de> Deserialize<'de> for NodeID { fn deserialize>(deserializer: D) -> Result { let mut k = [0; 32]; hex::decode_to_slice(String::deserialize(deserializer)?, &mut k).map_err(|_| { ::custom(format_args!("nodeid hex invalid")) })?; Ok(NodeID(k)) } }