Browse Source

normalize framerate (#122)

* normalize framerate

* simplify audio normalization settings
pull/123/head
Jason Dove 5 years ago committed by GitHub
parent
commit
4097288fed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      .gitignore
  2. 7
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  3. 7
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  4. 7
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  5. 5
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  6. 7
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  7. 7
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  8. 85
      ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs
  9. 10
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  10. 9
      ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs
  11. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs
  12. 15
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  13. 8
      ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs
  14. 3
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  15. 2
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  16. 1829
      ErsatzTV.Infrastructure/Migrations/20210331084648_Add_FFmpegProfile_FrameRate.Designer.cs
  17. 45
      ErsatzTV.Infrastructure/Migrations/20210331084648_Add_FFmpegProfile_FrameRate.cs
  18. 1826
      ErsatzTV.Infrastructure/Migrations/20210331092342_Remove_FFmpegProfile_NormalizeAudioCodec.Designer.cs
  19. 20
      ErsatzTV.Infrastructure/Migrations/20210331092342_Remove_FFmpegProfile_NormalizeAudioCodec.cs
  20. 11
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  21. 6
      ErsatzTV/Pages/FFmpeg.razor
  22. 11
      ErsatzTV/Pages/FFmpegEditor.razor
  23. 24
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

3
.gitignore vendored

@ -40,3 +40,6 @@ msbuild.wrn
core core
scripts/generate-api-sdk/swagger.json scripts/generate-api-sdk/swagger.json
docker-compose.override.yml

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

@ -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>>;
} }

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

@ -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) =>

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

@ -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>>;
} }

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

@ -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);
} }

7
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -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);
} }

7
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -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);

85
ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsServiceTests.cs

@ -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
}; };

10
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -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
}; };
} }

9
ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs

@ -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 =>
{ {

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs

@ -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; }
} }
} }

15
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -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)
{ {

8
ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs

@ -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);

3
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -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 =>

2
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -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(),

1829
ErsatzTV.Infrastructure/Migrations/20210331084648_Add_FFmpegProfile_FrameRate.Designer.cs generated

File diff suppressed because it is too large Load Diff

45
ErsatzTV.Infrastructure/Migrations/20210331084648_Add_FFmpegProfile_FrameRate.cs

@ -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);
}
}
}

1826
ErsatzTV.Infrastructure/Migrations/20210331092342_Remove_FFmpegProfile_NormalizeAudioCodec.Designer.cs generated

File diff suppressed because it is too large Load Diff

20
ErsatzTV.Infrastructure/Migrations/20210331092342_Remove_FFmpegProfile_NormalizeAudioCodec.cs

@ -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);
}
}

11
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -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")

6
ErsatzTV/Pages/FFmpeg.razor

@ -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>

11
ErsatzTV/Pages/FFmpegEditor.razor

@ -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>

24
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -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
); );
} }
} }

Loading…
Cancel
Save