diff options
author | metamuffin <metamuffin@disroot.org> | 2025-02-04 14:12:58 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-02-04 14:12:58 +0100 |
commit | 8118667fc80bdf0246b16dff08f5a522efea27cf (patch) | |
tree | 47d8bf8815ecc553b6fcd84aaeb1b5eda7c8777f | |
parent | a506544a748bbbb133fbe3743a686878d9fbcef1 (diff) | |
download | jellything-8118667fc80bdf0246b16dff08f5a522efea27cf.tar jellything-8118667fc80bdf0246b16dff08f5a522efea27cf.tar.bz2 jellything-8118667fc80bdf0246b16dff08f5a522efea27cf.tar.zst |
jellyfin to multiple files
-rw-r--r-- | server/src/routes/compat/jellyfin/mod.rs (renamed from server/src/routes/compat/jellyfin.rs) | 222 | ||||
-rw-r--r-- | server/src/routes/compat/jellyfin/models.rs | 199 | ||||
-rw-r--r-- | server/src/routes/mod.rs | 130 |
3 files changed, 317 insertions, 234 deletions
diff --git a/server/src/routes/compat/jellyfin.rs b/server/src/routes/compat/jellyfin/mod.rs index 17ddcb6..0fed578 100644 --- a/server/src/routes/compat/jellyfin.rs +++ b/server/src/routes/compat/jellyfin/mod.rs @@ -3,11 +3,13 @@ which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin <metamuffin.org> */ +pub mod models; + use crate::routes::{ stream::rocket_uri_macro_r_stream, ui::{ account::{login_logic, session::Session}, - assets::rocket_uri_macro_r_item_poster, + assets::{rocket_uri_macro_r_asset, rocket_uri_macro_r_item_poster}, error::MyResult, node::{aspect_class, DatabaseNodeUserDataExt}, }, @@ -19,8 +21,9 @@ use jellycommon::{ user::{NodeUserData, WatchedState}, MediaInfo, Node, NodeID, NodeKind, SourceTrack, SourceTrackKind, Visibility, }; +use models::*; use rocket::{get, post, response::Redirect, serde::json::Json, FromForm, State}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::{json, Value}; use std::{collections::BTreeMap, net::IpAddr}; @@ -133,22 +136,40 @@ pub fn r_jellyfin_displaypreferences_usersettings(_session: Session) -> Json<Val })) } +#[post("/DisplayPreferences/usersettings")] +pub fn r_jellyfin_displaypreferences_usersettings_post(_session: Session) {} + #[get("/Users/<id>")] pub fn r_jellyfin_users_id(session: Session, id: &str) -> Json<Value> { let _ = id; Json(user_object(session.user.name)) } -#[get("/Items/<id>/Images/Primary?<fillWidth>")] +#[get("/Items/<id>/Images/Primary?<fillWidth>&<tag>")] #[allow(non_snake_case)] pub fn r_jellyfin_items_image_primary( _session: Session, id: &str, fillWidth: Option<usize>, + tag: Option<String>, ) -> Redirect { - Redirect::permanent(rocket::uri!(r_item_poster(id, fillWidth))) + if let Some(tag) = tag { + Redirect::permanent(rocket::uri!(r_asset(tag, fillWidth))) + } else { + Redirect::permanent(rocket::uri!(r_item_poster(id, fillWidth))) + } } +#[get("/Items/<id>")] +#[allow(private_interfaces)] +pub fn r_jellyfin_items_item( + session: Session, + database: &State<Database>, + id: &str, +) -> MyResult<Json<JellyfinItem>> { + let (n, ud) = database.get_node_with_userdata(NodeID::from_slug(id), &session)?; + Ok(Json(item_object(&n, &ud))) +} #[get("/Users/<uid>/Items/<id>")] #[allow(private_interfaces)] pub fn r_jellyfin_users_items_item( @@ -158,8 +179,7 @@ pub fn r_jellyfin_users_items_item( id: &str, ) -> MyResult<Json<JellyfinItem>> { let _ = uid; - let (n, ud) = database.get_node_with_userdata(NodeID::from_slug(id), &session)?; - Ok(Json(item_object(&n, &ud))) + r_jellyfin_items_item(session, database, id) } #[derive(Debug, FromForm)] @@ -312,6 +332,16 @@ pub fn r_jellyfin_users_views( }))) } +#[get("/Items/<id>/Similar")] +pub fn r_jellyfin_items_similar(_session: Session, id: &str) -> Json<Value> { + let _ = id; + Json(json!({ + "Items": [], + "TotalRecordCount": 0, + "StartIndex": 0 + })) +} + #[get("/LiveTv/Programs/Recommended")] pub fn r_jellyfin_livetv_programs_recommended(_session: Session) -> Json<Value> { Json(json!({ @@ -477,120 +507,6 @@ pub fn r_jellyfin_users_authenticatebyname( }))) } -#[derive(Debug, Serialize, Deserialize)] -enum JellyfinItemType { - AudioBook, - Movie, - BoxSet, - Book, - Photo, - PhotoAlbum, - TvChannel, - LiveTvProgram, - Video, - Audio, - MusicAlbum, - CollectionFolder, -} - -#[derive(Debug, Clone, Serialize)] -enum JellyfinMediaStreamType { - Video, - Audio, - Subtitle, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "PascalCase")] -struct JellyfinMediaStream { - codec: String, - time_base: String, - video_range: String, - video_range_type: String, - audio_spatial_format: String, - display_title: String, - is_interlaced: bool, - is_avc: bool, - bit_rate: usize, - bit_depth: usize, - ref_frames: usize, - is_default: bool, - is_forced: bool, - is_hearing_impaired: bool, - height: Option<u64>, - width: Option<u64>, - average_frame_rate: Option<f64>, - real_frame_rate: Option<f64>, - reference_frame_rate: Option<f64>, - profile: String, - r#type: JellyfinMediaStreamType, - aspect_ratio: String, - index: usize, - is_external: bool, - is_text_subtitle_stream: bool, - supports_external_stream: bool, - pixel_format: String, - level: usize, - is_anamorphic: bool, - channel_layout: Option<String>, - channels: Option<usize>, - sample_rate: Option<f64>, - localized_default: String, - localized_external: String, -} - -#[derive(Debug, Clone, Serialize)] -enum JellyfinMediaSourceProtocol { - File, -} -#[derive(Debug, Clone, Serialize)] -enum JellyfinMediaSourceType { - Default, -} - -#[derive(Debug, Clone, Serialize)] -enum JellyfinVideoType { - VideoFile, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "PascalCase")] -struct JellyfinMediaSource { - protocol: JellyfinMediaSourceProtocol, - id: String, - path: String, - r#type: JellyfinMediaSourceType, - container: String, - size: usize, - name: String, - is_remote: bool, - e_tag: String, - run_time_ticks: f64, - read_at_native_framerate: bool, - ignore_dts: bool, - ignore_index: bool, - gen_pts_input: bool, - supports_transcoding: bool, - supports_direct_stream: bool, - supports_direct_play: bool, - is_infinite_stream: bool, - use_most_compatible_transcoding_profile: bool, - requires_opening: bool, - requires_closing: bool, - requires_looping: bool, - supports_probing: bool, - video_type: JellyfinVideoType, - media_streams: Vec<JellyfinMediaStream>, - media_attachments: Vec<()>, - formats: Vec<()>, - bitrate: usize, - required_http_headers: BTreeMap<(), ()>, - transcoding_sub_protocol: String, - default_audio_stream_index: usize, - default_subtitle_stream_index: usize, - has_segments: bool, -} - fn track_object(index: usize, track: &SourceTrack) -> JellyfinMediaStream { let fr = if let SourceTrackKind::Video { fps, .. } = &track.kind { Some(fps.unwrap_or_default()) @@ -721,58 +637,6 @@ fn media_source_object(node: &Node, m: &MediaInfo) -> JellyfinMediaSource { } } -#[derive(Debug, Serialize)] -#[serde(rename_all = "PascalCase")] -struct JellyfinItem { - name: String, - server_id: String, - id: String, - e_tag: String, - date_created: String, - can_delete: bool, - can_download: bool, - preferred_metadata_language: String, - preferred_metadata_country_code: String, - sort_name: String, - forced_sort_name: String, - external_urls: Vec<()>, - enable_media_source_display: bool, - custom_rating: String, - channel_id: Option<String>, - overview: String, - taglines: Vec<String>, - genres: Vec<()>, - play_access: Option<String>, - remote_trailers: Vec<()>, - provider_ids: BTreeMap<(), ()>, - is_folder: bool, - parent_id: String, - r#type: JellyfinItemType, - people: Vec<()>, - studios: Vec<()>, - genre_items: Vec<()>, - local_trailer_count: usize, - special_feature_count: usize, - child_count: usize, - locked_fields: Vec<()>, - lock_data: bool, - tags: Vec<String>, - user_data: Value, - display_preferences_id: String, - primary_image_aspect_ratio: f64, - collection_type: String, - image_tags: BTreeMap<String, String>, - backdrop_image_tags: Vec<()>, - location_type: Option<String>, - media_type: String, - video_type: Option<String>, - container: Option<String>, - run_time_ticks: Option<i64>, - media_sources: Option<Vec<JellyfinMediaSource>>, - media_streams: Option<Vec<JellyfinMediaStream>>, - path: Option<String>, -} - fn item_object(node: &Node, userdata: &NodeUserData) -> JellyfinItem { let media_source = node.media.as_ref().map(|m| media_source_object(node, m)); @@ -803,7 +667,19 @@ fn item_object(node: &Node, userdata: &NodeUserData) -> JellyfinItem { NodeKind::Movie | NodeKind::Video | NodeKind::ShortFormVideo => JellyfinItemType::Movie, NodeKind::Collection | _ => JellyfinItemType::CollectionFolder, }, - people: vec![], + people: node + .people + .iter() + .flat_map(|(_pg, ps)| { + ps.iter().map(|p| JellyfinPerson { + name: p.person.name.clone(), + id: p.person.ids.tmdb.unwrap_or_default().to_string(), + primary_image_tag: p.person.headshot.clone().map(|a| a.0).unwrap_or_default(), + role: p.characters.join(","), + r#type: JellyfinPersonType::Actor, + }) + }) + .collect(), studios: vec![], genre_items: vec![], local_trailer_count: 0, diff --git a/server/src/routes/compat/jellyfin/models.rs b/server/src/routes/compat/jellyfin/models.rs new file mode 100644 index 0000000..eeffa34 --- /dev/null +++ b/server/src/routes/compat/jellyfin/models.rs @@ -0,0 +1,199 @@ +/* + 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 serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::BTreeMap; + +#[derive(Debug, Serialize, Deserialize)] +pub(super) enum JellyfinItemType { + AudioBook, + Movie, + BoxSet, + Book, + Photo, + PhotoAlbum, + TvChannel, + LiveTvProgram, + Video, + Audio, + MusicAlbum, + CollectionFolder, +} + +#[derive(Debug, Clone, Serialize)] +pub(super) enum JellyfinMediaStreamType { + Video, + Audio, + Subtitle, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "PascalCase")] +pub(super) struct JellyfinMediaStream { + pub codec: String, + pub time_base: String, + pub video_range: String, + pub video_range_type: String, + pub audio_spatial_format: String, + pub display_title: String, + pub is_interlaced: bool, + pub is_avc: bool, + pub bit_rate: usize, + pub bit_depth: usize, + pub ref_frames: usize, + pub is_default: bool, + pub is_forced: bool, + pub is_hearing_impaired: bool, + pub height: Option<u64>, + pub width: Option<u64>, + pub average_frame_rate: Option<f64>, + pub real_frame_rate: Option<f64>, + pub reference_frame_rate: Option<f64>, + pub profile: String, + pub r#type: JellyfinMediaStreamType, + pub aspect_ratio: String, + pub index: usize, + pub is_external: bool, + pub is_text_subtitle_stream: bool, + pub supports_external_stream: bool, + pub pixel_format: String, + pub level: usize, + pub is_anamorphic: bool, + pub channel_layout: Option<String>, + pub channels: Option<usize>, + pub sample_rate: Option<f64>, + pub localized_default: String, + pub localized_external: String, +} + +#[derive(Debug, Clone, Serialize)] +pub(super) enum JellyfinMediaSourceProtocol { + File, +} +#[derive(Debug, Clone, Serialize)] +pub(super) enum JellyfinMediaSourceType { + Default, +} + +#[derive(Debug, Clone, Serialize)] +pub(super) enum JellyfinVideoType { + VideoFile, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "PascalCase")] +pub(super) struct JellyfinMediaSource { + pub protocol: JellyfinMediaSourceProtocol, + pub id: String, + pub path: String, + pub r#type: JellyfinMediaSourceType, + pub container: String, + pub size: usize, + pub name: String, + pub is_remote: bool, + pub e_tag: String, + pub run_time_ticks: f64, + pub read_at_native_framerate: bool, + pub ignore_dts: bool, + pub ignore_index: bool, + pub gen_pts_input: bool, + pub supports_transcoding: bool, + pub supports_direct_stream: bool, + pub supports_direct_play: bool, + pub is_infinite_stream: bool, + pub use_most_compatible_transcoding_profile: bool, + pub requires_opening: bool, + pub requires_closing: bool, + pub requires_looping: bool, + pub supports_probing: bool, + pub video_type: JellyfinVideoType, + pub media_streams: Vec<JellyfinMediaStream>, + pub media_attachments: Vec<()>, + pub formats: Vec<()>, + pub bitrate: usize, + pub required_http_headers: BTreeMap<(), ()>, + pub transcoding_sub_protocol: String, + pub default_audio_stream_index: usize, + pub default_subtitle_stream_index: usize, + pub has_segments: bool, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub(super) struct JellyfinItem { + pub name: String, + pub server_id: String, + pub id: String, + pub e_tag: String, + pub date_created: String, + pub can_delete: bool, + pub can_download: bool, + pub preferred_metadata_language: String, + pub preferred_metadata_country_code: String, + pub sort_name: String, + pub forced_sort_name: String, + pub external_urls: Vec<()>, + pub enable_media_source_display: bool, + pub custom_rating: String, + pub channel_id: Option<String>, + pub overview: String, + pub taglines: Vec<String>, + pub genres: Vec<()>, + pub play_access: Option<String>, + pub remote_trailers: Vec<()>, + pub provider_ids: BTreeMap<(), ()>, + pub is_folder: bool, + pub parent_id: String, + pub r#type: JellyfinItemType, + pub people: Vec<JellyfinPerson>, + pub studios: Vec<JellyfinStudio>, + pub genre_items: Vec<()>, + pub local_trailer_count: usize, + pub special_feature_count: usize, + pub child_count: usize, + pub locked_fields: Vec<()>, + pub lock_data: bool, + pub tags: Vec<String>, + pub user_data: Value, + pub display_preferences_id: String, + pub primary_image_aspect_ratio: f64, + pub collection_type: String, + pub image_tags: BTreeMap<String, String>, + pub backdrop_image_tags: Vec<()>, + pub location_type: Option<String>, + pub media_type: String, + pub video_type: Option<String>, + pub container: Option<String>, + pub run_time_ticks: Option<i64>, + pub media_sources: Option<Vec<JellyfinMediaSource>>, + pub media_streams: Option<Vec<JellyfinMediaStream>>, + pub path: Option<String>, +} + +#[derive(Debug, Serialize)] +pub(super) enum JellyfinPersonType { + Actor, + // Writer, + // Producer, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub(super) struct JellyfinPerson { + pub name: String, + pub id: String, + pub role: String, + pub r#type: JellyfinPersonType, + pub primary_image_tag: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "PascalCase")] +pub(super) struct JellyfinStudio { + pub name: String, + pub id: String, +} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 373146b..98ea4b0 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -10,8 +10,10 @@ use base64::Engine; use compat::{ jellyfin::{ r_jellyfin_artists, r_jellyfin_branding_configuration, r_jellyfin_branding_css, - r_jellyfin_displaypreferences_usersettings, r_jellyfin_items, - r_jellyfin_items_image_primary, r_jellyfin_items_intros, r_jellyfin_items_playbackinfo, + r_jellyfin_displaypreferences_usersettings, + r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_items, + r_jellyfin_items_image_primary, r_jellyfin_items_intros, r_jellyfin_items_item, + r_jellyfin_items_playbackinfo, r_jellyfin_items_similar, r_jellyfin_livetv_programs_recommended, r_jellyfin_persons, r_jellyfin_playback_bitratetest, r_jellyfin_quickconnect_enabled, r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing, @@ -112,82 +114,88 @@ pub fn build_rocket(database: Database, federation: Federation) -> Rocket<Build> .mount( "/", routes![ - r_home, - r_home_unpriv, - r_streamsync, - r_favicon, - r_asset, - r_item_backdrop, - r_item_poster, - r_person_asset, - r_search, - r_all_items_filter, - r_library_node_filter, - r_assets_style, - r_assets_font, - r_assets_js, - r_assets_js_map, - r_stream, - r_node_userdata, - r_node_thumbnail, - r_player, - r_node_userdata_progress, - r_node_userdata_watched, - r_node_userdata_rating, - r_account_login, + // Frontend r_account_login_post, - r_account_register, - r_account_register_post, - r_account_logout, + r_account_login, r_account_logout_post, + r_account_logout, + r_account_register_post, + r_account_register, + r_account_settings_post, + r_account_settings, r_admin_dashboard, + r_admin_delete_cache, + r_admin_import, r_admin_invite, - r_admin_remove_user, - r_admin_user, - r_admin_users, + r_admin_log_stream, + r_admin_log, r_admin_remove_invite, - r_admin_user_permission, - r_admin_delete_cache, + r_admin_remove_user, r_admin_transcode_posters, - r_admin_log, - r_admin_log_stream, - r_admin_import, r_admin_update_search, - r_account_settings, - r_account_settings_post, - r_api_version, + r_admin_user_permission, + r_admin_user, + r_admin_users, + r_all_items_filter, + r_asset, + r_assets_font, + r_assets_js_map, + r_assets_js, + r_assets_style, + r_favicon, + r_home_unpriv, + r_home, + r_item_backdrop, + r_item_poster, + r_library_node_filter, + r_node_thumbnail, + r_node_userdata_progress, + r_node_userdata_rating, + r_node_userdata_watched, + r_node_userdata, + r_person_asset, + r_player, + r_search, + r_stream, + r_streamsync, + // API r_api_account_login, - r_api_root, r_api_asset_token_raw, - r_youtube_watch, - r_youtube_channel, - r_jellyfin_system_info_public, - r_jellyfin_system_info_public_case, - r_jellyfin_quickconnect_enabled, - r_jellyfin_users_public, + r_api_root, + r_api_version, + // Compat + r_jellyfin_artists, r_jellyfin_branding_configuration, - r_jellyfin_users_authenticatebyname, - r_jellyfin_sessions_capabilities_full, - r_jellyfin_system_endpoint, r_jellyfin_branding_css, + r_jellyfin_displaypreferences_usersettings_post, r_jellyfin_displaypreferences_usersettings, - r_jellyfin_system_info, - r_jellyfin_users_id, - r_jellyfin_playback_bitratetest, - r_jellyfin_users_views, r_jellyfin_items_image_primary, - r_jellyfin_livetv_programs_recommended, - r_jellyfin_users_items, - r_jellyfin_users_items_item, - r_jellyfin_items, - r_jellyfin_persons, - r_jellyfin_artists, r_jellyfin_items_intros, + r_jellyfin_items_item, r_jellyfin_items_playbackinfo, - r_jellyfin_video_stream, - r_jellyfin_sessions_playing, + r_jellyfin_items_similar, + r_jellyfin_items, + r_jellyfin_livetv_programs_recommended, + r_jellyfin_persons, + r_jellyfin_playback_bitratetest, + r_jellyfin_quickconnect_enabled, + r_jellyfin_sessions_capabilities_full, r_jellyfin_sessions_playing_progress, + r_jellyfin_sessions_playing, r_jellyfin_socket, + r_jellyfin_system_endpoint, + r_jellyfin_system_info_public_case, + r_jellyfin_system_info_public, + r_jellyfin_system_info, + r_jellyfin_users_authenticatebyname, + r_jellyfin_users_id, + r_jellyfin_users_items_item, + r_jellyfin_users_items, + r_jellyfin_users_public, + r_jellyfin_users_views, + r_jellyfin_video_stream, + r_youtube_channel, + r_youtube_watch, ], ) } |