@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 @using ErsatzTV.FFmpeg.Format @using ErsatzTV.FFmpeg.Preset @using ErsatzTV.Validators @using FluentValidation.Results @using Microsoft.Extensions.Caching.Memory @implements IDisposable @inject NavigationManager NavigationManager @inject ILogger Logger @inject ISnackbar Snackbar @inject IMediator Mediator @inject IMemoryCache MemoryCache @inject PersistentComponentState ApplicationState @(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 is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) {
@(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device")
@foreach (string device in _vaapiDevices) { @device }
} } @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(); [Parameter] public int Id { get; set; } private FFmpegProfileEditViewModel _model = new(); private readonly FFmpegProfileEditViewModelValidator _validator = new(); private MudForm _form; private List _resolutions = new(); private List _hardwareAccelerationKinds = new(); private List _vaapiDisplays = []; private List _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() { if (!ApplicationState.TryTakeFromJson("_resolutions", out List restoredResolutions)) { _resolutions = await Mediator.Send(new GetAllResolutions(), _cts.Token); } else { _resolutions = restoredResolutions; } if (!ApplicationState.TryTakeFromJson("_hardwareAccelerationKinds", out List restoredHardwareAccelerationKinds)) { _hardwareAccelerationKinds = await Mediator.Send(new GetSupportedHardwareAccelerationKinds(), _cts.Token); } else { _hardwareAccelerationKinds = restoredHardwareAccelerationKinds; } if (IsEdit) { if (!ApplicationState.TryTakeFromJson("_model", out FFmpegProfileEditViewModel restoredProfile)) { Option maybeProfile = await Mediator.Send(new GetFFmpegProfileById(Id), _cts.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(), _cts.Token)); } if (!_hardwareAccelerationKinds.Contains(_model.HardwareAcceleration)) { _model.HardwareAcceleration = HardwareAccelerationKind.None; } if (!MemoryCache.TryGetValue("ffmpeg.render_devices", out List vaapiDevices)) { vaapiDevices = ["/dev/dri/renderD128"]; } _vaapiDevices = vaapiDevices.OrderBy(s => s).ToList(); if (!MemoryCache.TryGetValue("ffmpeg.vaapi_displays", out List vaapiDisplays)) { vaapiDisplays = ["drm"]; } _vaapiDisplays = vaapiDisplays.OrderBy(s => s).ToList(); } 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(); 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(); errorMessage.HeadOrNone().Match( error => { Snackbar.Add("Unexpected error saving ffmpeg profile"); Logger.LogError("Unexpected error saving ffmpeg profile: {Error}", error.Value); }, () => NavigationManager.NavigateTo("ffmpeg")); } } private static HardwareAccelerationMode MapAccel(HardwareAccelerationKind kind) => kind switch { HardwareAccelerationKind.Amf => HardwareAccelerationMode.Amf, HardwareAccelerationKind.Nvenc => HardwareAccelerationMode.Nvenc, HardwareAccelerationKind.Qsv => HardwareAccelerationMode.Qsv, HardwareAccelerationKind.Vaapi => HardwareAccelerationMode.Vaapi, HardwareAccelerationKind.VideoToolbox => HardwareAccelerationMode.VideoToolbox, _ => HardwareAccelerationMode.None }; private static string MapVideoFormat(FFmpegProfileVideoFormat format) => format switch { FFmpegProfileVideoFormat.H264 => VideoFormat.H264, FFmpegProfileVideoFormat.Hevc => VideoFormat.Hevc, _ => VideoFormat.Mpeg2Video }; }