From 39ca27cb3d64d46f294b6c702ec1656d4289608c Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 31 Jul 2024 17:04:42 +0200 Subject: [PATCH] Overlay Generated Channel Logo when active but no artwork is found (#1848) --- ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs | 41 +++++++++++++------- ErsatzTV.Core/Images/ChannelLogoGenerator.cs | 7 ++++ ErsatzTV/Controllers/ArtworkController.cs | 18 ++++----- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs index d540c1dd0..48c7535ce 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs @@ -1,9 +1,11 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Text; +using System.Text.Encodings.Web; using Bugsnag; using CliWrap; using CliWrap.Buffered; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Images; using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.FFmpeg.State; @@ -188,10 +190,15 @@ public class FFmpegProcessService None, await IsAnimated(ffprobePath, customPath)); case ChannelWatermarkImageSource.ChannelLogo: - Option maybeChannelPath = channel.Artwork - .Filter(a => a.ArtworkKind == ArtworkKind.Logo) - .HeadOrNone() - .Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + Option maybeChannelPath = (channel.Artwork.Count == 0) ? + //We have to generate the logo on the fly and save it to a local temp path + ChannelLogoGenerator.GenerateChannelLogoUrl(channel) : + //We have an artwork attached to the channel, let's use it :) + channel.Artwork + .Filter(a => a.ArtworkKind == ArtworkKind.Logo) + .HeadOrNone() + .Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + return new WatermarkOptions( await watermarkOverride.IfNoneAsync(watermark), maybeChannelPath, @@ -220,10 +227,14 @@ public class FFmpegProcessService None, await IsAnimated(ffprobePath, customPath)); case ChannelWatermarkImageSource.ChannelLogo: - Option maybeChannelPath = channel.Artwork - .Filter(a => a.ArtworkKind == ArtworkKind.Logo) - .HeadOrNone() - .Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + Option maybeChannelPath = (channel.Artwork.Count == 0) ? + //We have to generate the logo on the fly and save it to a local temp path + ChannelLogoGenerator.GenerateChannelLogoUrl(channel) : + //We have an artwork attached to the channel, let's use it :) + channel.Artwork + .Filter(a => a.ArtworkKind == ArtworkKind.Logo) + .HeadOrNone() + .Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); return new WatermarkOptions( await watermarkOverride.IfNoneAsync(channel.Watermark), maybeChannelPath, @@ -252,10 +263,14 @@ public class FFmpegProcessService None, await IsAnimated(ffprobePath, customPath)); case ChannelWatermarkImageSource.ChannelLogo: - Option maybeChannelPath = channel.Artwork - .Filter(a => a.ArtworkKind == ArtworkKind.Logo) - .HeadOrNone() - .Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + Option maybeChannelPath = (channel.Artwork.Count == 0) ? + //We have to generate the logo on the fly and save it to a local temp path + ChannelLogoGenerator.GenerateChannelLogoUrl(channel) : + //We have an artwork attached to the channel, let's use it :) + channel.Artwork + .Filter(a => a.ArtworkKind == ArtworkKind.Logo) + .HeadOrNone() + .Map(a => _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); return new WatermarkOptions( await watermarkOverride.IfNoneAsync(watermark), maybeChannelPath, diff --git a/ErsatzTV.Core/Images/ChannelLogoGenerator.cs b/ErsatzTV.Core/Images/ChannelLogoGenerator.cs index 7dfa32c74..283ca3feb 100644 --- a/ErsatzTV.Core/Images/ChannelLogoGenerator.cs +++ b/ErsatzTV.Core/Images/ChannelLogoGenerator.cs @@ -1,3 +1,4 @@ +using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Images; using Microsoft.Extensions.Logging; using SkiaSharp; @@ -6,6 +7,9 @@ namespace ErsatzTV.Core.Images; public class ChannelLogoGenerator : IChannelLogoGenerator { + public const string GetRoute = "/iptv/logos/gen"; + public const string GetRouteQueryParamName = "text"; + private readonly ILogger _logger; public ChannelLogoGenerator( @@ -14,6 +18,9 @@ public class ChannelLogoGenerator : IChannelLogoGenerator _logger = logger; } + public static Option GenerateChannelLogoUrl(Channel channel) => + $"http://localhost:{Settings.ListenPort}{GetRoute}?{GetRouteQueryParamName}={channel.WebEncodedName}"; + public Either GenerateChannelLogo( string text, int logoHeight, diff --git a/ErsatzTV/Controllers/ArtworkController.cs b/ErsatzTV/Controllers/ArtworkController.cs index ce7113a61..a377f382a 100644 --- a/ErsatzTV/Controllers/ArtworkController.cs +++ b/ErsatzTV/Controllers/ArtworkController.cs @@ -6,6 +6,7 @@ using ErsatzTV.Application.Plex; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Emby; +using ErsatzTV.Core.Images; using ErsatzTV.Core.Interfaces.Images; using ErsatzTV.Core.Jellyfin; using Flurl; @@ -260,14 +261,13 @@ public class ArtworkController : ControllerBase #endif } - [HttpGet("/iptv/logos/gen")] + [HttpGet(ChannelLogoGenerator.GetRoute)] public IActionResult GenerateChannelLogo( - string text, - CancellationToken cancellationToken) - { - return _channelLogoGenerator.GenerateChannelLogo(text, 100, 200, cancellationToken).Match( - Left: _ => new RedirectResult("/iptv/images/ersatztv-500.png"), - Right: img => File(img, "image/png") - ); - } + string text, // param name = ChannelLogoGenerator.GetRouteQueryParamName + CancellationToken cancellationToken) => + _channelLogoGenerator + .GenerateChannelLogo(text, 100, 200, cancellationToken).Match( + Left: _ => new RedirectResult("/iptv/images/ersatztv-500.png"), + Right: img => File(img, "image/png") + ); }