aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-02-04 14:12:58 +0100
committermetamuffin <metamuffin@disroot.org>2025-02-04 14:12:58 +0100
commit8118667fc80bdf0246b16dff08f5a522efea27cf (patch)
tree47d8bf8815ecc553b6fcd84aaeb1b5eda7c8777f
parenta506544a748bbbb133fbe3743a686878d9fbcef1 (diff)
downloadjellything-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.rs199
-rw-r--r--server/src/routes/mod.rs130
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,
],
)
}