Browse Source

add videotoolbox capabilities (#2239)

* implement videotoolbox hardware capabilities

* add videotoolbox troubleshooting info

* update changelog
pull/2240/head
Jason Dove 2 weeks ago committed by GitHub
parent
commit
0489741123
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 7
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  3. 2
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  4. 44
      ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs
  5. 3
      ErsatzTV.Application/Troubleshooting/TroubleshootingInfo.cs
  6. 1
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  7. 14
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs
  8. 22
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  9. 2
      ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs
  10. 150
      ErsatzTV.FFmpeg/Capabilities/VideoToolbox/VideoToolboxUtil.cs
  11. 78
      ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs
  12. 1
      ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj
  13. 1
      ErsatzTV.FFmpeg/Format/VideoProfile.cs
  14. 3
      ErsatzTV/Pages/FFmpegEditor.razor
  15. 89
      ErsatzTV/Pages/Troubleshooting.razor
  16. 43
      ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

3
CHANGELOG.md

@ -34,6 +34,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -34,6 +34,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- If the playlist has 3 items and none set to play all, it will schedule 3 items when `Count = 1`
- If the playlist has 3 items and none set to play all, it will schedule 6 items when `Count = 2`
- Using the same playlist in the same schedule for anything other than filler may cause undesired behavior
- Detect supported VideoToolbox hardware encoders
- Software encoders will automatically be used when hardware encoders are unavailable
- Add VideoToolbox Capabilities to Troubleshooting page
### Fixed
- Fix app startup with MySql/MariaDB

7
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs

@ -56,7 +56,12 @@ public class CreateFFmpegProfileHandler : @@ -56,7 +56,12 @@ public class CreateFFmpegProfileHandler :
VideoProfile = request.VideoProfile,
VideoPreset = request.VideoPreset,
AllowBFrames = request.AllowBFrames,
BitDepth = request.BitDepth,
// mpeg2video only supports 8-bit content
BitDepth = request.VideoFormat is FFmpegProfileVideoFormat.Mpeg2Video
? FFmpegProfileBitDepth.EightBit
: request.BitDepth,
VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize,
TonemapAlgorithm = request.TonemapAlgorithm,

2
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs

@ -48,7 +48,7 @@ public class @@ -48,7 +48,7 @@ public class
p.AllowBFrames = update.AllowBFrames;
// mpeg2video only supports 8-bit content
p.BitDepth = update.VideoFormat == FFmpegProfileVideoFormat.Mpeg2Video
p.BitDepth = update.VideoFormat is FFmpegProfileVideoFormat.Mpeg2Video
? FFmpegProfileBitDepth.EightBit
: update.BitDepth;

44
ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using ErsatzTV.Application.FFmpegProfiles;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.FFmpeg;
@ -72,8 +74,9 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -72,8 +74,9 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
.ToListAsync(cancellationToken);
string nvidiaCapabilities = null;
string qsvCapabilities = null;
string vaapiCapabilities = null;
StringBuilder qsvCapabilities = new();
StringBuilder vaapiCapabilities = new();
StringBuilder videoToolboxCapabilities = new();
Option<ConfigElement> maybeFFmpegPath =
await _configElementRepository.GetConfigElement(ConfigElementKey.FFmpegPath);
if (maybeFFmpegPath.IsNone)
@ -99,10 +102,11 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -99,10 +102,11 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
foreach (string qsvDevice in vaapiDevices)
{
QsvOutput output = await _hardwareCapabilitiesFactory.GetQsvOutput(ffmpegPath.Value, qsvDevice);
qsvCapabilities += $"Checking device {qsvDevice}{Environment.NewLine}";
qsvCapabilities += $"Exit Code: {output.ExitCode}{Environment.NewLine}{Environment.NewLine}";
qsvCapabilities += output.Output;
qsvCapabilities += Environment.NewLine + Environment.NewLine;
qsvCapabilities.AppendLine(CultureInfo.InvariantCulture, $"Checking device {qsvDevice}");
qsvCapabilities.AppendLine(CultureInfo.InvariantCulture, $"Exit Code: {output.ExitCode}");
qsvCapabilities.AppendLine();
qsvCapabilities.AppendLine(output.Output);
qsvCapabilities.AppendLine();
}
if (_runtimeInfo.IsOSPlatform(OSPlatform.Linux))
@ -119,13 +123,28 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -119,13 +123,28 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
Optional(GetDriverName(activeDriver)),
vaapiDevice))
{
vaapiCapabilities +=
$"Checking display [{display}] driver [{activeDriver}] device [{vaapiDevice}]{Environment.NewLine}{Environment.NewLine}";
vaapiCapabilities += output;
vaapiCapabilities += Environment.NewLine + Environment.NewLine;
vaapiCapabilities.AppendLine(
CultureInfo.InvariantCulture,
$"Checking display [{display}] driver [{activeDriver}] device [{vaapiDevice}]{Environment.NewLine}");
vaapiCapabilities.AppendLine();
vaapiCapabilities.AppendLine(output);
vaapiCapabilities.AppendLine();
}
}
}
if (_runtimeInfo.IsOSPlatform(OSPlatform.OSX))
{
var encoders = _hardwareCapabilitiesFactory.GetVideoToolboxEncoders();
videoToolboxCapabilities.AppendLine("VideoToolbox Encoders: ");
videoToolboxCapabilities.AppendLine();
foreach (var encoder in encoders)
{
videoToolboxCapabilities.AppendLine(CultureInfo.InvariantCulture, $"\t{encoder}");
}
videoToolboxCapabilities.AppendLine();
videoToolboxCapabilities.AppendLine();
}
}
}
@ -159,8 +178,9 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -159,8 +178,9 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
channels,
channelWatermarks,
nvidiaCapabilities,
qsvCapabilities,
vaapiCapabilities);
qsvCapabilities.ToString(),
vaapiCapabilities.ToString(),
videoToolboxCapabilities.ToString());
}
// lifted from GetFFmpegSettingsHandler

3
ErsatzTV.Application/Troubleshooting/TroubleshootingInfo.cs

@ -16,4 +16,5 @@ public record TroubleshootingInfo( @@ -16,4 +16,5 @@ public record TroubleshootingInfo(
List<ChannelWatermark> Watermarks,
string NvidiaCapabilities,
string QsvCapabilities,
string VaapiCapabilities);
string VaapiCapabilities,
string VideoToolboxCapabilities);

1
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -1139,6 +1139,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -1139,6 +1139,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
{
(VideoFormat.H264, VideoProfile.Main) => VideoProfile.Main,
(VideoFormat.H264, VideoProfile.High) => VideoProfile.High,
(VideoFormat.H264, VideoProfile.High10) => VideoProfile.High10,
_ => Option<string>.None
};

14
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs

@ -6,11 +6,15 @@ public record FFmpegKnownEncoder @@ -6,11 +6,15 @@ public record FFmpegKnownEncoder
public string Name { get; }
public static readonly FFmpegKnownEncoder H264VideoToolbox = new("h264_videotoolbox");
public static readonly FFmpegKnownEncoder HevcVideoToolbox = new("hevc_videotoolbox");
// only list the encoders that we actually check for
public static IList<string> AllEncoders =>
new[]
{
"h264_amf",
"hevc_amf"
};
[
"h264_amf",
"hevc_amf",
"h264_videotoolbox",
"hevc_videotoolbox"
];
}

22
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -7,6 +7,7 @@ using CliWrap; @@ -7,6 +7,7 @@ using CliWrap;
using CliWrap.Buffered;
using ErsatzTV.FFmpeg.Capabilities.Qsv;
using ErsatzTV.FFmpeg.Capabilities.Vaapi;
using ErsatzTV.FFmpeg.Capabilities.VideoToolbox;
using ErsatzTV.FFmpeg.GlobalOption.HardwareAcceleration;
using ErsatzTV.FFmpeg.Runtime;
using Hardware.Info;
@ -106,6 +107,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -106,6 +107,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities),
HardwareAccelerationMode.Qsv => await GetQsvCapabilities(ffmpegPath, vaapiDevice),
HardwareAccelerationMode.Vaapi => await GetVaapiCapabilities(vaapiDisplay, vaapiDriver, vaapiDevice),
HardwareAccelerationMode.VideoToolbox => new VideoToolboxHardwareCapabilities(ffmpegCapabilities, _logger),
HardwareAccelerationMode.Amf => new AmfHardwareCapabilities(),
_ => new DefaultHardwareCapabilities()
};
@ -113,6 +115,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -113,6 +115,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
public async Task<string> GetNvidiaOutput(string ffmpegPath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return string.Empty;
}
string[] arguments =
{
"-f", "lavfi",
@ -136,6 +143,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -136,6 +143,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
public async Task<QsvOutput> GetQsvOutput(string ffmpegPath, Option<string> qsvDevice)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return new QsvOutput(0, string.Empty);
}
var option = new QsvHardwareAccelerationOption(qsvDevice, FFmpegCapability.Software);
var arguments = option.GlobalOptions.ToList();
@ -155,6 +167,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -155,6 +167,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
public async Task<Option<string>> GetVaapiOutput(string display, Option<string> vaapiDriver, string vaapiDevice)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return Option<string>.None;
}
BufferedCommandResult whichResult = await Cli.Wrap("which")
.WithArguments("vainfo")
.WithValidation(CommandResultValidation.None)
@ -244,6 +261,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -244,6 +261,11 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return [];
}
public List<string> GetVideoToolboxEncoders()
{
return VideoToolboxUtil.GetAvailableEncoders();
}
private async Task<IReadOnlySet<string>> GetFFmpegCapabilities(
string ffmpegPath,
string capabilities,

2
ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilitiesFactory.cs

@ -25,4 +25,6 @@ public interface IHardwareCapabilitiesFactory @@ -25,4 +25,6 @@ public interface IHardwareCapabilitiesFactory
List<CpuModel> GetCpuList();
List<VideoControllerModel> GetVideoControllerList();
List<string> GetVideoToolboxEncoders();
}

150
ErsatzTV.FFmpeg/Capabilities/VideoToolbox/VideoToolboxUtil.cs

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
using System.Runtime.InteropServices;
using System.Text;
namespace ErsatzTV.FFmpeg.Capabilities.VideoToolbox;
internal static partial class VideoToolboxUtil
{
private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
private const string VideoToolbox = "/System/Library/Frameworks/VideoToolbox.framework/VideoToolbox";
private const string LibSystem = "/usr/lib/libSystem.dylib";
[LibraryImport(CoreFoundation)]
private static partial long CFArrayGetCount(IntPtr array);
[LibraryImport(CoreFoundation)]
private static partial IntPtr CFArrayGetValueAtIndex(IntPtr array, int index);
[LibraryImport(CoreFoundation)]
private static partial IntPtr CFDictionaryGetValue(IntPtr dict, IntPtr key);
[LibraryImport(CoreFoundation)]
private static partial IntPtr CFStringGetLength(IntPtr theString);
[LibraryImport(CoreFoundation)]
private static partial IntPtr CFStringGetCStringPtr(IntPtr theString, uint encoding);
[LibraryImport(CoreFoundation, StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.I1)]
private static partial bool CFStringGetCString(IntPtr theString, byte[] buffer, long bufferSize, uint encoding);
[LibraryImport(CoreFoundation)]
private static partial void CFRelease(IntPtr cf);
[LibraryImport(VideoToolbox)]
private static partial int VTCopyVideoEncoderList(IntPtr options, out IntPtr listOfEncoders);
[LibraryImport(LibSystem, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr dlopen(string path, int mode);
[LibraryImport(LibSystem, StringMarshalling = StringMarshalling.Utf8)]
private static partial IntPtr dlsym(IntPtr handle, string symbol);
[LibraryImport(LibSystem)]
private static partial int dlclose(IntPtr handle);
private static IntPtr GetCFString(string frameworkPath, string symbolName)
{
IntPtr frameworkHandle = dlopen(frameworkPath, 0); // RTLD_NOW
if (frameworkHandle == IntPtr.Zero)
{
return IntPtr.Zero;
}
try
{
IntPtr symbol = dlsym(frameworkHandle, symbolName);
return Marshal.ReadIntPtr(symbol);
}
finally
{
_ = dlclose(frameworkHandle);
}
}
private static string? CFStringToString(IntPtr cfString)
{
if (cfString == IntPtr.Zero)
{
return null;
}
const uint kCFStringEncodingUTF8 = 0x08000100;
IntPtr cStringPtr = CFStringGetCStringPtr(cfString, kCFStringEncodingUTF8);
if (cStringPtr != IntPtr.Zero)
{
return Marshal.PtrToStringAnsi(cStringPtr);
}
long length = CFStringGetLength(cfString);
if (length == 0)
{
return string.Empty;
}
long maxSize = length * 4 + 1;
byte[] buffer = new byte[maxSize];
if (CFStringGetCString(cfString, buffer, maxSize, kCFStringEncodingUTF8))
{
int terminator = Array.IndexOf(buffer, (byte)0);
int actualLength = terminator >= 0 ? terminator : buffer.Length;
return Encoding.UTF8.GetString(buffer, 0, actualLength);
}
return null;
}
internal static List<string> GetAvailableEncoders()
{
var encoderNames = new List<string>();
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return encoderNames;
}
IntPtr kVTVideoEncoderList_EncoderName = GetCFString(VideoToolbox, "kVTVideoEncoderList_EncoderName");
if (kVTVideoEncoderList_EncoderName == IntPtr.Zero)
{
Console.Error.WriteLine("Failed to load kVTVideoEncoderList_EncoderName symbol.");
return encoderNames;
}
IntPtr encoderList = IntPtr.Zero;
try
{
int status = VTCopyVideoEncoderList(IntPtr.Zero, out encoderList);
if (status != 0 || encoderList == IntPtr.Zero)
{
Console.Error.WriteLine($"VTCopyVideoEncoderList failed with status: {status}");
return encoderNames;
}
var count = (int)CFArrayGetCount(encoderList);
for (int i = 0; i < count; i++)
{
IntPtr encoderDict = CFArrayGetValueAtIndex(encoderList, i);
if (encoderDict == IntPtr.Zero) continue;
IntPtr encoderNameCfString = CFDictionaryGetValue(encoderDict, kVTVideoEncoderList_EncoderName);
string? encoderName = CFStringToString(encoderNameCfString);
if (!string.IsNullOrEmpty(encoderName))
{
encoderNames.Add(encoderName);
}
}
}
finally
{
if (encoderList != IntPtr.Zero)
{
CFRelease(encoderList);
}
}
return encoderNames;
}
}

78
ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using ErsatzTV.FFmpeg.Capabilities.VideoToolbox;
using ErsatzTV.FFmpeg.Format;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.FFmpeg.Capabilities;
public class VideoToolboxHardwareCapabilities : IHardwareCapabilities
{
private static readonly ConcurrentDictionary<string, bool> Encoders = new ();
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly ILogger _logger;
public VideoToolboxHardwareCapabilities(IFFmpegCapabilities ffmpegCapabilities, ILogger logger)
{
_ffmpegCapabilities = ffmpegCapabilities;
_logger = logger;
}
public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat, bool isHdr)
{
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return (videoFormat, bitDepth) switch
{
// 10-bit h264 decoding is likely not support by any hardware
(VideoFormat.H264, 10) => FFmpegCapability.Software,
_ => FFmpegCapability.Hardware
};
}
public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && Encoders.IsEmpty)
{
var encoderList = VideoToolboxUtil.GetAvailableEncoders();
_logger.LogDebug("VideoToolbox reports {Count} encoders", encoderList.Count);
// we only really care about h264 and hevc hardware encoders
foreach (var encoder in encoderList)
{
if (encoder.Contains("HEVC (HW)", StringComparison.OrdinalIgnoreCase))
{
Encoders.AddOrUpdate(VideoFormat.Hevc, true, (_, _) => true);
}
if (encoder.Contains("H.264 (HW)", StringComparison.OrdinalIgnoreCase))
{
Encoders.AddOrUpdate(VideoFormat.H264, true, (_, _) => true);
}
}
}
int bitDepth = maybePixelFormat.Map(pf => pf.BitDepth).IfNone(8);
return (videoFormat, bitDepth) switch
{
// 10-bit h264 encoding is not support by any hardware
(VideoFormat.H264, 10) => FFmpegCapability.Software,
(VideoFormat.H264, 8) =>
_ffmpegCapabilities.HasEncoder(FFmpegKnownEncoder.H264VideoToolbox) && Encoders.ContainsKey(videoFormat)
? FFmpegCapability.Hardware
: FFmpegCapability.Software,
(VideoFormat.Hevc, _) =>
_ffmpegCapabilities.HasEncoder(FFmpegKnownEncoder.HevcVideoToolbox) && Encoders.ContainsKey(videoFormat)
? FFmpegCapability.Hardware
: FFmpegCapability.Software,
_ => FFmpegCapability.Software
};
}
public Option<RateControlMode> GetRateControlMode(string videoFormat, Option<IPixelFormat> maybePixelFormat) =>
Option<RateControlMode>.None;
}

1
ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj

@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
<Nullable>enable</Nullable>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>

1
ErsatzTV.FFmpeg/Format/VideoProfile.cs

@ -4,4 +4,5 @@ public static class VideoProfile @@ -4,4 +4,5 @@ public static class VideoProfile
{
public const string Main = "main";
public const string High = "high";
public const string High10 = "high10";
}

3
ErsatzTV/Pages/FFmpegEditor.razor

@ -77,10 +77,11 @@ @@ -77,10 +77,11 @@
</div>
<MudSelect @bind-Value="_model.VideoProfile"
For="@(() => _model.VideoProfile)"
Disabled="@(_model.VideoFormat != FFmpegProfileVideoFormat.H264 || _model.HardwareAcceleration != HardwareAccelerationKind.Nvenc && _model.HardwareAcceleration != HardwareAccelerationKind.Qsv && _model.HardwareAcceleration != HardwareAccelerationKind.None)"
Disabled="@(_model.VideoFormat != FFmpegProfileVideoFormat.H264 || _model.HardwareAcceleration != HardwareAccelerationKind.Nvenc && _model.HardwareAcceleration != HardwareAccelerationKind.Qsv && _model.HardwareAcceleration != HardwareAccelerationKind.VideoToolbox && _model.HardwareAcceleration != HardwareAccelerationKind.None)"
Clearable="true">
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High">high</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High10">high10</MudSelectItem>
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">

89
ErsatzTV/Pages/Troubleshooting.razor

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
@page "/system/troubleshooting"
@using System.Runtime.InteropServices
@using System.Text.Json
@using System.Text.Json.Serialization
@using ErsatzTV.Application.Troubleshooting
@ -27,42 +28,63 @@ @@ -27,42 +28,63 @@
Copy
</MudButton>
</MudExpansionPanel>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">NVIDIA Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
<code @ref="_nvidiaView">@_nvidiaCapabilities</code>
</pre>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_nvidiaView)">
Copy
</MudButton>
</MudExpansionPanel>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">QSV Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
@if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
<MudText Typo="Typo.h5" Class="mt-10 mb-2">VideoToolbox Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
<code @ref="_videoToolboxView">@_videoToolboxCapabilities</code>
</pre>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_videoToolboxView)">
Copy
</MudButton>
</MudExpansionPanel>
}
else
{
<MudText Typo="Typo.h5" Class="mt-10 mb-2">NVIDIA Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
<code @ref="_nvidiaView">@_nvidiaCapabilities</code>
</pre>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_nvidiaView)">
Copy
</MudButton>
</MudExpansionPanel>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">QSV Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
<code @ref="_qsvView">@_qsvCapabilities</code>
</pre>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_qsvView)">
Copy
</MudButton>
</MudExpansionPanel>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">VAAPI Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_qsvView)">
Copy
</MudButton>
</MudExpansionPanel>
}
@if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
<MudText Typo="Typo.h5" Class="mt-10 mb-2">VAAPI Capabilities</MudText>
<MudDivider Class="mb-6"/>
<MudExpansionPanel Class="mb-6">
<div class="overflow-y-scroll" style="max-height: 500px">
<pre>
<code @ref="_vaapiView">@_vaapiCapabilities</code>
</pre>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_vaapiView)">
Copy
</MudButton>
</MudExpansionPanel>
</div>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="mt-4" OnClick="() => CopyToClipboard(_vaapiView)">
Copy
</MudButton>
</MudExpansionPanel>
}
</MudContainer>
</div>
</MudForm>
@ -73,10 +95,12 @@ @@ -73,10 +95,12 @@
private string _nvidiaCapabilities;
private string _qsvCapabilities;
private string _vaapiCapabilities;
private string _videoToolboxCapabilities;
private ElementReference _troubleshootingView;
private ElementReference _nvidiaView;
private ElementReference _qsvView;
private ElementReference _vaapiView;
private ElementReference _videoToolboxView;
public void Dispose()
{
@ -112,6 +136,7 @@ @@ -112,6 +136,7 @@
_nvidiaCapabilities = info.NvidiaCapabilities;
_qsvCapabilities = info.QsvCapabilities;
_vaapiCapabilities = info.VaapiCapabilities;
_videoToolboxCapabilities = info.VideoToolboxCapabilities;
}
catch (Exception ex)
{

43
ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.FFmpeg.Format;
using ErsatzTV.ViewModels;
using FluentValidation;
using FluentValidation.Results;
@ -7,37 +8,37 @@ namespace ErsatzTV.Validators; @@ -7,37 +8,37 @@ namespace ErsatzTV.Validators;
public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfileEditViewModel>
{
private static readonly List<FFmpegProfileVideoFormat> QsvFormats = new()
{
private static readonly List<FFmpegProfileVideoFormat> QsvFormats =
[
FFmpegProfileVideoFormat.H264,
FFmpegProfileVideoFormat.Hevc,
FFmpegProfileVideoFormat.Mpeg2Video
};
];
private static readonly List<FFmpegProfileVideoFormat> NvencFormats = new()
{
private static readonly List<FFmpegProfileVideoFormat> NvencFormats =
[
FFmpegProfileVideoFormat.H264,
FFmpegProfileVideoFormat.Hevc
};
];
private static readonly List<FFmpegProfileVideoFormat> VaapiFormats = new()
{
private static readonly List<FFmpegProfileVideoFormat> VaapiFormats =
[
FFmpegProfileVideoFormat.H264,
FFmpegProfileVideoFormat.Hevc,
FFmpegProfileVideoFormat.Mpeg2Video
};
];
private static readonly List<FFmpegProfileVideoFormat> VideoToolboxFormats = new()
{
private static readonly List<FFmpegProfileVideoFormat> VideoToolboxFormats =
[
FFmpegProfileVideoFormat.H264,
FFmpegProfileVideoFormat.Hevc
};
];
private static readonly List<FFmpegProfileVideoFormat> AmfFormats = new()
{
private static readonly List<FFmpegProfileVideoFormat> AmfFormats =
[
FFmpegProfileVideoFormat.H264,
FFmpegProfileVideoFormat.Hevc
};
];
public FFmpegProfileEditViewModelValidator()
{
@ -97,6 +98,18 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfi @@ -97,6 +98,18 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfi
() => RuleFor(x => x.BitDepth)
.Must(bd => bd is FFmpegProfileBitDepth.EightBit)
.WithMessage("Mpeg2Video does not support 10-bit content"));
When(
x => x.VideoFormat == FFmpegProfileVideoFormat.H264 && x.BitDepth == FFmpegProfileBitDepth.TenBit,
() => RuleFor(x => x.VideoProfile)
.Must(vp => vp == VideoProfile.High10)
.WithMessage("VideoProfile must be high10 with 10-bit h264"));
When(
x => x.VideoFormat == FFmpegProfileVideoFormat.H264 && x.BitDepth == FFmpegProfileBitDepth.EightBit,
() => RuleFor(x => x.VideoProfile)
.Must(vp => vp != VideoProfile.High10)
.WithMessage("VideoProfile cannot be high10 with 8-bit h264"));
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>

Loading…
Cancel
Save