From d324967afa6bed6f9b5475cbe65941e72a6ddbab Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Sat, 5 Jul 2025 13:07:01 +0000 Subject: [PATCH] use new form layout for ffmpeg profile editor (#2113) --- CHANGELOG.md | 1 + ErsatzTV/Pages/ChannelEditor.razor | 2 +- ErsatzTV/Pages/FFmpegEditor.razor | 455 ++++++++++-------- .../FFmpegProfileEditViewModelValidator.cs | 9 + 4 files changed, 265 insertions(+), 202 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a6ba3b34..65664b117 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Update form layout to be less cramped and to work better on mobile - All new (split) settings pages - Channel editor + - FFmpeg Profile editor ### Fixed - Fix QSV acceleration in docker with older Intel devices diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index 0e3309c35..fb86b0b10 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -20,7 +20,7 @@ - @(IsEdit ? "Save Changes" : "Add Channel") + @(IsEdit ? "Save Channel" : "Add Channel")
diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index ab8e59cfa..087522472 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -7,6 +7,8 @@ @using ErsatzTV.FFmpeg @using ErsatzTV.FFmpeg.Format @using ErsatzTV.FFmpeg.Preset +@using ErsatzTV.Validators +@using FluentValidation.Results @using Microsoft.Extensions.Caching.Memory @implements IDisposable @inject NavigationManager NavigationManager @@ -16,202 +18,255 @@ @inject IMemoryCache MemoryCache @inject PersistentComponentState ApplicationState - - @if (_editContext is not null) - { - - - - - - @(IsEdit ? "Edit FFmpeg Profile" : "Add FFmpeg Profile") - - - - - - - - General - - - - - - - @foreach (ResolutionViewModel resolution in _resolutions) - { - @resolution.Name - } - - - - - Scale and Pad - Stretch - Crop - - - - - Video - - h264 - hevc - mpeg-2 - - - main - high - - @{ - ICollection presets = AvailablePresets.ForAccelAndFormat(MapAccel(_model.HardwareAcceleration), MapVideoFormat(_model.VideoFormat)); - } - - @foreach (string preset in presets) - { - if (!string.IsNullOrWhiteSpace(preset)) - { - @preset - } - } - - - - - - 8-bit - 10-bit - - - - - - - - - - @foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds) - { - @hwAccel - } - - - @if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - @if (_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi) - { - - - @foreach (VaapiDriver driver in Enum.GetValues()) - { - @driver - } - - - } + + + @(IsEdit ? "Save Profile" : "Add Profile") + +
+ + General + + +
+ Name +
+ +
+ +
+ Thread Count +
+ +
+ +
+ Preferred Resolution +
+ + @foreach (ResolutionViewModel resolution in _resolutions) + { + @resolution.Name + } + +
+ +
+ Scaling Behavior +
+ + Scale and Pad + Stretch + Crop + +
+ Video + + +
+ Format +
+ + h264 + hevc + mpeg-2 + +
+ +
+ Profile +
+ + main + high + +
+ +
+ Preset +
+ @{ + ICollection presets = AvailablePresets.ForAccelAndFormat(MapAccel(_model.HardwareAcceleration), MapVideoFormat(_model.VideoFormat)); + } + + @foreach (string preset in presets) + { + if (!string.IsNullOrWhiteSpace(preset)) + { + @preset + } + } + +
+ +
+ Allow B-Frames +
+ +
+ +
+ Bit Depth +
+ + 8-bit + 10-bit + +
+ +
+ Bitrate +
+ +
+ +
+ Buffer Size +
+ +
+ +
+ Hardware Acceleration +
+ + @foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds) + { + @hwAccel + } + +
+ @if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi) + { + +
+ VAAPI Driver +
+ + @foreach (VaapiDriver driver in Enum.GetValues()) + { + @driver + } + +
+ +
+ VAAPI Display +
+ + @foreach (string display in _vaapiDisplays) + { + @display + } + +
+ } - @if (_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi) - { - - - @foreach (string display in _vaapiDisplays) - { - @display - } - - - } + @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) + { + +
+ @(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device") +
+ + @foreach (string device in _vaapiDevices) + { + @device + } + +
+ } + } - @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) - { - - - @foreach (string device in _vaapiDevices) - { - @device - } - - - } - } - @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) - { - - - - } - else - { - - - @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues()) - { - @algorithm - } - - - } - - - - - - - - - Audio - - aac - ac3 - - - - - - - - - - - - - - - - Off - loudnorm - - - - - - - - - - @(IsEdit ? "Save Changes" : "Add Profile") - - - - - } -
+ @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) + { + +
+ QSV Extra Hardware Frames +
+ +
+ } + else + { + +
+ Tonemap Algorithm +
+ + @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues()) + { + @algorithm + } + +
+ } + +
+ Normalize Frame Rate +
+ +
+ +
+ Auto Deinterlace Video +
+ +
+ Audio + + +
+ Format +
+ + aac + ac3 + +
+ +
+ Bitrate +
+ +
+ +
+ Buffer Size +
+ +
+ +
+ Channels +
+ +
+ +
+ Sample Rate +
+ +
+ +
+ Normalize Loudness +
+ + Off + loudnorm + +
+ +
+
@code { private readonly CancellationTokenSource _cts = new(); @@ -220,8 +275,8 @@ public int Id { get; set; } private FFmpegProfileEditViewModel _model = new(); - private EditContext _editContext; - private ValidationMessageStore _messageStore; + private readonly FFmpegProfileEditViewModelValidator _validator = new(); + private MudForm _form; private List _resolutions = new(); private List _hardwareAccelerationKinds = new(); @@ -294,9 +349,6 @@ _model.HardwareAcceleration = HardwareAccelerationKind.None; } - _editContext = new EditContext(_model); - _messageStore = new ValidationMessageStore(_editContext); - if (!MemoryCache.TryGetValue("ffmpeg.render_devices", out List vaapiDevices)) { vaapiDevices = ["/dev/dri/renderD128"]; @@ -325,8 +377,9 @@ private async Task HandleSubmitAsync() { - _messageStore.Clear(); - if (_editContext.Validate()) + await _form.Validate(); + ValidationResult result = await _validator.ValidateAsync(_model, _cts.Token); + if (result.IsValid) { Seq errorMessage = IsEdit ? (await Mediator.Send(_model.ToUpdate(), _cts.Token)).LeftToSeq() : (await Mediator.Send(_model.ToCreate(), _cts.Token)).LeftToSeq(); diff --git a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs index 864a72207..045d815fa 100644 --- a/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs +++ b/ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.ViewModels; using FluentValidation; +using FluentValidation.Results; namespace ErsatzTV.Validators; @@ -97,4 +98,12 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator bd is FFmpegProfileBitDepth.EightBit) .WithMessage("Mpeg2Video does not support 10-bit content")); } + + public Func>> ValidateValue => async (model, propertyName) => + { + ValidationResult result = await ValidateAsync(ValidationContext.CreateWithOptions((FFmpegProfileEditViewModel)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return []; + return result.Errors.Select(e => e.ErrorMessage); + }; }