mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
15 KiB
286 lines
15 KiB
@page "/settings" |
|
@using ErsatzTV.Application.FFmpegProfiles |
|
@using ErsatzTV.Application.FFmpegProfiles.Commands |
|
@using ErsatzTV.Application.FFmpegProfiles.Queries |
|
@using ErsatzTV.Application.HDHR.Commands |
|
@using ErsatzTV.Application.HDHR.Queries |
|
@using ErsatzTV.Application.MediaItems.Queries |
|
@using System.Globalization |
|
@using ErsatzTV.Application.Configuration.Queries |
|
@using Unit = LanguageExt.Unit |
|
@using ErsatzTV.Application.Configuration.Commands |
|
@using ErsatzTV.Application.Filler |
|
@using ErsatzTV.Application.Filler.Queries |
|
@using ErsatzTV.Application.Watermarks |
|
@using ErsatzTV.Application.Watermarks.Queries |
|
@using ErsatzTV.Core.Domain.Filler |
|
@using ErsatzTV.Core.FFmpeg |
|
@using Microsoft.AspNetCore.Components |
|
@using System.Threading |
|
@implements IDisposable |
|
@inject IMediator _mediator |
|
@inject ISnackbar _snackbar |
|
@inject ILogger<Settings> _logger |
|
|
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="display: flex; flex-direction: row"> |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudCard Class="mr-6 mb-6" Style="max-width: 400px"> |
|
<MudCardHeader> |
|
<CardHeaderContent> |
|
<MudText Typo="Typo.h6">FFmpeg Settings</MudText> |
|
</CardHeaderContent> |
|
</MudCardHeader> |
|
<MudCardContent> |
|
<MudForm @bind-IsValid="@_success"> |
|
<MudTextField T="string" Label="FFmpeg Path" @bind-Value="_ffmpegSettings.FFmpegPath" Validation="@(new Func<string, string>(ValidatePathExists))" Required="true" RequiredError="FFmpeg path is required!"/> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudTextField T="string" Label="FFprobe Path" @bind-Value="_ffmpegSettings.FFprobePath" Validation="@(new Func<string, string>(ValidatePathExists))" Required="true" RequiredError="FFprobe path is required!"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudSelect Label="Default Profile" @bind-Value="_ffmpegSettings.DefaultFFmpegProfileId" For="@(() => _ffmpegSettings.DefaultFFmpegProfileId)"> |
|
@foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) |
|
{ |
|
<MudSelectItem Value="@profile.Id">@profile.Name</MudSelectItem> |
|
} |
|
</MudSelect> |
|
</MudElement> |
|
<MudSelect Class="mt-3" Label="Preferred Language" @bind-Value="_ffmpegSettings.PreferredLanguageCode" For="@(() => _ffmpegSettings.PreferredLanguageCode)" Required="true" RequiredError="Preferred Language Code is required!"> |
|
@foreach (CultureInfo culture in _availableCultures) |
|
{ |
|
<MudSelectItem Value="@culture.ThreeLetterISOLanguageName">@culture.EnglishName</MudSelectItem> |
|
} |
|
</MudSelect> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudSwitch T="bool" |
|
Label="Save troubleshooting reports to disk" |
|
Color="Color.Primary" |
|
@bind-Checked="@_ffmpegSettings.SaveReports"/> |
|
</MudElement> |
|
<MudSelect Class="mt-3" |
|
Label="Global Watermark" |
|
@bind-Value="_ffmpegSettings.GlobalWatermarkId" |
|
For="@(() => _ffmpegSettings.GlobalWatermarkId)" |
|
Clearable="true"> |
|
<MudSelectItem T="int?" Value="@((int?) null)">(none)</MudSelectItem> |
|
@foreach (WatermarkViewModel watermark in _watermarks) |
|
{ |
|
<MudSelectItem T="int?" Value="@watermark.Id">@watermark.Name</MudSelectItem> |
|
} |
|
</MudSelect> |
|
<MudSelect Class="mt-3" |
|
Label="Global Fallback Filler" |
|
@bind-Value="_ffmpegSettings.GlobalFallbackFillerId" |
|
For="@(() => _ffmpegSettings.GlobalFallbackFillerId)" |
|
Clearable="true"> |
|
<MudSelectItem T="int?" Value="@((int?) null)">(none)</MudSelectItem> |
|
@foreach (FillerPresetViewModel fillerPreset in _fillerPresets) |
|
{ |
|
<MudSelectItem T="int?" Value="@fillerPreset.Id">@fillerPreset.Name</MudSelectItem> |
|
} |
|
</MudSelect> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudTextField T="int" |
|
Label="HLS Segmenter Idle Timeout" |
|
@bind-Value="_ffmpegSettings.HlsSegmenterIdleTimeout" |
|
Validation="@(new Func<int, string>(ValidateHlsSegmenterIdleTimeout))" |
|
Required="true" |
|
RequiredError="HLS Segmenter idle timeout is required!" |
|
Adornment="Adornment.End" |
|
AdornmentText="Seconds"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudTextField T="int" |
|
Label="Work-Ahead HLS Segmenter Limit" |
|
@bind-Value="_ffmpegSettings.WorkAheadSegmenterLimit" |
|
Validation="@(new Func<int, string>(ValidateWorkAheadSegmenterLimit))" |
|
Required="true" |
|
RequiredError="Work-ahead HLS Segmenter limit is required!"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudTextField T="int" |
|
Label="HLS Segmenter Initial Segment Count" |
|
@bind-Value="_ffmpegSettings.InitialSegmentCount" |
|
Validation="@(new Func<int, string>(ValidateInitialSegmentCount))" |
|
Required="true" |
|
RequiredError="HLS Segmenter initial segment count is required!"/> |
|
</MudElement> |
|
<MudElement HtmlTag="div" Class="mt-3"> |
|
<MudSwitch T="bool" |
|
Label="Use Legacy Transcoder Logic" |
|
Color="Color.Primary" |
|
@bind-Checked="@_ffmpegSettings.UseLegacyTranscoder"/> |
|
</MudElement> |
|
</MudForm> |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_success)" OnClick="@(_ => SaveFFmpegSettings())">Save Settings</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
<MudCard Class="mr-6 mb-auto" Style="width: 350px"> |
|
<MudCardHeader> |
|
<CardHeaderContent> |
|
<MudText Typo="Typo.h6">HDHomeRun Settings</MudText> |
|
</CardHeaderContent> |
|
</MudCardHeader> |
|
<MudCardContent> |
|
<MudForm @bind-IsValid="@_hdhrSuccess"> |
|
<MudTextField T="int" Label="Tuner Count" @bind-Value="_tunerCount" Validation="@(new Func<int, string>(ValidateTunerCount))" Required="true" RequiredError="Tuner count is required!"/> |
|
</MudForm> |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_hdhrSuccess)" OnClick="@(_ => SaveHDHRSettings())">Save Settings</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
<MudCard Class="mr-6" Style="width: 350px"> |
|
<MudCardHeader> |
|
<CardHeaderContent> |
|
<MudText Typo="Typo.h6">Scanner Settings</MudText> |
|
</CardHeaderContent> |
|
</MudCardHeader> |
|
<MudCardContent> |
|
<MudForm @bind-IsValid="@_scannerSuccess"> |
|
<MudTextField T="int" |
|
Label="Library Refresh Interval" |
|
@bind-Value="_libraryRefreshInterval" |
|
Validation="@(new Func<int, string>(ValidateLibraryRefreshInterval))" |
|
Required="true" |
|
RequiredError="Library refresh interval is required!" |
|
Adornment="Adornment.End" |
|
AdornmentText="Hours"/> |
|
</MudForm> |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_scannerSuccess)" OnClick="@(_ => SaveScannerSettings())">Save Settings</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
<MudCard Style="width: 350px"> |
|
<MudCardHeader> |
|
<CardHeaderContent> |
|
<MudText Typo="Typo.h6">Playout Settings</MudText> |
|
</CardHeaderContent> |
|
</MudCardHeader> |
|
<MudCardContent> |
|
<MudForm @bind-IsValid="@_playoutSuccess"> |
|
<MudTextField T="int" |
|
Label="Days To Build" |
|
@bind-Value="_playoutDaysToBuild" |
|
Validation="@(new Func<int, string>(ValidatePlayoutDaysToBuild))" |
|
Required="true" |
|
RequiredError="Days to build is required!" |
|
Adornment="Adornment.End" |
|
AdornmentText="Days"/> |
|
</MudForm> |
|
</MudCardContent> |
|
<MudCardActions> |
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" Disabled="@(!_playoutSuccess)" OnClick="@(_ => SavePlayoutSettings())">Save Settings</MudButton> |
|
</MudCardActions> |
|
</MudCard> |
|
</div> |
|
</MudContainer> |
|
|
|
@code { |
|
private readonly CancellationTokenSource _cts = new(); |
|
|
|
private bool _success; |
|
private bool _hdhrSuccess; |
|
private bool _scannerSuccess; |
|
private bool _playoutSuccess; |
|
private List<FFmpegProfileViewModel> _ffmpegProfiles; |
|
private FFmpegSettingsViewModel _ffmpegSettings; |
|
private List<CultureInfo> _availableCultures; |
|
private List<WatermarkViewModel> _watermarks; |
|
private List<FillerPresetViewModel> _fillerPresets; |
|
private int _tunerCount; |
|
private int _libraryRefreshInterval; |
|
private int _playoutDaysToBuild; |
|
|
|
public void Dispose() |
|
{ |
|
_cts.Cancel(); |
|
_cts.Dispose(); |
|
} |
|
|
|
protected override async Task OnParametersSetAsync() |
|
{ |
|
await LoadFFmpegProfilesAsync(); |
|
|
|
_ffmpegSettings = await _mediator.Send(new GetFFmpegSettings(), _cts.Token); |
|
_success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath); |
|
_availableCultures = await _mediator.Send(new GetAllLanguageCodes(), _cts.Token); |
|
_watermarks = await _mediator.Send(new GetAllWatermarks(), _cts.Token); |
|
_fillerPresets = await _mediator.Send(new GetAllFillerPresets(), _cts.Token) |
|
.Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList()); |
|
_tunerCount = await _mediator.Send(new GetHDHRTunerCount(), _cts.Token); |
|
_hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount)); |
|
_libraryRefreshInterval = await _mediator.Send(new GetLibraryRefreshInterval(), _cts.Token); |
|
_scannerSuccess = _libraryRefreshInterval > 0; |
|
_playoutDaysToBuild = await _mediator.Send(new GetPlayoutDaysToBuild(), _cts.Token); |
|
_playoutSuccess = _playoutDaysToBuild > 0; |
|
} |
|
|
|
private static string ValidatePathExists(string path) => !File.Exists(path) ? "Path does not exist" : null; |
|
|
|
private static string ValidateTunerCount(int tunerCount) => tunerCount <= 0 ? "Tuner count must be greater than zero" : null; |
|
|
|
private static string ValidateLibraryRefreshInterval(int libraryRefreshInterval) => libraryRefreshInterval <= 0 ? "Library refresh interval must be greater than zero" : null; |
|
|
|
private static string ValidatePlayoutDaysToBuild(int daysToBuild) => daysToBuild <= 0 ? "Days to build must be greater than zero" : null; |
|
|
|
private static string ValidateHlsSegmenterIdleTimeout(int idleTimeout) => idleTimeout < 30 ? "HLS Segmenter idle timeout must be greater than or equal to 30" : null; |
|
|
|
private static string ValidateWorkAheadSegmenterLimit(int limit) => limit < 0 ? "Work-Ahead HLS Segmenter limit must be greater than or equal to 0" : null; |
|
|
|
private static string ValidateInitialSegmentCount(int count) => count < 1 ? "HLS Segmenter initial segment count must be greater than or equal to 1" : null; |
|
|
|
private async Task LoadFFmpegProfilesAsync() => |
|
_ffmpegProfiles = await _mediator.Send(new GetAllFFmpegProfiles(), _cts.Token); |
|
|
|
private async Task SaveFFmpegSettings() |
|
{ |
|
Either<BaseError, Unit> result = await _mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings), _cts.Token); |
|
result.Match( |
|
Left: error => |
|
{ |
|
_snackbar.Add(error.Value, Severity.Error); |
|
_logger.LogError("Unexpected error saving FFmpeg settings: {Error}", error.Value); |
|
}, |
|
Right: _ => _snackbar.Add("Successfully saved FFmpeg settings", Severity.Success)); |
|
} |
|
|
|
private async Task SaveHDHRSettings() |
|
{ |
|
Either<BaseError, Unit> result = await _mediator.Send(new UpdateHDHRTunerCount(_tunerCount), _cts.Token); |
|
result.Match( |
|
Left: error => |
|
{ |
|
_snackbar.Add(error.Value, Severity.Error); |
|
_logger.LogError("Unexpected error saving HDHomeRun settings: {Error}", error.Value); |
|
}, |
|
Right: _ => _snackbar.Add("Successfully saved HDHomeRun settings", Severity.Success)); |
|
} |
|
|
|
private async Task SaveScannerSettings() |
|
{ |
|
Either<BaseError, Unit> result = await _mediator.Send(new UpdateLibraryRefreshInterval(_libraryRefreshInterval), _cts.Token); |
|
result.Match( |
|
Left: error => |
|
{ |
|
_snackbar.Add(error.Value, Severity.Error); |
|
_logger.LogError("Unexpected error saving scanner settings: {Error}", error.Value); |
|
}, |
|
Right: _ => _snackbar.Add("Successfully saved scanner settings", Severity.Success)); |
|
} |
|
|
|
private async Task SavePlayoutSettings() |
|
{ |
|
Either<BaseError, Unit> result = await _mediator.Send(new UpdatePlayoutDaysToBuild(_playoutDaysToBuild), _cts.Token); |
|
result.Match( |
|
Left: error => |
|
{ |
|
_snackbar.Add(error.Value, Severity.Error); |
|
_logger.LogError("Unexpected error saving playout settings: {Error}", error.Value); |
|
}, |
|
Right: _ => _snackbar.Add("Successfully saved playout settings", Severity.Success)); |
|
} |
|
|
|
} |