Browse Source

scaling behavior and normalize loudness (#1439)

* update changelog [no ci]

* add ffmpeg profile scaling behavior

* update dependencies

* add normalize loudness mode

* update changelog
pull/1441/head
Jason Dove 2 years ago committed by GitHub
parent
commit
694b6bbd91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  3. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  4. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  5. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  6. 3
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  7. 5
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  8. 4
      ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs
  9. 2
      ErsatzTV.Core/Api/FFmpegProfiles/FFmpegFullProfileResponseModel.cs
  10. 5
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  11. 8
      ErsatzTV.Core/Domain/NormalizeLoudnessMode.cs
  12. 7
      ErsatzTV.Core/Domain/ScalingBehavior.cs
  13. 26
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  14. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs
  15. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  16. 6
      ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs
  17. 8
      ErsatzTV.FFmpeg/AudioFilter.cs
  18. 14
      ErsatzTV.FFmpeg/Filter/NormalizeLoudnessFilter.cs
  19. 21
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  20. 4
      ErsatzTV.FFmpeg/State/AudioState.cs
  21. 4429
      ErsatzTV.Infrastructure.MySql/Migrations/20230921065244_Add_FFmpegProfileScalingBehavior.Designer.cs
  22. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20230921065244_Add_FFmpegProfileScalingBehavior.cs
  23. 4432
      ErsatzTV.Infrastructure.MySql/Migrations/20230921071426_Add_FFmpegProfileNormalizeLoudnessMode.Designer.cs
  24. 31
      ErsatzTV.Infrastructure.MySql/Migrations/20230921071426_Add_FFmpegProfileNormalizeLoudnessMode.cs
  25. 4429
      ErsatzTV.Infrastructure.MySql/Migrations/20230921072719_Remove_FFmpegProfileNormalizeLoudness.Designer.cs
  26. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20230921072719_Remove_FFmpegProfileNormalizeLoudness.cs
  27. 9
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  28. 4427
      ErsatzTV.Infrastructure.Sqlite/Migrations/20230921065135_Add_FFmpegProfileScalingBehavior.Designer.cs
  29. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20230921065135_Add_FFmpegProfileScalingBehavior.cs
  30. 4430
      ErsatzTV.Infrastructure.Sqlite/Migrations/20230921071647_Add_FFmpegProfileNormalizeLoudnessMode.Designer.cs
  31. 31
      ErsatzTV.Infrastructure.Sqlite/Migrations/20230921071647_Add_FFmpegProfileNormalizeLoudnessMode.cs
  32. 4427
      ErsatzTV.Infrastructure.Sqlite/Migrations/20230921072813_Remove_FFmpegProfileNormalizeLoudness.Designer.cs
  33. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20230921072813_Remove_FFmpegProfileNormalizeLoudness.cs
  34. 8
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  35. 2
      ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj
  36. 2
      ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj
  37. 2
      ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj
  38. 8
      ErsatzTV/Controllers/IptvController.cs
  39. 2
      ErsatzTV/ErsatzTV.csproj
  40. 12
      ErsatzTV/Pages/FFmpegEditor.razor
  41. 12
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

12
CHANGELOG.md

@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- Add `Scaling Behavior` option to FFmpeg Profile
- `Scale and Pad`: the default behavior and will maintain aspect ratio of all content
- `Stretch`: a new mode that will NOT maintain aspect ratio when normalizing source content to the desired resolution
### Changed
- Upgrade ffmpeg to 6.1, which is now *required* for all installs
- Use new ffmpeg throttling method to minimize cpu/gpu use without impacting audio normalization
- Change FFmpeg Profile `Normalize Loudness` setting from checkbox to dropdown
- `Off`: do not normalize loudness
- `loudnorm`: use `loudnorm` filter to normalize loudness (generally higher CPU use)
- `dynaudnorm`: use `dynaudnorm` filter to normalize loudness (generally lower CPU use)
## [0.8.2-beta] - 2023-09-14 ## [0.8.2-beta] - 2023-09-14
### Added ### Added

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

@ -12,6 +12,7 @@ public record CreateFFmpegProfile(
string VaapiDevice, string VaapiDevice,
int? QsvExtraHardwareFrames, int? QsvExtraHardwareFrames,
int ResolutionId, int ResolutionId,
ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
FFmpegProfileBitDepth BitDepth, FFmpegProfileBitDepth BitDepth,
int VideoBitrate, int VideoBitrate,
@ -19,7 +20,7 @@ public record CreateFFmpegProfile(
FFmpegProfileAudioFormat AudioFormat, FFmpegProfileAudioFormat AudioFormat,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
bool NormalizeLoudness, NormalizeLoudnessMode NormalizeLoudnessMode,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,

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

@ -46,6 +46,7 @@ public class CreateFFmpegProfileHandler :
VaapiDevice = request.VaapiDevice, VaapiDevice = request.VaapiDevice,
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames, QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
ResolutionId = resolutionId, ResolutionId = resolutionId,
ScalingBehavior = request.ScalingBehavior,
VideoFormat = request.VideoFormat, VideoFormat = request.VideoFormat,
BitDepth = request.BitDepth, BitDepth = request.BitDepth,
VideoBitrate = request.VideoBitrate, VideoBitrate = request.VideoBitrate,
@ -53,7 +54,7 @@ public class CreateFFmpegProfileHandler :
AudioFormat = request.AudioFormat, AudioFormat = request.AudioFormat,
AudioBitrate = request.AudioBitrate, AudioBitrate = request.AudioBitrate,
AudioBufferSize = request.AudioBufferSize, AudioBufferSize = request.AudioBufferSize,
NormalizeLoudness = request.NormalizeLoudness, NormalizeLoudnessMode = request.NormalizeLoudnessMode,
AudioChannels = request.AudioChannels, AudioChannels = request.AudioChannels,
AudioSampleRate = request.AudioSampleRate, AudioSampleRate = request.AudioSampleRate,
NormalizeFramerate = request.NormalizeFramerate, NormalizeFramerate = request.NormalizeFramerate,

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

@ -13,6 +13,7 @@ public record UpdateFFmpegProfile(
string VaapiDevice, string VaapiDevice,
int? QsvExtraHardwareFrames, int? QsvExtraHardwareFrames,
int ResolutionId, int ResolutionId,
ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
FFmpegProfileBitDepth BitDepth, FFmpegProfileBitDepth BitDepth,
int VideoBitrate, int VideoBitrate,
@ -20,7 +21,7 @@ public record UpdateFFmpegProfile(
FFmpegProfileAudioFormat AudioFormat, FFmpegProfileAudioFormat AudioFormat,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
bool NormalizeLoudness, NormalizeLoudnessMode NormalizeLoudnessMode,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,

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

@ -35,6 +35,7 @@ public class
p.VaapiDevice = update.VaapiDevice; p.VaapiDevice = update.VaapiDevice;
p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames; p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames;
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.ScalingBehavior = update.ScalingBehavior;
p.VideoFormat = update.VideoFormat; p.VideoFormat = update.VideoFormat;
// mpeg2video only supports 8-bit content // mpeg2video only supports 8-bit content
@ -47,7 +48,7 @@ public class
p.AudioFormat = update.AudioFormat; p.AudioFormat = update.AudioFormat;
p.AudioBitrate = update.AudioBitrate; p.AudioBitrate = update.AudioBitrate;
p.AudioBufferSize = update.AudioBufferSize; p.AudioBufferSize = update.AudioBufferSize;
p.NormalizeLoudness = update.NormalizeLoudness; p.NormalizeLoudnessMode = update.NormalizeLoudnessMode;
p.AudioChannels = update.AudioChannels; p.AudioChannels = update.AudioChannels;
p.AudioSampleRate = update.AudioSampleRate; p.AudioSampleRate = update.AudioSampleRate;
p.NormalizeFramerate = update.NormalizeFramerate; p.NormalizeFramerate = update.NormalizeFramerate;

3
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -13,6 +13,7 @@ public record FFmpegProfileViewModel(
string VaapiDevice, string VaapiDevice,
int? QsvExtraHardwareFrames, int? QsvExtraHardwareFrames,
ResolutionViewModel Resolution, ResolutionViewModel Resolution,
ScalingBehavior ScalingBehavior,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
FFmpegProfileBitDepth BitDepth, FFmpegProfileBitDepth BitDepth,
int VideoBitrate, int VideoBitrate,
@ -20,7 +21,7 @@ public record FFmpegProfileViewModel(
FFmpegProfileAudioFormat AudioFormat, FFmpegProfileAudioFormat AudioFormat,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
bool NormalizeLoudness, NormalizeLoudnessMode NormalizeLoudnessMode,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,

5
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -15,6 +15,7 @@ internal static class Mapper
profile.VaapiDevice, profile.VaapiDevice,
profile.QsvExtraHardwareFrames, profile.QsvExtraHardwareFrames,
Resolutions.Mapper.ProjectToViewModel(profile.Resolution), Resolutions.Mapper.ProjectToViewModel(profile.Resolution),
profile.ScalingBehavior,
profile.VideoFormat, profile.VideoFormat,
profile.BitDepth, profile.BitDepth,
profile.VideoBitrate, profile.VideoBitrate,
@ -22,7 +23,7 @@ internal static class Mapper
profile.AudioFormat, profile.AudioFormat,
profile.AudioBitrate, profile.AudioBitrate,
profile.AudioBufferSize, profile.AudioBufferSize,
profile.NormalizeLoudness, profile.NormalizeLoudnessMode,
profile.AudioChannels, profile.AudioChannels,
profile.AudioSampleRate, profile.AudioSampleRate,
profile.NormalizeFramerate, profile.NormalizeFramerate,
@ -51,7 +52,7 @@ internal static class Mapper
(int)ffmpegProfile.AudioFormat, (int)ffmpegProfile.AudioFormat,
ffmpegProfile.AudioBitrate, ffmpegProfile.AudioBitrate,
ffmpegProfile.AudioBufferSize, ffmpegProfile.AudioBufferSize,
ffmpegProfile.NormalizeLoudness, (int)ffmpegProfile.NormalizeLoudnessMode,
ffmpegProfile.AudioChannels, ffmpegProfile.AudioChannels,
ffmpegProfile.AudioSampleRate, ffmpegProfile.AudioSampleRate,
ffmpegProfile.NormalizeFramerate, ffmpegProfile.NormalizeFramerate,

4
ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs

@ -886,7 +886,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeLoudness = true NormalizeLoudnessMode = NormalizeLoudnessMode.LoudNorm
}; };
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings( FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
@ -902,7 +902,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
false, false,
None); None);
actual.NormalizeLoudness.Should().BeTrue(); actual.NormalizeLoudnessMode.Should().Be(NormalizeLoudnessMode.LoudNorm);
} }
} }

2
ErsatzTV.Core/Api/FFmpegProfiles/FFmpegFullProfileResponseModel.cs

@ -14,7 +14,7 @@ public record FFmpegFullProfileResponseModel(
int AudioFormat, int AudioFormat,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
bool NormalizeLoudness, int NormalizeLoudnessMode,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeFramerate, bool NormalizeFramerate,

5
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -13,6 +13,7 @@ public record FFmpegProfile
public int? QsvExtraHardwareFrames { get; set; } public int? QsvExtraHardwareFrames { get; set; }
public int ResolutionId { get; set; } public int ResolutionId { get; set; }
public Resolution Resolution { get; set; } public Resolution Resolution { get; set; }
public ScalingBehavior ScalingBehavior { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; } public FFmpegProfileVideoFormat VideoFormat { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; } public FFmpegProfileBitDepth BitDepth { get; set; }
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }
@ -20,7 +21,7 @@ public record FFmpegProfile
public FFmpegProfileAudioFormat AudioFormat { get; set; } public FFmpegProfileAudioFormat AudioFormat { get; set; }
public int AudioBitrate { get; set; } public int AudioBitrate { get; set; }
public int AudioBufferSize { get; set; } public int AudioBufferSize { get; set; }
public bool NormalizeLoudness { get; set; } public NormalizeLoudnessMode NormalizeLoudnessMode { get; set; }
public int AudioChannels { get; set; } public int AudioChannels { get; set; }
public int AudioSampleRate { get; set; } public int AudioSampleRate { get; set; }
public bool NormalizeFramerate { get; set; } public bool NormalizeFramerate { get; set; }
@ -39,7 +40,7 @@ public record FFmpegProfile
VideoBufferSize = 4000, VideoBufferSize = 4000,
AudioBitrate = 192, AudioBitrate = 192,
AudioBufferSize = 384, AudioBufferSize = 384,
NormalizeLoudness = true, NormalizeLoudnessMode = NormalizeLoudnessMode.DynAudNorm,
AudioChannels = 2, AudioChannels = 2,
AudioSampleRate = 48, AudioSampleRate = 48,
DeinterlaceVideo = true, DeinterlaceVideo = true,

8
ErsatzTV.Core/Domain/NormalizeLoudnessMode.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.Core.Domain;
public enum NormalizeLoudnessMode
{
Off = 0,
LoudNorm = 1,
DynAudNorm = 2
}

7
ErsatzTV.Core/Domain/ScalingBehavior.cs

@ -0,0 +1,7 @@
namespace ErsatzTV.Core.Domain;
public enum ScalingBehavior
{
ScaleAndPad = 0,
Stretch = 1
}

26
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -150,7 +150,12 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.AudioBufferSize, playbackSettings.AudioBufferSize,
playbackSettings.AudioSampleRate, playbackSettings.AudioSampleRate,
videoPath == audioPath ? playbackSettings.AudioDuration : Option<TimeSpan>.None, videoPath == audioPath ? playbackSettings.AudioDuration : Option<TimeSpan>.None,
playbackSettings.NormalizeLoudness); playbackSettings.NormalizeLoudnessMode switch
{
NormalizeLoudnessMode.LoudNorm => AudioFilter.LoudNorm,
NormalizeLoudnessMode.DynAudNorm => AudioFilter.DynAudNorm,
_ => AudioFilter.None
});
// don't log generated images, or hls direct, which are expected to have unknown format // don't log generated images, or hls direct, which are expected to have unknown format
bool isUnknownPixelFormatExpected = bool isUnknownPixelFormatExpected =
@ -301,15 +306,26 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.ts") ? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.ts")
: Option<string>.None; : Option<string>.None;
FrameSize scaledSize = ffmpegVideoStream.SquarePixelFrameSize(
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height));
var paddedSize = new FrameSize(
channel.FFmpegProfile.Resolution.Width,
channel.FFmpegProfile.Resolution.Height);
if (channel.FFmpegProfile.ScalingBehavior is ScalingBehavior.Stretch)
{
scaledSize = paddedSize;
}
var desiredState = new FrameState( var desiredState = new FrameState(
playbackSettings.RealtimeOutput, playbackSettings.RealtimeOutput,
fillerKind == FillerKind.Fallback, fillerKind == FillerKind.Fallback,
videoFormat, videoFormat,
Optional(videoStream.Profile), Optional(videoStream.Profile),
Optional(playbackSettings.PixelFormat), Optional(playbackSettings.PixelFormat),
ffmpegVideoStream.SquarePixelFrameSize( scaledSize,
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height)), paddedSize,
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height),
false, false,
playbackSettings.FrameRate, playbackSettings.FrameRate,
playbackSettings.VideoBitrate, playbackSettings.VideoBitrate,
@ -403,7 +419,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.AudioBufferSize, playbackSettings.AudioBufferSize,
playbackSettings.AudioSampleRate, playbackSettings.AudioSampleRate,
Option<TimeSpan>.None, Option<TimeSpan>.None,
false); AudioFilter.None);
var desiredState = new FrameState( var desiredState = new FrameState(
playbackSettings.RealtimeOutput, playbackSettings.RealtimeOutput,

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs

@ -26,6 +26,6 @@ public class FFmpegPlaybackSettings
public FFmpegProfileAudioFormat AudioFormat { get; set; } public FFmpegProfileAudioFormat AudioFormat { get; set; }
public bool Deinterlace { get; set; } public bool Deinterlace { get; set; }
public Option<int> VideoTrackTimeScale { get; set; } public Option<int> VideoTrackTimeScale { get; set; }
public bool NormalizeLoudness { get; set; } public NormalizeLoudnessMode NormalizeLoudnessMode { get; set; }
public Option<int> FrameRate { get; set; } public Option<int> FrameRate { get; set; }
} }

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -158,7 +158,7 @@ public static class FFmpegPlaybackSettingsCalculator
result.AudioSampleRate = ffmpegProfile.AudioSampleRate; result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
result.AudioDuration = outPoint - inPoint; result.AudioDuration = outPoint - inPoint;
result.NormalizeLoudness = ffmpegProfile.NormalizeLoudness; result.NormalizeLoudnessMode = ffmpegProfile.NormalizeLoudnessMode;
result.Deinterlace = ffmpegProfile.DeinterlaceVideo == true && result.Deinterlace = ffmpegProfile.DeinterlaceVideo == true &&
videoVersion.VideoScanKind == VideoScanKind.Interlaced; videoVersion.VideoScanKind == VideoScanKind.Interlaced;

6
ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs

@ -51,7 +51,7 @@ public class PipelineBuilderBaseTests
640, 640,
48, 48,
Option<TimeSpan>.None, Option<TimeSpan>.None,
false)); AudioFilter.None));
var desiredState = new FrameState( var desiredState = new FrameState(
true, true,
@ -139,7 +139,7 @@ public class PipelineBuilderBaseTests
640, 640,
48, 48,
Option<TimeSpan>.None, Option<TimeSpan>.None,
false)); AudioFilter.None));
var desiredState = new FrameState( var desiredState = new FrameState(
true, true,
@ -281,7 +281,7 @@ public class PipelineBuilderBaseTests
None, None,
None, None,
None, None,
false)); AudioFilter.None));
var desiredState = new FrameState( var desiredState = new FrameState(
true, true,

8
ErsatzTV.FFmpeg/AudioFilter.cs

@ -0,0 +1,8 @@
namespace ErsatzTV.FFmpeg;
public enum AudioFilter
{
None = 0,
LoudNorm = 1,
DynAudNorm = 2
}

14
ErsatzTV.FFmpeg/Filter/NormalizeLoudnessFilter.cs

@ -2,7 +2,19 @@
public class NormalizeLoudnessFilter : BaseFilter public class NormalizeLoudnessFilter : BaseFilter
{ {
public override string Filter => "loudnorm=I=-16:TP=-1.5:LRA=11"; private readonly AudioFilter _loudnessFilter;
public NormalizeLoudnessFilter(AudioFilter loudnessFilter)
{
_loudnessFilter = loudnessFilter;
}
public override string Filter => _loudnessFilter switch
{
AudioFilter.LoudNorm => "loudnorm=I=-16:TP=-1.5:LRA=11",
AudioFilter.DynAudNorm => "dynaudnorm",
_ => string.Empty
};
public override FrameState NextState(FrameState currentState) => currentState; public override FrameState NextState(FrameState currentState) => currentState;
} }

21
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -350,9 +350,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private void SetAudioLoudness(AudioInputFile audioInputFile) private void SetAudioLoudness(AudioInputFile audioInputFile)
{ {
if (audioInputFile.DesiredState.NormalizeLoudness) if (audioInputFile.DesiredState.NormalizeLoudnessFilter is not AudioFilter.None)
{ {
_audioInputFile.Iter(f => f.FilterSteps.Add(new NormalizeLoudnessFilter())); _audioInputFile.Iter(
f => f.FilterSteps.Add(
new NormalizeLoudnessFilter(audioInputFile.DesiredState.NormalizeLoudnessFilter)));
} }
} }
@ -598,11 +600,24 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
private void SetRealtimeInput(VideoInputFile videoInputFile, FrameState desiredState) private void SetRealtimeInput(VideoInputFile videoInputFile, FrameState desiredState)
{ {
var initialBurst = 0; int initialBurst;
if (!desiredState.Realtime) if (!desiredState.Realtime)
{ {
initialBurst = 180; initialBurst = 180;
} }
else
{
AudioFilter filter = _audioInputFile
.Map(a => a.DesiredState.NormalizeLoudnessFilter)
.IfNone(AudioFilter.None);
initialBurst = filter switch
{
AudioFilter.LoudNorm => 5,
AudioFilter.DynAudNorm => 15,
_ => 0
};
}
_audioInputFile.Iter(a => a.AddOption(new ReadrateInputOption(initialBurst))); _audioInputFile.Iter(a => a.AddOption(new ReadrateInputOption(initialBurst)));
videoInputFile.AddOption(new ReadrateInputOption(initialBurst)); videoInputFile.AddOption(new ReadrateInputOption(initialBurst));

4
ErsatzTV.FFmpeg/State/AudioState.cs

@ -7,7 +7,7 @@ public record AudioState(
Option<int> AudioBufferSize, Option<int> AudioBufferSize,
Option<int> AudioSampleRate, Option<int> AudioSampleRate,
Option<TimeSpan> AudioDuration, Option<TimeSpan> AudioDuration,
bool NormalizeLoudness) AudioFilter NormalizeLoudnessFilter)
{ {
public static readonly AudioState Copy = new( public static readonly AudioState Copy = new(
Format.AudioFormat.Copy, Format.AudioFormat.Copy,
@ -16,6 +16,6 @@ public record AudioState(
Option<int>.None, Option<int>.None,
Option<int>.None, Option<int>.None,
Option<TimeSpan>.None, Option<TimeSpan>.None,
false AudioFilter.None
); );
} }

4429
ErsatzTV.Infrastructure.MySql/Migrations/20230921065244_Add_FFmpegProfileScalingBehavior.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20230921065244_Add_FFmpegProfileScalingBehavior.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfileScalingBehavior : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ScalingBehavior",
table: "FFmpegProfile",
type: "int",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ScalingBehavior",
table: "FFmpegProfile");
}
}
}

4432
ErsatzTV.Infrastructure.MySql/Migrations/20230921071426_Add_FFmpegProfileNormalizeLoudnessMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure.MySql/Migrations/20230921071426_Add_FFmpegProfileNormalizeLoudnessMode.cs

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfileNormalizeLoudnessMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "NormalizeLoudnessMode",
table: "FFmpegProfile",
type: "int",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql("update FFmpegProfile set NormalizeLoudnessMode = 1 where NormalizeLoudness = 1");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "NormalizeLoudnessMode",
table: "FFmpegProfile");
}
}
}

4429
ErsatzTV.Infrastructure.MySql/Migrations/20230921072719_Remove_FFmpegProfileNormalizeLoudness.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20230921072719_Remove_FFmpegProfileNormalizeLoudness.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Remove_FFmpegProfileNormalizeLoudness : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "NormalizeLoudness",
table: "FFmpegProfile");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "NormalizeLoudness",
table: "FFmpegProfile",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}
}

9
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -16,7 +16,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "7.0.10") .HasAnnotation("ProductVersion", "7.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b => modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
@ -574,8 +574,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
.HasColumnType("tinyint(1)") .HasColumnType("tinyint(1)")
.HasDefaultValue(false); .HasDefaultValue(false);
b.Property<bool>("NormalizeLoudness") b.Property<int>("NormalizeLoudnessMode")
.HasColumnType("tinyint(1)"); .HasColumnType("int");
b.Property<int?>("QsvExtraHardwareFrames") b.Property<int?>("QsvExtraHardwareFrames")
.HasColumnType("int"); .HasColumnType("int");
@ -583,6 +583,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("ResolutionId") b.Property<int>("ResolutionId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("ScalingBehavior")
.HasColumnType("int");
b.Property<int>("ThreadCount") b.Property<int>("ThreadCount")
.HasColumnType("int"); .HasColumnType("int");

4427
ErsatzTV.Infrastructure.Sqlite/Migrations/20230921065135_Add_FFmpegProfileScalingBehavior.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20230921065135_Add_FFmpegProfileScalingBehavior.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfileScalingBehavior : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "ScalingBehavior",
table: "FFmpegProfile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ScalingBehavior",
table: "FFmpegProfile");
}
}
}

4430
ErsatzTV.Infrastructure.Sqlite/Migrations/20230921071647_Add_FFmpegProfileNormalizeLoudnessMode.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure.Sqlite/Migrations/20230921071647_Add_FFmpegProfileNormalizeLoudnessMode.cs

@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_FFmpegProfileNormalizeLoudnessMode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "NormalizeLoudnessMode",
table: "FFmpegProfile",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.Sql("update FFmpegProfile set NormalizeLoudnessMode = 1 where NormalizeLoudness = 1");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "NormalizeLoudnessMode",
table: "FFmpegProfile");
}
}
}

4427
ErsatzTV.Infrastructure.Sqlite/Migrations/20230921072813_Remove_FFmpegProfileNormalizeLoudness.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20230921072813_Remove_FFmpegProfileNormalizeLoudness.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Remove_FFmpegProfileNormalizeLoudness : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "NormalizeLoudness",
table: "FFmpegProfile");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "NormalizeLoudness",
table: "FFmpegProfile",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}
}

8
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -1,7 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Sqlite.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@ -16,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.7"); modelBuilder.HasAnnotation("ProductVersion", "7.0.11");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b => modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{ {
@ -573,7 +572,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
.HasDefaultValue(false); .HasDefaultValue(false);
b.Property<bool>("NormalizeLoudness") b.Property<int>("NormalizeLoudnessMode")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int?>("QsvExtraHardwareFrames") b.Property<int?>("QsvExtraHardwareFrames")
@ -582,6 +581,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("ResolutionId") b.Property<int>("ResolutionId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("ScalingBehavior")
.HasColumnType("INTEGER");
b.Property<int>("ThreadCount") b.Property<int>("ThreadCount")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

2
ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj

@ -14,7 +14,7 @@
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"> <PackageReference Include="NUnit.Analyzers" Version="3.7.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

2
ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj

@ -14,7 +14,7 @@
<PackageReference Include="CliWrap" Version="3.6.4" /> <PackageReference Include="CliWrap" Version="3.6.4" />
<PackageReference Include="Dapper" Version="2.0.151" /> <PackageReference Include="Dapper" Version="2.0.151" />
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.9.3" /> <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.9.3" />
<PackageReference Include="Jint" Version="3.0.0-beta-2051" /> <PackageReference Include="Jint" Version="3.0.0-beta-2052" />
<PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.Analysis.Common" Version="4.8.0-beta00016" />
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" /> <PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />

2
ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj

@ -15,7 +15,7 @@
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1"> <PackageReference Include="NUnit.Analyzers" Version="3.7.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

8
ErsatzTV/Controllers/IptvController.cs

@ -149,7 +149,7 @@ public class IptvController : ControllerBase
Option<TrimPlaylistResult> maybePlaylist = await worker.TrimPlaylist(now, cancellationToken); Option<TrimPlaylistResult> maybePlaylist = await worker.TrimPlaylist(now, cancellationToken);
foreach (TrimPlaylistResult result in maybePlaylist) foreach (TrimPlaylistResult result in maybePlaylist)
{ {
return Content(result.Playlist, "application/x-mpegurl"); return Content(result.Playlist, "application/vnd.apple.mpegurl");
} }
// TODO: better error here? // TODO: better error here?
@ -198,7 +198,7 @@ public class IptvController : ControllerBase
_logger.LogDebug( _logger.LogDebug(
"Session started; returning multi-variant playlist for channel {Channel}", "Session started; returning multi-variant playlist for channel {Channel}",
channelNumber); channelNumber);
return Content(GetMultiVariantPlaylist(channelNumber), "application/x-mpegurl"); return Content(GetMultiVariantPlaylist(channelNumber), "application/vnd.apple.mpegurl");
// return Redirect($"~/iptv/session/{channelNumber}/hls.m3u8"); // return Redirect($"~/iptv/session/{channelNumber}/hls.m3u8");
}, },
error => error =>
@ -209,7 +209,7 @@ public class IptvController : ControllerBase
_logger.LogDebug( _logger.LogDebug(
"Session is already active; returning multi-variant playlist for channel {Channel}", "Session is already active; returning multi-variant playlist for channel {Channel}",
channelNumber); channelNumber);
return Content(GetMultiVariantPlaylist(channelNumber), "application/x-mpegurl"); return Content(GetMultiVariantPlaylist(channelNumber), "application/vnd.apple.mpegurl");
// return RedirectPreserveMethod($"iptv/session/{channelNumber}/hls.m3u8"); // return RedirectPreserveMethod($"iptv/session/{channelNumber}/hls.m3u8");
default: default:
_logger.LogWarning( _logger.LogWarning(
@ -228,7 +228,7 @@ public class IptvController : ControllerBase
mode)) mode))
.Map( .Map(
r => r.Match<IActionResult>( r => r.Match<IActionResult>(
playlist => Content(playlist, "application/x-mpegurl"), playlist => Content(playlist, "application/vnd.apple.mpegurl"),
error => BadRequest(error.Value))); error => BadRequest(error.Value)));
} }
} }

2
ErsatzTV/ErsatzTV.csproj

@ -75,7 +75,7 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="MudBlazor" Version="6.9.0" /> <PackageReference Include="MudBlazor" Version="6.10.0" />
<PackageReference Include="NaturalSort.Extension" Version="4.0.0" /> <PackageReference Include="NaturalSort.Extension" Version="4.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" /> <PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="3.0.1" />

12
ErsatzTV/Pages/FFmpegEditor.razor

@ -42,6 +42,12 @@
} }
</MudSelect> </MudSelect>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudSelect Label="Scaling Behavior" @bind-Value="_model.ScalingBehavior" For="@(() => _model.ScalingBehavior)">
<MudSelectItem Value="@ScalingBehavior.ScaleAndPad">Scale and Pad</MudSelectItem>
<MudSelectItem Value="@ScalingBehavior.Stretch">Stretch</MudSelectItem>
</MudSelect>
</MudElement>
</MudItem> </MudItem>
<MudItem> <MudItem>
<MudText Typo="Typo.h6">Video</MudText> <MudText Typo="Typo.h6">Video</MudText>
@ -128,7 +134,11 @@
<MudTextField Label="Sample Rate" @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/> <MudTextField Label="Sample Rate" @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Label="Normalize Loudness" @bind-Checked="@_model.NormalizeLoudness" For="@(() => _model.NormalizeLoudness)"/> <MudSelect Label="Normalize Loudness" @bind-Value="_model.NormalizeLoudnessMode" For="@(() => _model.NormalizeLoudnessMode)">
<MudSelectItem Value="@NormalizeLoudnessMode.Off">Off</MudSelectItem>
<MudSelectItem Value="@NormalizeLoudnessMode.LoudNorm">loudnorm</MudSelectItem>
<MudSelectItem Value="@NormalizeLoudnessMode.DynAudNorm">dynaudnorm</MudSelectItem>
</MudSelect>
</MudElement> </MudElement>
</MudItem> </MudItem>
</MudGrid> </MudGrid>

12
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -18,12 +18,13 @@ public class FFmpegProfileEditViewModel
AudioChannels = viewModel.AudioChannels; AudioChannels = viewModel.AudioChannels;
AudioFormat = viewModel.AudioFormat; AudioFormat = viewModel.AudioFormat;
AudioSampleRate = viewModel.AudioSampleRate; AudioSampleRate = viewModel.AudioSampleRate;
NormalizeLoudness = viewModel.NormalizeLoudness; NormalizeLoudnessMode = viewModel.NormalizeLoudnessMode;
Id = viewModel.Id; Id = viewModel.Id;
Name = viewModel.Name; Name = viewModel.Name;
NormalizeFramerate = viewModel.NormalizeFramerate; NormalizeFramerate = viewModel.NormalizeFramerate;
DeinterlaceVideo = viewModel.DeinterlaceVideo; DeinterlaceVideo = viewModel.DeinterlaceVideo;
Resolution = viewModel.Resolution; Resolution = viewModel.Resolution;
ScalingBehavior = viewModel.ScalingBehavior;
ThreadCount = viewModel.ThreadCount; ThreadCount = viewModel.ThreadCount;
HardwareAcceleration = viewModel.HardwareAcceleration; HardwareAcceleration = viewModel.HardwareAcceleration;
VaapiDriver = viewModel.VaapiDriver; VaapiDriver = viewModel.VaapiDriver;
@ -40,12 +41,13 @@ public class FFmpegProfileEditViewModel
public int AudioChannels { get; set; } public int AudioChannels { get; set; }
public FFmpegProfileAudioFormat AudioFormat { get; set; } public FFmpegProfileAudioFormat AudioFormat { get; set; }
public int AudioSampleRate { get; set; } public int AudioSampleRate { get; set; }
public bool NormalizeLoudness { get; set; } public NormalizeLoudnessMode NormalizeLoudnessMode { get; set; }
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 DeinterlaceVideo { get; set; } public bool DeinterlaceVideo { get; set; }
public ResolutionViewModel Resolution { get; set; } public ResolutionViewModel Resolution { get; set; }
public ScalingBehavior ScalingBehavior { get; set; }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public HardwareAccelerationKind HardwareAcceleration { get; set; } public HardwareAccelerationKind HardwareAcceleration { get; set; }
public VaapiDriver VaapiDriver { get; set; } public VaapiDriver VaapiDriver { get; set; }
@ -65,6 +67,7 @@ public class FFmpegProfileEditViewModel
VaapiDevice, VaapiDevice,
QsvExtraHardwareFrames, QsvExtraHardwareFrames,
Resolution.Id, Resolution.Id,
ScalingBehavior,
VideoFormat, VideoFormat,
BitDepth, BitDepth,
VideoBitrate, VideoBitrate,
@ -72,7 +75,7 @@ public class FFmpegProfileEditViewModel
AudioFormat, AudioFormat,
AudioBitrate, AudioBitrate,
AudioBufferSize, AudioBufferSize,
NormalizeLoudness, NormalizeLoudnessMode,
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeFramerate, NormalizeFramerate,
@ -89,6 +92,7 @@ public class FFmpegProfileEditViewModel
VaapiDevice, VaapiDevice,
QsvExtraHardwareFrames, QsvExtraHardwareFrames,
Resolution.Id, Resolution.Id,
ScalingBehavior,
VideoFormat, VideoFormat,
BitDepth, BitDepth,
VideoBitrate, VideoBitrate,
@ -96,7 +100,7 @@ public class FFmpegProfileEditViewModel
AudioFormat, AudioFormat,
AudioBitrate, AudioBitrate,
AudioBufferSize, AudioBufferSize,
NormalizeLoudness, NormalizeLoudnessMode,
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeFramerate, NormalizeFramerate,

Loading…
Cancel
Save