feat(kiosk): bump v0.1.2 with video library support for movies, tv shows, and seasons

This commit is contained in:
bart
2026-11-05 18:46:32 +01:00
parent 4f2cdbd817
commit 3a407f90f7
8 changed files with 1321 additions and 115 deletions
+49 -2
View File
@@ -36,6 +36,8 @@ pub struct MediaItem {
pub artist_name: Option<String>,
pub genre: Option<String>,
pub index_number: Option<u32>,
pub parent_index_number: Option<u32>,
pub season_name: Option<String>,
pub year: Option<u32>,
pub overview: Option<String>,
pub rating: Option<f32>,
@@ -197,6 +199,7 @@ pub struct JellyfinClient {
client: reqwest::Client,
pub token: Arc<RwLock<Option<String>>>,
pub user_id: Arc<RwLock<Option<String>>>,
pub library: Arc<RwLock<MediaLibrary>>,
}
impl JellyfinClient {
@@ -206,6 +209,7 @@ impl JellyfinClient {
client: reqwest::Client::new(),
token: Arc::new(RwLock::new(None)),
user_id: Arc::new(RwLock::new(None)),
library: Arc::new(RwLock::new(MediaLibrary::default())),
}
}
@@ -256,6 +260,17 @@ impl JellyfinClient {
Ok(())
}
pub async fn ensure_authenticated(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if self.token.read().await.is_none() {
self.authenticate().await?;
}
Ok(())
}
pub async fn get_token(&self) -> Option<String> {
self.token.read().await.clone()
}
async fn get_authed_headers(&self) -> Result<HeaderMap, Box<dyn std::error::Error + Send + Sync>> {
let token_guard = self.token.read().await;
let token = token_guard.as_deref().ok_or("Not authenticated")?;
@@ -553,7 +568,7 @@ impl JellyfinClient {
let mut favorites = fetch_endpoint(&self.client, &url_fav, &headers).await;
sort_media_items_alphabetically(&mut favorites);
Ok(MediaLibrary {
let lib = MediaLibrary {
resume_items,
latest_movies,
latest_shows,
@@ -566,7 +581,35 @@ impl JellyfinClient {
photos,
live_channels,
favorites,
})
};
*self.library.write().await = lib.clone();
Ok(lib)
}
pub async fn get_library(&self) -> MediaLibrary {
{
let guard = self.library.read().await;
if !guard.all_movies.is_empty() || !guard.all_shows.is_empty() {
return guard.clone();
}
}
if let Ok(lib) = self.fetch_all_media().await {
return lib;
}
self.library.read().await.clone()
}
pub async fn fetch_show_episodes(&self, show_id: &str) -> Vec<MediaItem> {
let base_url = self.config.server_url.trim_end_matches('/');
if let Ok(headers) = self.get_authed_headers().await {
let user_id = self.user_id.read().await.clone().unwrap_or_default();
let url = format!(
"{}/Shows/{}/Episodes?userId={}&fields=Overview,ProductionYear,CommunityRating,ParentBackdropItemId,ParentPrimaryImageItemId,UserData,IndexNumber,ParentIndexNumber,SortName,Genres",
base_url, show_id, user_id
);
return fetch_endpoint(&self.client, &url, &headers).await;
}
Vec::new()
}
}
@@ -616,6 +659,8 @@ fn parse_items(json: &serde_json::Value) -> Vec<MediaItem> {
let sort_name = it["SortName"].as_str().map(|s| s.to_string());
let index_number = it["IndexNumber"].as_u64().map(|n| n as u32);
let parent_index_number = it["ParentIndexNumber"].as_u64().map(|n| n as u32);
let season_name = it["SeasonName"].as_str().map(|s| s.to_string());
if !id.is_empty() {
out.push(MediaItem {
@@ -627,6 +672,8 @@ fn parse_items(json: &serde_json::Value) -> Vec<MediaItem> {
artist_name,
genre,
index_number,
parent_index_number,
season_name,
year,
overview,
rating,