diff --git a/CHANGELOG.md b/CHANGELOG.md index f0427550a..48e9ded1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Fixed +- Fix regression from `v26.2.0` that caused channel logo watermarks to be ignored when the logo is a url + - This affected external logo urls and generated channel logos ## [26.7.0] - 2026-07-27 ### Added diff --git a/ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorChannelLogoTests.cs b/ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorChannelLogoTests.cs new file mode 100644 index 000000000..0804fa081 --- /dev/null +++ b/ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorChannelLogoTests.cs @@ -0,0 +1,130 @@ +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.FFmpeg; +using ErsatzTV.Core.Interfaces.Images; +using ErsatzTV.Core.Scheduling; +using Microsoft.Extensions.Logging.Abstractions; +using NSubstitute; +using NUnit.Framework; +using Shouldly; +using Testably.Abstractions.Testing; + +namespace ErsatzTV.Core.Tests.FFmpeg; + +[TestFixture] +public class WatermarkSelectorChannelLogoTests +{ + private const string CachedLogoPath = "/tmp/logo"; + + private static WatermarkSelector Selector() + { + var mockFileSystem = new MockFileSystem(); + mockFileSystem.Initialize().WithFile(CachedLogoPath); + + var fakeImageCache = Substitute.For(); + fakeImageCache.GetPathForImage(Arg.Any(), Arg.Is(ArtworkKind.Logo), Arg.Any>()) + .Returns(_ => CachedLogoPath); + + return new WatermarkSelector( + mockFileSystem, + fakeImageCache, + new DecoSelector(), + NullLogger.Instance); + } + + private static Channel ChannelWithLogo(params Artwork[] artwork) => + new(Guid.Empty) + { + Id = 0, + Name = "Test Channel", + StreamingMode = StreamingMode.TransportStream, + Artwork = [..artwork], + Watermark = new ChannelWatermark + { + Id = 1, + Name = "Channel", + ImageSource = ChannelWatermarkImageSource.ChannelLogo + } + }; + + [Test] + public void Should_Use_External_Url_Channel_Logo() + { + const string ExternalUrl = "https://example.com/images/logo.png"; + + Channel channel = ChannelWithLogo(new Artwork { ArtworkKind = ArtworkKind.Logo, Path = ExternalUrl }); + + Option result = Selector().GetWatermarkOptions( + channel, + Option.None, + Option.None, + true); + + result.IsSome.ShouldBeTrue(); + foreach (WatermarkOptions options in result) + { + options.ImagePath.ShouldBe(ExternalUrl); + } + } + + [Test] + public void Should_Use_Generated_Channel_Logo_Url_When_No_Logo_Artwork() + { + Channel channel = ChannelWithLogo(); + + Option result = Selector().GetWatermarkOptions( + channel, + Option.None, + Option.None, + true); + + result.IsSome.ShouldBeTrue(); + foreach (WatermarkOptions options in result) + { + options.ImagePath.ShouldContain("/iptv/logos/gen"); + } + } + + [Test] + public void Should_Use_Cached_Channel_Logo() + { + Channel channel = ChannelWithLogo(new Artwork { ArtworkKind = ArtworkKind.Logo, Path = "logo.png" }); + + Option result = Selector().GetWatermarkOptions( + channel, + Option.None, + Option.None, + true); + + result.IsSome.ShouldBeTrue(); + foreach (WatermarkOptions options in result) + { + options.ImagePath.ShouldBe(CachedLogoPath); + } + } + + [Test] + public void Should_Ignore_Missing_Cached_Channel_Logo() + { + var mockFileSystem = new MockFileSystem(); + + var fakeImageCache = Substitute.For(); + fakeImageCache.GetPathForImage(Arg.Any(), Arg.Is(ArtworkKind.Logo), Arg.Any>()) + .Returns(_ => "/tmp/missing-logo"); + + var selector = new WatermarkSelector( + mockFileSystem, + fakeImageCache, + new DecoSelector(), + NullLogger.Instance); + + Channel channel = ChannelWithLogo(new Artwork { ArtworkKind = ArtworkKind.Logo, Path = "logo.png" }); + + Option result = selector.GetWatermarkOptions( + channel, + Option.None, + Option.None, + true); + + result.IsNone.ShouldBeTrue(); + } +} diff --git a/ErsatzTV.Core/FFmpeg/WatermarkSelector.cs b/ErsatzTV.Core/FFmpeg/WatermarkSelector.cs index c777cf325..09eb19c21 100644 --- a/ErsatzTV.Core/FFmpeg/WatermarkSelector.cs +++ b/ErsatzTV.Core/FFmpeg/WatermarkSelector.cs @@ -238,7 +238,8 @@ public class WatermarkSelector( : imageCache.GetPathForImage(logoArtwork.Path, ArtworkKind.Logo, Option.None); } - if (fileSystem.File.Exists(channelPath)) + // urls (external logos, generated channel logos) can't be checked on the file system + if (Artwork.IsExternalUrl(channelPath) || fileSystem.File.Exists(channelPath)) { return new WatermarkOptions(watermark, channelPath, None); } @@ -287,7 +288,8 @@ public class WatermarkSelector( : imageCache.GetPathForImage(logoArtwork.Path, ArtworkKind.Logo, Option.None); } - if (fileSystem.File.Exists(channelPath)) + // urls (external logos, generated channel logos) can't be checked on the file system + if (Artwork.IsExternalUrl(channelPath) || fileSystem.File.Exists(channelPath)) { return new WatermarkOptions(channel.Watermark, channelPath, None); } @@ -336,7 +338,8 @@ public class WatermarkSelector( : imageCache.GetPathForImage(logoArtwork.Path, ArtworkKind.Logo, Option.None); } - if (fileSystem.File.Exists(channelPath)) + // urls (external logos, generated channel logos) can't be checked on the file system + if (Artwork.IsExternalUrl(channelPath) || fileSystem.File.Exists(channelPath)) { return new WatermarkOptions(watermark, channelPath, None); }