feat(library): add media library browsing functionality
- Introduced new `library` module in the API routes to handle media library requests. - Enhanced `AppState` to include a media provider for library interactions. - Defined new `IMediaProvider` trait methods for listing collections, series, and genres. - Implemented Jellyfin media provider methods for fetching collections and series. - Added frontend components for selecting series and displaying filter previews. - Created hooks for fetching collections, series, and genres from the library. - Updated media filter to support series name and search term. - Enhanced API client to handle new library-related endpoints.
This commit is contained in:
@@ -13,7 +13,7 @@ pub mod value_objects;
|
||||
// Re-export commonly used types
|
||||
pub use entities::*;
|
||||
pub use errors::{DomainError, DomainResult};
|
||||
pub use ports::IMediaProvider;
|
||||
pub use ports::{Collection, IMediaProvider, SeriesSummary};
|
||||
pub use repositories::*;
|
||||
pub use services::{ChannelService, ScheduleEngineService, UserService};
|
||||
pub use value_objects::*;
|
||||
|
||||
@@ -6,15 +6,56 @@
|
||||
//! these traits for each concrete source.
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::entities::{MediaItem};
|
||||
use crate::errors::DomainResult;
|
||||
use crate::value_objects::{MediaFilter, MediaItemId};
|
||||
use crate::entities::MediaItem;
|
||||
use crate::errors::{DomainError, DomainResult};
|
||||
use crate::value_objects::{ContentType, MediaFilter, MediaItemId};
|
||||
|
||||
// ============================================================================
|
||||
// Library browsing types
|
||||
// ============================================================================
|
||||
|
||||
/// A top-level media collection / library exposed by a provider.
|
||||
///
|
||||
/// In Jellyfin this maps to a virtual library (Movies, TV Shows, …).
|
||||
/// In Plex it maps to a section. The `id` is provider-specific and is used
|
||||
/// as the value for `MediaFilter::collections`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Collection {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
/// Provider-specific type hint, e.g. "movies", "tvshows". `None` when the
|
||||
/// provider does not expose this information.
|
||||
pub collection_type: Option<String>,
|
||||
}
|
||||
|
||||
/// Lightweight summary of a TV series available in the provider's library.
|
||||
/// Returned by `IMediaProvider::list_series` for the dashboard browser.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SeriesSummary {
|
||||
/// Provider-specific series ID (opaque — used for ParentId filtering).
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
/// Total number of episodes across all seasons, if the provider exposes it.
|
||||
pub episode_count: u32,
|
||||
pub genres: Vec<String>,
|
||||
pub year: Option<u16>,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Port trait
|
||||
// ============================================================================
|
||||
|
||||
/// Port for reading media content from an external provider.
|
||||
///
|
||||
/// Implementations live in the infra layer. One adapter per provider type
|
||||
/// (e.g. `JellyfinMediaProvider`, `PlexMediaProvider`, `LocalFileProvider`).
|
||||
///
|
||||
/// The three browsing methods (`list_collections`, `list_series`, `list_genres`)
|
||||
/// have default implementations that return an `InfrastructureError`. Adapters
|
||||
/// that support library browsing override them; those that don't (e.g. the
|
||||
/// `NoopMediaProvider`) inherit the default and return a clear error.
|
||||
#[async_trait]
|
||||
pub trait IMediaProvider: Send + Sync {
|
||||
/// Fetch metadata for all items matching `filter` from this provider.
|
||||
@@ -36,4 +77,38 @@ pub trait IMediaProvider: Send + Sync {
|
||||
/// URLs are intentionally *not* stored in the schedule because they may be
|
||||
/// short-lived (signed URLs, session tokens) or depend on client context.
|
||||
async fn get_stream_url(&self, item_id: &MediaItemId) -> DomainResult<String>;
|
||||
|
||||
/// List top-level collections (libraries/sections) available in this provider.
|
||||
///
|
||||
/// Used by the dashboard to populate the collections picker so users don't
|
||||
/// need to know provider-internal IDs.
|
||||
async fn list_collections(&self) -> DomainResult<Vec<Collection>> {
|
||||
Err(DomainError::InfrastructureError(
|
||||
"list_collections is not supported by this provider".into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// List TV series available in an optional collection.
|
||||
///
|
||||
/// `collection_id` corresponds to `Collection::id` returned by
|
||||
/// `list_collections`. Pass `None` to search across all libraries.
|
||||
async fn list_series(&self, collection_id: Option<&str>) -> DomainResult<Vec<SeriesSummary>> {
|
||||
let _ = collection_id;
|
||||
Err(DomainError::InfrastructureError(
|
||||
"list_series is not supported by this provider".into(),
|
||||
))
|
||||
}
|
||||
|
||||
/// List all genres available for a given content type.
|
||||
///
|
||||
/// Pass `None` to return genres across all content types.
|
||||
async fn list_genres(
|
||||
&self,
|
||||
content_type: Option<&ContentType>,
|
||||
) -> DomainResult<Vec<String>> {
|
||||
let _ = content_type;
|
||||
Err(DomainError::InfrastructureError(
|
||||
"list_genres is not supported by this provider".into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +606,12 @@ pub struct MediaFilter {
|
||||
/// Abstract groupings interpreted by each provider (Jellyfin library, Plex section,
|
||||
/// filesystem path, etc.). An empty list means "all available content".
|
||||
pub collections: Vec<String>,
|
||||
/// Filter by TV series name. Use with `content_type: Episode` and
|
||||
/// `strategy: Sequential` for ordered series playback (e.g. "iCarly").
|
||||
pub series_name: Option<String>,
|
||||
/// Free-text search term. Intended for library browsing; typically omitted
|
||||
/// during schedule generation.
|
||||
pub search_term: Option<String>,
|
||||
}
|
||||
|
||||
/// How the scheduling engine fills a time block with selected media items.
|
||||
|
||||
Reference in New Issue
Block a user