From e218ff9a6d8dcf9111add30fe9ccfe019729e8f5 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Wed, 15 Sep 2021 05:12:23 -0500 Subject: [PATCH] fix watermark when no video filters are required (#367) --- CHANGELOG.md | 1 + .../FFmpeg/FFmpegComplexFilterBuilderTests.cs | 156 ++++++++++++++++++ .../FFmpeg/FFmpegComplexFilterBuilder.cs | 25 ++- 3 files changed, 175 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cffa8c558..f5e7501fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix loading artwork in Kodi - Use fake image extension (`.jpg`) for artwork in M3U and XMLTV since Kodi detects MIME type from URL - Enable HEAD requests for IPTV image paths since Kodi requires those +- Properly display watermark when no other video filters (like scaling or padding) are required ## [0.0.56-alpha] - 2021-09-10 ### Added diff --git a/ErsatzTV.Core.Tests/FFmpeg/FFmpegComplexFilterBuilderTests.cs b/ErsatzTV.Core.Tests/FFmpeg/FFmpegComplexFilterBuilderTests.cs index 0fa7431c4..7c250b6e3 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/FFmpegComplexFilterBuilderTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/FFmpegComplexFilterBuilderTests.cs @@ -4,6 +4,7 @@ using ErsatzTV.Core.FFmpeg; using FluentAssertions; using LanguageExt; using NUnit.Framework; +using static LanguageExt.Prelude; namespace ErsatzTV.Core.Tests.FFmpeg { @@ -113,6 +114,161 @@ namespace ErsatzTV.Core.Tests.FFmpeg }); } + [Test] + [TestCase( + false, + false, + false, + ChannelWatermarkLocation.BottomLeft, + false, + 100, + "[0:0][1:v]overlay=x=134:y=H-h-54[v]", + "0:1", + "[v]")] + [TestCase( + false, + false, + false, + ChannelWatermarkLocation.BottomRight, + false, + 100, + "[0:0][1:v]overlay=x=W-w-134:y=H-h-54[v]", + "0:1", + "[v]")] + [TestCase( + false, + false, + false, + ChannelWatermarkLocation.TopLeft, + false, + 100, + "[0:0][1:v]overlay=x=134:y=54[v]", + "0:1", + "[v]")] + [TestCase( + false, + false, + false, + ChannelWatermarkLocation.TopRight, + false, + 100, + "[0:0][1:v]overlay=x=W-w-134:y=54[v]", + "0:1", + "[v]")] + [TestCase( + false, + false, + true, + ChannelWatermarkLocation.TopLeft, + false, + 100, + "[0:0][1:v]overlay=x=134:y=54:enable='lt(mod(mod(time(0),60*60),10*60),15)'[v]", + "0:1", + "[v]")] + [TestCase( + false, + false, + false, + ChannelWatermarkLocation.TopLeft, + true, + 100, + "[1:v]scale=384:-1[wmp];[0:0][wmp]overlay=x=134:y=54[v]", + "0:1", + "[v]")] + [TestCase( + false, + false, + false, + ChannelWatermarkLocation.TopLeft, + false, + 90, + "[1:v]format=yuva420p|yuva444p|yuva422p|rgba|abgr|bgra|gbrap|ya8,colorchannelmixer=aa=0.90[wmp];[0:0][wmp]overlay=x=134:y=54[v]", + "0:1", + "[v]")] + [TestCase( + false, + true, + false, + ChannelWatermarkLocation.TopLeft, + false, + 100, + "[0:0]yadif=1[vt];[vt][1:v]overlay=x=134:y=54[v]", + "0:1", + "[v]")] + [TestCase( + false, + true, + false, + ChannelWatermarkLocation.TopLeft, + true, + 100, + "[0:0]yadif=1[vt];[1:v]scale=384:-1[wmp];[vt][wmp]overlay=x=134:y=54[v]", + "0:1", + "[v]")] + [TestCase( + true, + true, + false, + ChannelWatermarkLocation.TopLeft, + false, + 100, + "[0:1]apad=whole_dur=3300000ms[a];[0:0]yadif=1[vt];[vt][1:v]overlay=x=134:y=54[v]", + "[a]", + "[v]")] + [TestCase( + true, + false, + false, + ChannelWatermarkLocation.TopLeft, + false, + 100, + "[0:1]apad=whole_dur=3300000ms[a];[0:0][1:v]overlay=x=134:y=54[v]", + "[a]", + "[v]")] + public void Should_Return_Watermark( + bool alignAudio, + bool deinterlace, + bool intermittent, + ChannelWatermarkLocation location, + bool scaled, + int opacity, + string expectedVideoFilter, + string expectedAudioLabel, + string expectedVideoLabel) + { + FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() + .WithWatermark( + Some( + new ChannelWatermark + { + Mode = intermittent + ? ChannelWatermarkMode.Intermittent + : ChannelWatermarkMode.Permanent, + DurationSeconds = intermittent ? 15 : 0, + FrequencyMinutes = intermittent ? 10 : 0, + Location = location, + Size = scaled ? ChannelWatermarkSize.Scaled : ChannelWatermarkSize.ActualSize, + WidthPercent = scaled ? 20 : 0, + Opacity = opacity, + HorizontalMarginPercent = 7, + VerticalMarginPercent = 5 + }), + new Resolution { Width = 1920, Height = 1080 }) + .WithDeinterlace(deinterlace) + .WithAlignedAudio(alignAudio ? Some(TimeSpan.FromMinutes(55)) : None); + + Option result = builder.Build(0, 1); + + result.IsSome.Should().BeTrue(); + result.IfSome( + filter => + { + filter.ComplexFilter.Should().Be(expectedVideoFilter); + filter.AudioLabel.Should().Be(expectedAudioLabel); + filter.VideoLabel.Should().Be(expectedVideoLabel); + }); + } + [Test] [TestCase(true, false, false, "[0:0]deinterlace_qsv[v]", "[v]")] [TestCase( diff --git a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs index a7c6004f2..68dd0665f 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs @@ -233,28 +233,39 @@ namespace ErsatzTV.Core.FFmpeg complexFilter.Append(audioLabel); } - if (videoFilterQueue.Any()) + if (videoFilterQueue.Any() || !string.IsNullOrWhiteSpace(watermarkOverlay)) { if (hasAudioFilters) { complexFilter.Append(';'); } - complexFilter.Append($"[{videoLabel}]"); - var filters = string.Join(",", videoFilterQueue); - complexFilter.Append(filters); + if (videoFilterQueue.Any()) + { + complexFilter.Append($"[{videoLabel}]"); + var filters = string.Join(",", videoFilterQueue); + complexFilter.Append(filters); + } if (!string.IsNullOrWhiteSpace(watermarkOverlay)) { - complexFilter.Append("[vt]"); + if (videoFilterQueue.Any()) + { + complexFilter.Append("[vt];"); + } + var watermarkLabel = "[1:v]"; if (!string.IsNullOrWhiteSpace(watermarkPreprocess)) { - complexFilter.Append($";{watermarkLabel}{watermarkPreprocess}[wmp]"); + complexFilter.Append($"{watermarkLabel}{watermarkPreprocess}[wmp];"); watermarkLabel = "[wmp]"; } - complexFilter.Append($";[vt]{watermarkLabel}{watermarkOverlay}"); + complexFilter.Append( + videoFilterQueue.Any() + ? $"[vt]{watermarkLabel}{watermarkOverlay}" + : $"[{videoLabel}]{watermarkLabel}{watermarkOverlay}"); + if (usesSoftwareFilters && acceleration != HardwareAccelerationKind.None) { complexFilter.Append(",hwupload");