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

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

@ -45,51 +45,63 @@ public class CreateFFmpegProfileHandler : @@ -45,51 +45,63 @@ public class CreateFFmpegProfileHandler :
CancellationToken cancellationToken) =>
(ValidateName(request), ValidateThreadCount(request),
await ResolutionMustExist(dbContext, request, cancellationToken))
.Apply((name, threadCount, resolutionId) => new FFmpegProfile
.Apply((name, threadCount, resolutionId) =>
{
Name = name,
ThreadCount = threadCount,
HardwareAcceleration = request.HardwareAcceleration,
VaapiDriver = request.VaapiDriver,
VaapiDevice = request.VaapiDevice,
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
ResolutionId = resolutionId,
ScalingBehavior = request.ScalingBehavior,
// only allow customization with VAAPI accel
PadMode = request.HardwareAcceleration switch
var hwAccel = request.NormalizeVideo
? request.HardwareAcceleration
: HardwareAccelerationKind.None;
return new FFmpegProfile
{
HardwareAccelerationKind.None => FilterMode.Software,
HardwareAccelerationKind.Vaapi => request.PadMode,
_ => FilterMode.HardwareIfPossible
},
VideoFormat = request.VideoFormat,
VideoProfile = request.VideoProfile,
VideoPreset = request.VideoPreset,
AllowBFrames = request.AllowBFrames,
// mpeg2video only supports 8-bit content
BitDepth = request.VideoFormat is FFmpegProfileVideoFormat.Mpeg2Video
? FFmpegProfileBitDepth.EightBit
: request.BitDepth,
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,
DeinterlaceVideo = request.DeinterlaceVideo
Name = name,
ThreadCount = threadCount,
NormalizeAudio = request.NormalizeAudio,
NormalizeVideo = request.NormalizeVideo,
HardwareAcceleration = hwAccel,
VaapiDriver = request.VaapiDriver,
VaapiDevice = request.VaapiDevice,
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
ResolutionId = resolutionId,
ScalingBehavior = request.ScalingBehavior,
// only allow customization with VAAPI accel
PadMode = hwAccel switch
{
HardwareAccelerationKind.None => FilterMode.Software,
HardwareAccelerationKind.Vaapi => request.PadMode,
_ => FilterMode.HardwareIfPossible
},
VideoFormat = request.NormalizeVideo ? request.VideoFormat : FFmpegProfileVideoFormat.Copy,
VideoProfile = request.VideoProfile,
VideoPreset = request.VideoPreset,
AllowBFrames = request.AllowBFrames,
// mpeg2video only supports 8-bit content
BitDepth = request.VideoFormat is FFmpegProfileVideoFormat.Mpeg2Video
? FFmpegProfileBitDepth.EightBit
: request.BitDepth,
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) =>

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

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

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

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

3
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

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

3
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

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

454
ErsatzTV/Pages/FFmpegEditor.razor

@ -38,266 +38,302 @@ @@ -38,266 +38,302 @@
</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>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Scaling Behavior</MudText>
<MudText>Normalize Audio</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>
<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>Pad Mode</MudText>
<MudText>Normalize Video</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>
<MudCheckBox @bind-Value="@_model.NormalizeVideo" For="@(() => _model.NormalizeVideo)" Dense="true"/>
</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">
<div class="d-flex">
<MudText>Format</MudText>
<MudText>Preferred Resolution</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))
<MudSelect @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
@foreach (ResolutionViewModel resolution in _resolutions)
{
<MudSelectItem Value="@FFmpegProfileVideoFormat.Av1">av1</MudSelectItem>
<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>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>
@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>
</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))
<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="@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>
<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)
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)
{
<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 is HardwareAccelerationKind.Vaapi)
}
@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>VAAPI Driver</MudText>
<MudText>QSV Extra Hardware Frames</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>
<MudTextField @bind-Value="_model.QsvExtraHardwareFrames" For="@(() => _model.QsvExtraHardwareFrames)"/>
</MudStack>
}
@if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)
else
{
<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>
<MudText>Tonemap Algorithm</MudText>
</div>
<MudSelect @bind-Value="_model.VaapiDevice" For="@(() => _model.VaapiDevice)">
@foreach (string device in _vaapiDevices)
<MudSelect @bind-Value="_model.TonemapAlgorithm" For="@(() => _model.TonemapAlgorithm)">
@foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues<FFmpegProfileTonemapAlgorithm>())
{
<MudSelectItem Value="@device">@device</MudSelectItem>
<MudSelectItem Value="@algorithm">@algorithm</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>
<MudText>Normalize Frame Rate</MudText>
</div>
<MudTextField @bind-Value="_model.QsvExtraHardwareFrames" For="@(() => _model.QsvExtraHardwareFrames)"/>
<MudCheckBox @bind-Value="@_model.NormalizeFramerate" For="@(() => _model.NormalizeFramerate)" Dense="true"/>
</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>
<MudText>Normalize Colors</MudText>
</div>
<MudSelect @bind-Value="_model.TonemapAlgorithm" For="@(() => _model.TonemapAlgorithm)">
@foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues<FFmpegProfileTonemapAlgorithm>())
{
<MudSelectItem Value="@algorithm">@algorithm</MudSelectItem>
}
</MudSelect>
<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>
}
<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>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"/>
<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)">
<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)
@if (_model.NormalizeAudio)
{
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<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>
<MudTextField @bind-Value="_model.TargetLoudness" For="@(() => _model.TargetLoudness)" Adornment="Adornment.End" AdornmentText="LUFS"/>
<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>

12
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

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

Loading…
Cancel
Save