206 lines
7.9 KiB
C#
206 lines
7.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Headers;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
|
using Jellyfin.Plugin.SRFPlay.Utilities;
|
|
using MediaBrowser.Common.Net;
|
|
using MediaBrowser.Controller.Entities;
|
|
using MediaBrowser.Controller.Providers;
|
|
using MediaBrowser.Model.Entities;
|
|
using MediaBrowser.Model.Providers;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.SRFPlay.Providers;
|
|
|
|
/// <summary>
|
|
/// Provides images for SRF Play content.
|
|
/// </summary>
|
|
public class SRFImageProvider : IRemoteImageProvider, IHasOrder
|
|
{
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
|
private readonly ILogger<SRFImageProvider> _logger;
|
|
private readonly IMediaCompositionFetcher _compositionFetcher;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SRFImageProvider"/> class.
|
|
/// </summary>
|
|
/// <param name="httpClientFactory">The HTTP client factory.</param>
|
|
/// <param name="loggerFactory">The logger factory.</param>
|
|
/// <param name="compositionFetcher">The media composition fetcher.</param>
|
|
public SRFImageProvider(
|
|
IHttpClientFactory httpClientFactory,
|
|
ILoggerFactory loggerFactory,
|
|
IMediaCompositionFetcher compositionFetcher)
|
|
{
|
|
_httpClientFactory = httpClientFactory;
|
|
_logger = loggerFactory.CreateLogger<SRFImageProvider>();
|
|
_compositionFetcher = compositionFetcher;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public string Name => "SRF Play";
|
|
|
|
/// <inheritdoc />
|
|
public int Order => 0;
|
|
|
|
/// <inheritdoc />
|
|
public bool Supports(BaseItem item)
|
|
{
|
|
// Support movies and episodes for now
|
|
return item is MediaBrowser.Controller.Entities.Movies.Movie ||
|
|
item is MediaBrowser.Controller.Entities.TV.Episode ||
|
|
item is MediaBrowser.Controller.Entities.TV.Series;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
|
{
|
|
return new List<ImageType>
|
|
{
|
|
ImageType.Primary,
|
|
ImageType.Backdrop,
|
|
ImageType.Thumb
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
|
{
|
|
var list = new List<RemoteImageInfo>();
|
|
|
|
try
|
|
{
|
|
// Check if item has SRF URN in provider IDs
|
|
if (!item.ProviderIds.TryGetValue("SRF", out var urn) || string.IsNullOrEmpty(urn))
|
|
{
|
|
_logger.LogDebug("No SRF URN found for item: {ItemName}", item.Name);
|
|
return list;
|
|
}
|
|
|
|
_logger.LogDebug("Fetching images for SRF URN: {Urn}", urn);
|
|
|
|
// Fetch media composition to get image URLs
|
|
var mediaComposition = await _compositionFetcher.GetMediaCompositionAsync(urn, cancellationToken).ConfigureAwait(false);
|
|
|
|
if (mediaComposition == null)
|
|
{
|
|
_logger.LogWarning("Failed to fetch media composition for URN: {Urn}", urn);
|
|
return list;
|
|
}
|
|
|
|
// Extract images from chapters
|
|
if (mediaComposition.ChapterList != null && mediaComposition.ChapterList.Count > 0)
|
|
{
|
|
var chapter = mediaComposition.ChapterList[0];
|
|
if (!string.IsNullOrEmpty(chapter.ImageUrl))
|
|
{
|
|
_logger.LogDebug("URN {Urn}: Adding chapter image: {ImageUrl}", urn, chapter.ImageUrl);
|
|
list.Add(new RemoteImageInfo
|
|
{
|
|
Url = chapter.ImageUrl,
|
|
Type = ImageType.Primary,
|
|
ProviderName = Name
|
|
});
|
|
|
|
list.Add(new RemoteImageInfo
|
|
{
|
|
Url = chapter.ImageUrl,
|
|
Type = ImageType.Thumb,
|
|
ProviderName = Name
|
|
});
|
|
}
|
|
else
|
|
{
|
|
_logger.LogDebug("URN {Urn}: No chapter image available", urn);
|
|
}
|
|
}
|
|
|
|
// Extract images from show
|
|
if (mediaComposition.Show != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(mediaComposition.Show.ImageUrl))
|
|
{
|
|
_logger.LogDebug("URN {Urn}: Adding show image: {ImageUrl}", urn, mediaComposition.Show.ImageUrl);
|
|
list.Add(new RemoteImageInfo
|
|
{
|
|
Url = mediaComposition.Show.ImageUrl,
|
|
Type = ImageType.Primary,
|
|
ProviderName = Name
|
|
});
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(mediaComposition.Show.BannerImageUrl))
|
|
{
|
|
_logger.LogDebug("URN {Urn}: Adding show banner: {BannerUrl}", urn, mediaComposition.Show.BannerImageUrl);
|
|
list.Add(new RemoteImageInfo
|
|
{
|
|
Url = mediaComposition.Show.BannerImageUrl,
|
|
Type = ImageType.Backdrop,
|
|
ProviderName = Name
|
|
});
|
|
}
|
|
}
|
|
|
|
_logger.LogInformation("URN {Urn}: Found {Count} images for '{ItemName}'", urn, list.Count, item.Name);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching images for item: {ItemName}", item.Name);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogDebug("Fetching image from URL: {Url}", url);
|
|
|
|
var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
|
|
|
|
// Create request with proper headers - SRF CDN requires User-Agent
|
|
var request = new HttpRequestMessage(HttpMethod.Get, new Uri(url));
|
|
request.Headers.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
request.Headers.Accept.ParseAdd("image/jpeg, image/png, image/webp, image/*;q=0.8");
|
|
|
|
var response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
|
|
|
var originalContentType = response.Content.Headers.ContentType?.MediaType;
|
|
_logger.LogDebug(
|
|
"Image response status: {StatusCode}, Content-Type: {ContentType}, Content-Length: {Length}",
|
|
response.StatusCode,
|
|
originalContentType ?? "null",
|
|
response.Content.Headers.ContentLength);
|
|
|
|
// Fix Content-Type if it's not a proper image type - SRF CDN often returns wrong content type
|
|
// Jellyfin needs correct Content-Type to process images
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var needsContentTypeFix = string.IsNullOrEmpty(originalContentType) ||
|
|
originalContentType == "binary/octet-stream" ||
|
|
originalContentType == "application/octet-stream" ||
|
|
!originalContentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase);
|
|
|
|
if (needsContentTypeFix)
|
|
{
|
|
// Determine correct content type from URL extension or default to JPEG
|
|
var contentType = MimeTypeHelper.GetImageContentType(url);
|
|
if (!string.IsNullOrEmpty(contentType))
|
|
{
|
|
_logger.LogInformation(
|
|
"Fixing Content-Type from '{OriginalType}' to '{NewType}' for {Url}",
|
|
originalContentType ?? "null",
|
|
contentType,
|
|
url);
|
|
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
|
|
}
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
}
|