diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs index 2da89a452..99337b5fa 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs @@ -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( int AudioChannels, int AudioSampleRate, bool NormalizeFramerate, + bool NormalizeColors, bool DeinterlaceVideo) : IRequest>; diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs index ec502b987..a3f0c76dc 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs @@ -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 ValidateName(CreateFFmpegProfile createFFmpegProfile) => diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs index 560b63581..71fd24878 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs @@ -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( int AudioChannels, int AudioSampleRate, bool NormalizeFramerate, + bool NormalizeColors, bool DeinterlaceVideo) : IRequest>; diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs index b8a1dc18f..f24a60663 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs @@ -27,9 +27,15 @@ public class UpdateFFmpegProfileHandler(IDbContextFactory 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 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 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 diff --git a/ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs b/ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs index 314ec9379..eaf215fef 100644 --- a/ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs +++ b/ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs @@ -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( int AudioChannels, int AudioSampleRate, bool NormalizeFramerate, + bool NormalizeColors, bool DeinterlaceVideo); diff --git a/ErsatzTV.Application/FFmpegProfiles/Mapper.cs b/ErsatzTV.Application/FFmpegProfiles/Mapper.cs index db9d35518..4272fdb35 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Mapper.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Mapper.cs @@ -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 profile.AudioChannels, profile.AudioSampleRate, profile.NormalizeFramerate, + profile.NormalizeColors, profile.DeinterlaceVideo == true); internal static FFmpegProfileResponseModel ProjectToResponseModel(FFmpegProfile ffmpegProfile) => diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index 69ff1ed19..1fba702e0 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -38,266 +38,302 @@
- Preferred Resolution -
- - @foreach (ResolutionViewModel resolution in _resolutions) - { - @resolution.Name - } - -
- -
- Scaling Behavior + Normalize Audio
- - Scale and Pad - Stretch - Crop - +
- Pad Mode + Normalize Video
- - Hardware If Possible - Software - +
- Video -
- Format + Preferred Resolution
- - h264 - hevc - mpeg-2 - @if (_model.HardwareAcceleration is (HardwareAccelerationKind.Nvenc or HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)) + + @foreach (ResolutionViewModel resolution in _resolutions) { - av1 + @resolution.Name }
- -
- Profile -
- - main - high - @if (_model.HardwareAcceleration is HardwareAccelerationKind.Nvenc) - { - high444p - } - else - { - high10 + @if (_model.NormalizeVideo) + { + +
+ Scaling Behavior +
+ + Scale and Pad + Stretch + Crop + +
+ +
+ Pad Mode +
+ + Hardware If Possible + Software + +
+ } + Video + + @if (_model.NormalizeVideo) + { + +
+ Format +
+ + h264 + hevc + mpeg-2 + @if (_model.HardwareAcceleration is (HardwareAccelerationKind.Nvenc or HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv)) + { + av1 + } + +
+ +
+ Profile +
+ + main + high + @if (_model.HardwareAcceleration is HardwareAccelerationKind.Nvenc) + { + high444p + } + else + { + high10 + } + +
+ +
+ Preset +
+ @{ + ICollection presets = FFmpegLibraryHelper.PresetsForFFmpegProfile( + _model.HardwareAcceleration, + _model.VideoFormat, + _model.BitDepth); } -
-
- -
- Preset -
- @{ - ICollection presets = FFmpegLibraryHelper.PresetsForFFmpegProfile( - _model.HardwareAcceleration, - _model.VideoFormat, - _model.BitDepth); - } - - @foreach (string preset in presets) - { - if (!string.IsNullOrWhiteSpace(preset)) + + @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) { - @preset + @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 + } + +
} - - - -
- Allow B-Frames -
- -
- -
- Bit Depth -
- - 8-bit - 10-bit - -
- -
- Bitrate -
- -
- -
- Buffer Size -
- -
- -
- Hardware Acceleration -
- - @foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds) + + @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) { - @hwAccel + +
+ @(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device") +
+ + @foreach (string device in _vaapiDevices) + { + @device + } + +
} -
-
- @if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi) + } + + @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) {
- VAAPI Driver + QSV Extra Hardware Frames
- - @foreach (VaapiDriver driver in Enum.GetValues()) - { - @driver - } - -
- -
- VAAPI Display -
- - @foreach (string display in _vaapiDisplays) - { - @display - } - +
} - - @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) + else {
- @(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device") + Tonemap Algorithm
- - @foreach (string device in _vaapiDevices) + + @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues()) { - @device + @algorithm }
} - } - @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) - {
- QSV Extra Hardware Frames + Normalize Frame Rate
- +
- } - else - {
- Tonemap Algorithm + Normalize Colors
- - @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues()) - { - @algorithm - } - + +
+ +
+ Auto Deinterlace Video +
+
} - -
- Normalize Frame Rate -
- -
- -
- Auto Deinterlace Video -
- -
+ else + { + Video will be copied as-is, including timestamps, which *will* cause issues with most clients + } Audio - -
- Format -
- - aac - ac3 - aac (latm) - -
- -
- Bitrate -
- -
- -
- Buffer Size -
- -
- -
- Channels -
- -
- -
- Sample Rate -
- -
- -
- Normalize Loudness -
- - Off - loudnorm - -
- @if (_model.NormalizeLoudnessMode is NormalizeLoudnessMode.LoudNorm) + @if (_model.NormalizeAudio) {
- Target Loudness + Format +
+ + aac + ac3 + aac (latm) + +
+ +
+ Bitrate +
+ +
+ +
+ Buffer Size +
+ +
+ +
+ Channels
- +
+ +
+ Sample Rate +
+ +
+ +
+ Normalize Loudness +
+ + Off + loudnorm + +
+ @if (_model.NormalizeLoudnessMode is NormalizeLoudnessMode.LoudNorm) + { + +
+ Target Loudness +
+ +
+ } + } + else + { + Audio will be copied as-is, including timestamps, which *will* cause issues with most clients } diff --git a/ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs b/ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs index bfe14835e..060d5ae91 100644 --- a/ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs +++ b/ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs @@ -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 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 } 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 new( Name, ThreadCount, + NormalizeAudio, + NormalizeVideo, HardwareAcceleration, VaapiDisplay, VaapiDriver, @@ -148,6 +156,7 @@ public class FFmpegProfileEditViewModel AudioChannels, AudioSampleRate, NormalizeFramerate, + NormalizeColors, DeinterlaceVideo ); @@ -156,6 +165,8 @@ public class FFmpegProfileEditViewModel Id, Name, ThreadCount, + NormalizeAudio, + NormalizeVideo, HardwareAcceleration, VaapiDisplay, VaapiDriver, @@ -180,6 +191,7 @@ public class FFmpegProfileEditViewModel AudioChannels, AudioSampleRate, NormalizeFramerate, + NormalizeColors, DeinterlaceVideo ); }