* lower gop size and keyframe interval * update changelog * fix build using latest dotnet sdk * fixes
505 lines
24 KiB
Plaintext
505 lines
24 KiB
Plaintext
@page "/ffmpeg/{Id:int}"
|
|
@page "/ffmpeg/add"
|
|
@using System.Runtime.InteropServices
|
|
@using ErsatzTV.Application.FFmpegProfiles
|
|
@using ErsatzTV.Application.Resolutions
|
|
@using ErsatzTV.Core.FFmpeg
|
|
@using ErsatzTV.FFmpeg.Format
|
|
@using ErsatzTV.Validators
|
|
@using FluentValidation.Results
|
|
@using Microsoft.Extensions.Caching.Memory
|
|
@implements IDisposable
|
|
@inject NavigationManager NavigationManager
|
|
@inject ILogger<FFmpegEditor> Logger
|
|
@inject ISnackbar Snackbar
|
|
@inject IMediator Mediator
|
|
@inject IMemoryCache MemoryCache
|
|
@inject PersistentComponentState ApplicationState
|
|
|
|
<MudForm Model="@_model" @ref="@_form" Validation="@(_validator.ValidateValue)" ValidationDelay="0" Style="max-height: 100%">
|
|
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-6" OnClick="@HandleSubmitAsync" StartIcon="@(IsEdit ? Icons.Material.Filled.Save : Icons.Material.Filled.Add)">@(IsEdit ? "Save Profile" : "Add Profile")</MudButton>
|
|
</MudPaper>
|
|
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
|
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
|
|
<MudText Typo="Typo.h5" Class="mb-2">General</MudText>
|
|
<MudDivider Class="mb-6"/>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Name</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.Name" For="@(() => _model.Name)" Immediate="true"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Thread Count</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="@_model.ThreadCount" For="@(() => _model.ThreadCount)"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Normalize Audio</MudText>
|
|
</div>
|
|
<MudCheckBox @bind-Value="@_model.NormalizeAudio" For="@(() => _model.NormalizeAudio)" Dense="true"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Normalize Video</MudText>
|
|
</div>
|
|
<MudCheckBox @bind-Value="@_model.NormalizeVideo" For="@(() => _model.NormalizeVideo)" Dense="true"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Preferred Resolution</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
|
|
@foreach (ResolutionViewModel resolution in _resolutions)
|
|
{
|
|
<MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
@if (_model.NormalizeVideo)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Scaling Behavior</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.ScalingBehavior" For="@(() => _model.ScalingBehavior)">
|
|
<MudSelectItem Value="@ScalingBehavior.ScaleAndPad">Scale and Pad</MudSelectItem>
|
|
<MudSelectItem Value="@ScalingBehavior.Stretch">Stretch</MudSelectItem>
|
|
<MudSelectItem Value="@ScalingBehavior.Crop">Crop</MudSelectItem>
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Pad Mode</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.PadMode"
|
|
For="@(() => _model.PadMode)"
|
|
Disabled="_model.HardwareAcceleration is not HardwareAccelerationKind.Vaapi"
|
|
HelperText="If hardware padding is green, software padding should be used instead">
|
|
<MudSelectItem Value="@FilterMode.HardwareIfPossible">Hardware If Possible</MudSelectItem>
|
|
<MudSelectItem Value="@FilterMode.Software">Software</MudSelectItem>
|
|
</MudSelect>
|
|
</MudStack>
|
|
}
|
|
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Video</MudText>
|
|
<MudDivider Class="mb-6"/>
|
|
@if (_model.NormalizeVideo)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Format</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.VideoFormat" For="@(() => _model.VideoFormat)">
|
|
<MudSelectItem Value="@FFmpegProfileVideoFormat.H264">h264</MudSelectItem>
|
|
<MudSelectItem Value="@FFmpegProfileVideoFormat.Hevc">hevc</MudSelectItem>
|
|
<MudSelectItem Value="@FFmpegProfileVideoFormat.Mpeg2Video">mpeg-2</MudSelectItem>
|
|
@if (_model.HardwareAcceleration is (HardwareAccelerationKind.Nvenc or HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv))
|
|
{
|
|
<MudSelectItem Value="@FFmpegProfileVideoFormat.Av1">av1</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Profile</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.VideoProfile"
|
|
For="@(() => _model.VideoProfile)"
|
|
Disabled="@(_model.VideoFormat != FFmpegProfileVideoFormat.H264 || !HardwareAccelSupportsH264Profile(_model.HardwareAcceleration))"
|
|
Clearable="true">
|
|
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem>
|
|
<MudSelectItem Value="@VideoProfile.High">high</MudSelectItem>
|
|
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Nvenc)
|
|
{
|
|
<MudSelectItem Value="@VideoProfile.High444p">high444p</MudSelectItem>
|
|
}
|
|
else
|
|
{
|
|
<MudSelectItem Value="@VideoProfile.High10">high10</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Preset</MudText>
|
|
</div>
|
|
@{
|
|
ICollection<string> presets = FFmpegLibraryHelper.PresetsForFFmpegProfile(
|
|
_model.HardwareAcceleration,
|
|
_model.VideoFormat,
|
|
_model.BitDepth);
|
|
}
|
|
<MudSelect @bind-Value="_model.VideoPreset"
|
|
For="@(() => _model.VideoPreset)"
|
|
Disabled="@(presets.Count == 0)"
|
|
Clearable="true">
|
|
@foreach (string preset in presets)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(preset))
|
|
{
|
|
<MudSelectItem Value="@preset">@preset</MudSelectItem>
|
|
}
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Allow B-Frames</MudText>
|
|
</div>
|
|
<MudCheckBox @bind-Value="@_model.AllowBFrames" For="@(() => _model.AllowBFrames)" Dense="true"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Bit Depth</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.BitDepth" For="@(() => _model.BitDepth)">
|
|
<MudSelectItem Value="@FFmpegProfileBitDepth.EightBit">8-bit</MudSelectItem>
|
|
<MudSelectItem Value="@FFmpegProfileBitDepth.TenBit">10-bit</MudSelectItem>
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Bitrate</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.VideoBitrate" For="@(() => _model.VideoBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Buffer Size</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Hardware Acceleration</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)">
|
|
@foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds)
|
|
{
|
|
<MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
@if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>VAAPI Driver</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.VaapiDriver" For="@(() => _model.VaapiDriver)">
|
|
@foreach (VaapiDriver driver in Enum.GetValues<VaapiDriver>())
|
|
{
|
|
<MudSelectItem Value="@driver">@driver</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>VAAPI Display</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.VaapiDisplay" For="@(() => _model.VaapiDisplay)">
|
|
@foreach (string display in _vaapiDisplays)
|
|
{
|
|
<MudSelectItem Value="@display">@display</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
}
|
|
|
|
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>@(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device")</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.VaapiDevice" For="@(() => _model.VaapiDevice)">
|
|
@foreach (string device in _vaapiDevices)
|
|
{
|
|
<MudSelectItem Value="@device">@device</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
}
|
|
}
|
|
|
|
@if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>QSV Extra Hardware Frames</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.QsvExtraHardwareFrames" For="@(() => _model.QsvExtraHardwareFrames)"/>
|
|
</MudStack>
|
|
}
|
|
else
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Tonemap Algorithm</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.TonemapAlgorithm" For="@(() => _model.TonemapAlgorithm)">
|
|
@foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues<FFmpegProfileTonemapAlgorithm>())
|
|
{
|
|
<MudSelectItem Value="@algorithm">@algorithm</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
</MudStack>
|
|
}
|
|
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Normalize Frame Rate</MudText>
|
|
</div>
|
|
<MudCheckBox @bind-Value="@_model.NormalizeFramerate" For="@(() => _model.NormalizeFramerate)" Dense="true"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Normalize Colors</MudText>
|
|
</div>
|
|
<MudCheckBox @bind-Value="@_model.NormalizeColors" For="@(() => _model.NormalizeColors)" Dense="true"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Auto Deinterlace Video</MudText>
|
|
</div>
|
|
<MudCheckBox @bind-Value="@_model.DeinterlaceVideo" For="@(() => _model.DeinterlaceVideo)" Dense="true"/>
|
|
</MudStack>
|
|
}
|
|
else
|
|
{
|
|
<MudText>Video will be copied as-is, including timestamps, which *will* cause issues with most clients</MudText>
|
|
}
|
|
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Audio</MudText>
|
|
<MudDivider Class="mb-6"/>
|
|
@if (_model.NormalizeAudio)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Format</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)" HelperText="@(_model.AudioFormat is FFmpegProfileAudioFormat.AacLatm ? "aac (latm) is ONLY intended for DVB-C; anything else should use aac instead which uses ADTS" : string.Empty)"> <MudSelectItem Value="@FFmpegProfileAudioFormat.Aac">aac</MudSelectItem>
|
|
<MudSelectItem Value="@FFmpegProfileAudioFormat.Ac3">ac3</MudSelectItem>
|
|
<MudSelectItem Value="@FFmpegProfileAudioFormat.AacLatm">aac (latm)</MudSelectItem>
|
|
</MudSelect>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Bitrate</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.AudioBitrate" For="@(() => _model.AudioBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Buffer Size</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.AudioBufferSize" For="@(() => _model.AudioBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Channels</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.AudioChannels" For="@(() => _model.AudioChannels)"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Sample Rate</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/>
|
|
</MudStack>
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Normalize Loudness</MudText>
|
|
</div>
|
|
<MudSelect @bind-Value="_model.NormalizeLoudnessMode" For="@(() => _model.NormalizeLoudnessMode)">
|
|
<MudSelectItem Value="@NormalizeLoudnessMode.Off">Off</MudSelectItem>
|
|
<MudSelectItem Value="@NormalizeLoudnessMode.LoudNorm">loudnorm</MudSelectItem>
|
|
</MudSelect>
|
|
</MudStack>
|
|
@if (_model.NormalizeLoudnessMode is NormalizeLoudnessMode.LoudNorm)
|
|
{
|
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
|
|
<div class="d-flex">
|
|
<MudText>Target Loudness</MudText>
|
|
</div>
|
|
<MudTextField @bind-Value="_model.TargetLoudness" For="@(() => _model.TargetLoudness)" Adornment="Adornment.End" AdornmentText="LUFS"/>
|
|
</MudStack>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<MudText>Audio will be copied as-is, including timestamps, which *will* cause issues with most clients</MudText>
|
|
}
|
|
</MudContainer>
|
|
</div>
|
|
</MudForm>
|
|
|
|
@code {
|
|
private CancellationTokenSource _cts;
|
|
|
|
[Parameter]
|
|
public int Id { get; set; }
|
|
|
|
private FFmpegProfileEditViewModel _model = new();
|
|
private readonly FFmpegProfileEditViewModelValidator _validator = new();
|
|
private MudForm _form;
|
|
|
|
private List<ResolutionViewModel> _resolutions = [];
|
|
private List<HardwareAccelerationKind> _hardwareAccelerationKinds = [];
|
|
private List<string> _vaapiDisplays = [];
|
|
private List<string> _vaapiDevices = [];
|
|
private PersistingComponentStateSubscription _persistingSubscription;
|
|
|
|
public void Dispose()
|
|
{
|
|
_persistingSubscription.Dispose();
|
|
|
|
_cts?.Cancel();
|
|
_cts?.Dispose();
|
|
}
|
|
|
|
protected override Task OnInitializedAsync()
|
|
{
|
|
_persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
|
|
|
|
return base.OnInitializedAsync();
|
|
}
|
|
|
|
protected override async Task OnParametersSetAsync()
|
|
{
|
|
_cts?.Cancel();
|
|
_cts?.Dispose();
|
|
_cts = new CancellationTokenSource();
|
|
var token = _cts.Token;
|
|
|
|
try
|
|
{
|
|
if (!ApplicationState.TryTakeFromJson("_resolutions", out List<ResolutionViewModel> restoredResolutions))
|
|
{
|
|
_resolutions = await Mediator.Send(new GetAllResolutions(), token);
|
|
}
|
|
else
|
|
{
|
|
_resolutions = restoredResolutions;
|
|
}
|
|
|
|
if (!ApplicationState.TryTakeFromJson("_hardwareAccelerationKinds", out List<HardwareAccelerationKind> restoredHardwareAccelerationKinds))
|
|
{
|
|
_hardwareAccelerationKinds = await Mediator.Send(new GetSupportedHardwareAccelerationKinds(), token);
|
|
}
|
|
else
|
|
{
|
|
_hardwareAccelerationKinds = restoredHardwareAccelerationKinds;
|
|
}
|
|
|
|
if (IsEdit)
|
|
{
|
|
if (!ApplicationState.TryTakeFromJson("_model", out FFmpegProfileEditViewModel restoredProfile))
|
|
{
|
|
Option<FFmpegProfileViewModel> maybeProfile = await Mediator.Send(new GetFFmpegProfileById(Id), token);
|
|
foreach (FFmpegProfileViewModel profile in maybeProfile)
|
|
{
|
|
_model = new FFmpegProfileEditViewModel(profile);
|
|
}
|
|
|
|
if (maybeProfile.IsNone)
|
|
{
|
|
NavigationManager.NavigateTo("404");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_model = restoredProfile;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_model = new FFmpegProfileEditViewModel(await Mediator.Send(new NewFFmpegProfile(), token));
|
|
}
|
|
|
|
if (!_hardwareAccelerationKinds.Contains(_model.HardwareAcceleration))
|
|
{
|
|
_model.HardwareAcceleration = HardwareAccelerationKind.None;
|
|
}
|
|
|
|
if (!MemoryCache.TryGetValue("ffmpeg.render_devices", out List<string> vaapiDevices))
|
|
{
|
|
vaapiDevices = ["/dev/dri/renderD128"];
|
|
}
|
|
|
|
_vaapiDevices = vaapiDevices.OrderBy(s => s).ToList();
|
|
|
|
if (!MemoryCache.TryGetValue("ffmpeg.vaapi_displays", out List<string> vaapiDisplays))
|
|
{
|
|
vaapiDisplays = ["drm"];
|
|
}
|
|
|
|
_vaapiDisplays = vaapiDisplays.OrderBy(s => s).ToList();
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// do nothing
|
|
}
|
|
}
|
|
|
|
private bool HardwareAccelSupportsH264Profile(HardwareAccelerationKind accel) =>
|
|
accel switch
|
|
{
|
|
HardwareAccelerationKind.None => true,
|
|
HardwareAccelerationKind.Nvenc => true,
|
|
HardwareAccelerationKind.Qsv => true,
|
|
HardwareAccelerationKind.Vaapi => true,
|
|
HardwareAccelerationKind.VideoToolbox => true,
|
|
_ => false,
|
|
};
|
|
|
|
private Task PersistData()
|
|
{
|
|
ApplicationState.PersistAsJson("_model", _model);
|
|
ApplicationState.PersistAsJson("_resolutions", _resolutions);
|
|
ApplicationState.PersistAsJson("_hardwareAccelerationKinds", _hardwareAccelerationKinds);
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private bool IsEdit => Id != 0;
|
|
|
|
private async Task HandleSubmitAsync()
|
|
{
|
|
await _form.Validate();
|
|
if (!_form.IsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var cts = new CancellationTokenSource();
|
|
|
|
try
|
|
{
|
|
ValidationResult result = await _validator.ValidateAsync(_model, cts.Token);
|
|
if (result.IsValid)
|
|
{
|
|
Seq<BaseError> errorMessage = IsEdit
|
|
? (await Mediator.Send(_model.ToUpdate(), cts.Token)).LeftToSeq()
|
|
: (await Mediator.Send(_model.ToCreate(), cts.Token)).LeftToSeq();
|
|
|
|
errorMessage.HeadOrNone().Match(
|
|
error =>
|
|
{
|
|
Snackbar.Add("Unexpected error saving ffmpeg profile");
|
|
Logger.LogError("Unexpected error saving ffmpeg profile: {Error}", error.Value);
|
|
},
|
|
() => NavigationManager.NavigateTo("ffmpeg"));
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// do nothing
|
|
}
|
|
}
|
|
}
|