Remove 9 dead methods, 6 unused constants, and redundant ReaderWriterLockSlim from MetadataCache. Consolidate repeated patterns into HasChapters, IsPlayable, and ToLowerString helpers. Extract shared API methods in SRFApiClient. Move variant manifest rewriting from controller to StreamProxyService. Make Auto quality distinct from HD. Update README architecture section.
167 lines
5.6 KiB
C#
167 lines
5.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Provides metadata for SRF Play episodes.
|
|
/// </summary>
|
|
public class SRFEpisodeProvider : IRemoteMetadataProvider<Episode, EpisodeInfo>
|
|
{
|
|
private readonly ILogger<SRFEpisodeProvider> _logger;
|
|
private readonly IMediaCompositionFetcher _compositionFetcher;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SRFEpisodeProvider"/> class.
|
|
/// </summary>
|
|
/// <param name="logger">The logger.</param>
|
|
/// <param name="compositionFetcher">The media composition fetcher.</param>
|
|
public SRFEpisodeProvider(
|
|
ILogger<SRFEpisodeProvider> logger,
|
|
IMediaCompositionFetcher compositionFetcher)
|
|
{
|
|
_logger = logger;
|
|
_compositionFetcher = compositionFetcher;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "SRF Play";
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
|
{
|
|
var results = new List<RemoteSearchResult>();
|
|
|
|
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 mediaComposition = await _compositionFetcher.GetMediaCompositionAsync(urn, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (mediaComposition?.HasChapters == true)
|
|
{
|
|
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<string, string>
|
|
{
|
|
{ "SRF", chapter.Urn }
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error searching for episode: {Name}", searchInfo.Name);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<MetadataResult<Episode>> GetMetadata(EpisodeInfo info, CancellationToken cancellationToken)
|
|
{
|
|
var result = new MetadataResult<Episode>();
|
|
|
|
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 mediaComposition = await _compositionFetcher.GetMediaCompositionAsync(urn, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (mediaComposition?.HasChapters != true)
|
|
{
|
|
_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<string, string>
|
|
{
|
|
{ "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;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
|
{
|
|
throw new NotImplementedException("Image handling is done by SRFImageProvider");
|
|
}
|
|
}
|