diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d0279c4..5c3920dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,9 +16,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - This means resetting the playout will reset the rerun history - Items will still be scheduled from the rerun collection if it is used before the first run collection - Otherwise, the rerun collection would be considered "empty" which prevents the playout build altogether +- Add `Rkmpp` hardware acceleration by @peterdey + - This is supported using jellyfin-ffmpeg7 on devices like Orange Pi 5 Plus and NanoPi R6S ### Fixed -- Fix green output when libplacebo tonemapping is used with NVIDIA acceleration +- Fix green output when libplacebo tonemapping is used with NVIDIA acceleration and 10-bit output in FFmpeg Profile ## [25.6.0] - 2025-09-14 ### Added diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs index 7a3dcae76..32eedcbd0 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs @@ -72,6 +72,11 @@ public class // result.Add(HardwareAccelerationKind.V4l2m2m); // } + if (ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.Rkmpp)) + { + result.Add(HardwareAccelerationKind.Rkmpp); + } + return result; } diff --git a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs index 54e0f5090..9cb312d52 100644 --- a/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs +++ b/ErsatzTV.Core/Domain/HardwareAccelerationKind.cs @@ -8,5 +8,6 @@ public enum HardwareAccelerationKind Vaapi = 3, VideoToolbox = 4, Amf = 5, - V4l2m2m = 6 + V4l2m2m = 6, + Rkmpp = 7 } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 4ae5b7c22..4f5821792 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -1129,6 +1129,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox, HardwareAccelerationKind.Amf => HardwareAccelerationMode.Amf, HardwareAccelerationKind.V4l2m2m => HardwareAccelerationMode.V4l2m2m, + HardwareAccelerationKind.Rkmpp => HardwareAccelerationMode.Rkmpp, _ => HardwareAccelerationMode.None }; } diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs index 6a46d926f..308638a2a 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs @@ -51,6 +51,7 @@ public class FFmpegCapabilities : IFFmpegCapabilities HardwareAccelerationMode.VideoToolbox => FFmpegKnownHardwareAcceleration.VideoToolbox, HardwareAccelerationMode.OpenCL => FFmpegKnownHardwareAcceleration.OpenCL, HardwareAccelerationMode.Vulkan => FFmpegKnownHardwareAcceleration.Vulkan, + HardwareAccelerationMode.Rkmpp => FFmpegKnownHardwareAcceleration.Rkmpp, _ => Option.None }; diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs index 34f2baa67..3b9893e4b 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs @@ -16,6 +16,8 @@ public record FFmpegKnownEncoder "h264_v4l2m2m", "hevc_v4l2m2m", "h264_videotoolbox", - "hevc_videotoolbox" + "hevc_videotoolbox", + "h264_rkmpp", + "hevc_rkmpp" ]; } diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs index adf36bd7d..bdc377261 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs @@ -10,6 +10,7 @@ public record FFmpegKnownHardwareAcceleration public static readonly FFmpegKnownHardwareAcceleration OpenCL = new("opencl"); public static readonly FFmpegKnownHardwareAcceleration Vulkan = new("vulkan"); public static readonly FFmpegKnownHardwareAcceleration V4l2m2m = new("v4l2m2m"); + public static readonly FFmpegKnownHardwareAcceleration Rkmpp = new("rkmpp"); private FFmpegKnownHardwareAcceleration(string Name) => this.Name = Name; @@ -24,6 +25,7 @@ public record FFmpegKnownHardwareAcceleration VideoToolbox.Name, OpenCL.Name, Vulkan.Name, - V4l2m2m.Name + V4l2m2m.Name, + Rkmpp.Name ]; } diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index eaf919207..3bf501cf3 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -110,6 +110,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareCapabilities(ffmpegCapabilities, _logger), HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(), HardwareAccelerationMode.V4l2m2m => new V4l2m2mHardwareCapabilities(ffmpegCapabilities), + HardwareAccelerationMode.Rkmpp => new RkmppHardwareCapabilities(), _ => new DefaultHardwareCapabilities() }; } diff --git a/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs new file mode 100644 index 000000000..f5065e178 --- /dev/null +++ b/ErsatzTV.FFmpeg/Capabilities/RkmppHardwareCapabilities.cs @@ -0,0 +1,34 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Capabilities; + +public class RkmppHardwareCapabilities : IHardwareCapabilities +{ + public FFmpegCapability CanDecode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat, + bool isHdr) => FFmpegCapability.Hardware; + + public FFmpegCapability CanEncode( + string videoFormat, + Option videoProfile, + Option maybePixelFormat) + { + int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8); + + return (videoFormat, bitDepth) switch + { + // 10-bit hevc encoding is not yet supported by ffmpeg + (VideoFormat.Hevc, 10) => FFmpegCapability.Software, + + // 10-bit h264 encoding is not support by any hardware + (VideoFormat.H264, 10) => FFmpegCapability.Software, + + _ => FFmpegCapability.Hardware + }; + } + + public Option GetRateControlMode(string videoFormat, Option maybePixelFormat) => + Option.None; +} \ No newline at end of file diff --git a/ErsatzTV.FFmpeg/Decoder/DecoderRkmpp.cs b/ErsatzTV.FFmpeg/Decoder/DecoderRkmpp.cs new file mode 100644 index 000000000..86d190e47 --- /dev/null +++ b/ErsatzTV.FFmpeg/Decoder/DecoderRkmpp.cs @@ -0,0 +1,27 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Decoder; + +public class DecoderRkmpp : DecoderBase +{ + protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Software; + + public override string Name => "implicit_rkmpp"; + public override string[] InputOptions(InputFile inputFile) => Array.Empty(); + + /* + public override string[] InputOptions(InputFile inputFile) => + new[] { "-hwaccel_output_format", "drm_prime" }; + */ + + public override FrameState NextState(FrameState currentState) + { + FrameState nextState = base.NextState(currentState); + + return currentState.PixelFormat.Match( + pixelFormat => pixelFormat.BitDepth == 8 + ? nextState with { PixelFormat = new PixelFormatNv12(pixelFormat.Name) } + : nextState with { PixelFormat = new PixelFormatNv15(pixelFormat.Name) }, + () => nextState); + } +} \ No newline at end of file diff --git a/ErsatzTV.FFmpeg/Encoder/Rkmpp/EncoderH264Rkmpp.cs b/ErsatzTV.FFmpeg/Encoder/Rkmpp/EncoderH264Rkmpp.cs new file mode 100644 index 000000000..4140b12af --- /dev/null +++ b/ErsatzTV.FFmpeg/Encoder/Rkmpp/EncoderH264Rkmpp.cs @@ -0,0 +1,32 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Encoder.Rkmpp; + +public class EncoderH264Rkmpp(Option maybeVideoProfile) : EncoderBase +{ + public override string Name => "h264_rkmpp"; + public override StreamKind Kind => StreamKind.Video; + + public override string[] OutputOptions + { + get + { + foreach (string videoProfile in maybeVideoProfile) + { + return + [ + "-c:v", Name, + "-profile:v", videoProfile.ToLowerInvariant() + ]; + } + + return base.OutputOptions; + } + } + + public override FrameState NextState(FrameState currentState) => currentState with + { + VideoFormat = VideoFormat.H264, + FrameDataLocation = FrameDataLocation.Hardware + }; +} \ No newline at end of file diff --git a/ErsatzTV.FFmpeg/Encoder/Rkmpp/EncoderHevcRkmpp.cs b/ErsatzTV.FFmpeg/Encoder/Rkmpp/EncoderHevcRkmpp.cs new file mode 100644 index 000000000..bfa6519e4 --- /dev/null +++ b/ErsatzTV.FFmpeg/Encoder/Rkmpp/EncoderHevcRkmpp.cs @@ -0,0 +1,26 @@ +using ErsatzTV.FFmpeg.Format; + +namespace ErsatzTV.FFmpeg.Encoder.Rkmpp; + +public class EncoderHevcRkmpp : EncoderBase +{ + private readonly int _desiredBitDepth; + + public EncoderHevcRkmpp(int desiredBitDepth) => _desiredBitDepth = desiredBitDepth; + + public override string Name => "hevc_rkmpp"; + public override StreamKind Kind => StreamKind.Video; + + public override string[] OutputOptions => base.OutputOptions.Concat( + new[] + { + "-profile:v", + _desiredBitDepth == 10 ? "main10" : "main" + }).ToArray(); + + public override FrameState NextState(FrameState currentState) => currentState with + { + VideoFormat = VideoFormat.Hevc, + FrameDataLocation = FrameDataLocation.Hardware + }; +} \ No newline at end of file diff --git a/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs b/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs index 092316352..fd6a9cd79 100644 --- a/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/SubtitleHardwareUploadFilter.cs @@ -31,6 +31,9 @@ public class SubtitleHardwareUploadFilter : BaseFilter // leave v4l2m2m in software since we use a software overlay filter HardwareAccelerationMode.V4l2m2m => string.Empty, + // leave rkmpp in software since we use a software overlay filter + HardwareAccelerationMode.Rkmpp => string.Empty, + _ => "hwupload" }; diff --git a/ErsatzTV.FFmpeg/Format/FFmpegFormat.cs b/ErsatzTV.FFmpeg/Format/FFmpegFormat.cs index 3458fda20..9999e530f 100644 --- a/ErsatzTV.FFmpeg/Format/FFmpegFormat.cs +++ b/ErsatzTV.FFmpeg/Format/FFmpegFormat.cs @@ -8,5 +8,6 @@ public class FFmpegFormat public const string YUVA420P = "yuva420p"; public const string P010LE = "p010le"; public const string NV12 = "nv12"; + public const string NV15 = "nv15"; public const string VAAPI = "vaapi"; } diff --git a/ErsatzTV.FFmpeg/Format/PixelFormatNv15.cs b/ErsatzTV.FFmpeg/Format/PixelFormatNv15.cs new file mode 100644 index 000000000..ad0254d32 --- /dev/null +++ b/ErsatzTV.FFmpeg/Format/PixelFormatNv15.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.FFmpeg.Format; + +public class PixelFormatNv15 : IPixelFormat +{ + public PixelFormatNv15(string name) => Name = name; + + public string Name { get; } + + public string FFmpegName => "nv15"; + public int BitDepth => 8; +} diff --git a/ErsatzTV.FFmpeg/GlobalOption/HardwareAcceleration/RkmppHardwareAccelerationOption.cs b/ErsatzTV.FFmpeg/GlobalOption/HardwareAcceleration/RkmppHardwareAccelerationOption.cs new file mode 100644 index 000000000..81f97a3c4 --- /dev/null +++ b/ErsatzTV.FFmpeg/GlobalOption/HardwareAcceleration/RkmppHardwareAccelerationOption.cs @@ -0,0 +1,11 @@ +namespace ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration; + +public class RkmppHardwareAccelerationOption : GlobalOption +{ + public override string[] GlobalOptions => new[] { "-hwaccel", "rkmpp" }; + + public override FrameState NextState(FrameState currentState) => currentState with + { + FrameDataLocation = FrameDataLocation.Hardware + }; +} diff --git a/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs b/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs index d3ff43823..8e35827b2 100644 --- a/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs +++ b/ErsatzTV.FFmpeg/HardwareAccelerationMode.cs @@ -10,5 +10,6 @@ public enum HardwareAccelerationMode Amf = 5, OpenCL = 6, Vulkan = 7, - V4l2m2m = 8 + V4l2m2m = 8, + Rkmpp = 9 } diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs index 70327417f..5cf0257fd 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs @@ -87,7 +87,22 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory fontsFolder, _logger), - // force software pipeline when content is HDR (and not VAAPI or NVENC or QSV) + HardwareAccelerationMode.Rkmpp when capabilities is not NoHardwareCapabilities => new + RkmppPipelineBuilder( + ffmpegCapabilities, + capabilities, + hardwareAccelerationMode, + videoInputFile, + audioInputFile, + watermarkInputFile, + subtitleInputFile, + concatInputFile, + graphicsEngineInput, + reportsFolder, + fontsFolder, + _logger), + + // force software pipeline when content is HDR (and not VAAPI or NVENC or QSV or Rkmpp) _ when isHdrContent => new SoftwarePipelineBuilder( ffmpegCapabilities, HardwareAccelerationMode.None, diff --git a/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs new file mode 100644 index 000000000..6c559d312 --- /dev/null +++ b/ErsatzTV.FFmpeg/Pipeline/RkmppPipelineBuilder.cs @@ -0,0 +1,160 @@ +using ErsatzTV.FFmpeg.Capabilities; +using ErsatzTV.FFmpeg.Decoder; +using ErsatzTV.FFmpeg.Encoder; +using ErsatzTV.FFmpeg.Encoder.Rkmpp; +using ErsatzTV.FFmpeg.Filter; +using ErsatzTV.FFmpeg.Format; +using ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration; +using ErsatzTV.FFmpeg.OutputFormat; +using ErsatzTV.FFmpeg.OutputOption; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.FFmpeg.Pipeline; + +public class RkmppPipelineBuilder : SoftwarePipelineBuilder +{ + private readonly IHardwareCapabilities _hardwareCapabilities; + private readonly ILogger _logger; + + public RkmppPipelineBuilder( + IFFmpegCapabilities ffmpegCapabilities, + IHardwareCapabilities hardwareCapabilities, + HardwareAccelerationMode hardwareAccelerationMode, + Option videoInputFile, + Option audioInputFile, + Option watermarkInputFile, + Option subtitleInputFile, + Option concatInputFile, + Option graphicsEngineInput, + string reportsFolder, + string fontsFolder, + ILogger logger) : base( + ffmpegCapabilities, + hardwareAccelerationMode, + videoInputFile, + audioInputFile, + watermarkInputFile, + subtitleInputFile, + concatInputFile, + graphicsEngineInput, + reportsFolder, + fontsFolder, + logger) + { + _hardwareCapabilities = hardwareCapabilities; + _logger = logger; + _logger.LogDebug("Using RkmppPipelineBuilder"); + } + + protected override FFmpegState SetAccelState( + VideoStream videoStream, + FFmpegState ffmpegState, + FrameState desiredState, + PipelineContext context, + ICollection pipelineSteps) + { + FFmpegCapability decodeCapability = _hardwareCapabilities.CanDecode( + videoStream.Codec, + videoStream.Profile, + videoStream.PixelFormat, + videoStream.ColorParams.IsHdr); + FFmpegCapability encodeCapability = _hardwareCapabilities.CanEncode( + desiredState.VideoFormat, + desiredState.VideoProfile, + desiredState.PixelFormat); + + // use software encoding (rawvideo) when piping to parent hls segmenter + if (ffmpegState.OutputFormat is OutputFormatKind.Nut) + { + encodeCapability = FFmpegCapability.Software; + _logger.LogDebug("Using software encoder"); + } + + if (decodeCapability is FFmpegCapability.Hardware) + { + pipelineSteps.Add(new RkmppHardwareAccelerationOption()); + _logger.LogDebug("Using RkmppHardwareAccelerationOption decoder"); + } + + // disable hw accel if decoder/encoder isn't supported + return ffmpegState with + { + DecoderHardwareAccelerationMode = decodeCapability == FFmpegCapability.Hardware + ? HardwareAccelerationMode.Rkmpp + : HardwareAccelerationMode.None, + EncoderHardwareAccelerationMode = encodeCapability == FFmpegCapability.Hardware + ? HardwareAccelerationMode.Rkmpp + : HardwareAccelerationMode.None + }; + } + + protected override Option SetDecoder( + VideoInputFile videoInputFile, + VideoStream videoStream, + FFmpegState ffmpegState, + PipelineContext context) + { + Option maybeDecoder = (ffmpegState.DecoderHardwareAccelerationMode, videoStream.Codec) switch + { + (HardwareAccelerationMode.Rkmpp, _) => new DecoderRkmpp(), + + _ => GetSoftwareDecoder(videoStream) + }; + + foreach (IDecoder decoder in maybeDecoder) + { + videoInputFile.AddOption(decoder); + return Some(decoder); + } + + return None; + } + + protected override Option GetEncoder( + FFmpegState ffmpegState, + FrameState currentState, + FrameState desiredState) => + (ffmpegState.EncoderHardwareAccelerationMode, desiredState.VideoFormat) switch + { + (HardwareAccelerationMode.Rkmpp, VideoFormat.Hevc) => + new EncoderHevcRkmpp(desiredState.BitDepth), + (HardwareAccelerationMode.Rkmpp, VideoFormat.H264) => + new EncoderH264Rkmpp(desiredState.VideoProfile), + + _ => GetSoftwareEncoder(ffmpegState, currentState, desiredState) + }; + + protected override List SetPixelFormat( + VideoStream videoStream, + Option desiredPixelFormat, + FrameState currentState, + ICollection pipelineSteps) + { + var result = new List(); + + foreach (IPixelFormat pixelFormat in desiredPixelFormat) + { + if (!videoStream.ColorParams.IsBt709) + { + // _logger.LogDebug("Adding colorspace filter"); + var colorspace = new ColorspaceFilter(currentState, videoStream, pixelFormat); + currentState = colorspace.NextState(currentState); + result.Add(colorspace); + } + + if (currentState.PixelFormat.Map(f => f.FFmpegName) != pixelFormat.FFmpegName) + { + _logger.LogDebug( + "Format {A} doesn't equal {B}", + currentState.PixelFormat.Map(f => f.FFmpegName), + pixelFormat.FFmpegName); + + //result.Add(new PixelFormatFilter(pixelFormat)); + } + + pipelineSteps.Add(new PixelFormatOutputOption(pixelFormat)); + } + + return result; + } +} diff --git a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs index 0bbe260c9..7fa6cc722 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/HardwareAccelerationHealthCheck.cs @@ -129,6 +129,9 @@ public class HardwareAccelerationHealthCheck : BaseHealthCheck, IHardwareAcceler case "videotoolbox": result.Add(HardwareAccelerationKind.VideoToolbox); break; + case "rkmpp": + result.Add(HardwareAccelerationKind.Rkmpp); + break; } } diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index ef0e93ac5..ba5bd953e 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -77,7 +77,7 @@ main high @@ -431,6 +431,7 @@ HardwareAccelerationKind.Vaapi => HardwareAccelerationMode.Vaapi, HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox, HardwareAccelerationKind.V4l2m2m => HardwareAccelerationMode.V4l2m2m, + HardwareAccelerationKind.Rkmpp => HardwareAccelerationMode.Rkmpp, _ => HardwareAccelerationMode.None }; diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs index 5bd96f88a..43b7169ea 100644 --- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs +++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs @@ -45,6 +45,12 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator RkmppFormats = + [ + FFmpegProfileVideoFormat.H264, + FFmpegProfileVideoFormat.Hevc + ]; public FFmpegProfileEditViewModelValidator() { RuleFor(x => x.Name).NotEmpty(); @@ -106,6 +112,14 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator x.HardwareAcceleration == HardwareAccelerationKind.Rkmpp, + () => + { + RuleFor(x => x.VideoFormat).Must(c => RkmppFormats.Contains(c)) + .WithMessage("Rkmpp supports formats (h264, hevc)"); + }); + When( x => x.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video, () => RuleFor(x => x.BitDepth)