Browse Source

always transcode and normalize, except with HLS Direct (#715)

* remove transcode, normalize video, normalize audio settings

* cleanup

* update changelog
pull/716/head
Jason Dove 3 years ago committed by GitHub
parent
commit
aa2c914d8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  3. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  4. 3
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  5. 9
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  6. 3
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  7. 7
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  8. 105
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  9. 322
      ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs
  10. 8
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  11. 113
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  12. 3
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  13. 3882
      ErsatzTV.Infrastructure/Migrations/20220328230341_Remove_FFmpegProfileTranscodeNormalizeVideoNormalizeAudio.Designer.cs
  14. 48
      ErsatzTV.Infrastructure/Migrations/20220328230341_Remove_FFmpegProfileTranscodeNormalizeVideoNormalizeAudio.cs
  15. 9
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  16. 41
      ErsatzTV/Pages/FFmpeg.razor
  17. 35
      ErsatzTV/Pages/FFmpegEditor.razor
  18. 17
      ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs
  19. 12
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

5
CHANGELOG.md

@ -15,7 +15,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- Change FFmpeg Profile video codec and audio codec text fields to select fields - Change FFmpeg Profile video codec and audio codec text fields to select fields
- The appropriate video encoder will be determined based on the video format and hardware acceleration selections - The appropriate video encoder will be determined based on the video format and hardware acceleration selections
- Remove FFmpeg Profile `Transcode`, `Normalize Video` and `Normalize Audio` settings
- All content will be transcoded and have audio and video normalized
- The only exception to this rule is `HLS Direct` streaming mode, which directly copies video and audio streams
## [0.4.4-alpha] - 2022-03-10 ## [0.4.4-alpha] - 2022-03-10
### Fixed ### Fixed

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

@ -7,12 +7,10 @@ namespace ErsatzTV.Application.FFmpegProfiles;
public record CreateFFmpegProfile( public record CreateFFmpegProfile(
string Name, string Name,
int ThreadCount, int ThreadCount,
bool Transcode,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
VaapiDriver VaapiDriver, VaapiDriver VaapiDriver,
string VaapiDevice, string VaapiDevice,
int ResolutionId, int ResolutionId,
bool NormalizeVideo,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,
@ -22,6 +20,5 @@ public record CreateFFmpegProfile(
bool NormalizeLoudness, bool NormalizeLoudness,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeAudio,
bool NormalizeFramerate, bool NormalizeFramerate,
bool DeinterlaceVideo) : IRequest<Either<BaseError, CreateFFmpegProfileResult>>; bool DeinterlaceVideo) : IRequest<Either<BaseError, CreateFFmpegProfileResult>>;

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

@ -39,12 +39,10 @@ public class CreateFFmpegProfileHandler :
{ {
Name = name, Name = name,
ThreadCount = threadCount, ThreadCount = threadCount,
Transcode = request.Transcode,
HardwareAcceleration = request.HardwareAcceleration, HardwareAcceleration = request.HardwareAcceleration,
VaapiDriver = request.VaapiDriver, VaapiDriver = request.VaapiDriver,
VaapiDevice = request.VaapiDevice, VaapiDevice = request.VaapiDevice,
ResolutionId = resolutionId, ResolutionId = resolutionId,
NormalizeVideo = request.NormalizeVideo,
VideoFormat = request.VideoFormat, VideoFormat = request.VideoFormat,
VideoBitrate = request.VideoBitrate, VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize, VideoBufferSize = request.VideoBufferSize,
@ -54,7 +52,6 @@ public class CreateFFmpegProfileHandler :
NormalizeLoudness = request.NormalizeLoudness, NormalizeLoudness = request.NormalizeLoudness,
AudioChannels = request.AudioChannels, AudioChannels = request.AudioChannels,
AudioSampleRate = request.AudioSampleRate, AudioSampleRate = request.AudioSampleRate,
NormalizeAudio = request.NormalizeAudio,
NormalizeFramerate = request.NormalizeFramerate, NormalizeFramerate = request.NormalizeFramerate,
DeinterlaceVideo = request.DeinterlaceVideo DeinterlaceVideo = request.DeinterlaceVideo
}); });

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

@ -8,12 +8,10 @@ public record UpdateFFmpegProfile(
int FFmpegProfileId, int FFmpegProfileId,
string Name, string Name,
int ThreadCount, int ThreadCount,
bool Transcode,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
VaapiDriver VaapiDriver, VaapiDriver VaapiDriver,
string VaapiDevice, string VaapiDevice,
int ResolutionId, int ResolutionId,
bool NormalizeVideo,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,
@ -23,6 +21,5 @@ public record UpdateFFmpegProfile(
bool NormalizeLoudness, bool NormalizeLoudness,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeAudio,
bool NormalizeFramerate, bool NormalizeFramerate,
bool DeinterlaceVideo) : IRequest<Either<BaseError, UpdateFFmpegProfileResult>>; bool DeinterlaceVideo) : IRequest<Either<BaseError, UpdateFFmpegProfileResult>>;

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

@ -30,24 +30,21 @@ public class
{ {
p.Name = update.Name; p.Name = update.Name;
p.ThreadCount = update.ThreadCount; p.ThreadCount = update.ThreadCount;
p.Transcode = update.Transcode;
p.HardwareAcceleration = update.HardwareAcceleration; p.HardwareAcceleration = update.HardwareAcceleration;
p.VaapiDriver = update.VaapiDriver; p.VaapiDriver = update.VaapiDriver;
p.VaapiDevice = update.VaapiDevice; p.VaapiDevice = update.VaapiDevice;
p.ResolutionId = update.ResolutionId; p.ResolutionId = update.ResolutionId;
p.NormalizeVideo = update.Transcode && update.NormalizeVideo;
p.VideoFormat = update.VideoFormat; p.VideoFormat = update.VideoFormat;
p.VideoBitrate = update.VideoBitrate; p.VideoBitrate = update.VideoBitrate;
p.VideoBufferSize = update.VideoBufferSize; p.VideoBufferSize = update.VideoBufferSize;
p.AudioFormat = update.AudioFormat; p.AudioFormat = update.AudioFormat;
p.AudioBitrate = update.AudioBitrate; p.AudioBitrate = update.AudioBitrate;
p.AudioBufferSize = update.AudioBufferSize; p.AudioBufferSize = update.AudioBufferSize;
p.NormalizeLoudness = update.Transcode && update.NormalizeLoudness; p.NormalizeLoudness = update.NormalizeLoudness;
p.AudioChannels = update.AudioChannels; p.AudioChannels = update.AudioChannels;
p.AudioSampleRate = update.AudioSampleRate; p.AudioSampleRate = update.AudioSampleRate;
p.NormalizeAudio = update.Transcode && update.NormalizeAudio; p.NormalizeFramerate = update.NormalizeFramerate;
p.NormalizeFramerate = update.Transcode && update.NormalizeFramerate; p.DeinterlaceVideo = update.DeinterlaceVideo;
p.DeinterlaceVideo = update.Transcode && update.DeinterlaceVideo;
await dbContext.SaveChangesAsync(); await dbContext.SaveChangesAsync();
return new UpdateFFmpegProfileResult(p.Id); return new UpdateFFmpegProfileResult(p.Id);
} }

3
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -8,12 +8,10 @@ public record FFmpegProfileViewModel(
int Id, int Id,
string Name, string Name,
int ThreadCount, int ThreadCount,
bool Transcode,
HardwareAccelerationKind HardwareAcceleration, HardwareAccelerationKind HardwareAcceleration,
VaapiDriver VaapiDriver, VaapiDriver VaapiDriver,
string VaapiDevice, string VaapiDevice,
ResolutionViewModel Resolution, ResolutionViewModel Resolution,
bool NormalizeVideo,
FFmpegProfileVideoFormat VideoFormat, FFmpegProfileVideoFormat VideoFormat,
int VideoBitrate, int VideoBitrate,
int VideoBufferSize, int VideoBufferSize,
@ -23,6 +21,5 @@ public record FFmpegProfileViewModel(
bool NormalizeLoudness, bool NormalizeLoudness,
int AudioChannels, int AudioChannels,
int AudioSampleRate, int AudioSampleRate,
bool NormalizeAudio,
bool NormalizeFramerate, bool NormalizeFramerate,
bool DeinterlaceVideo); bool DeinterlaceVideo);

7
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -10,12 +10,10 @@ internal static class Mapper
profile.Id, profile.Id,
profile.Name, profile.Name,
profile.ThreadCount, profile.ThreadCount,
profile.Transcode,
profile.HardwareAcceleration, profile.HardwareAcceleration,
profile.VaapiDriver, profile.VaapiDriver,
profile.VaapiDevice, profile.VaapiDevice,
Project(profile.Resolution), Project(profile.Resolution),
profile.NormalizeVideo,
profile.VideoFormat, profile.VideoFormat,
profile.VideoBitrate, profile.VideoBitrate,
profile.VideoBufferSize, profile.VideoBufferSize,
@ -25,9 +23,8 @@ internal static class Mapper
profile.NormalizeLoudness, profile.NormalizeLoudness,
profile.AudioChannels, profile.AudioChannels,
profile.AudioSampleRate, profile.AudioSampleRate,
profile.NormalizeAudio, profile.NormalizeFramerate,
profile.NormalizeVideo && profile.NormalizeFramerate, profile.DeinterlaceVideo == true);
profile.DeinterlaceVideo);
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);

105
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -166,85 +166,48 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
foreach (BaseError error in maybePlayoutItem.LeftToSeq()) foreach (BaseError error in maybePlayoutItem.LeftToSeq())
{ {
var offlineTranscodeMessage = Option<TimeSpan> maybeDuration = await dbContext.PlayoutItems
$"offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'";
Option<TimeSpan> maybeDuration = await Optional(channel.FFmpegProfile.Transcode)
.Where(transcode => transcode)
.Match(
_ => dbContext.PlayoutItems
.Filter(pi => pi.Playout.ChannelId == channel.Id) .Filter(pi => pi.Playout.ChannelId == channel.Id)
.Filter(pi => pi.Start > now.UtcDateTime) .Filter(pi => pi.Start > now.UtcDateTime)
.OrderBy(pi => pi.Start) .OrderBy(pi => pi.Start)
.FirstOrDefaultAsync() .FirstOrDefaultAsync(cancellationToken)
.Map(Optional) .Map(Optional)
.MapT(pi => pi.StartOffset - now), .MapT(pi => pi.StartOffset - now);
() => Option<TimeSpan>.None.AsTask());
DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now); DateTimeOffset finish = maybeDuration.Match(d => now.Add(d), () => now);
switch (error) switch (error)
{ {
case UnableToLocatePlayoutItem: case UnableToLocatePlayoutItem:
if (channel.FFmpegProfile.Transcode) Process offlineProcess = await ffmpegProcessService.ForError(
{ ffmpegPath,
Process errorProcess = await ffmpegProcessService.ForError( channel,
ffmpegPath, maybeDuration,
channel, "Channel is Offline",
maybeDuration, request.HlsRealtime,
"Channel is Offline", request.PtsOffset);
request.HlsRealtime,
request.PtsOffset); return new PlayoutItemProcessModel(offlineProcess, finish);
return new PlayoutItemProcessModel(errorProcess, finish);
}
else
{
var message =
$"Unable to locate playout item for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message);
}
case PlayoutItemDoesNotExistOnDisk: case PlayoutItemDoesNotExistOnDisk:
if (channel.FFmpegProfile.Transcode) Process doesNotExistProcess = await ffmpegProcessService.ForError(
{ ffmpegPath,
Process errorProcess = await ffmpegProcessService.ForError( channel,
ffmpegPath, maybeDuration,
channel, error.Value,
maybeDuration, request.HlsRealtime,
error.Value, request.PtsOffset);
request.HlsRealtime,
request.PtsOffset); return new PlayoutItemProcessModel(doesNotExistProcess, finish);
return new PlayoutItemProcessModel(errorProcess, finish);
}
else
{
var message =
$"Playout item does not exist on disk for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message);
}
default: default:
if (channel.FFmpegProfile.Transcode) Process errorProcess = await ffmpegProcessService.ForError(
{ ffmpegPath,
Process errorProcess = await ffmpegProcessService.ForError( channel,
ffmpegPath, maybeDuration,
channel, "Channel is Offline",
maybeDuration, request.HlsRealtime,
"Channel is Offline", request.PtsOffset);
request.HlsRealtime,
request.PtsOffset); return new PlayoutItemProcessModel(errorProcess, finish);
return new PlayoutItemProcessModel(errorProcess, finish);
}
else
{
var message =
$"Unexpected error locating playout item for channel {channel.Number}; {offlineTranscodeMessage}";
return BaseError.New(message);
}
} }
} }
@ -282,17 +245,13 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
// TODO: shuffle? does it really matter since we loop anyway // TODO: shuffle? does it really matter since we loop anyway
MediaItem item = items[new Random().Next(items.Count)]; MediaItem item = items[new Random().Next(items.Count)];
Option<TimeSpan> maybeDuration = await Optional(channel.FFmpegProfile.Transcode) Option<TimeSpan> maybeDuration = await dbContext.PlayoutItems
.Where(transcode => transcode)
.Match(
_ => dbContext.PlayoutItems
.Filter(pi => pi.Playout.ChannelId == channel.Id) .Filter(pi => pi.Playout.ChannelId == channel.Id)
.Filter(pi => pi.Start > now.UtcDateTime) .Filter(pi => pi.Start > now.UtcDateTime)
.OrderBy(pi => pi.Start) .OrderBy(pi => pi.Start)
.FirstOrDefaultAsync() .FirstOrDefaultAsync()
.Map(Optional) .Map(Optional)
.MapT(pi => pi.StartOffset - now), .MapT(pi => pi.StartOffset - now);
() => Option<TimeSpan>.None.AsTask());
MediaVersion version = item.GetHeadVersion(); MediaVersion version = item.GetHeadVersion();

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

@ -9,13 +9,15 @@ namespace ErsatzTV.Core.Tests.FFmpeg;
[TestFixture] [TestFixture]
public class FFmpegPlaybackSettingsCalculatorTests public class FFmpegPlaybackSettingsCalculatorTests
{ {
private static readonly MediaVersion TestVersion = new() { SampleAspectRatio = "1:1", Width = 1920, Height = 1080 };
[TestFixture] [TestFixture]
public class CalculateSettings public class CalculateSettings
{ {
private readonly FFmpegPlaybackSettingsCalculator _calculator; private readonly FFmpegPlaybackSettingsCalculator _calculator;
public CalculateSettings() => _calculator = new FFmpegPlaybackSettingsCalculator(); public CalculateSettings() => _calculator = new FFmpegPlaybackSettingsCalculator();
[Test] [Test]
public void Should_Not_GenPts_ForHlsSegmenter() public void Should_Not_GenPts_ForHlsSegmenter()
{ {
@ -24,7 +26,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingSegmenter, StreamingMode.HttpLiveStreamingSegmenter,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -47,7 +49,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -68,7 +70,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingSegmenter, StreamingMode.HttpLiveStreamingSegmenter,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -89,7 +91,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -112,7 +114,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect, StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -135,7 +137,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -156,7 +158,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect, StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,
@ -179,7 +181,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
now, now,
@ -203,7 +205,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect, StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
now, now,
@ -217,33 +219,11 @@ public class FFmpegPlaybackSettingsCalculatorTests
actual.StreamSeek.IfNone(TimeSpan.Zero).Should().Be(TimeSpan.FromMinutes(5)); actual.StreamSeek.IfNone(TimeSpan.Zero).Should().Be(TimeSpan.FromMinutes(5));
} }
[Test]
public void ShouldNot_SetScaledSize_When_NotNormalizingVideo_ForTransportStream()
{
FFmpegProfile ffmpegProfile = TestProfile() with { NormalizeVideo = false };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
new MediaVersion(),
new MediaStream(),
new MediaStream(),
DateTimeOffset.Now,
DateTimeOffset.Now,
TimeSpan.Zero,
TimeSpan.Zero,
false,
None);
actual.ScaledSize.IsNone.Should().BeTrue();
}
[Test] [Test]
public void ShouldNot_SetScaledSize_When_ContentIsCorrectSize_ForTransportStream() public void ShouldNot_SetScaledSize_When_ContentIsCorrectSize_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@ -271,7 +251,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@ -299,7 +278,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@ -328,8 +306,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@ -358,8 +334,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1280, Height = 720 } Resolution = new Resolution { Width = 1280, Height = 720 }
}; };
@ -389,8 +363,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 } Resolution = new Resolution { Width = 1920, Height = 1080 }
}; };
@ -414,43 +386,12 @@ public class FFmpegPlaybackSettingsCalculatorTests
actual.PadToDesiredResolution.Should().BeFalse(); actual.PadToDesiredResolution.Should().BeFalse();
} }
[Test]
public void Should_NotPadToDesiredResolution_When_NotNormalizingVideo()
{
FFmpegProfile ffmpegProfile = TestProfile() with
{
Transcode = true,
NormalizeVideo = false,
Resolution = new Resolution { Width = 1920, Height = 1080 }
};
// not anamorphic
var version = new MediaVersion { Width = 1918, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
new MediaStream(),
new MediaStream(),
DateTimeOffset.Now,
DateTimeOffset.Now,
TimeSpan.Zero,
TimeSpan.Zero,
false,
None);
actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse();
}
[Test] [Test]
public void Should_SetDesiredVideoFormat_When_ContentIsPadded_ForTransportStream() public void Should_SetDesiredVideoFormat_When_ContentIsPadded_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoFormat = FFmpegProfileVideoFormat.H264 VideoFormat = FFmpegProfileVideoFormat.H264
}; };
@ -478,12 +419,10 @@ public class FFmpegPlaybackSettingsCalculatorTests
[Test] [Test]
public void public void
Should_SetDesiredVideoFormat_When_ContentIsCorrectSize_And_NormalizingVideo_ForTransportStream() Should_SetDesiredVideoFormat_When_ContentIsCorrectSize_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoFormat = FFmpegProfileVideoFormat.H264 VideoFormat = FFmpegProfileVideoFormat.H264
}; };
@ -511,13 +450,10 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void public void Should_SetCopyVideoFormat_When_ContentIsCorrectSize_ForHttpLiveStreamingDirect()
Should_SetCopyVideoFormat_When_ContentIsCorrectSize_And_NormalizingVideo_ForHttpLiveStreaming()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoFormat = FFmpegProfileVideoFormat.H264 VideoFormat = FFmpegProfileVideoFormat.H264
}; };
@ -549,8 +485,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoFormat = FFmpegProfileVideoFormat.H264 VideoFormat = FFmpegProfileVideoFormat.H264
}; };
@ -577,85 +511,11 @@ public class FFmpegPlaybackSettingsCalculatorTests
actual.VideoFormat.Should().Be(FFmpegProfileVideoFormat.H264); actual.VideoFormat.Should().Be(FFmpegProfileVideoFormat.H264);
} }
[Test]
public void
Should_SetCopyVideoFormat_When_ContentIsCorrectSize_And_NotNormalizingVideo_ForTransportStream()
{
var ffmpegProfile = new FFmpegProfile
{
Transcode = true,
NormalizeVideo = false,
Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoFormat = FFmpegProfileVideoFormat.H264
};
// not anamorphic
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
new MediaStream { Codec = "mpeg2video" },
new MediaStream(),
DateTimeOffset.Now,
DateTimeOffset.Now,
TimeSpan.Zero,
TimeSpan.Zero,
false,
None);
actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse();
actual.VideoFormat.Should().Be(FFmpegProfileVideoFormat.Copy);
}
[Test]
public void
Should_SetCopyVideoFormat_AndCopyAudioFormat_When_NotTranscoding_ForTransportStream()
{
var ffmpegProfile = new FFmpegProfile
{
Transcode = false,
NormalizeVideo = true,
NormalizeAudio = true,
NormalizeLoudness = true,
Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoFormat = FFmpegProfileVideoFormat.H264
};
// not anamorphic
var version = new MediaVersion
{ Width = 1920, Height = 1080, SampleAspectRatio = "1:1" };
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
new MediaStream { Codec = "mpeg2video" },
new MediaStream(),
DateTimeOffset.Now,
DateTimeOffset.Now,
TimeSpan.Zero,
TimeSpan.Zero,
false,
None);
actual.ScaledSize.IsNone.Should().BeTrue();
actual.PadToDesiredResolution.Should().BeFalse();
actual.VideoFormat.Should().Be(FFmpegProfileVideoFormat.Copy);
actual.NormalizeLoudness.Should().BeFalse();
actual.AudioFormat.Should().Be(FFmpegProfileAudioFormat.Copy);
}
[Test] [Test]
public void Should_SetVideoBitrate_When_ContentIsPadded_ForTransportStream() public void Should_SetVideoBitrate_When_ContentIsPadded_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoBitrate = 2525 VideoBitrate = 2525
}; };
@ -682,12 +542,10 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetVideoBitrate_When_ContentIsCorrectSize_And_NormalizingVideo_ForTransportStream() public void Should_SetVideoBitrate_When_ContentIsCorrectSize_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoBitrate = 2525 VideoBitrate = 2525
}; };
@ -719,8 +577,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoBufferSize = 2525 VideoBufferSize = 2525
}; };
@ -747,13 +603,10 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void public void Should_SetVideoBufferSize_When_ContentIsCorrectSize_ForTransportStream()
Should_SetVideoBufferSize_When_ContentIsCorrectSize_And_NormalizingVideo_ForTransportStream()
{ {
var ffmpegProfile = new FFmpegProfile var ffmpegProfile = new FFmpegProfile
{ {
Transcode = true,
NormalizeVideo = true,
Resolution = new Resolution { Width = 1920, Height = 1080 }, Resolution = new Resolution { Width = 1920, Height = 1080 },
VideoBufferSize = 2525 VideoBufferSize = 2525
}; };
@ -781,21 +634,17 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetDesiredAudioFormat_When_NormalizingAudio_With_CorrectFormat_ForTransportStream() public void Should_SetDesiredAudioFormat_With_CorrectFormat_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioFormat = FFmpegProfileAudioFormat.Aac AudioFormat = FFmpegProfileAudioFormat.Aac
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "aac" }, new MediaStream { Codec = "aac" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -809,48 +658,17 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetCopyAudioFormat_When_NotNormalizingAudio_ForTransportStream() public void Should_SetDesiredAudioFormat_ForTransportStream()
{
FFmpegProfile ffmpegProfile = TestProfile() with
{
NormalizeAudio = false,
AudioFormat = FFmpegProfileAudioFormat.Aac
};
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
new MediaStream(),
new MediaStream { Codec = "ac3" },
DateTimeOffset.Now,
DateTimeOffset.Now,
TimeSpan.Zero,
TimeSpan.Zero,
false,
None);
actual.AudioFormat.Should().Be(FFmpegProfileAudioFormat.Copy);
}
[Test]
public void Should_SetDesiredAudioFormat_When_NormalizingAudio_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioFormat = FFmpegProfileAudioFormat.Aac AudioFormat = FFmpegProfileAudioFormat.Aac
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -864,21 +682,17 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetCopyAudioFormat_When_NormalizingAudio_ForHttpLiveStreaming() public void Should_SetCopyAudioFormat_ForHttpLiveStreamingDirect()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioFormat = FFmpegProfileAudioFormat.Aac AudioFormat = FFmpegProfileAudioFormat.Aac
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.HttpLiveStreamingDirect, StreamingMode.HttpLiveStreamingDirect,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -892,22 +706,18 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioBitrate_When_NormalizingAudio_With_CorrectFormat_ForTransportStream() public void Should_SetAudioBitrate_With_CorrectFormat_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioBitrate = 2424, AudioBitrate = 2424,
AudioFormat = FFmpegProfileAudioFormat.Ac3 AudioFormat = FFmpegProfileAudioFormat.Ac3
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -921,22 +731,18 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioBufferSize_When_NormalizingAudio_With_CorrectFormat_ForTransportStream() public void Should_SetAudioBufferSize_With_CorrectFormat_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioBufferSize = 2424, AudioBufferSize = 2424,
AudioFormat = FFmpegProfileAudioFormat.Ac3 AudioFormat = FFmpegProfileAudioFormat.Ac3
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -950,22 +756,18 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioChannels_When_NormalizingAudio_With_CorrectFormat_ForTransportStream() public void Should_SetAudioChannels_With_CorrectFormat_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioFormat = FFmpegProfileAudioFormat.Ac3, AudioFormat = FFmpegProfileAudioFormat.Ac3,
AudioChannels = 6 AudioChannels = 6
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -979,22 +781,18 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioSampleRate_When_NormalizingAudio_With_CorrectFormat_ForTransportStream() public void Should_SetAudioSampleRate_With_CorrectFormat_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioFormat = FFmpegProfileAudioFormat.Ac3, AudioFormat = FFmpegProfileAudioFormat.Ac3,
AudioSampleRate = 48 AudioSampleRate = 48
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -1008,21 +806,17 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioChannels_When_NormalizingAudio_ForTransportStream() public void Should_SetAudioChannels_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioChannels = 6 AudioChannels = 6
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -1036,21 +830,17 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioSampleRate_When_NormalizingAudio_ForTransportStream() public void Should_SetAudioSampleRate_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioSampleRate = 48 AudioSampleRate = 48
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -1064,17 +854,15 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetAudioDuration_When_NormalizingAudio_With_CorrectFormat_ForTransportStream() public void Should_SetAudioDuration_With_CorrectFormat_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
AudioSampleRate = 48, AudioSampleRate = 48,
AudioFormat = FFmpegProfileAudioFormat.Ac3 AudioFormat = FFmpegProfileAudioFormat.Ac3
}; };
var version = new MediaVersion { Duration = TimeSpan.FromMinutes(5) }; // not pulled from here var version = new MediaVersion { SampleAspectRatio = "1:1", Width = 1920, Height = 1080, Duration = TimeSpan.FromMinutes(5) }; // not pulled from here
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
@ -1093,21 +881,17 @@ public class FFmpegPlaybackSettingsCalculatorTests
} }
[Test] [Test]
public void Should_SetNormalizeLoudness_When_NormalizingAudio_ForTransportStream() public void Should_SetNormalizeLoudness_ForTransportStream()
{ {
FFmpegProfile ffmpegProfile = TestProfile() with FFmpegProfile ffmpegProfile = TestProfile() with
{ {
Transcode = true,
NormalizeAudio = true,
NormalizeLoudness = true NormalizeLoudness = true
}; };
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
version, TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream { Codec = "ac3" }, new MediaStream { Codec = "ac3" },
DateTimeOffset.Now, DateTimeOffset.Now,
@ -1119,34 +903,6 @@ public class FFmpegPlaybackSettingsCalculatorTests
actual.NormalizeLoudness.Should().BeTrue(); actual.NormalizeLoudness.Should().BeTrue();
} }
[Test]
public void Should_NotSetNormalizeLoudness_When_NotNormalizingAudio_ForTransportStream()
{
FFmpegProfile ffmpegProfile = TestProfile() with
{
Transcode = true,
NormalizeAudio = false,
NormalizeLoudness = true
};
var version = new MediaVersion();
FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream,
ffmpegProfile,
version,
new MediaStream(),
new MediaStream { Codec = "ac3" },
DateTimeOffset.Now,
DateTimeOffset.Now,
TimeSpan.Zero,
TimeSpan.Zero,
false,
None);
actual.NormalizeLoudness.Should().BeFalse();
}
} }
[TestFixture] [TestFixture]
@ -1165,7 +921,7 @@ public class FFmpegPlaybackSettingsCalculatorTests
FFmpegPlaybackSettings actual = _calculator.CalculateSettings( FFmpegPlaybackSettings actual = _calculator.CalculateSettings(
StreamingMode.TransportStream, StreamingMode.TransportStream,
ffmpegProfile, ffmpegProfile,
new MediaVersion(), TestVersion,
new MediaStream(), new MediaStream(),
new MediaStream(), new MediaStream(),
DateTimeOffset.Now, DateTimeOffset.Now,

8
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -7,14 +7,12 @@ public record FFmpegProfile
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public bool Transcode { get; set; }
public HardwareAccelerationKind HardwareAcceleration { get; set; } public HardwareAccelerationKind HardwareAcceleration { get; set; }
public VaapiDriver VaapiDriver { get; set; } public VaapiDriver VaapiDriver { get; set; }
public string VaapiDevice { get; set; } public string VaapiDevice { get; set; }
public int ResolutionId { get; set; } public int ResolutionId { get; set; }
public Resolution Resolution { get; set; } public Resolution Resolution { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; } public FFmpegProfileVideoFormat VideoFormat { 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 FFmpegProfileAudioFormat AudioFormat { get; set; } public FFmpegProfileAudioFormat AudioFormat { get; set; }
@ -23,16 +21,14 @@ public record FFmpegProfile
public bool NormalizeLoudness { get; set; } public bool NormalizeLoudness { get; set; }
public int AudioChannels { get; set; } public int AudioChannels { get; set; }
public int AudioSampleRate { get; set; } public int AudioSampleRate { get; set; }
public bool NormalizeAudio { get; set; }
public bool NormalizeFramerate { get; set; } public bool NormalizeFramerate { get; set; }
public bool DeinterlaceVideo { get; set; } public bool? DeinterlaceVideo { get; set; }
public static FFmpegProfile New(string name, Resolution resolution) => public static FFmpegProfile New(string name, Resolution resolution) =>
new() new()
{ {
Name = name, Name = name,
ThreadCount = 0, ThreadCount = 0,
Transcode = true,
ResolutionId = resolution.Id, ResolutionId = resolution.Id,
Resolution = resolution, Resolution = resolution,
VideoFormat = FFmpegProfileVideoFormat.H264, VideoFormat = FFmpegProfileVideoFormat.H264,
@ -44,8 +40,6 @@ public record FFmpegProfile
NormalizeLoudness = true, NormalizeLoudness = true,
AudioChannels = 2, AudioChannels = 2,
AudioSampleRate = 48, AudioSampleRate = 48,
NormalizeVideo = true,
NormalizeAudio = true,
DeinterlaceVideo = true, DeinterlaceVideo = true,
NormalizeFramerate = false, NormalizeFramerate = false,
HardwareAcceleration = HardwareAccelerationKind.None HardwareAcceleration = HardwareAccelerationKind.None

113
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -103,84 +103,65 @@ public class FFmpegPlaybackSettingsCalculator
} }
IDisplaySize sizeAfterScaling = result.ScaledSize.IfNone(videoVersion); IDisplaySize sizeAfterScaling = result.ScaledSize.IfNone(videoVersion);
if (ffmpegProfile.Transcode && ffmpegProfile.NormalizeVideo && if (!sizeAfterScaling.IsSameSizeAs(ffmpegProfile.Resolution))
!sizeAfterScaling.IsSameSizeAs(ffmpegProfile.Resolution))
{ {
result.PadToDesiredResolution = true; result.PadToDesiredResolution = true;
} }
if (ffmpegProfile.Transcode && ffmpegProfile.NormalizeVideo) if (ffmpegProfile.NormalizeFramerate)
{ {
if (ffmpegProfile.NormalizeFramerate) result.FrameRate = targetFramerate;
{
result.FrameRate = targetFramerate;
}
result.VideoTrackTimeScale = 90000;
} }
result.VideoTrackTimeScale = 90000;
foreach (MediaStream stream in videoStream.Where(s => s.AttachedPic == false)) foreach (MediaStream stream in videoStream.Where(s => s.AttachedPic == false))
{ {
if (result.ScaledSize.IsSome || result.PadToDesiredResolution || result.VideoFormat = ffmpegProfile.VideoFormat;
NeedToNormalizeVideo(ffmpegProfile)) result.VideoBitrate = ffmpegProfile.VideoBitrate;
{ result.VideoBufferSize = ffmpegProfile.VideoBufferSize;
result.VideoFormat = ffmpegProfile.VideoFormat;
result.VideoBitrate = ffmpegProfile.VideoBitrate; result.VideoDecoder =
result.VideoBufferSize = ffmpegProfile.VideoBufferSize; (result.HardwareAcceleration, stream.Codec, stream.PixelFormat) switch
{
result.VideoDecoder = (HardwareAccelerationKind.Nvenc, "h264", "yuv420p10le" or "yuv444p" or "yuv444p10le"
(result.HardwareAcceleration, stream.Codec, stream.PixelFormat) switch ) =>
{ "h264",
(HardwareAccelerationKind.Nvenc, "h264", "yuv420p10le" or "yuv444p" or "yuv444p10le" (HardwareAccelerationKind.Nvenc, "hevc", "yuv444p" or "yuv444p10le") => "hevc",
) => (HardwareAccelerationKind.Nvenc, "h264", _) => "h264_cuvid",
"h264", (HardwareAccelerationKind.Nvenc, "hevc", _) => "hevc_cuvid",
(HardwareAccelerationKind.Nvenc, "hevc", "yuv444p" or "yuv444p10le") => "hevc", (HardwareAccelerationKind.Nvenc, "mpeg2video", _) => "mpeg2_cuvid",
(HardwareAccelerationKind.Nvenc, "h264", _) => "h264_cuvid", (HardwareAccelerationKind.Nvenc, "mpeg4", _) => "mpeg4_cuvid",
(HardwareAccelerationKind.Nvenc, "hevc", _) => "hevc_cuvid", (HardwareAccelerationKind.Qsv, "h264", _) => "h264_qsv",
(HardwareAccelerationKind.Nvenc, "mpeg2video", _) => "mpeg2_cuvid", (HardwareAccelerationKind.Qsv, "hevc", _) => "hevc_qsv",
(HardwareAccelerationKind.Nvenc, "mpeg4", _) => "mpeg4_cuvid", (HardwareAccelerationKind.Qsv, "mpeg2video", _) => "mpeg2_qsv",
(HardwareAccelerationKind.Qsv, "h264", _) => "h264_qsv",
(HardwareAccelerationKind.Qsv, "hevc", _) => "hevc_qsv", // temp disable mpeg4 hardware decoding for all vaapi
(HardwareAccelerationKind.Qsv, "mpeg2video", _) => "mpeg2_qsv", // TODO: check for codec support
(HardwareAccelerationKind.Vaapi, "mpeg4", _) => "mpeg4",
// temp disable mpeg4 hardware decoding for all vaapi
// TODO: check for codec support _ => null
(HardwareAccelerationKind.Vaapi, "mpeg4", _) => "mpeg4", };
_ => null
};
}
else
{
result.VideoFormat = FFmpegProfileVideoFormat.Copy;
}
} }
if (ffmpegProfile.Transcode && ffmpegProfile.NormalizeAudio) result.AudioFormat = ffmpegProfile.AudioFormat;
{ result.AudioBitrate = ffmpegProfile.AudioBitrate;
result.AudioFormat = ffmpegProfile.AudioFormat; result.AudioBufferSize = ffmpegProfile.AudioBufferSize;
result.AudioBitrate = ffmpegProfile.AudioBitrate;
result.AudioBufferSize = ffmpegProfile.AudioBufferSize;
audioStream.IfSome( audioStream.IfSome(
stream => stream =>
{
if (stream.Channels != ffmpegProfile.AudioChannels)
{ {
if (stream.Channels != ffmpegProfile.AudioChannels) result.AudioChannels = ffmpegProfile.AudioChannels;
{ }
result.AudioChannels = ffmpegProfile.AudioChannels; });
}
});
result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
result.AudioDuration = outPoint - inPoint;
result.NormalizeLoudness = ffmpegProfile.NormalizeLoudness;
}
else
{
result.AudioFormat = FFmpegProfileAudioFormat.Copy;
}
result.Deinterlace = ffmpegProfile.DeinterlaceVideo && result.AudioSampleRate = ffmpegProfile.AudioSampleRate;
result.AudioDuration = outPoint - inPoint;
result.NormalizeLoudness = ffmpegProfile.NormalizeLoudness;
result.Deinterlace = ffmpegProfile.DeinterlaceVideo == true &&
videoVersion.VideoScanKind == VideoScanKind.Interlaced; videoVersion.VideoScanKind == VideoScanKind.Interlaced;
break; break;
@ -202,7 +183,6 @@ public class FFmpegPlaybackSettingsCalculator
} }
private static bool NeedToScale(FFmpegProfile ffmpegProfile, MediaVersion version) => private static bool NeedToScale(FFmpegProfile ffmpegProfile, MediaVersion version) =>
ffmpegProfile.Transcode && ffmpegProfile.NormalizeVideo &&
IsIncorrectSize(ffmpegProfile.Resolution, version) || IsIncorrectSize(ffmpegProfile.Resolution, version) ||
IsTooLarge(ffmpegProfile.Resolution, version) || IsTooLarge(ffmpegProfile.Resolution, version) ||
IsOddSize(version); IsOddSize(version);
@ -219,9 +199,6 @@ public class FFmpegPlaybackSettingsCalculator
private static bool IsOddSize(MediaVersion version) => private static bool IsOddSize(MediaVersion version) =>
version.Height % 2 == 1 || version.Width % 2 == 1; version.Height % 2 == 1 || version.Width % 2 == 1;
private static bool NeedToNormalizeVideo(FFmpegProfile ffmpegProfile) =>
ffmpegProfile.Transcode && ffmpegProfile.NormalizeVideo;
private static IDisplaySize CalculateScaledSize(FFmpegProfile ffmpegProfile, MediaVersion version) private static IDisplaySize CalculateScaledSize(FFmpegProfile ffmpegProfile, MediaVersion version)
{ {
IDisplaySize sarSize = SARSize(version); IDisplaySize sarSize = SARSize(version);

3
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -295,8 +295,7 @@ public class FFmpegProcessService : IFFmpegProcessService
Option<ChannelWatermark> watermarkOverride, Option<ChannelWatermark> watermarkOverride,
Option<string> watermarkPath) Option<string> watermarkPath)
{ {
if (channel.StreamingMode != StreamingMode.HttpLiveStreamingDirect && channel.FFmpegProfile.Transcode && if (channel.StreamingMode != StreamingMode.HttpLiveStreamingDirect)
channel.FFmpegProfile.NormalizeVideo)
{ {
if (videoVersion is CoverArtMediaVersion) if (videoVersion is CoverArtMediaVersion)
{ {

3882
ErsatzTV.Infrastructure/Migrations/20220328230341_Remove_FFmpegProfileTranscodeNormalizeVideoNormalizeAudio.Designer.cs generated

File diff suppressed because it is too large Load Diff

48
ErsatzTV.Infrastructure/Migrations/20220328230341_Remove_FFmpegProfileTranscodeNormalizeVideoNormalizeAudio.cs

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

9
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -516,9 +516,6 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("NormalizeAudio")
.HasColumnType("INTEGER");
b.Property<bool>("NormalizeFramerate") b.Property<bool>("NormalizeFramerate")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER") .HasColumnType("INTEGER")
@ -527,18 +524,12 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<bool>("NormalizeLoudness") b.Property<bool>("NormalizeLoudness")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("NormalizeVideo")
.HasColumnType("INTEGER");
b.Property<int>("ResolutionId") b.Property<int>("ResolutionId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int>("ThreadCount") b.Property<int>("ThreadCount")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("Transcode")
.HasColumnType("INTEGER");
b.Property<string>("VaapiDevice") b.Property<string>("VaapiDevice")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

41
ErsatzTV/Pages/FFmpeg.razor

@ -11,52 +11,35 @@
<MudTable Hover="true" Items="_ffmpegProfiles"> <MudTable Hover="true" Items="_ffmpegProfiles">
<ToolBarContent> <ToolBarContent>
<MudText Typo="Typo.h6">FFmpeg Profiles</MudText> <MudText Typo="Typo.h6">FFmpeg Profiles</MudText>
<MudSpacer/>
<MudText Color="Color.Tertiary">Colored settings will be normalized</MudText>
</ToolBarContent> </ToolBarContent>
<ColGroup> <ColGroup>
<col/> <col/>
<col/> <col/>
<col/> <col/>
<col/> <col/>
<col/>
<col style="width: 180px;"/> <col style="width: 180px;"/>
</ColGroup> </ColGroup>
<HeaderContent> <HeaderContent>
<MudTh>Name</MudTh> <MudTh>Name</MudTh>
<MudTh>Transcode</MudTh>
<MudTh>Resolution</MudTh> <MudTh>Resolution</MudTh>
<MudTh>Video Codec</MudTh> <MudTh>Video Format</MudTh>
<MudTh>Audio Codec</MudTh> <MudTh>Audio Format</MudTh>
<MudTh/> <MudTh/>
</HeaderContent> </HeaderContent>
<RowTemplate> <RowTemplate>
<MudTd DataLabel="Name">@context.Name</MudTd> <MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Transcode"> <MudTd DataLabel="Resolution">@context.Resolution.Name</MudTd>
@(context.Transcode ? "Yes" : "No")
</MudTd>
<MudTd DataLabel="Resolution">
<MudText Color="@(context.Transcode && context.NormalizeVideo ? Color.Tertiary : Color.Inherit)">
@context.Resolution.Name
</MudText>
</MudTd>
<MudTd DataLabel="Video Format"> <MudTd DataLabel="Video Format">
<MudText Color="@(context.Transcode && context.NormalizeVideo ? Color.Tertiary : Color.Inherit)"> @if (context.HardwareAcceleration != HardwareAccelerationKind.None)
@if (context.HardwareAcceleration != HardwareAccelerationKind.None) {
{ @($"{context.VideoFormat.ToString().ToLowerInvariant()} / {context.HardwareAcceleration.ToString().ToLowerInvariant()}")
@($"{context.VideoFormat.ToString().ToLowerInvariant()} / {context.HardwareAcceleration.ToString().ToLowerInvariant()}") }
} else
else {
{ @context.VideoFormat.ToString().ToLowerInvariant()
@context.VideoFormat.ToString().ToLowerInvariant() }
}
</MudText>
</MudTd>
<MudTd DataLabel="Audio Format">
<MudText Color="@(context.Transcode && context.NormalizeAudio ? Color.Tertiary : Color.Inherit)">
@context.AudioFormat.ToString().ToLowerInvariant()
</MudText>
</MudTd> </MudTd>
<MudTd DataLabel="Audio Format">@context.AudioFormat.ToString().ToLowerInvariant()</MudTd>
<MudTd> <MudTd>
<div style="align-items: center; display: flex;"> <div style="align-items: center; display: flex;">
<MudTooltip Text="Edit Profile"> <MudTooltip Text="Edit Profile">

35
ErsatzTV/Pages/FFmpegEditor.razor

@ -31,32 +31,29 @@
<MudTextField Label="Thread Count" @bind-Value="@_model.ThreadCount" For="@(() => _model.ThreadCount)"/> <MudTextField Label="Thread Count" @bind-Value="@_model.ThreadCount" For="@(() => _model.ThreadCount)"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudSelect Disabled="@(!_model.Transcode)" Label="Preferred Resolution" @bind-Value="_model.Resolution" For="@(() => _model.Resolution)"> <MudSelect Label="Preferred Resolution" @bind-Value="_model.Resolution" For="@(() => _model.Resolution)">
@foreach (ResolutionViewModel resolution in _resolutions) @foreach (ResolutionViewModel resolution in _resolutions)
{ {
<MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem> <MudSelectItem Value="@resolution">@resolution.Name</MudSelectItem>
} }
</MudSelect> </MudSelect>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Label="Transcode" @bind-Checked="@_model.Transcode" For="@(() => _model.Transcode)"/>
</MudElement>
</MudItem> </MudItem>
<MudItem> <MudItem>
<MudText Typo="Typo.h6">Video</MudText> <MudText Typo="Typo.h6">Video</MudText>
<MudSelect Disabled="@(!_model.Transcode)" Label="Format" @bind-Value="_model.VideoFormat" For="@(() => _model.VideoFormat)"> <MudSelect Label="Format" @bind-Value="_model.VideoFormat" For="@(() => _model.VideoFormat)">
<MudSelectItem Value="@FFmpegProfileVideoFormat.H264">h264</MudSelectItem> <MudSelectItem Value="@FFmpegProfileVideoFormat.H264">h264</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileVideoFormat.Hevc">hevc</MudSelectItem> <MudSelectItem Value="@FFmpegProfileVideoFormat.Hevc">hevc</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileVideoFormat.Mpeg2Video">mpeg-2</MudSelectItem> <MudSelectItem Value="@FFmpegProfileVideoFormat.Mpeg2Video">mpeg-2</MudSelectItem>
</MudSelect> </MudSelect>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Bitrate" @bind-Value="_model.VideoBitrate" For="@(() => _model.VideoBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/> <MudTextField Label="Bitrate" @bind-Value="_model.VideoBitrate" For="@(() => _model.VideoBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/> <MudTextField Label="Buffer Size" @bind-Value="_model.VideoBufferSize" For="@(() => _model.VideoBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudSelect Disabled="@(!_model.Transcode)" Label="Hardware Acceleration" @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)"> <MudSelect Label="Hardware Acceleration" @bind-Value="_model.HardwareAcceleration" For="@(() => _model.HardwareAcceleration)">
@foreach (HardwareAccelerationKind hwAccel in Enum.GetValues<HardwareAccelerationKind>()) @foreach (HardwareAccelerationKind hwAccel in Enum.GetValues<HardwareAccelerationKind>())
{ {
<MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem> <MudSelectItem Value="@hwAccel">@hwAccel</MudSelectItem>
@ -80,38 +77,32 @@
</MudSelect> </MudSelect>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Video" @bind-Checked="@_model.NormalizeVideo" For="@(() => _model.NormalizeVideo)"/> <MudCheckBox Label="Normalize Frame Rate" @bind-Checked="@_model.NormalizeFramerate" For="@(() => _model.NormalizeFramerate)"/>
</MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Frame Rate" @bind-Checked="@_model.NormalizeFramerate" For="@(() => _model.NormalizeFramerate)"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Auto Deinterlace Video" @bind-Checked="@_model.DeinterlaceVideo" For="@(() => _model.DeinterlaceVideo)"/> <MudCheckBox Label="Auto Deinterlace Video" @bind-Checked="@_model.DeinterlaceVideo" For="@(() => _model.DeinterlaceVideo)"/>
</MudElement> </MudElement>
</MudItem> </MudItem>
<MudItem> <MudItem>
<MudText Typo="Typo.h6">Audio</MudText> <MudText Typo="Typo.h6">Audio</MudText>
<MudSelect Disabled="@(!_model.Transcode)" Label="Format" @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)"> <MudSelect Label="Format" @bind-Value="_model.AudioFormat" For="@(() => _model.AudioFormat)">
<MudSelectItem Value="@FFmpegProfileAudioFormat.Aac">aac</MudSelectItem> <MudSelectItem Value="@FFmpegProfileAudioFormat.Aac">aac</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileAudioFormat.Ac3">ac3</MudSelectItem> <MudSelectItem Value="@FFmpegProfileAudioFormat.Ac3">ac3</MudSelectItem>
</MudSelect> </MudSelect>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Bitrate" @bind-Value="_model.AudioBitrate" For="@(() => _model.AudioBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/> <MudTextField Label="Bitrate" @bind-Value="_model.AudioBitrate" For="@(() => _model.AudioBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
</MudElement>
<MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Buffer Size" @bind-Value="_model.AudioBufferSize" For="@(() => _model.AudioBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Channels" @bind-Value="_model.AudioChannels" For="@(() => _model.AudioChannels)"/> <MudTextField Label="Buffer Size" @bind-Value="_model.AudioBufferSize" For="@(() => _model.AudioBufferSize)" Adornment="Adornment.End" AdornmentText="kBit"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudTextField Disabled="@(!_model.Transcode)" Label="Sample Rate" @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/> <MudTextField Label="Channels" @bind-Value="_model.AudioChannels" For="@(() => _model.AudioChannels)"/>
</MudElement> </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)"/> <MudTextField Label="Sample Rate" @bind-Value="_model.AudioSampleRate" For="@(() => _model.AudioSampleRate)" Adornment="Adornment.End" AdornmentText="kHz"/>
</MudElement> </MudElement>
<MudElement HtmlTag="div" Class="mt-3"> <MudElement HtmlTag="div" Class="mt-3">
<MudCheckBox Disabled="@(!_model.Transcode)" Label="Normalize Loudness" @bind-Checked="@_model.NormalizeLoudness" For="@(() => _model.NormalizeLoudness)"/> <MudCheckBox Label="Normalize Loudness" @bind-Checked="@_model.NormalizeLoudness" For="@(() => _model.NormalizeLoudness)"/>
</MudElement> </MudElement>
</MudItem> </MudItem>
</MudGrid> </MudGrid>

17
ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

@ -37,18 +37,13 @@ public class FFmpegProfileEditViewModelValidator : AbstractValidator<FFmpegProfi
RuleFor(x => x.Name).NotEmpty(); RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.ThreadCount).GreaterThanOrEqualTo(0); RuleFor(x => x.ThreadCount).GreaterThanOrEqualTo(0);
When( RuleFor(x => x.VideoFormat).NotEqual(FFmpegProfileVideoFormat.None);
x => x.Transcode, RuleFor(x => x.VideoBitrate).GreaterThan(0);
() => RuleFor(x => x.VideoBufferSize).GreaterThan(0);
{
RuleFor(x => x.VideoFormat).NotEqual(FFmpegProfileVideoFormat.None);
RuleFor(x => x.VideoBitrate).GreaterThan(0);
RuleFor(x => x.VideoBufferSize).GreaterThan(0);
RuleFor(x => x.AudioFormat).NotEqual(FFmpegProfileAudioFormat.None); RuleFor(x => x.AudioFormat).NotEqual(FFmpegProfileAudioFormat.None);
RuleFor(x => x.AudioBitrate).GreaterThan(0); RuleFor(x => x.AudioBitrate).GreaterThan(0);
RuleFor(x => x.AudioChannels).GreaterThan(0); RuleFor(x => x.AudioChannels).GreaterThan(0);
});
When( When(
x => x.HardwareAcceleration == HardwareAccelerationKind.Qsv, x => x.HardwareAcceleration == HardwareAccelerationKind.Qsv,

12
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -21,13 +21,10 @@ public class FFmpegProfileEditViewModel
NormalizeLoudness = viewModel.NormalizeLoudness; NormalizeLoudness = viewModel.NormalizeLoudness;
Id = viewModel.Id; Id = viewModel.Id;
Name = viewModel.Name; Name = viewModel.Name;
NormalizeAudio = viewModel.NormalizeAudio;
NormalizeVideo = viewModel.NormalizeVideo;
NormalizeFramerate = viewModel.NormalizeFramerate; NormalizeFramerate = viewModel.NormalizeFramerate;
DeinterlaceVideo = viewModel.DeinterlaceVideo; DeinterlaceVideo = viewModel.DeinterlaceVideo;
Resolution = viewModel.Resolution; Resolution = viewModel.Resolution;
ThreadCount = viewModel.ThreadCount; ThreadCount = viewModel.ThreadCount;
Transcode = viewModel.Transcode;
HardwareAcceleration = viewModel.HardwareAcceleration; HardwareAcceleration = viewModel.HardwareAcceleration;
VaapiDriver = viewModel.VaapiDriver; VaapiDriver = viewModel.VaapiDriver;
VaapiDevice = viewModel.VaapiDevice; VaapiDevice = viewModel.VaapiDevice;
@ -44,13 +41,10 @@ public class FFmpegProfileEditViewModel
public bool NormalizeLoudness { get; set; } public bool NormalizeLoudness { get; set; }
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 NormalizeVideo { get; set; }
public bool NormalizeFramerate { get; set; } public bool NormalizeFramerate { get; set; }
public bool DeinterlaceVideo { get; set; } public bool DeinterlaceVideo { get; set; }
public ResolutionViewModel Resolution { get; set; } public ResolutionViewModel Resolution { get; set; }
public int ThreadCount { get; set; } public int ThreadCount { get; set; }
public bool Transcode { get; set; }
public HardwareAccelerationKind HardwareAcceleration { get; set; } public HardwareAccelerationKind HardwareAcceleration { get; set; }
public VaapiDriver VaapiDriver { get; set; } public VaapiDriver VaapiDriver { get; set; }
public string VaapiDevice { get; set; } public string VaapiDevice { get; set; }
@ -62,12 +56,10 @@ public class FFmpegProfileEditViewModel
new( new(
Name, Name,
ThreadCount, ThreadCount,
Transcode,
HardwareAcceleration, HardwareAcceleration,
VaapiDriver, VaapiDriver,
VaapiDevice, VaapiDevice,
Resolution.Id, Resolution.Id,
NormalizeVideo,
VideoFormat, VideoFormat,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,
@ -77,7 +69,6 @@ public class FFmpegProfileEditViewModel
NormalizeLoudness, NormalizeLoudness,
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeAudio,
NormalizeFramerate, NormalizeFramerate,
DeinterlaceVideo DeinterlaceVideo
); );
@ -87,12 +78,10 @@ public class FFmpegProfileEditViewModel
Id, Id,
Name, Name,
ThreadCount, ThreadCount,
Transcode,
HardwareAcceleration, HardwareAcceleration,
VaapiDriver, VaapiDriver,
VaapiDevice, VaapiDevice,
Resolution.Id, Resolution.Id,
NormalizeVideo,
VideoFormat, VideoFormat,
VideoBitrate, VideoBitrate,
VideoBufferSize, VideoBufferSize,
@ -102,7 +91,6 @@ public class FFmpegProfileEditViewModel
NormalizeLoudness, NormalizeLoudness,
AudioChannels, AudioChannels,
AudioSampleRate, AudioSampleRate,
NormalizeAudio,
NormalizeFramerate, NormalizeFramerate,
DeinterlaceVideo DeinterlaceVideo
); );

Loading…
Cancel
Save