using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.SRFPlay.Api; using Jellyfin.Plugin.SRFPlay.Services; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.SRFPlay.Providers; /// /// Provides metadata for SRF Play episodes. /// public class SRFEpisodeProvider : IRemoteMetadataProvider { private readonly ILogger _logger; private readonly ILoggerFactory _loggerFactory; private readonly IHttpClientFactory _httpClientFactory; private readonly MetadataCache _metadataCache; /// /// Initializes a new instance of the class. /// /// The logger factory. /// The HTTP client factory. /// The metadata cache. public SRFEpisodeProvider( ILoggerFactory loggerFactory, IHttpClientFactory httpClientFactory, MetadataCache metadataCache) { _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(); _httpClientFactory = httpClientFactory; _metadataCache = metadataCache; } /// public string Name => "SRF Play"; /// public async Task> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken) { var results = new List(); try { // Check if we have a URN to search with if (searchInfo.ProviderIds.TryGetValue("SRF", out var urn) && !string.IsNullOrEmpty(urn)) { _logger.LogDebug("Searching for episode with URN: {Urn}", urn); var config = Plugin.Instance?.Configuration; if (config == null) { return results; } // Try cache first var mediaComposition = _metadataCache.GetMediaComposition(urn, config.CacheDurationMinutes); // If not in cache, fetch from API if (mediaComposition == null) { using var apiClient = new SRFApiClient(_loggerFactory); mediaComposition = await apiClient.GetMediaCompositionByUrnAsync(urn, cancellationToken).ConfigureAwait(false); if (mediaComposition != null) { _metadataCache.SetMediaComposition(urn, mediaComposition); } } if (mediaComposition?.ChapterList != null && mediaComposition.ChapterList.Count > 0) { var chapter = mediaComposition.ChapterList[0]; results.Add(new RemoteSearchResult { Name = chapter.Title, Overview = chapter.Description ?? chapter.Lead, ImageUrl = chapter.ImageUrl, SearchProviderName = Name, ProviderIds = new Dictionary { { "SRF", chapter.Urn } } }); } } } catch (Exception ex) { _logger.LogError(ex, "Error searching for episode: {Name}", searchInfo.Name); } return results; } /// public async Task> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken) { var result = new MetadataResult(); try { // Check if we have a URN if (!info.ProviderIds.TryGetValue("SRF", out var urn) || string.IsNullOrEmpty(urn)) { _logger.LogDebug("No SRF URN found for episode: {Name}", info.Name); return result; } _logger.LogDebug("Fetching metadata for episode URN: {Urn}", urn); var config = Plugin.Instance?.Configuration; if (config == null) { return result; } // Try cache first var mediaComposition = _metadataCache.GetMediaComposition(urn, config.CacheDurationMinutes); // If not in cache, fetch from API if (mediaComposition == null) { using var apiClient = new SRFApiClient(_loggerFactory); mediaComposition = await apiClient.GetMediaCompositionByUrnAsync(urn, cancellationToken).ConfigureAwait(false); if (mediaComposition != null) { _metadataCache.SetMediaComposition(urn, mediaComposition); } } if (mediaComposition?.ChapterList == null || mediaComposition.ChapterList.Count == 0) { _logger.LogWarning("No chapter information found for URN: {Urn}", urn); return result; } // Get the first chapter (main video) var chapter = mediaComposition.ChapterList[0]; result.Item = new Episode { Name = chapter.Title, Overview = chapter.Description ?? chapter.Lead, ProviderIds = new Dictionary { { "SRF", chapter.Urn } } }; // Set episode and season numbers if available if (chapter.EpisodeNumber.HasValue) { result.Item.IndexNumber = chapter.EpisodeNumber; } if (chapter.SeasonNumber.HasValue) { result.Item.ParentIndexNumber = chapter.SeasonNumber; } // Set premiere date if available if (chapter.Date.HasValue) { result.Item.PremiereDate = chapter.Date; } // Set runtime (convert from milliseconds to ticks) if (chapter.Duration > 0) { result.Item.RunTimeTicks = TimeSpan.FromMilliseconds(chapter.Duration).Ticks; } // Set series information if available if (mediaComposition.Show != null) { result.Item.SeriesName = mediaComposition.Show.Title; // Set series provider ID on the episode if (!string.IsNullOrEmpty(mediaComposition.Show.Urn)) { result.Item.SetProviderId("SRF_Series", mediaComposition.Show.Urn); } } result.HasMetadata = true; _logger.LogDebug("Successfully fetched metadata for episode: {Title}", chapter.Title); } catch (Exception ex) { _logger.LogError(ex, "Error fetching metadata for episode: {Name}", info.Name); } return result; } /// public Task GetImageResponse(string url, CancellationToken cancellationToken) { throw new NotImplementedException("Image handling is done by SRFImageProvider"); } }