scaling behavior and normalize loudness (#1439)
* update changelog [no ci] * add ffmpeg profile scaling behavior * update dependencies * add normalize loudness mode * update changelog
This commit is contained in:
@@ -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/).
|
||||
|
||||
## [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
|
||||
### Added
|
||||
|
||||
@@ -12,6 +12,7 @@ public record CreateFFmpegProfile(
|
||||
string VaapiDevice,
|
||||
int? QsvExtraHardwareFrames,
|
||||
int ResolutionId,
|
||||
ScalingBehavior ScalingBehavior,
|
||||
FFmpegProfileVideoFormat VideoFormat,
|
||||
FFmpegProfileBitDepth BitDepth,
|
||||
int VideoBitrate,
|
||||
@@ -19,7 +20,7 @@ public record CreateFFmpegProfile(
|
||||
FFmpegProfileAudioFormat AudioFormat,
|
||||
int AudioBitrate,
|
||||
int AudioBufferSize,
|
||||
bool NormalizeLoudness,
|
||||
NormalizeLoudnessMode NormalizeLoudnessMode,
|
||||
int AudioChannels,
|
||||
int AudioSampleRate,
|
||||
bool NormalizeFramerate,
|
||||
|
||||
@@ -46,6 +46,7 @@ public class CreateFFmpegProfileHandler :
|
||||
VaapiDevice = request.VaapiDevice,
|
||||
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
|
||||
ResolutionId = resolutionId,
|
||||
ScalingBehavior = request.ScalingBehavior,
|
||||
VideoFormat = request.VideoFormat,
|
||||
BitDepth = request.BitDepth,
|
||||
VideoBitrate = request.VideoBitrate,
|
||||
@@ -53,7 +54,7 @@ public class CreateFFmpegProfileHandler :
|
||||
AudioFormat = request.AudioFormat,
|
||||
AudioBitrate = request.AudioBitrate,
|
||||
AudioBufferSize = request.AudioBufferSize,
|
||||
NormalizeLoudness = request.NormalizeLoudness,
|
||||
NormalizeLoudnessMode = request.NormalizeLoudnessMode,
|
||||
AudioChannels = request.AudioChannels,
|
||||
AudioSampleRate = request.AudioSampleRate,
|
||||
NormalizeFramerate = request.NormalizeFramerate,
|
||||
|
||||
@@ -13,6 +13,7 @@ public record UpdateFFmpegProfile(
|
||||
string VaapiDevice,
|
||||
int? QsvExtraHardwareFrames,
|
||||
int ResolutionId,
|
||||
ScalingBehavior ScalingBehavior,
|
||||
FFmpegProfileVideoFormat VideoFormat,
|
||||
FFmpegProfileBitDepth BitDepth,
|
||||
int VideoBitrate,
|
||||
@@ -20,7 +21,7 @@ public record UpdateFFmpegProfile(
|
||||
FFmpegProfileAudioFormat AudioFormat,
|
||||
int AudioBitrate,
|
||||
int AudioBufferSize,
|
||||
bool NormalizeLoudness,
|
||||
NormalizeLoudnessMode NormalizeLoudnessMode,
|
||||
int AudioChannels,
|
||||
int AudioSampleRate,
|
||||
bool NormalizeFramerate,
|
||||
|
||||
@@ -35,6 +35,7 @@ public class
|
||||
p.VaapiDevice = update.VaapiDevice;
|
||||
p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames;
|
||||
p.ResolutionId = update.ResolutionId;
|
||||
p.ScalingBehavior = update.ScalingBehavior;
|
||||
p.VideoFormat = update.VideoFormat;
|
||||
|
||||
// mpeg2video only supports 8-bit content
|
||||
@@ -47,7 +48,7 @@ public class
|
||||
p.AudioFormat = update.AudioFormat;
|
||||
p.AudioBitrate = update.AudioBitrate;
|
||||
p.AudioBufferSize = update.AudioBufferSize;
|
||||
p.NormalizeLoudness = update.NormalizeLoudness;
|
||||
p.NormalizeLoudnessMode = update.NormalizeLoudnessMode;
|
||||
p.AudioChannels = update.AudioChannels;
|
||||
p.AudioSampleRate = update.AudioSampleRate;
|
||||
p.NormalizeFramerate = update.NormalizeFramerate;
|
||||
|
||||
@@ -13,6 +13,7 @@ public record FFmpegProfileViewModel(
|
||||
string VaapiDevice,
|
||||
int? QsvExtraHardwareFrames,
|
||||
ResolutionViewModel Resolution,
|
||||
ScalingBehavior ScalingBehavior,
|
||||
FFmpegProfileVideoFormat VideoFormat,
|
||||
FFmpegProfileBitDepth BitDepth,
|
||||
int VideoBitrate,
|
||||
@@ -20,7 +21,7 @@ public record FFmpegProfileViewModel(
|
||||
FFmpegProfileAudioFormat AudioFormat,
|
||||
int AudioBitrate,
|
||||
int AudioBufferSize,
|
||||
bool NormalizeLoudness,
|
||||
NormalizeLoudnessMode NormalizeLoudnessMode,
|
||||
int AudioChannels,
|
||||
int AudioSampleRate,
|
||||
bool NormalizeFramerate,
|
||||
|
||||
@@ -15,6 +15,7 @@ internal static class Mapper
|
||||
profile.VaapiDevice,
|
||||
profile.QsvExtraHardwareFrames,
|
||||
Resolutions.Mapper.ProjectToViewModel(profile.Resolution),
|
||||
profile.ScalingBehavior,
|
||||
profile.VideoFormat,
|
||||
profile.BitDepth,
|
||||
profile.VideoBitrate,
|
||||
@@ -22,7 +23,7 @@ internal static class Mapper
|
||||
profile.AudioFormat,
|
||||
profile.AudioBitrate,
|
||||
profile.AudioBufferSize,
|
||||
profile.NormalizeLoudness,
|
||||
profile.NormalizeLoudnessMode,
|
||||
profile.AudioChannels,
|
||||
profile.AudioSampleRate,
|
||||
profile.NormalizeFramerate,
|
||||
@@ -51,7 +52,7 @@ internal static class Mapper
|
||||
(int)ffmpegProfile.AudioFormat,
|
||||
ffmpegProfile.AudioBitrate,
|
||||
ffmpegProfile.AudioBufferSize,
|
||||
ffmpegProfile.NormalizeLoudness,
|
||||
(int)ffmpegProfile.NormalizeLoudnessMode,
|
||||
ffmpegProfile.AudioChannels,
|
||||
ffmpegProfile.AudioSampleRate,
|
||||
ffmpegProfile.NormalizeFramerate,
|
||||
|
||||
@@ -886,7 +886,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
|
||||
{
|
||||
FFmpegProfile ffmpegProfile = TestProfile() with
|
||||
{
|
||||
NormalizeLoudness = true
|
||||
NormalizeLoudnessMode = NormalizeLoudnessMode.LoudNorm
|
||||
};
|
||||
|
||||
FFmpegPlaybackSettings actual = FFmpegPlaybackSettingsCalculator.CalculateSettings(
|
||||
@@ -902,7 +902,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
|
||||
false,
|
||||
None);
|
||||
|
||||
actual.NormalizeLoudness.Should().BeTrue();
|
||||
actual.NormalizeLoudnessMode.Should().Be(NormalizeLoudnessMode.LoudNorm);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public record FFmpegFullProfileResponseModel(
|
||||
int AudioFormat,
|
||||
int AudioBitrate,
|
||||
int AudioBufferSize,
|
||||
bool NormalizeLoudness,
|
||||
int NormalizeLoudnessMode,
|
||||
int AudioChannels,
|
||||
int AudioSampleRate,
|
||||
bool NormalizeFramerate,
|
||||
|
||||
@@ -13,6 +13,7 @@ public record FFmpegProfile
|
||||
public int? QsvExtraHardwareFrames { get; set; }
|
||||
public int ResolutionId { get; set; }
|
||||
public Resolution Resolution { get; set; }
|
||||
public ScalingBehavior ScalingBehavior { get; set; }
|
||||
public FFmpegProfileVideoFormat VideoFormat { get; set; }
|
||||
public FFmpegProfileBitDepth BitDepth { get; set; }
|
||||
public int VideoBitrate { get; set; }
|
||||
@@ -20,7 +21,7 @@ public record FFmpegProfile
|
||||
public FFmpegProfileAudioFormat AudioFormat { get; set; }
|
||||
public int AudioBitrate { get; set; }
|
||||
public int AudioBufferSize { get; set; }
|
||||
public bool NormalizeLoudness { get; set; }
|
||||
public NormalizeLoudnessMode NormalizeLoudnessMode { get; set; }
|
||||
public int AudioChannels { get; set; }
|
||||
public int AudioSampleRate { get; set; }
|
||||
public bool NormalizeFramerate { get; set; }
|
||||
@@ -39,7 +40,7 @@ public record FFmpegProfile
|
||||
VideoBufferSize = 4000,
|
||||
AudioBitrate = 192,
|
||||
AudioBufferSize = 384,
|
||||
NormalizeLoudness = true,
|
||||
NormalizeLoudnessMode = NormalizeLoudnessMode.DynAudNorm,
|
||||
AudioChannels = 2,
|
||||
AudioSampleRate = 48,
|
||||
DeinterlaceVideo = true,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
public enum NormalizeLoudnessMode
|
||||
{
|
||||
Off = 0,
|
||||
LoudNorm = 1,
|
||||
DynAudNorm = 2
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace ErsatzTV.Core.Domain;
|
||||
|
||||
public enum ScalingBehavior
|
||||
{
|
||||
ScaleAndPad = 0,
|
||||
Stretch = 1
|
||||
}
|
||||
@@ -150,7 +150,12 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
|
||||
playbackSettings.AudioBufferSize,
|
||||
playbackSettings.AudioSampleRate,
|
||||
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
|
||||
bool isUnknownPixelFormatExpected =
|
||||
@@ -301,15 +306,26 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
|
||||
? Path.Combine(FileSystemLayout.TranscodeFolder, channel.Number, "live%06d.ts")
|
||||
: 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(
|
||||
playbackSettings.RealtimeOutput,
|
||||
fillerKind == FillerKind.Fallback,
|
||||
videoFormat,
|
||||
Optional(videoStream.Profile),
|
||||
Optional(playbackSettings.PixelFormat),
|
||||
ffmpegVideoStream.SquarePixelFrameSize(
|
||||
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height)),
|
||||
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height),
|
||||
scaledSize,
|
||||
paddedSize,
|
||||
false,
|
||||
playbackSettings.FrameRate,
|
||||
playbackSettings.VideoBitrate,
|
||||
@@ -403,7 +419,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
|
||||
playbackSettings.AudioBufferSize,
|
||||
playbackSettings.AudioSampleRate,
|
||||
Option<TimeSpan>.None,
|
||||
false);
|
||||
AudioFilter.None);
|
||||
|
||||
var desiredState = new FrameState(
|
||||
playbackSettings.RealtimeOutput,
|
||||
|
||||
@@ -26,6 +26,6 @@ public class FFmpegPlaybackSettings
|
||||
public FFmpegProfileAudioFormat AudioFormat { get; set; }
|
||||
public bool Deinterlace { get; set; }
|
||||
public Option<int> VideoTrackTimeScale { get; set; }
|
||||
public bool NormalizeLoudness { get; set; }
|
||||
public NormalizeLoudnessMode NormalizeLoudnessMode { get; set; }
|
||||
public Option<int> FrameRate { get; set; }
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public static class FFmpegPlaybackSettingsCalculator
|
||||
|
||||
result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
|
||||
result.AudioDuration = outPoint - inPoint;
|
||||
result.NormalizeLoudness = ffmpegProfile.NormalizeLoudness;
|
||||
result.NormalizeLoudnessMode = ffmpegProfile.NormalizeLoudnessMode;
|
||||
|
||||
result.Deinterlace = ffmpegProfile.DeinterlaceVideo == true &&
|
||||
videoVersion.VideoScanKind == VideoScanKind.Interlaced;
|
||||
|
||||
@@ -51,7 +51,7 @@ public class PipelineBuilderBaseTests
|
||||
640,
|
||||
48,
|
||||
Option<TimeSpan>.None,
|
||||
false));
|
||||
AudioFilter.None));
|
||||
|
||||
var desiredState = new FrameState(
|
||||
true,
|
||||
@@ -139,7 +139,7 @@ public class PipelineBuilderBaseTests
|
||||
640,
|
||||
48,
|
||||
Option<TimeSpan>.None,
|
||||
false));
|
||||
AudioFilter.None));
|
||||
|
||||
var desiredState = new FrameState(
|
||||
true,
|
||||
@@ -281,7 +281,7 @@ public class PipelineBuilderBaseTests
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
false));
|
||||
AudioFilter.None));
|
||||
|
||||
var desiredState = new FrameState(
|
||||
true,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace ErsatzTV.FFmpeg;
|
||||
|
||||
public enum AudioFilter
|
||||
{
|
||||
None = 0,
|
||||
LoudNorm = 1,
|
||||
DynAudNorm = 2
|
||||
}
|
||||
@@ -2,7 +2,19 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -350,9 +350,11 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
|
||||
|
||||
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,12 +600,25 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
|
||||
|
||||
private void SetRealtimeInput(VideoInputFile videoInputFile, FrameState desiredState)
|
||||
{
|
||||
var initialBurst = 0;
|
||||
int initialBurst;
|
||||
if (!desiredState.Realtime)
|
||||
{
|
||||
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)));
|
||||
videoInputFile.AddOption(new ReadrateInputOption(initialBurst));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ public record AudioState(
|
||||
Option<int> AudioBufferSize,
|
||||
Option<int> AudioSampleRate,
|
||||
Option<TimeSpan> AudioDuration,
|
||||
bool NormalizeLoudness)
|
||||
AudioFilter NormalizeLoudnessFilter)
|
||||
{
|
||||
public static readonly AudioState Copy = new(
|
||||
Format.AudioFormat.Copy,
|
||||
@@ -16,6 +16,6 @@ public record AudioState(
|
||||
Option<int>.None,
|
||||
Option<int>.None,
|
||||
Option<TimeSpan>.None,
|
||||
false
|
||||
AudioFilter.None
|
||||
);
|
||||
}
|
||||
|
||||
ErsatzTV.Infrastructure.MySql/Migrations/20230921065244_Add_FFmpegProfileScalingBehavior.Designer.cs
Generated
+4429
File diff suppressed because it is too large
Load Diff
+29
@@ -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
File diff suppressed because it is too large
Load Diff
+31
@@ -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
File diff suppressed because it is too large
Load Diff
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.10")
|
||||
.HasAnnotation("ProductVersion", "7.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||
@@ -574,8 +574,8 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
.HasColumnType("tinyint(1)")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("NormalizeLoudness")
|
||||
.HasColumnType("tinyint(1)");
|
||||
b.Property<int>("NormalizeLoudnessMode")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("QsvExtraHardwareFrames")
|
||||
.HasColumnType("int");
|
||||
@@ -583,6 +583,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
|
||||
b.Property<int>("ResolutionId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ScalingBehavior")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ThreadCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
|
||||
+4427
File diff suppressed because it is too large
Load Diff
+29
@@ -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
File diff suppressed because it is too large
Load Diff
+31
@@ -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
File diff suppressed because it is too large
Load Diff
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ErsatzTV.Infrastructure.Data;
|
||||
using ErsatzTV.Infrastructure.Sqlite.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
@@ -16,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.7");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.11");
|
||||
|
||||
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
|
||||
{
|
||||
@@ -573,7 +572,7 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("NormalizeLoudness")
|
||||
b.Property<int>("NormalizeLoudnessMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("QsvExtraHardwareFrames")
|
||||
@@ -582,6 +581,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
|
||||
b.Property<int>("ResolutionId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingBehavior")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ThreadCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<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>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<PackageReference Include="CliWrap" Version="3.6.4" />
|
||||
<PackageReference Include="Dapper" Version="2.0.151" />
|
||||
<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.Analysis.Common" Version="4.8.0-beta00016" />
|
||||
<PackageReference Include="Lucene.Net.QueryParser" Version="4.8.0-beta00016" />
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
||||
<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>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -149,7 +149,7 @@ public class IptvController : ControllerBase
|
||||
Option<TrimPlaylistResult> maybePlaylist = await worker.TrimPlaylist(now, cancellationToken);
|
||||
foreach (TrimPlaylistResult result in maybePlaylist)
|
||||
{
|
||||
return Content(result.Playlist, "application/x-mpegurl");
|
||||
return Content(result.Playlist, "application/vnd.apple.mpegurl");
|
||||
}
|
||||
|
||||
// TODO: better error here?
|
||||
@@ -198,7 +198,7 @@ public class IptvController : ControllerBase
|
||||
_logger.LogDebug(
|
||||
"Session started; returning multi-variant playlist for channel {Channel}",
|
||||
channelNumber);
|
||||
return Content(GetMultiVariantPlaylist(channelNumber), "application/x-mpegurl");
|
||||
return Content(GetMultiVariantPlaylist(channelNumber), "application/vnd.apple.mpegurl");
|
||||
// return Redirect($"~/iptv/session/{channelNumber}/hls.m3u8");
|
||||
},
|
||||
error =>
|
||||
@@ -209,7 +209,7 @@ public class IptvController : ControllerBase
|
||||
_logger.LogDebug(
|
||||
"Session is already active; returning multi-variant playlist for channel {Channel}",
|
||||
channelNumber);
|
||||
return Content(GetMultiVariantPlaylist(channelNumber), "application/x-mpegurl");
|
||||
return Content(GetMultiVariantPlaylist(channelNumber), "application/vnd.apple.mpegurl");
|
||||
// return RedirectPreserveMethod($"iptv/session/{channelNumber}/hls.m3u8");
|
||||
default:
|
||||
_logger.LogWarning(
|
||||
@@ -228,7 +228,7 @@ public class IptvController : ControllerBase
|
||||
mode))
|
||||
.Map(
|
||||
r => r.Match<IActionResult>(
|
||||
playlist => Content(playlist, "application/x-mpegurl"),
|
||||
playlist => Content(playlist, "application/vnd.apple.mpegurl"),
|
||||
error => BadRequest(error.Value)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</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="Refit.HttpClientFactory" Version="7.0.0" />
|
||||
<PackageReference Include="Serilog" Version="3.0.1" />
|
||||
|
||||
@@ -42,6 +42,12 @@
|
||||
}
|
||||
</MudSelect>
|
||||
</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>
|
||||
<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"/>
|
||||
</MudElement>
|
||||
<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>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@@ -18,12 +18,13 @@ public class FFmpegProfileEditViewModel
|
||||
AudioChannels = viewModel.AudioChannels;
|
||||
AudioFormat = viewModel.AudioFormat;
|
||||
AudioSampleRate = viewModel.AudioSampleRate;
|
||||
NormalizeLoudness = viewModel.NormalizeLoudness;
|
||||
NormalizeLoudnessMode = viewModel.NormalizeLoudnessMode;
|
||||
Id = viewModel.Id;
|
||||
Name = viewModel.Name;
|
||||
NormalizeFramerate = viewModel.NormalizeFramerate;
|
||||
DeinterlaceVideo = viewModel.DeinterlaceVideo;
|
||||
Resolution = viewModel.Resolution;
|
||||
ScalingBehavior = viewModel.ScalingBehavior;
|
||||
ThreadCount = viewModel.ThreadCount;
|
||||
HardwareAcceleration = viewModel.HardwareAcceleration;
|
||||
VaapiDriver = viewModel.VaapiDriver;
|
||||
@@ -40,12 +41,13 @@ public class FFmpegProfileEditViewModel
|
||||
public int AudioChannels { get; set; }
|
||||
public FFmpegProfileAudioFormat AudioFormat { get; set; }
|
||||
public int AudioSampleRate { get; set; }
|
||||
public bool NormalizeLoudness { get; set; }
|
||||
public NormalizeLoudnessMode NormalizeLoudnessMode { get; set; }
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool NormalizeFramerate { get; set; }
|
||||
public bool DeinterlaceVideo { get; set; }
|
||||
public ResolutionViewModel Resolution { get; set; }
|
||||
public ScalingBehavior ScalingBehavior { get; set; }
|
||||
public int ThreadCount { get; set; }
|
||||
public HardwareAccelerationKind HardwareAcceleration { get; set; }
|
||||
public VaapiDriver VaapiDriver { get; set; }
|
||||
@@ -65,6 +67,7 @@ public class FFmpegProfileEditViewModel
|
||||
VaapiDevice,
|
||||
QsvExtraHardwareFrames,
|
||||
Resolution.Id,
|
||||
ScalingBehavior,
|
||||
VideoFormat,
|
||||
BitDepth,
|
||||
VideoBitrate,
|
||||
@@ -72,7 +75,7 @@ public class FFmpegProfileEditViewModel
|
||||
AudioFormat,
|
||||
AudioBitrate,
|
||||
AudioBufferSize,
|
||||
NormalizeLoudness,
|
||||
NormalizeLoudnessMode,
|
||||
AudioChannels,
|
||||
AudioSampleRate,
|
||||
NormalizeFramerate,
|
||||
@@ -89,6 +92,7 @@ public class FFmpegProfileEditViewModel
|
||||
VaapiDevice,
|
||||
QsvExtraHardwareFrames,
|
||||
Resolution.Id,
|
||||
ScalingBehavior,
|
||||
VideoFormat,
|
||||
BitDepth,
|
||||
VideoBitrate,
|
||||
@@ -96,7 +100,7 @@ public class FFmpegProfileEditViewModel
|
||||
AudioFormat,
|
||||
AudioBitrate,
|
||||
AudioBufferSize,
|
||||
NormalizeLoudness,
|
||||
NormalizeLoudnessMode,
|
||||
AudioChannels,
|
||||
AudioSampleRate,
|
||||
NormalizeFramerate,
|
||||
|
||||
Reference in New Issue
Block a user