diff --git a/CHANGELOG.md b/CHANGELOG.md index b040c44d2..d6af852a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] ### Fixed - Fix song sorting with `Chronological` and `Shuffle In Order` playback orders +- Fix watermark on scaled and/or padded video with NVIDIA acceleration +- Fix playback of interlaced mpeg2video content with NVIDIA acceleration ## [0.4.2-alpha] - 2022-02-26 ### Fixed diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj index 1ffa8117e..ad152d802 100644 --- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj +++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj @@ -29,4 +29,10 @@ + + + Always + + + diff --git a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs index 6949f6adf..3edbff92f 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs @@ -47,8 +47,25 @@ namespace ErsatzTV.Core.Tests.FFmpeg WithPadding } + public enum Watermark + { + None, + PermanentOpaque, + PermanentTransparent, + IntermittentOpaque, + IntermittentTransparent + // TODO: animated vs static + } + private class TestData { + public static Watermark[] Watermarks = + { + Watermark.None, + Watermark.PermanentOpaque, + Watermark.PermanentTransparent + }; + public static Padding[] Paddings = { Padding.NoPadding, @@ -160,6 +177,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg Padding padding, [ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))] VideoScanKind videoScanKind, + [ValueSource(typeof(TestData), nameof(TestData.Watermarks))] + Watermark watermark, // [ValueSource(typeof(TestData), nameof(TestData.SoftwareCodecs))] string profileCodec, // [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration) [ValueSource(typeof(TestData), nameof(TestData.NvidiaCodecs))] string profileCodec, @@ -209,10 +228,20 @@ namespace ErsatzTV.Core.Tests.FFmpeg p1.ExitCode.Should().Be(0); } + var imageCache = new Mock(); + + // always return the static watermark resource + imageCache.Setup( + ic => ic.GetPathForImage( + It.IsAny(), + It.Is(x => x == ArtworkKind.Watermark), + It.IsAny>())) + .Returns(Path.Combine(TestContext.CurrentContext.TestDirectory, "Resources", "ErsatzTV.png")); + var oldService = new FFmpegProcessService( new FFmpegPlaybackSettingsCalculator(), new FakeStreamSelector(), - new Mock().Object, + imageCache.Object, new Mock().Object, new Mock>().Object); @@ -262,6 +291,51 @@ namespace ErsatzTV.Core.Tests.FFmpeg DateTimeOffset now = DateTimeOffset.Now; + Option channelWatermark = Option.None; + switch (watermark) + { + case Watermark.None: + break; + case Watermark.IntermittentOpaque: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Intermittent, + // TODO: how do we make sure this actually appears + FrequencyMinutes = 1, + DurationSeconds = 2, + Opacity = 100 + }; + break; + case Watermark.IntermittentTransparent: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Intermittent, + // TODO: how do we make sure this actually appears + FrequencyMinutes = 1, + DurationSeconds = 2, + Opacity = 80 + }; + break; + case Watermark.PermanentOpaque: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Permanent, + Opacity = 100 + }; + break; + case Watermark.PermanentTransparent: + channelWatermark = new ChannelWatermark + { + ImageSource = ChannelWatermarkImageSource.Custom, + Mode = ChannelWatermarkMode.Permanent, + Opacity = 80 + }; + break; + } + Process process = await service.ForPlayoutItem( "ffmpeg", false, @@ -283,7 +357,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg now, now + TimeSpan.FromSeconds(5), now, - None, + channelWatermark, VaapiDriver.Default, "/dev/dri/renderD128", false, diff --git a/ErsatzTV.Core.Tests/Resources/ErsatzTV.png b/ErsatzTV.Core.Tests/Resources/ErsatzTV.png new file mode 100644 index 000000000..b053a5a66 Binary files /dev/null and b/ErsatzTV.Core.Tests/Resources/ErsatzTV.png differ diff --git a/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs b/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs index 4a17ed01d..d292f2b89 100644 --- a/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs +++ b/ErsatzTV.FFmpeg/Decoder/AvailableDecoders.cs @@ -8,7 +8,11 @@ namespace ErsatzTV.FFmpeg.Decoder; public static class AvailableDecoders { - public static Option ForVideoFormat(FFmpegState ffmpegState, FrameState currentState, ILogger logger) + public static Option ForVideoFormat( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState, + ILogger logger) { return (ffmpegState.HardwareAccelerationMode, currentState.VideoFormat, currentState.PixelFormat.Match(pf => pf.Name, () => string.Empty)) switch @@ -20,7 +24,8 @@ public static class AvailableDecoders => new DecoderH264(), (HardwareAccelerationMode.Nvenc, VideoFormat.H264, _) => new DecoderH264Cuvid(), - (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid(), + (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg2Video, _) => new DecoderMpeg2Cuvid( + desiredState.Deinterlaced), (HardwareAccelerationMode.Nvenc, VideoFormat.Vc1, _) => new DecoderVc1Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Vp9, _) => new DecoderVp9Cuvid(), (HardwareAccelerationMode.Nvenc, VideoFormat.Mpeg4, _) => new DecoderMpeg4Cuvid(), diff --git a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs index fa9e209c6..980eff9e1 100644 --- a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs +++ b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderMpeg2Cuvid.cs @@ -2,6 +2,13 @@ public class DecoderMpeg2Cuvid : DecoderBase { + private readonly bool _contentIsInterlaced; + + public DecoderMpeg2Cuvid(bool contentIsInterlaced) + { + _contentIsInterlaced = contentIsInterlaced; + } + public override string Name => "mpeg2_cuvid"; public override IList InputOptions(InputFile inputFile) { @@ -13,5 +20,6 @@ public class DecoderMpeg2Cuvid : DecoderBase return result; } - protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; + protected override FrameDataLocation OutputFrameDataLocation => + _contentIsInterlaced ? FrameDataLocation.Software : FrameDataLocation.Hardware; } diff --git a/ErsatzTV.FFmpeg/PipelineBuilder.cs b/ErsatzTV.FFmpeg/PipelineBuilder.cs index 9b6984a87..a86acf19a 100644 --- a/ErsatzTV.FFmpeg/PipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/PipelineBuilder.cs @@ -180,7 +180,7 @@ public class PipelineBuilder _pipelineSteps.Add(step); } - foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(ffmpegState, currentState, _logger)) + foreach (IDecoder decoder in AvailableDecoders.ForVideoFormat(ffmpegState, currentState, desiredState, _logger)) { foreach (VideoInputFile videoInputFile in _videoInputFile) { @@ -327,7 +327,8 @@ public class PipelineBuilder currentState = sarStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(sarStep)); } - else if (_watermarkInputFile.IsSome && currentState.PixelFormat != desiredState.PixelFormat) + + if (_watermarkInputFile.IsSome && currentState.PixelFormat != desiredState.PixelFormat) { // this should only happen with nvenc? // use scale filter to fix pixel format