normalize framerate (#122)

* normalize framerate

* simplify audio normalization settings
This commit is contained in:
Jason Dove
2021-03-31 09:34:52 +00:00
committed by GitHub
parent 90f775aab4
commit 4097288fed
23 changed files with 3832 additions and 117 deletions
+3
View File
@@ -40,3 +40,6 @@ msbuild.wrn
core core
scripts/generate-api-sdk/swagger.json scripts/generate-api-sdk/swagger.json
docker-compose.override.yml
@@ -11,17 +11,16 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
bool Transcode, bool Transcode,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
int ResolutionId, int ResolutionId,
bool NormalizeResolution, bool NormalizeVideo,
string VideoCodec, string VideoCodec,
bool NormalizeVideoCodec,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,
string AudioCodec, string AudioCodec,
bool NormalizeAudioCodec,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
int AudioVolume, int AudioVolume,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeAudio) : IRequest<Either<BaseError, FFmpegProfileViewModel>>; bool NormalizeAudio,
string FrameRate) : IRequest<Either<BaseError, FFmpegProfileViewModel>>;
} }
@@ -43,19 +43,18 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
Transcode = request.Transcode, Transcode = request.Transcode,
HardwareAcceleration = request.HardwareAcceleration, HardwareAcceleration = request.HardwareAcceleration,
ResolutionId = resolutionId, ResolutionId = resolutionId,
NormalizeResolution = request.NormalizeResolution, NormalizeVideo = request.NormalizeVideo,
VideoCodec = request.VideoCodec, VideoCodec = request.VideoCodec,
NormalizeVideoCodec = request.NormalizeVideoCodec,
VideoBitrate = request.VideoBitrate, VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize, VideoBufferSize = request.VideoBufferSize,
AudioCodec = request.AudioCodec, AudioCodec = request.AudioCodec,
NormalizeAudioCodec = request.NormalizeAudioCodec,
AudioBitrate = request.AudioBitrate, AudioBitrate = request.AudioBitrate,
AudioBufferSize = request.AudioBufferSize, AudioBufferSize = request.AudioBufferSize,
AudioVolume = request.AudioVolume, AudioVolume = request.AudioVolume,
AudioChannels = request.AudioChannels, AudioChannels = request.AudioChannels,
AudioSampleRate = request.AudioSampleRate, AudioSampleRate = request.AudioSampleRate,
NormalizeAudio = request.NormalizeAudio NormalizeAudio = request.NormalizeAudio,
FrameRate = request.FrameRate
}); });
private Validation<BaseError, string> ValidateName(CreateFFmpegProfile createFFmpegProfile) => private Validation<BaseError, string> ValidateName(CreateFFmpegProfile createFFmpegProfile) =>
@@ -12,17 +12,16 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
bool Transcode, bool Transcode,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
int ResolutionId, int ResolutionId,
bool NormalizeResolution, bool NormalizeVideo,
string VideoCodec, string VideoCodec,
bool NormalizeVideoCodec,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,
string AudioCodec, string AudioCodec,
bool NormalizeAudioCodec,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
int AudioVolume, int AudioVolume,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeAudio) : IRequest<Either<BaseError, FFmpegProfileViewModel>>; bool NormalizeAudio,
string FrameRate) : IRequest<Either<BaseError, FFmpegProfileViewModel>>;
} }
@@ -37,19 +37,18 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
p.Transcode = update.Transcode; p.Transcode = update.Transcode;
p.HardwareAcceleration = update.HardwareAcceleration; p.HardwareAcceleration = update.HardwareAcceleration;
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.NormalizeResolution = update.NormalizeResolution; p.NormalizeVideo = update.NormalizeVideo;
p.VideoCodec = update.VideoCodec; p.VideoCodec = update.VideoCodec;
p.NormalizeVideoCodec = update.NormalizeVideoCodec;
p.VideoBitrate = update.VideoBitrate; p.VideoBitrate = update.VideoBitrate;
p.VideoBufferSize = update.VideoBufferSize; p.VideoBufferSize = update.VideoBufferSize;
p.AudioCodec = update.AudioCodec; p.AudioCodec = update.AudioCodec;
p.NormalizeAudioCodec = update.NormalizeAudioCodec;
p.AudioBitrate = update.AudioBitrate; p.AudioBitrate = update.AudioBitrate;
p.AudioBufferSize = update.AudioBufferSize; p.AudioBufferSize = update.AudioBufferSize;
p.AudioVolume = update.AudioVolume; p.AudioVolume = update.AudioVolume;
p.AudioChannels = update.AudioChannels; p.AudioChannels = update.AudioChannels;
p.AudioSampleRate = update.AudioSampleRate; p.AudioSampleRate = update.AudioSampleRate;
p.NormalizeAudio = update.NormalizeAudio; p.NormalizeAudio = update.NormalizeAudio;
p.FrameRate = update.FrameRate;
await _ffmpegProfileRepository.Update(p); await _ffmpegProfileRepository.Update(p);
return ProjectToViewModel(p); return ProjectToViewModel(p);
} }
@@ -10,17 +10,16 @@ namespace ErsatzTV.Application.FFmpegProfiles
bool Transcode, bool Transcode,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
ResolutionViewModel Resolution, ResolutionViewModel Resolution,
bool NormalizeResolution, bool NormalizeVideo,
string VideoCodec, string VideoCodec,
bool NormalizeVideoCodec,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,
string AudioCodec, string AudioCodec,
bool NormalizeAudioCodec,
int AudioBitrate, int AudioBitrate,
int AudioBufferSize, int AudioBufferSize,
int AudioVolume, int AudioVolume,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeAudio); bool NormalizeAudio,
string FrameRate);
} }
@@ -13,19 +13,18 @@ namespace ErsatzTV.Application.FFmpegProfiles
profile.Transcode, profile.Transcode,
profile.HardwareAcceleration, profile.HardwareAcceleration,
Project(profile.Resolution), Project(profile.Resolution),
profile.NormalizeResolution, profile.NormalizeVideo,
profile.VideoCodec, profile.VideoCodec,
profile.NormalizeVideoCodec,
profile.VideoBitrate, profile.VideoBitrate,
profile.VideoBufferSize, profile.VideoBufferSize,
profile.AudioCodec, profile.AudioCodec,
profile.NormalizeAudioCodec,
profile.AudioBitrate, profile.AudioBitrate,
profile.AudioBufferSize, profile.AudioBufferSize,
profile.AudioVolume, profile.AudioVolume,
profile.AudioChannels, profile.AudioChannels,
profile.AudioSampleRate, profile.AudioSampleRate,
profile.NormalizeAudio); profile.NormalizeAudio,
profile.FrameRate);
private static ResolutionViewModel Project(Resolution resolution) => private static ResolutionViewModel Project(Resolution resolution) =>
new(resolution.Id, resolution.Name, resolution.Width, resolution.Height); new(resolution.Id, resolution.Name, resolution.Width, resolution.Height);
@@ -163,9 +163,9 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void ShouldNot_SetScaledSize_When_NotNormalizingResolution_ForTransportStream() public void ShouldNot_SetScaledSize_When_NotNormalizingVideo_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeResolution = false }; FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeVideo = false };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
@@ -184,7 +184,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@@ -208,7 +208,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@@ -232,7 +232,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@@ -257,7 +257,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@@ -282,7 +282,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@@ -303,11 +303,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_NotPadToDesiredResolution_When_NotNormalizingResolution() public void Should_NotPadToDesiredResolution_When_NotNormalizingVideo()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeResolution = false, NormalizeVideo = false,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@@ -332,9 +332,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = false,
VideoCodec = "testCodec" VideoCodec = "testCodec"
}; };
@@ -357,13 +356,12 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[Test] [Test]
public void public void
Should_SetDesiredVideoCodec_When_ContentIsCorrectSize_And_NormalizingWrongCodec_ForTransportStream() Should_SetDesiredVideoCodec_When_ContentIsCorrectSize_And_NormalizingVideo_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = true,
VideoCodec = "testCodec" VideoCodec = "testCodec"
}; };
@@ -387,13 +385,12 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[Test] [Test]
public void public void
Should_SetCopyVideoCodec_When_ContentIsCorrectSize_And_NormalizingWrongCodec_ForHttpLiveStreaming() Should_SetCopyVideoCodec_When_ContentIsCorrectSize_And_NormalizingVideo_ForHttpLiveStreaming()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = true,
VideoCodec = "testCodec" VideoCodec = "testCodec"
}; };
@@ -420,9 +417,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = true,
VideoCodec = "libx264" VideoCodec = "libx264"
}; };
@@ -446,13 +442,12 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[Test] [Test]
public void public void
Should_SetCopyVideoCodec_When_ContentIsCorrectSize_And_NotNormalizingWrongCodec_ForTransportStream() Should_SetCopyVideoCodec_When_ContentIsCorrectSize_And_NotNormalizingVideo_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = false,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = false,
VideoCodec = "libx264" VideoCodec = "libx264"
}; };
@@ -479,9 +474,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = false,
VideoBitrate = 2525 VideoBitrate = 2525
}; };
@@ -503,13 +497,12 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetVideoBitrate_When_ContentIsCorrectSize_And_NormalizingWrongCodec_ForTransportStream() public void Should_SetVideoBitrate_When_ContentIsCorrectSize_And_NormalizingVideo_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = true,
VideoBitrate = 2525 VideoBitrate = 2525
}; };
@@ -536,9 +529,8 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = false,
VideoBufferSize = 2525 VideoBufferSize = 2525
}; };
@@ -561,13 +553,12 @@ namespace ErsatzTV.Core.Tests.FFmpeg
[Test] [Test]
public void public void
Should_SetVideoBufferSize_When_ContentIsCorrectSize_And_NormalizingWrongCodec_ForTransportStream() Should_SetVideoBufferSize_When_ContentIsCorrectSize_And_NormalizingVideo_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
NormalizeResolution = true, NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
NormalizeVideoCodec = true,
VideoBufferSize = 2525 VideoBufferSize = 2525
}; };
@@ -594,7 +585,7 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true, NormalizeAudio = true,
AudioCodec = "aac" AudioCodec = "aac"
}; };
@@ -613,11 +604,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetCopyAudioCodec_When_NotNormalizingWrongCodec_ForTransportStream() public void Should_SetCopyAudioCodec_When_NotNormalizingVideo_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = false, NormalizeAudio = false,
AudioCodec = "aac" AudioCodec = "aac"
}; };
@@ -636,11 +627,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetDesiredAudioCodec_When_NormalizingWrongCodec_ForTransportStream() public void Should_SetDesiredAudioCodec_When_NormalizingVideo_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true, NormalizeAudio = true,
AudioCodec = "aac" AudioCodec = "aac"
}; };
@@ -659,11 +650,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetCopyAudioCodec_When_NormalizingWrongCodec_ForHttpLiveStreaming() public void Should_SetCopyAudioCodec_When_NormalizingVideo_ForHttpLiveStreaming()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true, NormalizeAudio = true,
AudioCodec = "aac" AudioCodec = "aac"
}; };
@@ -682,11 +673,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetAudioBitrate_When_NormalizingWrongCodec_ForTransportStream() public void Should_SetAudioBitrate_When_NormalizingVideo_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true, NormalizeAudio = true,
AudioBitrate = 2424 AudioBitrate = 2424
}; };
@@ -705,11 +696,11 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetAudioBufferSize_When_NormalizingWrongCodec_ForTransportStream() public void Should_SetAudioBufferSize_When_NormalizingVideo_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true, NormalizeAudio = true,
AudioBufferSize = 2424 AudioBufferSize = 2424
}; };
@@ -732,7 +723,6 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true,
NormalizeAudio = true, NormalizeAudio = true,
AudioCodec = "ac3", AudioCodec = "ac3",
AudioChannels = 6 AudioChannels = 6
@@ -757,7 +747,6 @@ namespace ErsatzTV.Core.Tests.FFmpeg
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true,
NormalizeAudio = true, NormalizeAudio = true,
AudioCodec = "ac3", AudioCodec = "ac3",
AudioSampleRate = 48 AudioSampleRate = 48
@@ -778,11 +767,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetAudioChannels_When_NormalizingWrongCodecAndAudio_ForTransportStream() public void Should_SetAudioChannels_When_NormalizingVideoAndAudio_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true,
NormalizeAudio = true, NormalizeAudio = true,
AudioChannels = 6 AudioChannels = 6
}; };
@@ -802,11 +790,10 @@ namespace ErsatzTV.Core.Tests.FFmpeg
} }
[Test] [Test]
public void Should_SetAudioSampleRate_When_NormalizingWrongCodecAndAudio_ForTransportStream() public void Should_SetAudioSampleRate_When_NormalizingVideoAndAudio_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeAudioCodec = true,
NormalizeAudio = true, NormalizeAudio = true,
AudioSampleRate = 48 AudioSampleRate = 48
}; };
+4 -6
View File
@@ -9,13 +9,12 @@
public HardwareAccelerationKind HardwareAcceleration { get; set; } public HardwareAccelerationKind HardwareAcceleration { get; set; }
public int ResolutionId { get; set; } public int ResolutionId { get; set; }
public Resolution Resolution { get; set; } public Resolution Resolution { get; set; }
public bool NormalizeResolution { get; set; }
public string VideoCodec { get; set; } public string VideoCodec { get; set; }
public bool NormalizeVideoCodec { get; set; } public bool NormalizeVideo { get; set; }
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; } public int VideoBufferSize { get; set; }
public string FrameRate { get; set; }
public string AudioCodec { get; set; } public string AudioCodec { get; set; }
public bool NormalizeAudioCodec { get; set; }
public int AudioBitrate { get; set; } public int AudioBitrate { get; set; }
public int AudioBufferSize { get; set; } public int AudioBufferSize { get; set; }
public int AudioVolume { get; set; } public int AudioVolume { get; set; }
@@ -40,9 +39,8 @@
AudioVolume = 100, AudioVolume = 100,
AudioChannels = 2, AudioChannels = 2,
AudioSampleRate = 48, AudioSampleRate = 48,
NormalizeResolution = true, NormalizeVideo = true,
NormalizeVideoCodec = true, FrameRate = "24",
NormalizeAudioCodec = true,
NormalizeAudio = true NormalizeAudio = true
}; };
} }
@@ -13,6 +13,7 @@ namespace ErsatzTV.Core.FFmpeg
{ {
private Option<TimeSpan> _audioDuration = None; private Option<TimeSpan> _audioDuration = None;
private bool _deinterlace; private bool _deinterlace;
private Option<string> _frameRate = None;
private Option<HardwareAccelerationKind> _hardwareAccelerationKind = None; private Option<HardwareAccelerationKind> _hardwareAccelerationKind = None;
private string _inputCodec; private string _inputCodec;
private Option<IDisplaySize> _padToSize = None; private Option<IDisplaySize> _padToSize = None;
@@ -54,6 +55,12 @@ namespace ErsatzTV.Core.FFmpeg
return this; return this;
} }
public FFmpegComplexFilterBuilder WithFrameRate(Option<string> frameRate)
{
_frameRate = frameRate;
return this;
}
public Option<FFmpegComplexFilter> Build(int videoStreamIndex, int audioStreamIndex) public Option<FFmpegComplexFilter> Build(int videoStreamIndex, int audioStreamIndex)
{ {
var complexFilter = new StringBuilder(); var complexFilter = new StringBuilder();
@@ -104,6 +111,8 @@ namespace ErsatzTV.Core.FFmpeg
} }
} }
_frameRate.IfSome(frameRate => filterQueue.Add($"fps=fps={frameRate}"));
_scaleToSize.IfSome( _scaleToSize.IfSome(
size => size =>
{ {
@@ -16,7 +16,6 @@ namespace ErsatzTV.Core.FFmpeg
public Option<TimeSpan> StreamSeek { get; set; } public Option<TimeSpan> StreamSeek { get; set; }
public Option<IDisplaySize> ScaledSize { get; set; } public Option<IDisplaySize> ScaledSize { get; set; }
public bool PadToDesiredResolution { get; set; } public bool PadToDesiredResolution { get; set; }
public string ScalingAlgorithm => "fast_bilinear"; // TODO: from config, add tests
public string VideoCodec { get; set; } public string VideoCodec { get; set; }
public Option<int> VideoBitrate { get; set; } public Option<int> VideoBitrate { get; set; }
public Option<int> VideoBufferSize { get; set; } public Option<int> VideoBufferSize { get; set; }
@@ -27,5 +26,6 @@ namespace ErsatzTV.Core.FFmpeg
public Option<TimeSpan> AudioDuration { get; set; } public Option<TimeSpan> AudioDuration { get; set; }
public string AudioCodec { get; set; } public string AudioCodec { get; set; }
public bool Deinterlace { get; set; } public bool Deinterlace { get; set; }
public Option<string> FrameRate { get; set; }
} }
} }
@@ -81,11 +81,18 @@ namespace ErsatzTV.Core.FFmpeg
} }
IDisplaySize sizeAfterScaling = result.ScaledSize.IfNone(version); IDisplaySize sizeAfterScaling = result.ScaledSize.IfNone(version);
if (ffmpegProfile.NormalizeResolution && !sizeAfterScaling.IsSameSizeAs(ffmpegProfile.Resolution)) if (ffmpegProfile.NormalizeVideo && !sizeAfterScaling.IsSameSizeAs(ffmpegProfile.Resolution))
{ {
result.PadToDesiredResolution = true; result.PadToDesiredResolution = true;
} }
if (ffmpegProfile.NormalizeVideo)
{
result.FrameRate = string.IsNullOrWhiteSpace(ffmpegProfile.FrameRate)
? None
: Some(ffmpegProfile.FrameRate);
}
if (result.ScaledSize.IsSome || result.PadToDesiredResolution || if (result.ScaledSize.IsSome || result.PadToDesiredResolution ||
NeedToNormalizeVideoCodec(ffmpegProfile, videoStream)) NeedToNormalizeVideoCodec(ffmpegProfile, videoStream))
{ {
@@ -141,7 +148,7 @@ namespace ErsatzTV.Core.FFmpeg
}; };
private static bool NeedToScale(FFmpegProfile ffmpegProfile, MediaVersion version) => private static bool NeedToScale(FFmpegProfile ffmpegProfile, MediaVersion version) =>
ffmpegProfile.NormalizeResolution && ffmpegProfile.NormalizeVideo &&
IsIncorrectSize(ffmpegProfile.Resolution, version) || IsIncorrectSize(ffmpegProfile.Resolution, version) ||
IsTooLarge(ffmpegProfile.Resolution, version) || IsTooLarge(ffmpegProfile.Resolution, version) ||
IsOddSize(version); IsOddSize(version);
@@ -159,10 +166,10 @@ namespace ErsatzTV.Core.FFmpeg
version.Height % 2 == 1 || version.Width % 2 == 1; version.Height % 2 == 1 || version.Width % 2 == 1;
private static bool NeedToNormalizeVideoCodec(FFmpegProfile ffmpegProfile, MediaStream videoStream) => private static bool NeedToNormalizeVideoCodec(FFmpegProfile ffmpegProfile, MediaStream videoStream) =>
ffmpegProfile.NormalizeVideoCodec && ffmpegProfile.VideoCodec != videoStream.Codec; ffmpegProfile.NormalizeVideo && ffmpegProfile.VideoCodec != videoStream.Codec;
private static bool NeedToNormalizeAudioCodec(FFmpegProfile ffmpegProfile, MediaStream audioStream) => private static bool NeedToNormalizeAudioCodec(FFmpegProfile ffmpegProfile, MediaStream audioStream) =>
ffmpegProfile.NormalizeAudioCodec && ffmpegProfile.AudioCodec != audioStream.Codec; ffmpegProfile.NormalizeAudio && ffmpegProfile.AudioCodec != audioStream.Codec;
private static IDisplaySize CalculateScaledSize(FFmpegProfile ffmpegProfile, MediaVersion version) private static IDisplaySize CalculateScaledSize(FFmpegProfile ffmpegProfile, MediaVersion version)
{ {
+7 -1
View File
@@ -229,7 +229,7 @@ namespace ErsatzTV.Core.FFmpeg
const string X = "x=(w-text_w)/2"; const string X = "x=(w-text_w)/2";
const string Y = "y=(h-text_h)/3*2"; const string Y = "y=(h-text_h)/3*2";
string fontSize = text.Length > 60 ? "fontsize=40" : "fontsize=60"; string fontSize = text.Length > 80 ? "fontsize=30" : text.Length > 60 ? "fontsize=40" : "fontsize=60";
return WithFilterComplex( return WithFilterComplex(
$"[0:0]scale={desiredResolution.Width}:{desiredResolution.Height},drawtext={FONT_FILE}:{fontSize}:{FONT_COLOR}:{X}:{Y}:text='{text}'[v]", $"[0:0]scale={desiredResolution.Width}:{desiredResolution.Height},drawtext={FONT_FILE}:{fontSize}:{FONT_COLOR}:{X}:{Y}:text='{text}'[v]",
@@ -324,6 +324,12 @@ namespace ErsatzTV.Core.FFmpeg
return this; return this;
} }
public FFmpegProcessBuilder WithFrameRate(Option<string> frameRate)
{
_complexFilterBuilder = _complexFilterBuilder.WithFrameRate(frameRate);
return this;
}
public FFmpegProcessBuilder WithDeinterlace(bool deinterlace) public FFmpegProcessBuilder WithDeinterlace(bool deinterlace)
{ {
_complexFilterBuilder = _complexFilterBuilder.WithDeinterlace(deinterlace); _complexFilterBuilder = _complexFilterBuilder.WithDeinterlace(deinterlace);
+2 -1
View File
@@ -48,7 +48,8 @@ namespace ErsatzTV.Core.FFmpeg
.WithFormatFlags(playbackSettings.FormatFlags) .WithFormatFlags(playbackSettings.FormatFlags)
.WithRealtimeOutput(playbackSettings.RealtimeOutput) .WithRealtimeOutput(playbackSettings.RealtimeOutput)
.WithSeek(playbackSettings.StreamSeek) .WithSeek(playbackSettings.StreamSeek)
.WithInputCodec(path, playbackSettings.HardwareAcceleration, videoStream.Codec); .WithInputCodec(path, playbackSettings.HardwareAcceleration, videoStream.Codec)
.WithFrameRate(playbackSettings.FrameRate);
playbackSettings.ScaledSize.Match( playbackSettings.ScaledSize.Match(
scaledSize => scaledSize =>
+1 -1
View File
@@ -235,7 +235,7 @@ namespace ErsatzTV.Core.Scheduling
peekMediaItem => peekMediaItem =>
{ {
customGroup = true; customGroup = true;
MediaVersion peekVersion = peekMediaItem switch MediaVersion peekVersion = peekMediaItem switch
{ {
Movie m => m.MediaVersions.Head(), Movie m => m.MediaVersions.Head(),
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_FFmpegProfile_FrameRate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
"NormalizeResolution",
"FFmpegProfile");
migrationBuilder.RenameColumn(
"NormalizeVideoCodec",
"FFmpegProfile",
"NormalizeVideo");
migrationBuilder.AddColumn<string>(
"FrameRate",
"FFmpegProfile",
"TEXT",
nullable: true,
defaultValue: "24");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
"FrameRate",
"FFmpegProfile");
migrationBuilder.RenameColumn(
"NormalizeVideo",
"FFmpegProfile",
"NormalizeVideoCodec");
migrationBuilder.AddColumn<bool>(
"NormalizeResolution",
"FFmpegProfile",
"INTEGER",
nullable: false,
defaultValue: false);
}
}
}
@@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Remove_FFmpegProfile_NormalizeAudioCodec : Migration
{
protected override void Up(MigrationBuilder migrationBuilder) =>
migrationBuilder.DropColumn(
"NormalizeAudioCodec",
"FFmpegProfile");
protected override void Down(MigrationBuilder migrationBuilder) =>
migrationBuilder.AddColumn<bool>(
"NormalizeAudioCodec",
"FFmpegProfile",
"INTEGER",
nullable: false,
defaultValue: false);
}
}
@@ -240,6 +240,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<int>("AudioVolume") b.Property<int>("AudioVolume")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("FrameRate")
.HasColumnType("TEXT");
b.Property<int>("HardwareAcceleration") b.Property<int>("HardwareAcceleration")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@@ -249,13 +252,7 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<bool>("NormalizeAudio") b.Property<bool>("NormalizeAudio")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("NormalizeAudioCodec") b.Property<bool>("NormalizeVideo")
.HasColumnType("INTEGER");
b.Property<bool>("NormalizeResolution")
.HasColumnType("INTEGER");
b.Property<bool>("NormalizeVideoCodec")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("ResolutionId") b.Property<int>("ResolutionId")
+3 -3
View File
@@ -74,17 +74,17 @@
@(context.Transcode ? "Yes" : "No") @(context.Transcode ? "Yes" : "No")
</MudTd> </MudTd>
<MudTd DataLabel="Resolution"> <MudTd DataLabel="Resolution">
<MudText Color="@(context.Transcode && context.NormalizeResolution ? Color.Tertiary : Color.Inherit)"> <MudText Color="@(context.Transcode && context.NormalizeVideo ? Color.Tertiary : Color.Inherit)">
@context.Resolution.Name @context.Resolution.Name
</MudText> </MudText>
</MudTd> </MudTd>
<MudTd DataLabel="Video Codec"> <MudTd DataLabel="Video Codec">
<MudText Color="@(context.Transcode && context.NormalizeVideoCodec ? Color.Tertiary : Color.Inherit)"> <MudText Color="@(context.Transcode && context.NormalizeVideo ? Color.Tertiary : Color.Inherit)">
@context.VideoCodec @context.VideoCodec
</MudText> </MudText>
</MudTd> </MudTd>
<MudTd DataLabel="Audio Codec"> <MudTd DataLabel="Audio Codec">
<MudText Color="@(context.Transcode && context.NormalizeAudioCodec ? Color.Tertiary : Color.Inherit)"> <MudText Color="@(context.Transcode && context.NormalizeAudio ? Color.Tertiary : Color.Inherit)">
@context.AudioCodec @context.AudioCodec
</MudText> </MudText>
</MudTd> </MudTd>
+4 -7
View File
@@ -58,6 +58,9 @@
} }
</MudSelect> </MudSelect>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Frame Rate" @bind-Value="_model.FrameRate" For="@(() => _model.FrameRate)" Adornment="Adornment.End" AdornmentText="fps"/>
</MudElement>
</MudItem> </MudItem>
<MudItem> <MudItem>
<MudText Typo="Typo.h6">Audio</MudText> <MudText Typo="Typo.h6">Audio</MudText>
@@ -80,13 +83,7 @@
</MudItem> </MudItem>
<MudItem> <MudItem>
<MudText Typo="Typo.h6">Normalization</MudText> <MudText Typo="Typo.h6">Normalization</MudText>
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Resolution" @bind-Checked="@_model.NormalizeResolution" For="@(() => _model.NormalizeResolution)"/> <MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Video" @bind-Checked="@_model.NormalizeVideo" For="@(() => _model.NormalizeVideo)"/>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Video Codec" @bind-Checked="@_model.NormalizeVideoCodec" For="@(() => _model.NormalizeVideoCodec)"/>
</MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio Codec" @bind-Checked="@_model.NormalizeAudioCodec" For="@(() => _model.NormalizeAudioCodec)"/>
</MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio" @bind-Checked="@_model.NormalizeAudio" For="@(() => _model.NormalizeAudio)"/> <MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Audio" @bind-Checked="@_model.NormalizeAudio" For="@(() => _model.NormalizeAudio)"/>
</MudElement> </MudElement>
@@ -22,9 +22,7 @@ namespace ErsatzTV.ViewModels
Id = viewModel.Id; Id = viewModel.Id;
Name = viewModel.Name; Name = viewModel.Name;
NormalizeAudio = viewModel.NormalizeAudio; NormalizeAudio = viewModel.NormalizeAudio;
NormalizeAudioCodec = viewModel.NormalizeAudioCodec; NormalizeVideo = viewModel.NormalizeVideo;
NormalizeResolution = viewModel.NormalizeResolution;
NormalizeVideoCodec = viewModel.NormalizeVideoCodec;
Resolution = viewModel.Resolution; Resolution = viewModel.Resolution;
ThreadCount = viewModel.ThreadCount; ThreadCount = viewModel.ThreadCount;
Transcode = viewModel.Transcode; Transcode = viewModel.Transcode;
@@ -32,6 +30,7 @@ namespace ErsatzTV.ViewModels
VideoBitrate = viewModel.VideoBitrate; VideoBitrate = viewModel.VideoBitrate;
VideoBufferSize = viewModel.VideoBufferSize; VideoBufferSize = viewModel.VideoBufferSize;
VideoCodec = viewModel.VideoCodec; VideoCodec = viewModel.VideoCodec;
FrameRate = viewModel.FrameRate;
} }
public int AudioBitrate { get; set; } public int AudioBitrate { get; set; }
@@ -43,9 +42,7 @@ namespace ErsatzTV.ViewModels
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public bool NormalizeAudio { get; set; } public bool NormalizeAudio { get; set; }
public bool NormalizeAudioCodec { get; set; } public bool NormalizeVideo { get; set; }
public bool NormalizeResolution { get; set; }
public bool NormalizeVideoCodec { get; set; }
public ResolutionViewModel Resolution { get; set; } public ResolutionViewModel Resolution { get; set; }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public bool Transcode { get; set; } public bool Transcode { get; set; }
@@ -53,6 +50,7 @@ namespace ErsatzTV.ViewModels
public int VideoBitrate { get; set; } public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; } public int VideoBufferSize { get; set; }
public string VideoCodec { get; set; } public string VideoCodec { get; set; }
public string FrameRate { get; set; }
public CreateFFmpegProfile ToCreate() => public CreateFFmpegProfile ToCreate() =>
new( new(
@@ -61,19 +59,18 @@ namespace ErsatzTV.ViewModels
Transcode, Transcode,
HardwareAcceleration, HardwareAcceleration,
Resolution.Id, Resolution.Id,
NormalizeResolution, NormalizeVideo,
VideoCodec, VideoCodec,
NormalizeVideoCodec,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,
AudioCodec, AudioCodec,
NormalizeAudioCodec,
AudioBitrate, AudioBitrate,
AudioBufferSize, AudioBufferSize,
AudioVolume, AudioVolume,
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeAudio NormalizeAudio,
FrameRate
); );
public UpdateFFmpegProfile ToUpdate() => public UpdateFFmpegProfile ToUpdate() =>
@@ -84,19 +81,18 @@ namespace ErsatzTV.ViewModels
Transcode, Transcode,
HardwareAcceleration, HardwareAcceleration,
Resolution.Id, Resolution.Id,
NormalizeResolution, NormalizeVideo,
VideoCodec, VideoCodec,
NormalizeVideoCodec,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,
AudioCodec, AudioCodec,
NormalizeAudioCodec,
AudioBitrate, AudioBitrate,
AudioBufferSize, AudioBufferSize,
AudioVolume, AudioVolume,
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeAudio NormalizeAudio,
FrameRate
); );
} }
} }