mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-31 03:02:24 +01:00
addded Emby/Jellyfin catalog import to media-server (#927)
- Emby/Jellyfin client: token/api_key/login auth, paging, Range playback, artwork - Jellyfin uses only Authorization: MediaBrowser header (Jellyfin 12 compatible) - AnyMediaServerCatalogClient dispatch, shared helpers reused by Plex - Update/Refresh for Emby/Jellyfin inputs in the Web UI - BREAKING: remove unused media_server options (playback, image_policy, catalog.refresh_mode/refresh_on_startup/include_paths/include_user_state)
This commit is contained in:
@@ -9,8 +9,8 @@ use super::{
|
||||
telemetry::{
|
||||
finalize_input_telemetry, input_telemetry_for_fetch, is_cluster_input, persist_finalized_cluster_snapshots,
|
||||
},
|
||||
CacheStatusScope, InputDownloadResult, LibraryProvider, PendingInputStatusUpdate, PlaylistDownloadResult,
|
||||
PlaylistProcessingContext, PlaylistUpdateExecutionRef, PlexProvider, StalkerProvider,
|
||||
CacheStatusScope, InputDownloadResult, LibraryProvider, MediaServerProvider, PendingInputStatusUpdate,
|
||||
PlaylistDownloadResult, PlaylistProcessingContext, PlaylistUpdateExecutionRef, StalkerProvider,
|
||||
PIPELINE_TRANSPARENCY_CLUSTERS,
|
||||
};
|
||||
use crate::{input_cache, metadata_sink::MetadataUpdateSink, processor::StalkerRefreshMode};
|
||||
@@ -171,15 +171,7 @@ async fn playlist_download_from_input<E: EventSink>(
|
||||
StalkerProvider::new(stalker_refresh_mode, !config.disk_based_processing).fetch(&request).await
|
||||
}
|
||||
InputType::Library => LibraryProvider.fetch(&request).await,
|
||||
InputType::Plex => PlexProvider.fetch(&request).await,
|
||||
InputType::Emby | InputType::Jellyfin => {
|
||||
UnsupportedProvider::new(
|
||||
"media-server",
|
||||
format!("media-server input '{}' is configured but catalog import is not implemented yet", input.name),
|
||||
)
|
||||
.fetch(&request)
|
||||
.await
|
||||
}
|
||||
InputType::Plex | InputType::Emby | InputType::Jellyfin => MediaServerProvider.fetch(&request).await,
|
||||
InputType::Staged => {
|
||||
UnsupportedProvider::new(
|
||||
"staged",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::providers::{LibraryProvider, PlexProvider, StalkerProvider, XmltvEpgProvider};
|
||||
use super::providers::{LibraryProvider, MediaServerProvider, StalkerProvider, XmltvEpgProvider};
|
||||
use crate::{
|
||||
fetched_playlist::FetchedPlaylist,
|
||||
input_cache,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! The [`PlaylistProvider`] implementations whose orchestration lives in this crate.
|
||||
//!
|
||||
//! The M3U and Xtream providers ship with the client code in `tuliprox-iptv`. These three
|
||||
//! cannot: Stalker's resumable refresh, the local library scan and the Plex catalog import
|
||||
//! all reach into `tuliprox-repository` and this crate's own processors, and `tuliprox-iptv`
|
||||
//! cannot: Stalker's resumable refresh, the local library scan and the media-server catalog
|
||||
//! import (Plex, Emby, Jellyfin) all reach into `tuliprox-repository` and this crate's own processors, and `tuliprox-iptv`
|
||||
//! sits below both. The trait is what lets them keep that dependency direction and still
|
||||
//! answer the dispatcher in one shape.
|
||||
|
||||
@@ -67,20 +67,20 @@ impl PlaylistProvider for LibraryProvider {
|
||||
}
|
||||
}
|
||||
|
||||
/// Plex media server catalog import.
|
||||
/// Media server catalog import for Plex, Emby and Jellyfin inputs.
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub struct PlexProvider;
|
||||
pub struct MediaServerProvider;
|
||||
|
||||
impl PlaylistProvider for PlexProvider {
|
||||
fn name(&self) -> &'static str { "plex" }
|
||||
impl PlaylistProvider for MediaServerProvider {
|
||||
fn name(&self) -> &'static str { "media-server" }
|
||||
|
||||
async fn fetch(&self, request: &PlaylistFetchRequest<'_>) -> PlaylistFetch {
|
||||
let (groups, errors) = download_plex_media_server_playlist(request.client, request.input).await;
|
||||
let (groups, errors) = download_media_server_playlist(request.client, request.input).await;
|
||||
PlaylistFetch::groups(groups).with_errors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_plex_media_server_playlist(
|
||||
async fn download_media_server_playlist(
|
||||
client: &reqwest::Client,
|
||||
input: &ConfigInput,
|
||||
) -> (Vec<PlaylistGroup>, Vec<TuliproxError>) {
|
||||
@@ -94,7 +94,7 @@ async fn download_plex_media_server_playlist(
|
||||
);
|
||||
};
|
||||
let http_client = MediaServerHttpClient::new(client.clone());
|
||||
let plex_client = match input.plex_catalog_client(http_client) {
|
||||
let catalog_client = match input.media_server_catalog_client(http_client) {
|
||||
Ok(client) => client,
|
||||
Err(error) => return (vec![], vec![TuliproxError::Download(error.to_string())]),
|
||||
};
|
||||
@@ -103,7 +103,7 @@ async fn download_plex_media_server_playlist(
|
||||
request_delay_ms: media_server.catalog.request_delay_ms,
|
||||
};
|
||||
|
||||
match refresh_media_server_catalog_complete_before_publish(&plex_client, policy).await {
|
||||
match refresh_media_server_catalog_complete_before_publish(&catalog_client, policy).await {
|
||||
Ok(snapshot) => (media_server_catalog_snapshot_to_playlist(&snapshot), vec![]),
|
||||
Err(error) => (vec![], vec![TuliproxError::Download(error.to_string())]),
|
||||
}
|
||||
@@ -154,3 +154,31 @@ impl<E: EventSink + Clone + 'static, M: MetadataUpdateSink> EpgProvider for Xmlt
|
||||
Ok(guide.map_or(EpgOutcome::NotConfigured, EpgOutcome::Guide))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{download_media_server_playlist, MediaServerProvider};
|
||||
use shared::{error::ErrorKind, model::InputType};
|
||||
use tuliprox_core::model::ConfigInput;
|
||||
use tuliprox_iptv::provider::PlaylistProvider;
|
||||
|
||||
#[tokio::test]
|
||||
async fn media_server_inputs_without_media_server_block_report_download_error() {
|
||||
let client = reqwest::Client::new();
|
||||
for input_type in [InputType::Plex, InputType::Emby, InputType::Jellyfin] {
|
||||
let input = ConfigInput { name: "ms".into(), input_type, media_server: None, ..ConfigInput::default() };
|
||||
|
||||
let (groups, errors) = download_media_server_playlist(&client, &input).await;
|
||||
|
||||
assert!(groups.is_empty());
|
||||
assert_eq!(errors.len(), 1);
|
||||
assert_eq!(errors[0].kind(), ErrorKind::Download, "{input_type:?}");
|
||||
assert!(errors[0].message().contains("media_server"), "{}", errors[0].message());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_server_provider_is_named_media_server() {
|
||||
assert_eq!(MediaServerProvider.name(), "media-server");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user