From 3b254735e606d08babc46bd0747026bd749666ba Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Sun, 26 Oct 2025 08:51:24 -0500 Subject: [PATCH] fix transcoding tests; fix vaapi subtitle crop (#2568) * fix transcoding tests using text subtitles * fix vaapi picture subtitle overlay with crop * more test improvements --- CHANGELOG.md | 1 + ...layoutItemProcessByChannelNumberHandler.cs | 1 + .../PrepareTroubleshootingPlaybackHandler.cs | 1 + .../FFmpeg/FFmpegLibraryProcessService.cs | 28 +++++++++---- .../FFmpeg/IFFmpegProcessService.cs | 1 + .../Pipeline/VaapiPipelineBuilder.cs | 16 ++++++++ .../Core/FFmpeg/TranscodingTests.cs | 41 +++++++++++-------- 7 files changed, 64 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a1bcd5b..4eda161d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Improve reliability of live remote streams; they should transcode closer to realtime in most cases - Dramatically improve stream startup time - VAAPI: fix scaling image-based subtitles (e.g. dvdsub) +- VAAPI: fix overlaying picture subtitles with scaling behavior crop - Fix HLS Segmenter (fmp4) on Windows - Playback troubleshooting: wait for at least 2 initial segments (up to configured initial segment count) to reduce stalls - Fix Trakt List sync diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 219571766..5c65b9dae 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -437,6 +437,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< request.TargetFramerate, Option.None, _ => { }, + canProxy: true, cancellationToken); var result = new PlayoutItemProcessModel( diff --git a/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs b/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs index c203e99e2..71543c90f 100644 --- a/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs +++ b/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs @@ -251,6 +251,7 @@ public class PrepareTroubleshootingPlaybackHandler( Option.None, FileSystemLayout.TranscodeTroubleshootingFolder, _ => { }, + canProxy: true, cancellationToken); return playoutItemResult; diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 013f56ea1..bf0426147 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -86,6 +86,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService Option targetFramerate, Option customReportsFolder, Action pipelineAction, + bool canProxy, CancellationToken cancellationToken) { MediaStream videoStream = await _ffmpegStreamSelector.SelectVideoStream(videoVersion); @@ -153,17 +154,20 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService maybeSubtitle = allSubtitles.HeadOrNone(); } - foreach (Subtitle subtitle in maybeSubtitle) + if (canProxy) { - if (subtitle.SubtitleKind == SubtitleKind.Sidecar || subtitle is - { SubtitleKind: SubtitleKind.Embedded, IsImage: false, IsExtracted: true }) + foreach (Subtitle subtitle in maybeSubtitle) { - // proxy to avoid dealing with escaping - subtitle.Path = $"http://localhost:{Settings.StreamingPort}/media/subtitle/{subtitle.Id}"; - - foreach (TimeSpan seek in playbackSettings.StreamSeek) + if (subtitle.SubtitleKind == SubtitleKind.Sidecar || subtitle is + { SubtitleKind: SubtitleKind.Embedded, IsImage: false, IsExtracted: true }) { - subtitle.Path += $"?seekToMs={(int)seek.TotalMilliseconds}"; + // proxy to avoid dealing with escaping + subtitle.Path = $"http://localhost:{Settings.StreamingPort}/media/subtitle/{subtitle.Id}"; + + foreach (TimeSpan seek in playbackSettings.StreamSeek) + { + subtitle.Path += $"?seekToMs={(int)seek.TotalMilliseconds}"; + } } } } @@ -282,7 +286,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService subtitle.Codec, StreamKind.Video); - string path = subtitle.IsImage ? videoPath : subtitle.Path; + string subtitlePath = subtitle.Path; + if (!canProxy && !subtitle.IsImage && subtitle.IsExtracted) + { + subtitlePath = Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitlePath); + } + + string path = subtitle.IsImage ? videoPath : subtitlePath; SubtitleMethod method = SubtitleMethod.Burn; if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect) diff --git a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs index 495aeb602..fd6ef6b31 100644 --- a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs +++ b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs @@ -41,6 +41,7 @@ public interface IFFmpegProcessService Option targetFramerate, Option customReportsFolder, Action pipelineAction, + bool canProxy, CancellationToken cancellationToken); Task ForError( diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs index 65c97dd6b..4a68f4935 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs @@ -510,6 +510,14 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder subtitle.FilterSteps.Add(scaleFilter); } + foreach (FrameSize croppedSize in currentState.CroppedSize) + { + var cropStep = new CropFilter( + currentState with { FrameDataLocation = FrameDataLocation.Software }, + croppedSize); + subtitle.FilterSteps.Add(cropStep); + } + var subtitlesFilter = new OverlaySubtitleFilter(pf); subtitleOverlayFilterSteps.Add(subtitlesFilter); } @@ -523,6 +531,14 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder subtitle.FilterSteps.Add(scaleFilter); } + foreach (FrameSize croppedSize in currentState.CroppedSize) + { + var cropStep = new CropFilter( + currentState with { FrameDataLocation = FrameDataLocation.Software }, + croppedSize); + subtitle.FilterSteps.Add(cropStep); + } + var subtitleHardwareUpload = new HardwareUploadVaapiFilter(false); subtitle.FilterSteps.Add(subtitleHardwareUpload); diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index 1410717af..d0efca0f2 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -113,7 +113,7 @@ public class TranscodingTests public static Watermark[] Watermarks = [ Watermark.None, - //Watermark.PermanentOpaqueScaled, + Watermark.PermanentOpaqueScaled, // Watermark.PermanentOpaqueActualSize, // Watermark.PermanentTransparentScaled, // Watermark.PermanentTransparentActualSize @@ -122,8 +122,8 @@ public class TranscodingTests public static Subtitle[] Subtitles = [ Subtitle.None, - //Subtitle.Picture, - // Subtitle.Text + Subtitle.Picture, + Subtitle.Text ]; public static Padding[] Paddings = @@ -151,7 +151,7 @@ public class TranscodingTests new("libx264", "yuv420p", "tv", "smpte170m", "bt709", "smpte170m"), // // // // // // // example format that requires setparams filter - //new("libx264", "yuv420p", string.Empty, string.Empty, string.Empty, string.Empty), + new("libx264", "yuv420p", string.Empty, string.Empty, string.Empty, string.Empty), // // // // // // // new("libx264", "yuvj420p"), //new("libx264", "yuv420p10le"), @@ -159,7 +159,7 @@ public class TranscodingTests // // // // // // // new("mpeg1video", "yuv420p"), // // // // - //new("mpeg2video", "yuv420p"), + new("mpeg2video", "yuv420p"), // // //new InputFormat("libx265", "yuv420p"), //new("libx265", "yuv420p10le"), @@ -188,7 +188,7 @@ public class TranscodingTests public static FFmpegProfileBitDepth[] BitDepths = [ FFmpegProfileBitDepth.EightBit, - //FFmpegProfileBitDepth.TenBit + FFmpegProfileBitDepth.TenBit ]; public static FFmpegProfileVideoFormat[] VideoFormats = @@ -200,10 +200,10 @@ public class TranscodingTests public static HardwareAccelerationKind[] TestAccelerations = [ - //HardwareAccelerationKind.None, - HardwareAccelerationKind.Nvenc - //HardwareAccelerationKind.Vaapi - //HardwareAccelerationKind.Qsv, + HardwareAccelerationKind.None, + //HardwareAccelerationKind.Nvenc + HardwareAccelerationKind.Vaapi, + HardwareAccelerationKind.Qsv, // HardwareAccelerationKind.VideoToolbox, // HardwareAccelerationKind.Amf ]; @@ -388,7 +388,7 @@ public class TranscodingTests watermarks, [], "drm", - VaapiDriver.RadeonSI, + VaapiDriver.iHD, "/dev/dri/renderD128", Option.None, false, @@ -400,6 +400,7 @@ public class TranscodingTests None, Option.None, _ => { }, + canProxy: false, CancellationToken.None); // Console.WriteLine($"ffmpeg arguments {process.Arguments}"); @@ -410,7 +411,7 @@ public class TranscodingTests profileBitDepth, profileVideoFormat, profileAcceleration, - VaapiDriver.RadeonSI, + VaapiDriver.iHD, localStatisticsProvider, streamingMode, () => videoVersion); @@ -444,7 +445,14 @@ public class TranscodingTests [ValueSource(typeof(TestData), nameof(TestData.StreamingModes))] StreamingMode streamingMode) { - NvEncSharpRedirector.Init(); + try + { + NvEncSharpRedirector.Init(); + } + catch (FileNotFoundException) + { + // do nothing + } string file = fileToTest; if (string.IsNullOrWhiteSpace(file)) @@ -602,7 +610,7 @@ public class TranscodingTests // TODO: bit depth - bool hasPadding = filterChain.VideoFilterSteps.Any(s => s is PadFilter); + bool hasPadding = filterChain.VideoFilterSteps.Any(s => s is PadFilter or PadVaapiFilter); // TODO: optimize out padding // hasPadding.ShouldBe(padding == Padding.WithPadding); @@ -699,7 +707,7 @@ public class TranscodingTests watermarks, [], "drm", - VaapiDriver.RadeonSI, + VaapiDriver.iHD, "/dev/dri/renderD128", Option.None, false, @@ -711,6 +719,7 @@ public class TranscodingTests None, Option.None, PipelineAction, + canProxy: false, CancellationToken.None); // Console.WriteLine($"ffmpeg arguments {string.Join(" ", process.StartInfo.ArgumentList)}"); @@ -721,7 +730,7 @@ public class TranscodingTests profileBitDepth, profileVideoFormat, profileAcceleration, - VaapiDriver.RadeonSI, + VaapiDriver.iHD, localStatisticsProvider, streamingMode, () => v);