From 357dfee050c0d5959a632658de23c0d091c30075 Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sun, 18 Sep 2022 11:04:02 -0500 Subject: [PATCH] nvidia and software mode scaling improvements (#965) * convert to square pixels before software scaling * convert to square pixels in nvidia scale filter * more scaling fixes; position watermark within padded content * fix image subtitle scaling * fix qsv scaling * update dependencies --- CHANGELOG.md | 2 + .../ErsatzTV.Core.Tests.csproj | 4 +- ErsatzTV.Core/ErsatzTV.Core.csproj | 4 +- .../FFmpeg/FFmpegLibraryProcessService.cs | 13 ++- ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs | 20 ++-- ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj | 2 +- .../Filter/AvailableScaleFilters.cs | 18 +++- .../Filter/AvailableSubtitleOverlayFilters.cs | 4 +- .../AvailableWatermarkOverlayFilters.cs | 14 ++- ErsatzTV.FFmpeg/Filter/ComplexFilter.cs | 98 ++++++++++--------- .../Filter/Cuda/OverlaySubtitleCudaFilter.cs | 2 +- .../Filter/Cuda/OverlayWatermarkCudaFilter.cs | 11 ++- .../Filter/Cuda/ScaleCudaFilter.cs | 33 ++++++- .../Filter/Cuda/SubtitleScaleNppFilter.cs | 2 +- .../Filter/OverlaySubtitleFilter.cs | 2 +- .../Filter/OverlayWatermarkFilter.cs | 29 +++++- .../Filter/Qsv/OverlaySubtitleQsvFilter.cs | 6 +- .../Filter/Qsv/OverlayWatermarkQsvFilter.cs | 11 ++- ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs | 42 +++++--- .../Filter/Qsv/SubtitleScaleQsvFilter.cs | 2 +- ErsatzTV.FFmpeg/Filter/ScaleFilter.cs | 33 +++++-- ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs | 4 +- ErsatzTV.FFmpeg/FrameState.cs | 7 +- ErsatzTV.FFmpeg/InputFile.cs | 1 + ErsatzTV.FFmpeg/MediaStream.cs | 94 +++++++++++++++++- ErsatzTV.FFmpeg/PipelineBuilder.cs | 68 +++++++------ .../ErsatzTV.Infrastructure.csproj | 6 +- ErsatzTV/ErsatzTV.csproj | 14 +-- ErsatzTV/Program.cs | 2 +- 29 files changed, 377 insertions(+), 171 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc6ca8b2..27b1f0fda 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 typo introduced in `0.6.7-beta` that stopped QSV HEVC encoder from working +- Fix scaling logic for `Nvidia` acceleration and software mode +- Attempt to position watermarks within content (not over added black padding) ## [0.6.7-beta] - 2022-09-05 ### Fixed diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj index de2d502e0..47fbcdfe9 100644 --- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj +++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj @@ -14,7 +14,7 @@ - + @@ -24,7 +24,7 @@ - + diff --git a/ErsatzTV.Core/ErsatzTV.Core.csproj b/ErsatzTV.Core/ErsatzTV.Core.csproj index 71bd035a1..6c3416b86 100644 --- a/ErsatzTV.Core/ErsatzTV.Core.csproj +++ b/ErsatzTV.Core/ErsatzTV.Core.csproj @@ -16,14 +16,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index e02df834a..8448df0d8 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -147,6 +147,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService videoStream.Codec, AvailablePixelFormats.ForPixelFormat(videoStream.PixelFormat, _logger), new FrameSize(videoVersion.Width, videoVersion.Height), + videoVersion.SampleAspectRatio, videoVersion.DisplayAspectRatio, videoVersion.RFrameRate, videoPath != audioPath); // still image when paths are different @@ -214,10 +215,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService false, // TODO: fallback filler needs to loop videoFormat, desiredPixelFormat, - await playbackSettings.ScaledSize.Map(ss => new FrameSize(ss.Width, ss.Height)) - .IfNoneAsync(new FrameSize(videoVersion.Width, videoVersion.Height)), + ffmpegVideoStream.SquarePixelFrameSize( + new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height)), new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height), - channel.FFmpegProfile.Resolution.Width == 640 ? "4:3" : "16:9", + false, playbackSettings.FrameRate, playbackSettings.VideoBitrate, playbackSettings.VideoBufferSize, @@ -314,7 +315,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService new PixelFormatYuv420P(), new FrameSize(desiredResolution.Width, desiredResolution.Height), new FrameSize(desiredResolution.Width, desiredResolution.Height), - desiredResolution.Width == 640 ? "4:3" : "16:9", + false, playbackSettings.FrameRate, playbackSettings.VideoBitrate, playbackSettings.VideoBufferSize, @@ -342,6 +343,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService VideoFormat.GeneratedImage, new PixelFormatUnknown(), // leave this unknown so we convert to desired yuv420p new FrameSize(videoVersion.Width, videoVersion.Height), + videoVersion.SampleAspectRatio, videoVersion.DisplayAspectRatio, None, true); @@ -434,7 +436,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService { var videoInputFile = new VideoInputFile( inputFile, - new List { new(0, string.Empty, None, FrameSize.Unknown, string.Empty, None, true) }); + new List { new(0, string.Empty, None, FrameSize.Unknown, string.Empty, string.Empty, None, true) }); var pipelineBuilder = new PipelineBuilder( _runtimeInfo, @@ -516,6 +518,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService new PixelFormatUnknown(), new FrameSize(1, 1), string.Empty, + string.Empty, Option.None, !options.IsAnimated) }, diff --git a/ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs b/ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs index 95b83a964..799bc22f3 100644 --- a/ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs +++ b/ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs @@ -26,7 +26,7 @@ public class PipelineGeneratorTests var videoInputFile = new VideoInputFile( "/tmp/whatever.mkv", new List - { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "16:9", "24", false) }); + { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) }); var audioInputFile = new AudioInputFile( "/tmp/whatever.mkv", @@ -47,7 +47,7 @@ public class PipelineGeneratorTests new PixelFormatYuv420P(), new FrameSize(1920, 1080), new FrameSize(1920, 1080), - "16:9", + false, Option.None, 2000, 4000, @@ -99,7 +99,7 @@ public class PipelineGeneratorTests var videoInputFile = new VideoInputFile( "/tmp/whatever.mkv", new List - { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "16:9", "24", false) }); + { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) }); var audioInputFile = new AudioInputFile( "/tmp/whatever.mkv", @@ -120,7 +120,7 @@ public class PipelineGeneratorTests new PixelFormatYuv420P(), new FrameSize(1920, 1080), new FrameSize(1920, 1080), - "16:9", + false, Option.None, 2000, 4000, @@ -198,7 +198,7 @@ public class PipelineGeneratorTests var videoInputFile = new VideoInputFile( "/tmp/whatever.mkv", new List - { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "16:9", "24", false) }); + { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) }); var audioInputFile = new AudioInputFile( "/tmp/whatever.mkv", @@ -219,7 +219,7 @@ public class PipelineGeneratorTests new PixelFormatYuv420P(), new FrameSize(1920, 1080), new FrameSize(1920, 1080), - "16:9", + false, Option.None, 2000, 4000, @@ -273,7 +273,7 @@ public class PipelineGeneratorTests var videoInputFile = new VideoInputFile( "/tmp/whatever.mkv", new List - { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "16:9", "24", false) }); + { new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) }); Option audioInputFile = Option.None; @@ -284,7 +284,7 @@ public class PipelineGeneratorTests new PixelFormatYuv420P(), new FrameSize(1920, 1080), new FrameSize(1920, 1080), - "16:9", + false, Option.None, 2000, 4000, @@ -341,7 +341,7 @@ public class PipelineGeneratorTests "/test/input/file.png", new List { - new(0, string.Empty, Option.None, FrameSize.Unknown, string.Empty, Option.None, true) + new(0, string.Empty, Option.None, FrameSize.Unknown, string.Empty, string.Empty, Option.None, true) }); var pipelineBuilder = new PipelineBuilder( @@ -360,7 +360,7 @@ public class PipelineGeneratorTests string command = PrintCommand(videoInputFile, None, None, None, result); command.Should().Be( - "-nostdin -hide_banner -nostats -loglevel error -i /test/input/file.png -vf scale=-1:200 /test/output/file.jpg"); + "-nostdin -hide_banner -nostats -loglevel error -i /test/input/file.png -vf scale=-1:200:force_original_aspect_ratio=decrease /test/output/file.jpg"); } private static string PrintCommand( diff --git a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj index 2a391fb85..9d7ade830 100644 --- a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj +++ b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj @@ -10,7 +10,7 @@ - + diff --git a/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs b/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs index 34c5a9169..6f2d670b2 100644 --- a/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs +++ b/ErsatzTV.FFmpeg/Filter/AvailableScaleFilters.cs @@ -8,19 +8,27 @@ namespace ErsatzTV.FFmpeg.Filter; public static class AvailableScaleFilters { public static IPipelineFilterStep ForAcceleration( - IRuntimeInfo runtimeInfo, + IRuntimeInfo _, HardwareAccelerationMode accelMode, FrameState currentState, FrameSize scaledSize, FrameSize paddedSize, - int extraHardwareFrames) => + int extraHardwareFrames, + bool isAnamorphicEdgeCase, + string sampleAspectRatio) => accelMode switch { - HardwareAccelerationMode.Nvenc => new ScaleCudaFilter(currentState, scaledSize, paddedSize), + HardwareAccelerationMode.Nvenc => + new ScaleCudaFilter(currentState, scaledSize, paddedSize, isAnamorphicEdgeCase), HardwareAccelerationMode.Qsv when currentState.FrameDataLocation == FrameDataLocation.Hardware || scaledSize == paddedSize => - new ScaleQsvFilter(runtimeInfo, currentState, scaledSize, paddedSize, extraHardwareFrames), + new ScaleQsvFilter( + currentState, + scaledSize, + extraHardwareFrames, + isAnamorphicEdgeCase, + sampleAspectRatio), HardwareAccelerationMode.Vaapi => new ScaleVaapiFilter(currentState, scaledSize, paddedSize), - _ => new ScaleFilter(currentState, scaledSize, paddedSize) + _ => new ScaleFilter(currentState, scaledSize, paddedSize, isAnamorphicEdgeCase) }; } diff --git a/ErsatzTV.FFmpeg/Filter/AvailableSubtitleOverlayFilters.cs b/ErsatzTV.FFmpeg/Filter/AvailableSubtitleOverlayFilters.cs index 0b12bf9ba..5ac31f5bc 100644 --- a/ErsatzTV.FFmpeg/Filter/AvailableSubtitleOverlayFilters.cs +++ b/ErsatzTV.FFmpeg/Filter/AvailableSubtitleOverlayFilters.cs @@ -5,11 +5,11 @@ namespace ErsatzTV.FFmpeg.Filter; public static class AvailableSubtitleOverlayFilters { - public static IPipelineFilterStep ForAcceleration(HardwareAccelerationMode accelMode, FrameState currentState) => + public static IPipelineFilterStep ForAcceleration(HardwareAccelerationMode accelMode) => accelMode switch { HardwareAccelerationMode.Nvenc => new OverlaySubtitleCudaFilter(), - HardwareAccelerationMode.Qsv => new OverlaySubtitleQsvFilter(currentState), + HardwareAccelerationMode.Qsv => new OverlaySubtitleQsvFilter(), _ => new OverlaySubtitleFilter() }; } diff --git a/ErsatzTV.FFmpeg/Filter/AvailableWatermarkOverlayFilters.cs b/ErsatzTV.FFmpeg/Filter/AvailableWatermarkOverlayFilters.cs index 398a47066..a2bb71bb3 100644 --- a/ErsatzTV.FFmpeg/Filter/AvailableWatermarkOverlayFilters.cs +++ b/ErsatzTV.FFmpeg/Filter/AvailableWatermarkOverlayFilters.cs @@ -1,6 +1,7 @@ using ErsatzTV.FFmpeg.Filter.Cuda; using ErsatzTV.FFmpeg.Filter.Qsv; using ErsatzTV.FFmpeg.State; +using Microsoft.Extensions.Logging; namespace ErsatzTV.FFmpeg.Filter; @@ -8,13 +9,16 @@ public static class AvailableWatermarkOverlayFilters { public static IPipelineFilterStep ForAcceleration( HardwareAccelerationMode accelMode, - FrameState currentState, WatermarkState watermarkState, - FrameSize resolution) => + FrameSize resolution, + FrameSize squarePixelFrameSize, + ILogger logger) => accelMode switch { - HardwareAccelerationMode.Nvenc => new OverlayWatermarkCudaFilter(currentState, watermarkState, resolution), - HardwareAccelerationMode.Qsv => new OverlayWatermarkQsvFilter(currentState, watermarkState, resolution), - _ => new OverlayWatermarkFilter(currentState, watermarkState, resolution) + HardwareAccelerationMode.Nvenc => + new OverlayWatermarkCudaFilter(watermarkState, resolution, squarePixelFrameSize, logger), + HardwareAccelerationMode.Qsv => + new OverlayWatermarkQsvFilter(watermarkState, resolution, squarePixelFrameSize, logger), + _ => new OverlayWatermarkFilter(watermarkState, resolution, squarePixelFrameSize, logger) }; } diff --git a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs index 87cf32b83..b3cba1a8c 100644 --- a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs @@ -1,5 +1,6 @@ using ErsatzTV.FFmpeg.Environment; using ErsatzTV.FFmpeg.Format; +using Microsoft.Extensions.Logging; namespace ErsatzTV.FFmpeg.Filter; @@ -8,6 +9,7 @@ public class ComplexFilter : IPipelineStep private readonly FrameState _currentState; private readonly FFmpegState _ffmpegState; private readonly string _fontsDir; + private readonly ILogger _logger; private readonly Option _maybeAudioInputFile; private readonly Option _maybeSubtitleInputFile; private readonly Option _maybeVideoInputFile; @@ -22,7 +24,8 @@ public class ComplexFilter : IPipelineStep Option maybeWatermarkInputFile, Option maybeSubtitleInputFile, FrameSize resolution, - string fontsDir) + string fontsDir, + ILogger logger) { _currentState = currentState; _ffmpegState = ffmpegState; @@ -32,6 +35,7 @@ public class ComplexFilter : IPipelineStep _maybeSubtitleInputFile = maybeSubtitleInputFile; _resolution = resolution; _fontsDir = fontsDir; + _logger = logger; } public IList EnvironmentVariables => Array.Empty(); @@ -147,48 +151,56 @@ public class ComplexFilter : IPipelineStep watermarkLabel = $"[{watermarkLabel}]"; } - IPipelineFilterStep overlayFilter = AvailableWatermarkOverlayFilters.ForAcceleration( - _ffmpegState.EncoderHardwareAccelerationMode, - _currentState, - watermarkInputFile.DesiredState, - _resolution); - - if (overlayFilter.Filter != string.Empty) + foreach (VideoInputFile videoInputFile in _maybeVideoInputFile) { - string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex) - ? $"[{videoLabel}]" - : videoLabel; - - // vaapi uses software overlay and needs to upload - // videotoolbox seems to require a hwupload for hevc - // also wait to upload if a subtitle overlay is coming - string uploadDownloadFilter = string.Empty; - if (_maybeSubtitleInputFile.IsNone && - (_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi || - _ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox && - _currentState.VideoFormat == VideoFormat.Hevc)) + foreach (VideoStream stream in videoInputFile.VideoStreams) { - uploadDownloadFilter = new HardwareUploadFilter(_ffmpegState).Filter; + IPipelineFilterStep overlayFilter = AvailableWatermarkOverlayFilters.ForAcceleration( + _ffmpegState.EncoderHardwareAccelerationMode, + watermarkInputFile.DesiredState, + _resolution, + stream.SquarePixelFrameSize(_resolution), + _logger); + + if (overlayFilter.Filter != string.Empty) + { + string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex) + ? $"[{videoLabel}]" + : videoLabel; + + // vaapi uses software overlay and needs to upload + // videotoolbox seems to require a hwupload for hevc + // also wait to upload if a subtitle overlay is coming + string uploadDownloadFilter = string.Empty; + if (_maybeSubtitleInputFile.IsNone && + (_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi || + _ffmpegState.EncoderHardwareAccelerationMode == + HardwareAccelerationMode.VideoToolbox && + _currentState.VideoFormat == VideoFormat.Hevc)) + { + uploadDownloadFilter = new HardwareUploadFilter(_ffmpegState).Filter; + } + + if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) && + _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi && + _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox && + _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Amf) + { + uploadDownloadFilter = new HardwareDownloadFilter(_currentState).Filter; + } + + if (!string.IsNullOrWhiteSpace(uploadDownloadFilter)) + { + uploadDownloadFilter = "," + uploadDownloadFilter; + } + + watermarkOverlayFilterComplex = + $"{tempVideoLabel}{watermarkLabel}{overlayFilter.Filter}{uploadDownloadFilter}[vf]"; + + // change the mapped label + videoLabel = "[vf]"; + } } - - if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) && - _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi && - _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox && - _ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Amf) - { - uploadDownloadFilter = new HardwareDownloadFilter(_currentState).Filter; - } - - if (!string.IsNullOrWhiteSpace(uploadDownloadFilter)) - { - uploadDownloadFilter = "," + uploadDownloadFilter; - } - - watermarkOverlayFilterComplex = - $"{tempVideoLabel}{watermarkLabel}{overlayFilter.Filter}{uploadDownloadFilter}[vf]"; - - // change the mapped label - videoLabel = "[vf]"; } } } @@ -216,10 +228,8 @@ public class ComplexFilter : IPipelineStep string filter; if (subtitleInputFile.IsImageBased) { - IPipelineFilterStep overlayFilter = - AvailableSubtitleOverlayFilters.ForAcceleration( - _ffmpegState.EncoderHardwareAccelerationMode, - _currentState); + IPipelineFilterStep overlayFilter = AvailableSubtitleOverlayFilters.ForAcceleration( + _ffmpegState.EncoderHardwareAccelerationMode); filter = overlayFilter.Filter; } else diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/OverlaySubtitleCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/OverlaySubtitleCudaFilter.cs index 7c9aa7f6c..9fe462ee6 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/OverlaySubtitleCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/OverlaySubtitleCudaFilter.cs @@ -2,6 +2,6 @@ public class OverlaySubtitleCudaFilter : BaseFilter { - public override string Filter => "overlay_cuda"; + public override string Filter => "overlay_cuda=x=(W-w)/2:y=(H-h)/2"; public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/OverlayWatermarkCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/OverlayWatermarkCudaFilter.cs index 8a4163f6c..5d7dcd0fa 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/OverlayWatermarkCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/OverlayWatermarkCudaFilter.cs @@ -1,16 +1,19 @@ using ErsatzTV.FFmpeg.State; +using Microsoft.Extensions.Logging; namespace ErsatzTV.FFmpeg.Filter.Cuda; public class OverlayWatermarkCudaFilter : OverlayWatermarkFilter { public OverlayWatermarkCudaFilter( - FrameState currentState, WatermarkState watermarkState, - FrameSize resolution) : base( - currentState, + FrameSize resolution, + FrameSize squarePixelFrameSize, + ILogger logger) : base( watermarkState, - resolution) + resolution, + squarePixelFrameSize, + logger) { } diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs index 9b72ecbaa..eb611d468 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs @@ -6,13 +6,19 @@ public class ScaleCudaFilter : BaseFilter { private readonly FrameState _currentState; private readonly FrameSize _paddedSize; + private readonly bool _isAnamorphicEdgeCase; private readonly FrameSize _scaledSize; - public ScaleCudaFilter(FrameState currentState, FrameSize scaledSize, FrameSize paddedSize) + public ScaleCudaFilter( + FrameState currentState, + FrameSize scaledSize, + FrameSize paddedSize, + bool isAnamorphicEdgeCase) { _currentState = currentState; _scaledSize = scaledSize; _paddedSize = paddedSize; + _isAnamorphicEdgeCase = isAnamorphicEdgeCase; } public override string Filter @@ -30,6 +36,13 @@ public class ScaleCudaFilter : BaseFilter } else { + string aspectRatio = string.Empty; + if (_scaledSize != _paddedSize) + { + aspectRatio = ":force_original_aspect_ratio=decrease"; + } + + string squareScale = string.Empty; string targetSize = $"{_paddedSize.Width}:{_paddedSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) @@ -37,7 +50,20 @@ public class ScaleCudaFilter : BaseFilter format = $":format={pixelFormat.FFmpegName}"; } - scale = $"scale_cuda={targetSize}{format}"; + if (_isAnamorphicEdgeCase) + { + squareScale = $"scale_cuda=iw:sar*ih{format},setsar=1,"; + } + else if (_currentState.IsAnamorphic) + { + squareScale = $"scale_cuda=iw*sar:ih{format},setsar=1,"; + } + else + { + aspectRatio += ",setsar=1"; + } + + scale = $"{squareScale}scale_cuda={targetSize}{format}{aspectRatio}"; } // TODO: this might not always upload to hardware, so NextState could be inaccurate @@ -56,6 +82,7 @@ public class ScaleCudaFilter : BaseFilter { ScaledSize = _scaledSize, PaddedSize = _scaledSize, - FrameDataLocation = FrameDataLocation.Hardware + FrameDataLocation = FrameDataLocation.Hardware, + IsAnamorphic = false // this filter always outputs square pixels }; } diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs index a9dded557..e66423b06 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/SubtitleScaleNppFilter.cs @@ -29,7 +29,7 @@ public class SubtitleScaleNppFilter : BaseFilter format = $":format={pixelFormat.FFmpegName}"; } - scale = $"scale_npp={targetSize}{format}"; + scale = $"scale_npp={targetSize}{format}:force_original_aspect_ratio=1"; } return scale; diff --git a/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs b/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs index abff7d21c..f8a925473 100644 --- a/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/OverlaySubtitleFilter.cs @@ -2,6 +2,6 @@ public class OverlaySubtitleFilter : BaseFilter { - public override string Filter => "overlay"; + public override string Filter => "overlay=x=(W-w)/2:y=(H-h)/2"; public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs b/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs index 7b0f19faa..3bfdb0275 100644 --- a/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs @@ -1,18 +1,25 @@ using ErsatzTV.FFmpeg.State; +using Microsoft.Extensions.Logging; namespace ErsatzTV.FFmpeg.Filter; public class OverlayWatermarkFilter : BaseFilter { - private readonly FrameState _currentState; private readonly FrameSize _resolution; + private readonly FrameSize _squarePixelFrameSize; + private readonly ILogger _logger; private readonly WatermarkState _watermarkState; - public OverlayWatermarkFilter(FrameState currentState, WatermarkState watermarkState, FrameSize resolution) + public OverlayWatermarkFilter( + WatermarkState watermarkState, + FrameSize resolution, + FrameSize squarePixelFrameSize, + ILogger logger) { - _currentState = currentState; _watermarkState = watermarkState; _resolution = resolution; + _squarePixelFrameSize = squarePixelFrameSize; + _logger = logger; } public override string Filter => $"overlay={Position}"; @@ -21,8 +28,20 @@ public class OverlayWatermarkFilter : BaseFilter { get { - double horizontalMargin = Math.Round(_watermarkState.HorizontalMarginPercent / 100.0 * _resolution.Width); - double verticalMargin = Math.Round(_watermarkState.VerticalMarginPercent / 100.0 * _resolution.Height); + int horizontalPadding = _resolution.Width - _squarePixelFrameSize.Width; + int verticalPadding = _resolution.Height - _squarePixelFrameSize.Height; + + _logger.LogDebug( + $"Resolution: {_resolution.Width}x{_resolution.Height}"); + _logger.LogDebug( + $"Square Pix: {_squarePixelFrameSize.Width}x{_squarePixelFrameSize.Height}"); + + double horizontalMargin = Math.Round( + _watermarkState.HorizontalMarginPercent / 100.0 * _squarePixelFrameSize.Width + + horizontalPadding / 2.0); + double verticalMargin = Math.Round( + _watermarkState.VerticalMarginPercent / 100.0 * _squarePixelFrameSize.Height + + verticalPadding / 2.0); return _watermarkState.Location switch { diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs index 3a40c0d2b..a3eed1255 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/OverlaySubtitleQsvFilter.cs @@ -2,12 +2,8 @@ public class OverlaySubtitleQsvFilter : BaseFilter { - private readonly FrameState _currentState; - - public OverlaySubtitleQsvFilter(FrameState currentState) => _currentState = currentState; - public override string Filter => - $"overlay_qsv=eof_action=endall:shortest=1:repeatlast=0:w={_currentState.PaddedSize.Width}:h={_currentState.PaddedSize.Height}"; + $"overlay_qsv=eof_action=endall:shortest=1:repeatlast=0:x=(W-w)/2:y=(H-h)/2"; public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/OverlayWatermarkQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/OverlayWatermarkQsvFilter.cs index 4ee24fb2d..524e58ce3 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/OverlayWatermarkQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/OverlayWatermarkQsvFilter.cs @@ -1,16 +1,19 @@ using ErsatzTV.FFmpeg.State; +using Microsoft.Extensions.Logging; namespace ErsatzTV.FFmpeg.Filter.Qsv; public class OverlayWatermarkQsvFilter : OverlayWatermarkFilter { public OverlayWatermarkQsvFilter( - FrameState currentState, WatermarkState watermarkState, - FrameSize resolution) : base( - currentState, + FrameSize resolution, + FrameSize squarePixelFrameSize, + ILogger logger) : base( watermarkState, - resolution) + resolution, + squarePixelFrameSize, + logger) { } diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs index 81cedeb62..49c189ba0 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/ScaleQsvFilter.cs @@ -1,29 +1,27 @@ -using System.Runtime.InteropServices; -using ErsatzTV.FFmpeg.Format; -using ErsatzTV.FFmpeg.Runtime; +using ErsatzTV.FFmpeg.Format; namespace ErsatzTV.FFmpeg.Filter.Qsv; public class ScaleQsvFilter : BaseFilter { - private readonly IRuntimeInfo _runtimeInfo; private readonly FrameState _currentState; private readonly FrameSize _scaledSize; - private readonly FrameSize _paddedSize; private readonly int _extraHardwareFrames; + private readonly bool _isAnamorphicEdgeCase; + private readonly string _sampleAspectRatio; public ScaleQsvFilter( - IRuntimeInfo runtimeInfo, FrameState currentState, FrameSize scaledSize, - FrameSize paddedSize, - int extraHardwareFrames) + int extraHardwareFrames, + bool isAnamorphicEdgeCase, + string sampleAspectRatio) { - _runtimeInfo = runtimeInfo; _currentState = currentState; _scaledSize = scaledSize; - _paddedSize = paddedSize; _extraHardwareFrames = extraHardwareFrames; + _isAnamorphicEdgeCase = isAnamorphicEdgeCase; + _sampleAspectRatio = sampleAspectRatio; } public override string Filter @@ -44,16 +42,29 @@ public class ScaleQsvFilter : BaseFilter } else { + string squareScale = string.Empty; + string targetSize = $"w={_scaledSize.Width}:h={_scaledSize.Height}"; string format = string.Empty; foreach (IPixelFormat pixelFormat in _currentState.PixelFormat) { format = $":format={pixelFormat.FFmpegName}"; } - string targetSize = _runtimeInfo.IsOSPlatform(OSPlatform.Windows) - ? $"w={_paddedSize.Width}:h={_paddedSize.Height}" - : $"w={_scaledSize.Width}:h={_scaledSize.Height}"; - scale = $"vpp_qsv={targetSize}{format}"; + string sar = _sampleAspectRatio.Replace(':', '/'); + if (_isAnamorphicEdgeCase) + { + squareScale = $"vpp_qsv=w=iw:h={sar}*ih{format},setsar=1,"; + } + else if (_currentState.IsAnamorphic) + { + squareScale = $"vpp_qsv=w=iw*{sar}:h=ih{format},setsar=1,"; + } + else + { + format += ",setsar=1"; + } + + scale = $"{squareScale}vpp_qsv={targetSize}{format}"; } if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) @@ -75,6 +86,7 @@ public class ScaleQsvFilter : BaseFilter { ScaledSize = _scaledSize, PaddedSize = _scaledSize, - FrameDataLocation = FrameDataLocation.Hardware + FrameDataLocation = FrameDataLocation.Hardware, + IsAnamorphic = false // this filter always outputs square pixels }; } diff --git a/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs b/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs index b577536b9..e849155fd 100644 --- a/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Qsv/SubtitleScaleQsvFilter.cs @@ -31,7 +31,7 @@ public class SubtitleScaleQsvFilter : BaseFilter string targetSize = $"w={_paddedSize.Width}:h={_paddedSize.Height}"; // use software scaling - scale = $"scale={targetSize}"; + scale = $"scale={targetSize}:force_original_aspect_ratio=decrease"; } string initialPixelFormat = _currentState.PixelFormat.Match(pf => pf.FFmpegName, FFmpegFormat.NV12); diff --git a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs index 1b5f3edfa..cba8db350 100644 --- a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs @@ -6,13 +6,15 @@ public class ScaleFilter : BaseFilter { private readonly FrameState _currentState; private readonly FrameSize _paddedSize; + private readonly bool _isAnamorphicEdgeCase; private readonly FrameSize _scaledSize; - public ScaleFilter(FrameState currentState, FrameSize scaledSize, FrameSize paddedSize) + public ScaleFilter(FrameState currentState, FrameSize scaledSize, FrameSize paddedSize, bool isAnamorphicEdgeCase) { _currentState = currentState; _scaledSize = scaledSize; _paddedSize = paddedSize; + _isAnamorphicEdgeCase = isAnamorphicEdgeCase; } public override string Filter @@ -25,8 +27,19 @@ public class ScaleFilter : BaseFilter aspectRatio = ":force_original_aspect_ratio=decrease"; } - string scale = - $"scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; + string scale; + if (_isAnamorphicEdgeCase) + { + scale = $"scale=iw:sar*ih,setsar=1,scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; + } + else if (_currentState.IsAnamorphic) + { + scale = $"scale=iw*sar:ih,setsar=1,scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio}"; + } + else + { + scale = $"scale={_paddedSize.Width}:{_paddedSize.Height}:flags=fast_bilinear{aspectRatio},setsar=1"; + } string hwdownload = string.Empty; if (_currentState.FrameDataLocation == FrameDataLocation.Hardware) @@ -45,10 +58,12 @@ public class ScaleFilter : BaseFilter } } - public override FrameState NextState(FrameState currentState) => currentState with - { - ScaledSize = _scaledSize, - PaddedSize = _scaledSize, - FrameDataLocation = FrameDataLocation.Software - }; + public override FrameState NextState(FrameState currentState) => + currentState with + { + ScaledSize = _scaledSize, + PaddedSize = _scaledSize, + FrameDataLocation = FrameDataLocation.Software, + IsAnamorphic = false // this filter always outputs square pixels + }; } diff --git a/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs b/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs index 2d0b785e1..2428a8c83 100644 --- a/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ScaleImageFilter.cs @@ -6,9 +6,7 @@ public class ScaleImageFilter : BaseFilter public ScaleImageFilter(FrameSize scaledSize) => _scaledSize = scaledSize; - public override string Filter => $"scale={_scaledSize.Width}:{_scaledSize.Height}"; - - // public override IList OutputOptions => new List { "-q:v", "2" }; + public override string Filter => $"scale={_scaledSize.Width}:{_scaledSize.Height}:force_original_aspect_ratio=decrease"; public override FrameState NextState(FrameState currentState) => currentState with { diff --git a/ErsatzTV.FFmpeg/FrameState.cs b/ErsatzTV.FFmpeg/FrameState.cs index 160a4945d..9c81536e9 100644 --- a/ErsatzTV.FFmpeg/FrameState.cs +++ b/ErsatzTV.FFmpeg/FrameState.cs @@ -9,10 +9,13 @@ public record FrameState( Option PixelFormat, FrameSize ScaledSize, FrameSize PaddedSize, - string DisplayAspectRatio, + bool IsAnamorphic, Option FrameRate, Option VideoBitrate, Option VideoBufferSize, Option VideoTrackTimeScale, bool Deinterlaced, - FrameDataLocation FrameDataLocation = FrameDataLocation.Unknown); + FrameDataLocation FrameDataLocation = FrameDataLocation.Unknown) +{ + public string FFmpegAspectRatio => PaddedSize.Width == 640 ? "4/3" : "16/9"; +} diff --git a/ErsatzTV.FFmpeg/InputFile.cs b/ErsatzTV.FFmpeg/InputFile.cs index 4eb32987d..a2500fe07 100644 --- a/ErsatzTV.FFmpeg/InputFile.cs +++ b/ErsatzTV.FFmpeg/InputFile.cs @@ -20,6 +20,7 @@ public record ConcatInputFile(string Url, FrameSize Resolution) : InputFile( Option.None, Resolution, string.Empty, + string.Empty, Option.None, false) }) diff --git a/ErsatzTV.FFmpeg/MediaStream.cs b/ErsatzTV.FFmpeg/MediaStream.cs index 3c90b436b..c5c0986a8 100644 --- a/ErsatzTV.FFmpeg/MediaStream.cs +++ b/ErsatzTV.FFmpeg/MediaStream.cs @@ -1,4 +1,5 @@ -using ErsatzTV.FFmpeg.Format; +using System.ComponentModel.DataAnnotations; +using ErsatzTV.FFmpeg.Format; namespace ErsatzTV.FFmpeg; @@ -14,9 +15,98 @@ public record VideoStream( string Codec, Option PixelFormat, FrameSize FrameSize, + string SampleAspectRatio, string DisplayAspectRatio, Option FrameRate, bool StillImage) : MediaStream( Index, Codec, - StreamKind.Video); + StreamKind.Video) +{ + public bool IsAnamorphic + { + get + { + // square pixels + if (SampleAspectRatio == "1:1") + { + return false; + } + + // 0:1 is "unspecified", so anything other than that will be non-square/anamorphic + if (SampleAspectRatio != "0:1") + { + return true; + } + + // SAR 0:1 && DAR 0:1 (both unspecified) means square + if (DisplayAspectRatio == "0:1") + { + return false; + } + + // DAR == W:H is square + return DisplayAspectRatio != $"{FrameSize.Width}:{FrameSize.Height}"; + } + } + + // TODO: figure out what's really causing this + public bool IsAnamorphicEdgeCase + { + get + { + try + { + string[] split = SampleAspectRatio.Split(':'); + var num = double.Parse(split[0]); + var den = double.Parse(split[1]); + + if (num <= den) + { + return false; + } + + double sar = num / den; + double res = (double)FrameSize.Width / FrameSize.Height; + return res - sar > 0.01; + } + catch + { + return false; + } + } + } + + public FrameSize SquarePixelFrameSize(FrameSize resolution) + { + int width = FrameSize.Width; + int height = FrameSize.Height; + + if (IsAnamorphic) + { + string[] split = SampleAspectRatio.Split(':'); + var num = double.Parse(split[0]); + var den = double.Parse(split[1]); + + bool edgeCase = IsAnamorphicEdgeCase; + + width = edgeCase + ? FrameSize.Width + : (int)Math.Floor(FrameSize.Width * num / den); + + height = edgeCase + ? (int)Math.Floor(FrameSize.Height * num / den) + : FrameSize.Height; + } + + double widthPercent = (double)resolution.Width / width; + double heightPercent = (double)resolution.Height / height; + double minPercent = Math.Min(widthPercent, heightPercent); + + var result = new FrameSize( + (int)Math.Floor(width * minPercent), + (int)Math.Floor(height * minPercent)); + + return result; + } +} diff --git a/ErsatzTV.FFmpeg/PipelineBuilder.cs b/ErsatzTV.FFmpeg/PipelineBuilder.cs index bc9fbd4cd..df7403bd2 100644 --- a/ErsatzTV.FFmpeg/PipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/PipelineBuilder.cs @@ -192,7 +192,7 @@ public class PipelineBuilder videoStream.PixelFormat, videoStream.FrameSize, videoStream.FrameSize, - videoStream.DisplayAspectRatio, + videoStream.IsAnamorphic, initialFrameRate, Option.None, Option.None, @@ -376,7 +376,8 @@ public class PipelineBuilder IPipelineFilterStep scaleStep = new ScaleFilter( currentState, desiredState.ScaledSize, - desiredState.PaddedSize); + desiredState.PaddedSize, + videoStream.IsAnamorphicEdgeCase); currentState = scaleStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleStep)); @@ -385,12 +386,12 @@ public class PipelineBuilder currentState = padStep.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); - if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio) - { - IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); - currentState = darStep.NextState(currentState); - _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); - } + // if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio) + // { + // IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); + // currentState = darStep.NextState(currentState); + // _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); + // } } } else if (currentState.ScaledSize != desiredState.ScaledSize) @@ -401,7 +402,9 @@ public class PipelineBuilder currentState, desiredState.ScaledSize, desiredState.PaddedSize, - ffmpegState.QsvExtraHardwareFrames); + ffmpegState.QsvExtraHardwareFrames, + videoStream.IsAnamorphicEdgeCase, + videoStream.SampleAspectRatio); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); @@ -413,13 +416,13 @@ public class PipelineBuilder _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); } - if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio || - ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv) - { - IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); - currentState = darStep.NextState(currentState); - _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); - } + // if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio || + // ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv) + // { + // IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); + // currentState = darStep.NextState(currentState); + // _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); + // } } else if (currentState.PaddedSize != desiredState.PaddedSize) { @@ -429,7 +432,9 @@ public class PipelineBuilder currentState, desiredState.ScaledSize, desiredState.PaddedSize, - ffmpegState.QsvExtraHardwareFrames); + ffmpegState.QsvExtraHardwareFrames, + videoStream.IsAnamorphicEdgeCase, + videoStream.SampleAspectRatio); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); @@ -440,13 +445,13 @@ public class PipelineBuilder _videoInputFile.Iter(f => f.FilterSteps.Add(padStep)); } - if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio || - ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv) - { - IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); - currentState = darStep.NextState(currentState); - _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); - } + // if (videoStream.DisplayAspectRatio == desiredState.DisplayAspectRatio || + // ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Qsv) + // { + // IPipelineFilterStep darStep = new SetDarFilter(desiredState.DisplayAspectRatio); + // currentState = darStep.NextState(currentState); + // _videoInputFile.Iter(f => f.FilterSteps.Add(darStep)); + // } } if (hasOverlay && currentState.PixelFormat.Map(pf => pf.FFmpegName) != @@ -485,7 +490,9 @@ public class PipelineBuilder currentState, desiredState.ScaledSize, desiredState.PaddedSize, - ffmpegState.QsvExtraHardwareFrames); + ffmpegState.QsvExtraHardwareFrames, + videoStream.IsAnamorphicEdgeCase, + videoStream.SampleAspectRatio); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); } @@ -516,7 +523,9 @@ public class PipelineBuilder currentState, desiredState.ScaledSize, desiredState.PaddedSize, - ffmpegState.QsvExtraHardwareFrames); + ffmpegState.QsvExtraHardwareFrames, + videoStream.IsAnamorphicEdgeCase, + videoStream.SampleAspectRatio); currentState = scaleFilter.NextState(currentState); videoInputFile.FilterSteps.Add(scaleFilter); } @@ -670,7 +679,9 @@ public class PipelineBuilder currentState, desiredState.ScaledSize, desiredState.PaddedSize, - ffmpegState.QsvExtraHardwareFrames); + ffmpegState.QsvExtraHardwareFrames, + videoStream.IsAnamorphicEdgeCase, + videoStream.SampleAspectRatio); currentState = scaleFilter.NextState(currentState); _videoInputFile.Iter(f => f.FilterSteps.Add(scaleFilter)); } @@ -800,7 +811,8 @@ public class PipelineBuilder _watermarkInputFile, _subtitleInputFile, currentState.PaddedSize, - _fontsFolder); + _fontsFolder, + _logger); _pipelineSteps.Add(complexFilter); } diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index 48386c47a..3a5ba2458 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -14,12 +14,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index 84571e5ca..bfe7adec7 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -55,16 +55,16 @@ - + - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -73,12 +73,12 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - + diff --git a/ErsatzTV/Program.cs b/ErsatzTV/Program.cs index be718dcff..695ed3c9f 100644 --- a/ErsatzTV/Program.cs +++ b/ErsatzTV/Program.cs @@ -64,7 +64,7 @@ public class Program } finally { - Log.CloseAndFlush(); + await Log.CloseAndFlushAsync(); } }