This commit is contained in:
bart
2026-11-04 19:41:25 +01:00
parent 99cbc00357
commit d77cdfedab
12 changed files with 2181 additions and 605 deletions
+38 -44
View File
@@ -50,6 +50,16 @@ pub struct MediaItem {
impl MediaItem {
pub fn primary_image_id(&self) -> &str {
if let Some(ref p_id) = self.parent_primary_image_item_id {
if p_id.starts_with("http://") || p_id.starts_with("https://") {
return p_id;
}
}
if let Some(ref b_id) = self.parent_backdrop_item_id {
if b_id.starts_with("http://") || b_id.starts_with("https://") {
return b_id;
}
}
if self.has_primary_image {
&self.id
} else if let Some(ref p_id) = self.parent_primary_image_item_id {
@@ -268,7 +278,25 @@ impl JellyfinClient {
);
let mut items = fetch_endpoint(&self.client, &url, &headers).await;
// 2. If empty, maybe this is an artist: try fetching albums via albumArtistIds
// 2. If empty, maybe this is a TV Show (Series): fetch seasons
if items.is_empty() {
let url_seasons = format!(
"{}/Shows/{}/Seasons?userId={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,SortName,Genres",
base_url, folder_id, user_id
);
items = fetch_endpoint(&self.client, &url_seasons, &headers).await;
}
// 3. If empty, maybe this is a Season: fetch episodes
if items.is_empty() {
let url_episodes = format!(
"{}/Shows/{}/Episodes?userId={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,SortName,Genres",
base_url, folder_id, user_id
);
items = fetch_endpoint(&self.client, &url_episodes, &headers).await;
}
// 4. If empty, maybe this is an artist: try fetching albums via albumArtistIds
if items.is_empty() {
let url_albums = format!(
"{}/Users/{}/Items?albumArtistIds={}&includeItemTypes=MusicAlbum&recursive=true&sortBy=SortName&limit=500&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,SortName,Genres",
@@ -277,7 +305,7 @@ impl JellyfinClient {
items = fetch_endpoint(&self.client, &url_albums, &headers).await;
}
// 3. If still empty, try artistIds for albums
// 5. If still empty, try artistIds for albums
if items.is_empty() {
let url_albums2 = format!(
"{}/Users/{}/Items?artistIds={}&includeItemTypes=MusicAlbum&recursive=true&sortBy=SortName&limit=500&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,SortName,Genres",
@@ -286,7 +314,7 @@ impl JellyfinClient {
items = fetch_endpoint(&self.client, &url_albums2, &headers).await;
}
// 4. If still empty (e.g. ABBA, *NSYNC who have no albums, only compilation tracks), fetch individual audio songs directly
// 6. If still empty (e.g. ABBA, *NSYNC who have no albums, only compilation tracks), fetch individual audio songs directly
if items.is_empty() {
let url_songs = format!(
"{}/Users/{}/Items?artistIds={}&includeItemTypes=Audio&recursive=true&sortBy=SortName&limit=500&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,SortName,Genres",
@@ -295,8 +323,8 @@ impl JellyfinClient {
items = fetch_endpoint(&self.client, &url_songs, &headers).await;
}
// If the folder items are Audio tracks, sort them by index_number
if items.iter().any(|i| i.item_type == "Audio") {
// If the folder items are Audio tracks, Seasons, or Episodes, sort them by index_number
if items.iter().any(|i| i.item_type == "Audio" || i.item_type == "Season" || i.item_type == "Episode") {
items.sort_by_key(|i| i.index_number.unwrap_or(u32::MAX));
} else {
sort_media_items_alphabetically(&mut items);
@@ -312,10 +340,6 @@ impl JellyfinClient {
let guard = self.token.read().await;
guard.clone().unwrap_or_default()
};
let user_id = {
let guard = self.user_id.read().await;
guard.clone().unwrap_or_default()
};
// 0. YouTube Video or direct stream URL
if item.item_type == "YouTubeVideo"
@@ -339,43 +363,13 @@ impl JellyfinClient {
});
}
// 2. Series -> resolve next unplayed episode
if item.item_type == "Series" {
if let Ok(headers) = self.get_authed_headers().await {
let next_url = format!("{}/Shows/NextUp?seriesId={}&userId={}&fields=Genres", base_url, item.id, user_id);
let episodes = fetch_endpoint(&self.client, &next_url, &headers).await;
if let Some(ep) = episodes.first() {
return Ok(PlayableTarget::Stream {
url: ep.stream_url(base_url, &token),
title: format!("{}: {}", item.name, ep.name),
media_type: "video".to_string(),
artist: None,
genre: ep.genre.clone().or_else(|| item.genre.clone()),
});
}
// Fallback: episode 1
let ep1_url = format!(
"{}/Users/{}/Items?parentId={}&includeItemTypes=Episode&recursive=true&sortBy=SortName&limit=1&fields=Genres",
base_url, user_id, item.id
);
let ep1 = fetch_endpoint(&self.client, &ep1_url, &headers).await;
if let Some(ep) = ep1.first() {
return Ok(PlayableTarget::Stream {
url: ep.stream_url(base_url, &token),
title: format!("{}: {}", item.name, ep.name),
media_type: "video".to_string(),
artist: None,
genre: ep.genre.clone().or_else(|| item.genre.clone()),
});
}
}
}
// 3. PhotoAlbum, Folder, MusicArtist, or MusicAlbum -> Open folder in UI
if item.item_type == "PhotoAlbum"
// 2. Series, Season, PhotoAlbum, Folder, MusicArtist, or MusicAlbum -> Open folder in UI
if item.item_type == "Series"
|| item.item_type == "Season"
|| item.item_type == "PhotoAlbum"
|| item.item_type == "MusicArtist"
|| item.item_type == "MusicAlbum"
|| (item.is_folder && item.item_type != "Series")
|| item.is_folder
{
return Ok(PlayableTarget::OpenFolder {
folder_id: item.id.clone(),