Browse Source

update editor

pull/2802/head
Jason Dove 6 months ago
parent
commit
2912d6d2c4
No known key found for this signature in database
  1. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  2. 98
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  3. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  4. 11
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  5. 3
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  6. 3
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  7. 454
      ErsatzTV/Pages/FFmpegEditor.razor
  8. 12
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

3
ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs

@ -7,6 +7,8 @@ namespace ErsatzTV.Application.FFmpegProfiles;
public record CreateFFmpegProfile( public record CreateFFmpegProfile(
string Name, string Name,
int ThreadCount, int ThreadCount,
bool NormalizeAudio,
bool NormalizeVideo,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
string VaapiDisplay, string VaapiDisplay,
VaapiDriver VaapiDriver, VaapiDriver VaapiDriver,
@ -31,4 +33,5 @@ public record CreateFFmpegProfile(
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,
bool NormalizeColors,
bool DeinterlaceVideo) : IRequest<Either<BaseError, CreateFFmpegProfileResult>>; bool DeinterlaceVideo) : IRequest<Either<BaseError, CreateFFmpegProfileResult>>;

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

@ -45,51 +45,63 @@ public class CreateFFmpegProfileHandler :
CancellationToken cancellationToken) => CancellationToken cancellationToken) =>
(ValidateName(request), ValidateThreadCount(request), (ValidateName(request), ValidateThreadCount(request),
await ResolutionMustExist(dbContext, request, cancellationToken)) await ResolutionMustExist(dbContext, request, cancellationToken))
.Apply((name, threadCount, resolutionId) => new FFmpegProfile .Apply((name, threadCount, resolutionId) =>
{ {
Name = name, var hwAccel = request.NormalizeVideo
ThreadCount = threadCount, ? request.HardwareAcceleration
HardwareAcceleration = request.HardwareAcceleration, : HardwareAccelerationKind.None;
VaapiDriver = request.VaapiDriver,
VaapiDevice = request.VaapiDevice, return new FFmpegProfile
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
ResolutionId = resolutionId,
ScalingBehavior = request.ScalingBehavior,
// only allow customization with VAAPI accel
PadMode = request.HardwareAcceleration switch
{ {
HardwareAccelerationKind.None => FilterMode.Software, Name = name,
HardwareAccelerationKind.Vaapi => request.PadMode, ThreadCount = threadCount,
_ => FilterMode.HardwareIfPossible
}, NormalizeAudio = request.NormalizeAudio,
NormalizeVideo = request.NormalizeVideo,
VideoFormat = request.VideoFormat,
VideoProfile = request.VideoProfile, HardwareAcceleration = hwAccel,
VideoPreset = request.VideoPreset, VaapiDriver = request.VaapiDriver,
AllowBFrames = request.AllowBFrames, VaapiDevice = request.VaapiDevice,
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
// mpeg2video only supports 8-bit content ResolutionId = resolutionId,
BitDepth = request.VideoFormat is FFmpegProfileVideoFormat.Mpeg2Video ScalingBehavior = request.ScalingBehavior,
? FFmpegProfileBitDepth.EightBit
: request.BitDepth, // only allow customization with VAAPI accel
PadMode = hwAccel switch
VideoBitrate = request.VideoBitrate, {
VideoBufferSize = request.VideoBufferSize, HardwareAccelerationKind.None => FilterMode.Software,
TonemapAlgorithm = request.TonemapAlgorithm, HardwareAccelerationKind.Vaapi => request.PadMode,
AudioFormat = request.AudioFormat, _ => FilterMode.HardwareIfPossible
AudioBitrate = request.AudioBitrate, },
AudioBufferSize = request.AudioBufferSize,
VideoFormat = request.NormalizeVideo ? request.VideoFormat : FFmpegProfileVideoFormat.Copy,
NormalizeLoudnessMode = request.NormalizeLoudnessMode, VideoProfile = request.VideoProfile,
TargetLoudness = request.NormalizeLoudnessMode is NormalizeLoudnessMode.LoudNorm VideoPreset = request.VideoPreset,
? request.TargetLoudness AllowBFrames = request.AllowBFrames,
: null,
// mpeg2video only supports 8-bit content
AudioChannels = request.AudioChannels, BitDepth = request.VideoFormat is FFmpegProfileVideoFormat.Mpeg2Video
AudioSampleRate = request.AudioSampleRate, ? FFmpegProfileBitDepth.EightBit
NormalizeFramerate = request.NormalizeFramerate, : request.BitDepth,
DeinterlaceVideo = request.DeinterlaceVideo
VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize,
TonemapAlgorithm = request.TonemapAlgorithm,
AudioFormat = request.AudioFormat,
AudioBitrate = request.AudioBitrate,
AudioBufferSize = request.AudioBufferSize,
NormalizeLoudnessMode = request.NormalizeLoudnessMode,
TargetLoudness = request.NormalizeLoudnessMode is NormalizeLoudnessMode.LoudNorm
? request.TargetLoudness
: null,
AudioChannels = request.AudioChannels,
AudioSampleRate = request.AudioSampleRate,
NormalizeFramerate = request.NormalizeFramerate,
NormalizeColors = request.NormalizeColors,
DeinterlaceVideo = request.DeinterlaceVideo
};
}); });
private static Validation<BaseError, string> ValidateName(CreateFFmpegProfile createFFmpegProfile) => private static Validation<BaseError, string> ValidateName(CreateFFmpegProfile createFFmpegProfile) =>

3
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs

@ -8,6 +8,8 @@ public record UpdateFFmpegProfile(
int FFmpegProfileId, int FFmpegProfileId,
string Name, string Name,
int ThreadCount, int ThreadCount,
bool NormalizeAudio,
bool NormalizeVideo,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
string VaapiDisplay, string VaapiDisplay,
VaapiDriver VaapiDriver, VaapiDriver VaapiDriver,
@ -32,4 +34,5 @@ public record UpdateFFmpegProfile(
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,
bool NormalizeColors,
bool DeinterlaceVideo) : IRequest<Either<BaseError, UpdateFFmpegProfileResult>>; bool DeinterlaceVideo) : IRequest<Either<BaseError, UpdateFFmpegProfileResult>>;

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

@ -27,9 +27,15 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa
UpdateFFmpegProfile update, UpdateFFmpegProfile update,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var hwAccel = update.NormalizeVideo
? update.HardwareAcceleration
: HardwareAccelerationKind.None;
p.Name = update.Name; p.Name = update.Name;
p.ThreadCount = update.ThreadCount; p.ThreadCount = update.ThreadCount;
p.HardwareAcceleration = update.HardwareAcceleration; p.NormalizeAudio = update.NormalizeAudio;
p.NormalizeVideo = update.NormalizeVideo;
p.HardwareAcceleration = hwAccel;
p.VaapiDisplay = update.VaapiDisplay; p.VaapiDisplay = update.VaapiDisplay;
p.VaapiDriver = update.VaapiDriver; p.VaapiDriver = update.VaapiDriver;
p.VaapiDevice = update.VaapiDevice; p.VaapiDevice = update.VaapiDevice;
@ -37,7 +43,7 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.ScalingBehavior = update.ScalingBehavior; p.ScalingBehavior = update.ScalingBehavior;
p.PadMode = update.PadMode; p.PadMode = update.PadMode;
p.VideoFormat = update.VideoFormat; p.VideoFormat = update.NormalizeVideo ? update.VideoFormat : FFmpegProfileVideoFormat.Copy;
p.VideoProfile = update.VideoProfile; p.VideoProfile = update.VideoProfile;
p.VideoPreset = update.VideoPreset; p.VideoPreset = update.VideoPreset;
p.AllowBFrames = update.AllowBFrames; p.AllowBFrames = update.AllowBFrames;
@ -79,6 +85,7 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory<TvContext> dbContextFa
p.AudioChannels = update.AudioChannels; p.AudioChannels = update.AudioChannels;
p.AudioSampleRate = update.AudioSampleRate; p.AudioSampleRate = update.AudioSampleRate;
p.NormalizeFramerate = update.NormalizeFramerate; p.NormalizeFramerate = update.NormalizeFramerate;
p.NormalizeColors = update.NormalizeColors;
p.DeinterlaceVideo = update.DeinterlaceVideo; p.DeinterlaceVideo = update.DeinterlaceVideo;
// don't save invalid preset // don't save invalid preset

3
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -8,6 +8,8 @@ public record FFmpegProfileViewModel(
int Id, int Id,
string Name, string Name,
int ThreadCount, int ThreadCount,
bool NormalizeAudio,
bool NormalizeVideo,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
string VaapiDisplay, string VaapiDisplay,
VaapiDriver VaapiDriver, VaapiDriver VaapiDriver,
@ -32,4 +34,5 @@ public record FFmpegProfileViewModel(
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,
bool NormalizeColors,
bool DeinterlaceVideo); bool DeinterlaceVideo);

3
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -10,6 +10,8 @@ internal static class Mapper
profile.Id, profile.Id,
profile.Name, profile.Name,
profile.ThreadCount, profile.ThreadCount,
profile.NormalizeAudio,
profile.NormalizeVideo,
profile.HardwareAcceleration, profile.HardwareAcceleration,
profile.VaapiDisplay ?? "drm", profile.VaapiDisplay ?? "drm",
profile.VaapiDriver, profile.VaapiDriver,
@ -34,6 +36,7 @@ internal static class Mapper
profile.AudioChannels, profile.AudioChannels,
profile.AudioSampleRate, profile.AudioSampleRate,
profile.NormalizeFramerate, profile.NormalizeFramerate,
profile.NormalizeColors,
profile.DeinterlaceVideo == true); profile.DeinterlaceVideo == true);
internal static FFmpegProfileResponseModel ProjectToResponseModel(FFmpegProfile ffmpegProfile) => internal static FFmpegProfileResponseModel ProjectToResponseModel(FFmpegProfile ffmpegProfile) =>

454
ErsatzTV/Pages/FFmpegEditor.razor

@ -38,266 +38,302 @@
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Preferred Resolution</MudText> <MudText>Normalize Audio</MudText>
</div>
<MudSelect @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
@foreach (ResolutionViewModel resolution in _resolutions)
{
<MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Scaling Behavior</MudText>
</div> </div>
<MudSelect @bind-Value="_model.ScalingBehavior" For="@(() => _model.ScalingBehavior)"> <MudCheckBox @bind-Value="@_model.NormalizeAudio" For="@(() => _model.NormalizeAudio)" Dense="true"/>
<MudSelectItem Value="@ScalingBehavior.ScaleAndPad">Scale and Pad</MudSelectItem>
<MudSelectItem Value="@ScalingBehavior.Stretch">Stretch</MudSelectItem>
<MudSelectItem Value="@ScalingBehavior.Crop">Crop</MudSelectItem>
</MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Pad Mode</MudText> <MudText>Normalize Video</MudText>
</div> </div>
<MudSelect @bind-Value="_model.PadMode" <MudCheckBox @bind-Value="@_model.NormalizeVideo" For="@(() => _model.NormalizeVideo)" Dense="true"/>
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> </MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Video</MudText>
<MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Format</MudText> <MudText>Preferred Resolution</MudText>
</div> </div>
<MudSelect @bind-Value="_model.VideoFormat" For="@(() => _model.VideoFormat)"> <MudSelect @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
<MudSelectItem Value="@FFmpegProfileVideoFormat.H264">h264</MudSelectItem> @foreach (ResolutionViewModel resolution in _resolutions)
<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> <MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem>
} }
</MudSelect> </MudSelect>
</MudStack> </MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> @if (_model.NormalizeVideo)
<div class="d-flex"> {
<MudText>Profile</MudText> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
</div> <div class="d-flex">
<MudSelect @bind-Value="_model.VideoProfile" <MudText>Scaling Behavior</MudText>
For="@(() => _model.VideoProfile)" </div>
Disabled="@(_model.VideoFormat != FFmpegProfileVideoFormat.H264 || !HardwareAccelSupportsH264Profile(_model.HardwareAcceleration))" <MudSelect @bind-Value="_model.ScalingBehavior" For="@(() => _model.ScalingBehavior)">
Clearable="true"> <MudSelectItem Value="@ScalingBehavior.ScaleAndPad">Scale and Pad</MudSelectItem>
<MudSelectItem Value="@VideoProfile.Main">main</MudSelectItem> <MudSelectItem Value="@ScalingBehavior.Stretch">Stretch</MudSelectItem>
<MudSelectItem Value="@VideoProfile.High">high</MudSelectItem> <MudSelectItem Value="@ScalingBehavior.Crop">Crop</MudSelectItem>
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Nvenc) </MudSelect>
{ </MudStack>
<MudSelectItem Value="@VideoProfile.High444p">high444p</MudSelectItem> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
} <div class="d-flex">
else <MudText>Pad Mode</MudText>
{ </div>
<MudSelectItem Value="@VideoProfile.High10">high10</MudSelectItem> <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> <MudSelect @bind-Value="_model.VideoPreset"
</MudStack> For="@(() => _model.VideoPreset)"
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> Disabled="@(presets.Count == 0)"
<div class="d-flex"> Clearable="true">
<MudText>Preset</MudText> @foreach (string preset in presets)
</div> {
@{ if (!string.IsNullOrWhiteSpace(preset))
ICollection<string> presets = FFmpegLibraryHelper.PresetsForFFmpegProfile( {
_model.HardwareAcceleration, <MudSelectItem Value="@preset">@preset</MudSelectItem>
_model.VideoFormat, }
_model.BitDepth); }
} </MudSelect>
<MudSelect @bind-Value="_model.VideoPreset" </MudStack>
For="@(() => _model.VideoPreset)" <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
Disabled="@(presets.Count == 0)" <div class="d-flex">
Clearable="true"> <MudText>Allow B-Frames</MudText>
@foreach (string preset in presets) </div>
{ <MudCheckBox @bind-Value="@_model.AllowBFrames" For="@(() => _model.AllowBFrames)" Dense="true"/>
if (!string.IsNullOrWhiteSpace(preset)) </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="@preset">@preset</MudSelectItem> <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>
} }
</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>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> <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>
} }
</MudSelect> }
</MudStack>
@if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv)
{
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi)
{ {
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>VAAPI Driver</MudText> <MudText>QSV Extra Hardware Frames</MudText>
</div> </div>
<MudSelect @bind-Value="_model.VaapiDriver" For="@(() => _model.VaapiDriver)"> <MudTextField @bind-Value="_model.QsvExtraHardwareFrames" For="@(() => _model.QsvExtraHardwareFrames)"/>
@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> </MudStack>
} }
else
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)
{ {
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>@(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device")</MudText> <MudText>Tonemap Algorithm</MudText>
</div> </div>
<MudSelect @bind-Value="_model.VaapiDevice" For="@(() => _model.VaapiDevice)"> <MudSelect @bind-Value="_model.TonemapAlgorithm" For="@(() => _model.TonemapAlgorithm)">
@foreach (string device in _vaapiDevices) @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues<FFmpegProfileTonemapAlgorithm>())
{ {
<MudSelectItem Value="@device">@device</MudSelectItem> <MudSelectItem Value="@algorithm">@algorithm</MudSelectItem>
} }
</MudSelect> </MudSelect>
</MudStack> </MudStack>
} }
}
@if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>QSV Extra Hardware Frames</MudText> <MudText>Normalize Frame Rate</MudText>
</div> </div>
<MudTextField @bind-Value="_model.QsvExtraHardwareFrames" For="@(() => _model.QsvExtraHardwareFrames)"/> <MudCheckBox @bind-Value="@_model.NormalizeFramerate" For="@(() => _model.NormalizeFramerate)" Dense="true"/>
</MudStack> </MudStack>
}
else
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Tonemap Algorithm</MudText> <MudText>Normalize Colors</MudText>
</div> </div>
<MudSelect @bind-Value="_model.TonemapAlgorithm" For="@(() => _model.TonemapAlgorithm)"> <MudCheckBox @bind-Value="@_model.NormalizeColors" For="@(() => _model.NormalizeColors)" Dense="true"/>
@foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues<FFmpegProfileTonemapAlgorithm>()) </MudStack>
{ <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelectItem Value="@algorithm">@algorithm</MudSelectItem> <div class="d-flex">
} <MudText>Auto Deinterlace Video</MudText>
</MudSelect> </div>
<MudCheckBox @bind-Value="@_model.DeinterlaceVideo" For="@(() => _model.DeinterlaceVideo)" Dense="true"/>
</MudStack> </MudStack>
} }
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> else
<div class="d-flex"> {
<MudText>Normalize Frame Rate</MudText> <MudText>Video will be copied as-is, including timestamps, which *will* cause issues with most clients</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>Auto Deinterlace Video</MudText>
</div>
<MudCheckBox @bind-Value="@_model.DeinterlaceVideo" For="@(() => _model.DeinterlaceVideo)" Dense="true"/>
</MudStack>
<MudText Typo="Typo.h5" Class="mt-10 mb-2">Audio</MudText> <MudText Typo="Typo.h5" Class="mt-10 mb-2">Audio</MudText>
<MudDivider Class="mb-6"/> <MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> @if (_model.NormalizeAudio)
<div class="d-flex">
<MudText>Format</MudText>
</div>
<MudSelect @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)">
<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"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">
<MudText>Target Loudness</MudText> <MudText>Format</MudText>
</div>
<MudSelect @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)">
<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> </div>
<MudTextField @bind-Value="_model.TargetLoudness" For="@(() => _model.TargetLoudness)" Adornment="Adornment.End" AdornmentText="LUFS"/> <MudTextField @bind-Value="_model.AudioChannels" For="@(() => _model.AudioChannels)"/>
</MudStack> </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> </MudContainer>
</div> </div>

12
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -23,11 +23,14 @@ public class FFmpegProfileEditViewModel
Id = viewModel.Id; Id = viewModel.Id;
Name = viewModel.Name; Name = viewModel.Name;
NormalizeFramerate = viewModel.NormalizeFramerate; NormalizeFramerate = viewModel.NormalizeFramerate;
NormalizeColors = viewModel.NormalizeColors;
DeinterlaceVideo = viewModel.DeinterlaceVideo; DeinterlaceVideo = viewModel.DeinterlaceVideo;
Resolution = viewModel.Resolution; Resolution = viewModel.Resolution;
ScalingBehavior = viewModel.ScalingBehavior; ScalingBehavior = viewModel.ScalingBehavior;
PadMode = viewModel.PadMode; PadMode = viewModel.PadMode;
ThreadCount = viewModel.ThreadCount; ThreadCount = viewModel.ThreadCount;
NormalizeAudio = viewModel.NormalizeAudio;
NormalizeVideo = viewModel.NormalizeVideo;
HardwareAcceleration = viewModel.HardwareAcceleration; HardwareAcceleration = viewModel.HardwareAcceleration;
VaapiDisplay = viewModel.VaapiDisplay; VaapiDisplay = viewModel.VaapiDisplay;
VaapiDriver = viewModel.VaapiDriver; VaapiDriver = viewModel.VaapiDriver;
@ -74,6 +77,7 @@ public class FFmpegProfileEditViewModel
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool NormalizeFramerate { get; set; } public bool NormalizeFramerate { get; set; }
public bool NormalizeColors { get; set; }
public bool DeinterlaceVideo { get; set; } public bool DeinterlaceVideo { get; set; }
public ResolutionViewModel Resolution { get; set; } public ResolutionViewModel Resolution { get; set; }
public ScalingBehavior ScalingBehavior { get; set; } public ScalingBehavior ScalingBehavior { get; set; }
@ -92,6 +96,8 @@ public class FFmpegProfileEditViewModel
} }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public bool NormalizeAudio { get; set; }
public bool NormalizeVideo { get; set; }
public HardwareAccelerationKind HardwareAcceleration { get; set; } public HardwareAccelerationKind HardwareAcceleration { get; set; }
public string VaapiDisplay { get; set; } public string VaapiDisplay { get; set; }
public VaapiDriver VaapiDriver { get; set; } public VaapiDriver VaapiDriver { get; set; }
@ -124,6 +130,8 @@ public class FFmpegProfileEditViewModel
new( new(
Name, Name,
ThreadCount, ThreadCount,
NormalizeAudio,
NormalizeVideo,
HardwareAcceleration, HardwareAcceleration,
VaapiDisplay, VaapiDisplay,
VaapiDriver, VaapiDriver,
@ -148,6 +156,7 @@ public class FFmpegProfileEditViewModel
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeFramerate, NormalizeFramerate,
NormalizeColors,
DeinterlaceVideo DeinterlaceVideo
); );
@ -156,6 +165,8 @@ public class FFmpegProfileEditViewModel
Id, Id,
Name, Name,
ThreadCount, ThreadCount,
NormalizeAudio,
NormalizeVideo,
HardwareAcceleration, HardwareAcceleration,
VaapiDisplay, VaapiDisplay,
VaapiDriver, VaapiDriver,
@ -180,6 +191,7 @@ public class FFmpegProfileEditViewModel
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeFramerate, NormalizeFramerate,
NormalizeColors,
DeinterlaceVideo DeinterlaceVideo
); );
} }

Loading…
Cancel
Save