Browse Source

normalize bit depth (#1031)

* normalize bit depth and color for nvenc

* fix hls direct

* update changelog

* add bit depth option to ffmpeg profile
pull/1032/head
Jason Dove 3 years ago committed by GitHub
parent
commit
7439ded43d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfile.cs
  3. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  4. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfile.cs
  5. 1
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  6. 1
      ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs
  7. 1
      ErsatzTV.Application/FFmpegProfiles/Mapper.cs
  8. 40
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  9. 1
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  10. 7
      ErsatzTV.Core/Domain/FFmpegProfileBitDepth.cs
  11. 4
      ErsatzTV.Core/Domain/MediaItem/MediaStream.cs
  12. 27
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  13. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs
  14. 8
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  15. 10
      ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs
  16. 12
      ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs
  17. 14
      ErsatzTV.FFmpeg/ColorParams.cs
  18. 16
      ErsatzTV.FFmpeg/CommandGenerator.cs
  19. 2
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs
  20. 2
      ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs
  21. 43
      ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs
  22. 168
      ErsatzTV.FFmpeg/Filter/ComplexFilter.cs
  23. 3
      ErsatzTV.FFmpeg/Filter/Cuda/OverlayWatermarkCudaFilter.cs
  24. 3
      ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs
  25. 3
      ErsatzTV.FFmpeg/Filter/Qsv/OverlayWatermarkQsvFilter.cs
  26. 3
      ErsatzTV.FFmpeg/Filter/SubtitlesFilter.cs
  27. 45
      ErsatzTV.FFmpeg/Filter/TonemapFilter.cs
  28. 1
      ErsatzTV.FFmpeg/InputFile.cs
  29. 4
      ErsatzTV.FFmpeg/MediaStream.cs
  30. 17
      ErsatzTV.FFmpeg/PipelineBuilder.cs
  31. 4
      ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs
  32. 4315
      ErsatzTV.Infrastructure/Migrations/20221029131840_Add_FFmpegProfileBitDepth.Designer.cs
  33. 26
      ErsatzTV.Infrastructure/Migrations/20221029131840_Add_FFmpegProfileBitDepth.cs
  34. 4327
      ErsatzTV.Infrastructure/Migrations/20221103002717_Add_MediaStreamColor.Designer.cs
  35. 55
      ErsatzTV.Infrastructure/Migrations/20221103002717_Add_MediaStreamColor.cs
  36. 4327
      ErsatzTV.Infrastructure/Migrations/20221104021107_Reset_AllStatistics20221103.Designer.cs
  37. 31
      ErsatzTV.Infrastructure/Migrations/20221104021107_Reset_AllStatistics20221103.cs
  38. 17
      ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs
  39. 4
      ErsatzTV.sln
  40. 1
      ErsatzTV.sln.DotSettings
  41. 5
      ErsatzTV/Pages/FFmpegEditor.razor
  42. 4
      ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

2
CHANGELOG.md

@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix parsing song metadata from OGG audio files
### Added
- Add (required) bit depth normalization option to ffmpeg profile
- This can help if your card only supports e.g. h264 encoding, normalizing to 8 bits will allow the hardware encoder to be used
- Add audio stream selector scripts for episodes and movies
- This will let you customize which audio stream is selected for playback
- Episodes are passed the following data:

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

@ -13,6 +13,7 @@ public record CreateFFmpegProfile( @@ -13,6 +13,7 @@ public record CreateFFmpegProfile(
int? QsvExtraHardwareFrames,
int ResolutionId,
FFmpegProfileVideoFormat VideoFormat,
FFmpegProfileBitDepth BitDepth,
int VideoBitrate,
int VideoBufferSize,
FFmpegProfileAudioFormat AudioFormat,

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

@ -47,6 +47,7 @@ public class CreateFFmpegProfileHandler : @@ -47,6 +47,7 @@ public class CreateFFmpegProfileHandler :
QsvExtraHardwareFrames = request.QsvExtraHardwareFrames,
ResolutionId = resolutionId,
VideoFormat = request.VideoFormat,
BitDepth = request.BitDepth,
VideoBitrate = request.VideoBitrate,
VideoBufferSize = request.VideoBufferSize,
AudioFormat = request.AudioFormat,

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

@ -14,6 +14,7 @@ public record UpdateFFmpegProfile( @@ -14,6 +14,7 @@ public record UpdateFFmpegProfile(
int? QsvExtraHardwareFrames,
int ResolutionId,
FFmpegProfileVideoFormat VideoFormat,
FFmpegProfileBitDepth BitDepth,
int VideoBitrate,
int VideoBufferSize,
FFmpegProfileAudioFormat AudioFormat,

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

@ -36,6 +36,7 @@ public class @@ -36,6 +36,7 @@ public class
p.QsvExtraHardwareFrames = update.QsvExtraHardwareFrames;
p.ResolutionId = update.ResolutionId;
p.VideoFormat = update.VideoFormat;
p.BitDepth = update.BitDepth;
p.VideoBitrate = update.VideoBitrate;
p.VideoBufferSize = update.VideoBufferSize;
p.AudioFormat = update.AudioFormat;

1
ErsatzTV.Application/FFmpegProfiles/FFmpegProfileViewModel.cs

@ -14,6 +14,7 @@ public record FFmpegProfileViewModel( @@ -14,6 +14,7 @@ public record FFmpegProfileViewModel(
int? QsvExtraHardwareFrames,
ResolutionViewModel Resolution,
FFmpegProfileVideoFormat VideoFormat,
FFmpegProfileBitDepth BitDepth,
int VideoBitrate,
int VideoBufferSize,
FFmpegProfileAudioFormat AudioFormat,

1
ErsatzTV.Application/FFmpegProfiles/Mapper.cs

@ -17,6 +17,7 @@ internal static class Mapper @@ -17,6 +17,7 @@ internal static class Mapper
profile.QsvExtraHardwareFrames,
Project(profile.Resolution),
profile.VideoFormat,
profile.BitDepth,
profile.VideoBitrate,
profile.VideoBufferSize,
profile.AudioFormat,

40
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -113,26 +113,26 @@ public class TranscodingTests @@ -113,26 +113,26 @@ public class TranscodingTests
public static InputFormat[] InputFormats =
{
new("libx264", "yuv420p"),
new("libx264", "yuvj420p"),
// new("libx264", "yuvj420p"),
new("libx264", "yuv420p10le"),
// new("libx264", "yuv444p10le"),
new("mpeg1video", "yuv420p"),
new("mpeg2video", "yuv420p"),
// new("mpeg1video", "yuv420p"),
//
// new("mpeg2video", "yuv420p"),
new("libx265", "yuv420p"),
new("libx265", "yuv420p10le"),
new("mpeg4", "yuv420p"),
new("libvpx-vp9", "yuv420p"),
// new("libaom-av1", "yuv420p")
// av1 yuv420p10le 51
new("msmpeg4v2", "yuv420p"),
new("msmpeg4v3", "yuv420p")
// new("mpeg4", "yuv420p"),
//
// new("libvpx-vp9", "yuv420p"),
//
// // new("libaom-av1", "yuv420p")
// // av1 yuv420p10le 51
//
// new("msmpeg4v2", "yuv420p"),
// new("msmpeg4v3", "yuv420p")
// wmv3 yuv420p 1
};
@ -143,6 +143,12 @@ public class TranscodingTests @@ -143,6 +143,12 @@ public class TranscodingTests
new() { Width = 1280, Height = 720 }
};
public static FFmpegProfileBitDepth[] BitDepths =
{
FFmpegProfileBitDepth.EightBit,
FFmpegProfileBitDepth.TenBit
};
public static HardwareAccelerationKind[] NoAcceleration =
{
HardwareAccelerationKind.None
@ -187,6 +193,8 @@ public class TranscodingTests @@ -187,6 +193,8 @@ public class TranscodingTests
InputFormat inputFormat,
[ValueSource(typeof(TestData), nameof(TestData.Resolutions))]
Resolution profileResolution,
[ValueSource(typeof(TestData), nameof(TestData.BitDepths))]
FFmpegProfileBitDepth profileBitDepth,
[ValueSource(typeof(TestData), nameof(TestData.Paddings))]
Padding padding,
[ValueSource(typeof(TestData), nameof(TestData.VideoScanKinds))]
@ -213,8 +221,7 @@ public class TranscodingTests @@ -213,8 +221,7 @@ public class TranscodingTests
}
}
string name = GetStringSha256Hash(
$"{inputFormat.Encoder}_{inputFormat.PixelFormat}_{videoScanKind}_{padding}_{watermark}_{subtitle}_{profileResolution}_{profileVideoFormat}_{profileAcceleration}");
string name = GetStringSha256Hash($"{inputFormat.Encoder}_{inputFormat.PixelFormat}_{videoScanKind}_{padding}");
string file = Path.Combine(TestContext.CurrentContext.TestDirectory, $"{name}.mkv");
if (!File.Exists(file))
@ -478,7 +485,8 @@ public class TranscodingTests @@ -478,7 +485,8 @@ public class TranscodingTests
HardwareAcceleration = profileAcceleration,
VideoFormat = profileVideoFormat,
AudioFormat = FFmpegProfileAudioFormat.Aac,
DeinterlaceVideo = true
DeinterlaceVideo = true,
BitDepth = profileBitDepth
},
StreamingMode = StreamingMode.TransportStream,
SubtitleMode = subtitleMode

1
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -14,6 +14,7 @@ public record FFmpegProfile @@ -14,6 +14,7 @@ public record FFmpegProfile
public int ResolutionId { get; set; }
public Resolution Resolution { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; }
public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; }
public FFmpegProfileAudioFormat AudioFormat { get; set; }

7
ErsatzTV.Core/Domain/FFmpegProfileBitDepth.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
namespace ErsatzTV.Core.Domain;
public enum FFmpegProfileBitDepth
{
EightBit = 0,
TenBit = 1
}

4
ErsatzTV.Core/Domain/MediaItem/MediaStream.cs

@ -14,6 +14,10 @@ public class MediaStream @@ -14,6 +14,10 @@ public class MediaStream
public bool Forced { get; set; }
public bool AttachedPic { get; set; }
public string PixelFormat { get; set; }
public string ColorRange { get; set; }
public string ColorSpace { get; set; }
public string ColorTransfer { get; set; }
public string ColorPrimaries { get; set; }
public int BitsPerRawSample { get; set; }
public string FileName { get; set; }
public string MimeType { get; set; }

27
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -147,6 +147,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -147,6 +147,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
videoStream.Index,
videoStream.Codec,
AvailablePixelFormats.ForPixelFormat(videoStream.PixelFormat, _logger),
new ColorParams(
videoStream.ColorRange,
videoStream.ColorSpace,
videoStream.ColorTransfer,
videoStream.ColorPrimaries),
new FrameSize(videoVersion.Width, videoVersion.Height),
videoVersion.SampleAspectRatio,
videoVersion.DisplayAspectRatio,
@ -208,14 +213,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -208,14 +213,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
: Option<string>.None;
// normalize songs to yuv420p
Option<IPixelFormat> desiredPixelFormat =
videoPath == audioPath ? ffmpegVideoStream.PixelFormat : new PixelFormatYuv420P();
IPixelFormat desiredPixelFormat =
videoPath == audioPath ? playbackSettings.PixelFormat : new PixelFormatYuv420P();
var desiredState = new FrameState(
playbackSettings.RealtimeOutput,
false, // TODO: fallback filler needs to loop
videoFormat,
desiredPixelFormat,
Optional(desiredPixelFormat),
ffmpegVideoStream.SquarePixelFrameSize(
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height)),
new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height),
@ -343,6 +348,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -343,6 +348,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
0,
VideoFormat.GeneratedImage,
new PixelFormatUnknown(), // leave this unknown so we convert to desired yuv420p
ColorParams.Default,
new FrameSize(videoVersion.Width, videoVersion.Height),
videoVersion.SampleAspectRatio,
videoVersion.DisplayAspectRatio,
@ -437,7 +443,19 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -437,7 +443,19 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
{
var videoInputFile = new VideoInputFile(
inputFile,
new List<VideoStream> { new(0, string.Empty, None, FrameSize.Unknown, string.Empty, string.Empty, None, true) });
new List<VideoStream>
{
new(
0,
string.Empty,
None,
ColorParams.Default,
FrameSize.Unknown,
string.Empty,
string.Empty,
None,
true)
});
var pipelineBuilder = new PipelineBuilder(
_runtimeInfo,
@ -517,6 +535,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -517,6 +535,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
options.ImageStreamIndex.IfNone(0),
"unknown",
new PixelFormatUnknown(),
ColorParams.Default,
new FrameSize(1, 1),
string.Empty,
string.Empty,

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettings.cs

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.Core.FFmpeg;
@ -14,6 +15,7 @@ public class FFmpegPlaybackSettings @@ -14,6 +15,7 @@ public class FFmpegPlaybackSettings
public Option<IDisplaySize> ScaledSize { get; set; }
public bool PadToDesiredResolution { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; }
public IPixelFormat PixelFormat { get; set; }
public Option<int> VideoBitrate { get; set; }
public Option<int> VideoBufferSize { get; set; }
public Option<int> AudioBitrate { get; set; }

8
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.Core.FFmpeg;
@ -144,6 +145,13 @@ public class FFmpegPlaybackSettingsCalculator @@ -144,6 +145,13 @@ public class FFmpegPlaybackSettingsCalculator
};
}
result.PixelFormat = ffmpegProfile.BitDepth switch
{
FFmpegProfileBitDepth.TenBit => new PixelFormatYuv420P10Le(),
_ => new PixelFormatYuv420P()
// _ => new PixelFormatYuv420P10Le()
};
result.AudioFormat = ffmpegProfile.AudioFormat;
result.AudioBitrate = ffmpegProfile.AudioBitrate;
result.AudioBufferSize = ffmpegProfile.AudioBufferSize;

10
ErsatzTV.Core/Metadata/LocalStatisticsProvider.cs

@ -399,7 +399,11 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -399,7 +399,11 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
Index = videoStream.index,
Codec = videoStream.codec_name,
Profile = (videoStream.profile ?? string.Empty).ToLowerInvariant(),
PixelFormat = (videoStream.pix_fmt ?? string.Empty).ToLowerInvariant()
PixelFormat = (videoStream.pix_fmt ?? string.Empty).ToLowerInvariant(),
ColorRange = (videoStream.color_range ?? string.Empty).ToLowerInvariant(),
ColorSpace = (videoStream.color_space ?? string.Empty).ToLowerInvariant(),
ColorTransfer = (videoStream.color_transfer ?? string.Empty).ToLowerInvariant(),
ColorPrimaries = (videoStream.color_primaries ?? string.Empty).ToLowerInvariant()
};
if (int.TryParse(videoStream.bits_per_raw_sample, out int bitsPerRawSample))
@ -538,6 +542,10 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider @@ -538,6 +542,10 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider
string sample_aspect_ratio,
string display_aspect_ratio,
string pix_fmt,
string color_range,
string color_space,
string color_transfer,
string color_primaries,
string field_order,
string r_frame_rate,
string bits_per_raw_sample,

12
ErsatzTV.FFmpeg.Tests/PipelineBuilderTests.cs

@ -26,7 +26,7 @@ public class PipelineGeneratorTests @@ -26,7 +26,7 @@ public class PipelineGeneratorTests
var videoInputFile = new VideoInputFile(
"/tmp/whatever.mkv",
new List<VideoStream>
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), ColorParams.Default, new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
var audioInputFile = new AudioInputFile(
"/tmp/whatever.mkv",
@ -99,7 +99,7 @@ public class PipelineGeneratorTests @@ -99,7 +99,7 @@ public class PipelineGeneratorTests
var videoInputFile = new VideoInputFile(
"/tmp/whatever.mkv",
new List<VideoStream>
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), ColorParams.Default, new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
var audioInputFile = new AudioInputFile(
"/tmp/whatever.mkv",
@ -198,7 +198,7 @@ public class PipelineGeneratorTests @@ -198,7 +198,7 @@ public class PipelineGeneratorTests
var videoInputFile = new VideoInputFile(
"/tmp/whatever.mkv",
new List<VideoStream>
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), ColorParams.Default, new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
var audioInputFile = new AudioInputFile(
"/tmp/whatever.mkv",
@ -216,7 +216,7 @@ public class PipelineGeneratorTests @@ -216,7 +216,7 @@ public class PipelineGeneratorTests
true,
false,
VideoFormat.Copy,
new PixelFormatYuv420P(),
Option<IPixelFormat>.None,
new FrameSize(1920, 1080),
new FrameSize(1920, 1080),
false,
@ -273,7 +273,7 @@ public class PipelineGeneratorTests @@ -273,7 +273,7 @@ public class PipelineGeneratorTests
var videoInputFile = new VideoInputFile(
"/tmp/whatever.mkv",
new List<VideoStream>
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
{ new(0, VideoFormat.H264, new PixelFormatYuv420P(), ColorParams.Default, new FrameSize(1920, 1080), "1:1", "16:9", "24", false) });
Option<AudioInputFile> audioInputFile = Option<AudioInputFile>.None;
@ -341,7 +341,7 @@ public class PipelineGeneratorTests @@ -341,7 +341,7 @@ public class PipelineGeneratorTests
"/test/input/file.png",
new List<VideoStream>
{
new(0, string.Empty, Option<IPixelFormat>.None, FrameSize.Unknown, string.Empty, string.Empty, Option<string>.None, true)
new(0, string.Empty, Option<IPixelFormat>.None, ColorParams.Default, FrameSize.Unknown, string.Empty, string.Empty, Option<string>.None, true)
});
var pipelineBuilder = new PipelineBuilder(

14
ErsatzTV.FFmpeg/ColorParams.cs

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
namespace ErsatzTV.FFmpeg;
public record ColorParams(string ColorRange, string ColorSpace, string ColorTransfer, string ColorPrimaries)
{
public static readonly ColorParams Default = new("tv", "bt709", "bt709", "bt709");
public bool IsHdr => ColorTransfer is "arib-std-b67" or "smpte2084";
public bool IsUnknown => string.IsNullOrWhiteSpace(ColorSpace) &&
string.IsNullOrWhiteSpace(ColorTransfer) &&
string.IsNullOrWhiteSpace(ColorPrimaries);
public bool IsBt709 => this == Default;
}

16
ErsatzTV.FFmpeg/CommandGenerator.cs

@ -1,4 +1,6 @@ @@ -1,4 +1,6 @@
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Encoder;
using ErsatzTV.FFmpeg.Environment;
using ErsatzTV.FFmpeg.Filter;
using ErsatzTV.FFmpeg.Option;
namespace ErsatzTV.FFmpeg;
@ -80,8 +82,18 @@ public static class CommandGenerator @@ -80,8 +82,18 @@ public static class CommandGenerator
arguments.AddRange(step.FilterOptions);
}
foreach (IPipelineStep step in pipelineSteps.Filter(s => s is not StreamSeekFilterOption))
// rearrange complex filter output options directly after video encoder
var sortedSteps = pipelineSteps.Filter(s => s is not StreamSeekFilterOption && s is not ComplexFilter).ToList();
Option<IPipelineStep> maybeComplex = pipelineSteps.Find(s => s is ComplexFilter);
foreach (IPipelineStep complex in maybeComplex)
{
int encoderIndex = sortedSteps.FindIndex(s => s is EncoderBase { Kind: StreamKind.Video });
sortedSteps.Insert(encoderIndex + 1, complex);
}
foreach (IPipelineStep step in sortedSteps)
{
arguments.AddRange(step.OutputOptions);
}

2
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderH264Nvenc.cs

@ -44,6 +44,6 @@ public class EncoderH264Nvenc : EncoderBase @@ -44,6 +44,6 @@ public class EncoderH264Nvenc : EncoderBase
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.H264,
FrameDataLocation = FrameDataLocation.Hardware
// FrameDataLocation = FrameDataLocation.Hardware
};
}

2
ErsatzTV.FFmpeg/Encoder/Nvenc/EncoderHevcNvenc.cs

@ -44,6 +44,6 @@ public class EncoderHevcNvenc : EncoderBase @@ -44,6 +44,6 @@ public class EncoderHevcNvenc : EncoderBase
public override FrameState NextState(FrameState currentState) => currentState with
{
VideoFormat = VideoFormat.Hevc,
FrameDataLocation = FrameDataLocation.Hardware
// FrameDataLocation = FrameDataLocation.Hardware
};
}

43
ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Filter;
public class ColorspaceFilter : BaseFilter
{
private readonly VideoStream _videoStream;
private readonly IPixelFormat _desiredPixelFormat;
public ColorspaceFilter(VideoStream videoStream, IPixelFormat desiredPixelFormat)
{
_videoStream = videoStream;
_desiredPixelFormat = desiredPixelFormat;
}
public override FrameState NextState(FrameState currentState) =>
currentState with
{
PixelFormat = Some(_desiredPixelFormat),
FrameDataLocation = FrameDataLocation.Software
};
public override string Filter
{
get
{
string setParams = string.Empty;
if (_videoStream.ColorParams.IsUnknown)
{
setParams = "setparams=range=tv:colorspace=bt709:color_trc=bt709:color_primaries=bt709";
}
string colorspace = _desiredPixelFormat.BitDepth switch
{
10 when !_videoStream.ColorParams.IsUnknown => "colorspace=all=bt709:format=yuv420p10",
8 when !_videoStream.ColorParams.IsUnknown => "colorspace=all=bt709:format=yuv420p",
_ => string.Empty
};
return string.Join(',', new[] { setParams, colorspace }.Filter(s => !string.IsNullOrWhiteSpace(s)));
}
}
}

168
ErsatzTV.FFmpeg/Filter/ComplexFilter.cs

@ -12,9 +12,11 @@ public class ComplexFilter : IPipelineStep @@ -12,9 +12,11 @@ public class ComplexFilter : IPipelineStep
private readonly ILogger _logger;
private readonly Option<AudioInputFile> _maybeAudioInputFile;
private readonly Option<SubtitleInputFile> _maybeSubtitleInputFile;
private readonly Option<IPixelFormat> _desiredPixelFormat;
private readonly Option<VideoInputFile> _maybeVideoInputFile;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
private readonly FrameSize _resolution;
private readonly List<string> _outputOptions;
public ComplexFilter(
FrameState currentState,
@ -23,6 +25,7 @@ public class ComplexFilter : IPipelineStep @@ -23,6 +25,7 @@ public class ComplexFilter : IPipelineStep
Option<AudioInputFile> maybeAudioInputFile,
Option<WatermarkInputFile> maybeWatermarkInputFile,
Option<SubtitleInputFile> maybeSubtitleInputFile,
Option<IPixelFormat> desiredPixelFormat,
FrameSize resolution,
string fontsDir,
ILogger logger)
@ -33,20 +36,25 @@ public class ComplexFilter : IPipelineStep @@ -33,20 +36,25 @@ public class ComplexFilter : IPipelineStep
_maybeAudioInputFile = maybeAudioInputFile;
_maybeWatermarkInputFile = maybeWatermarkInputFile;
_maybeSubtitleInputFile = maybeSubtitleInputFile;
_desiredPixelFormat = desiredPixelFormat;
_resolution = resolution;
_fontsDir = fontsDir;
_logger = logger;
_outputOptions = new List<string>();
}
public IList<EnvironmentVariable> EnvironmentVariables => Array.Empty<EnvironmentVariable>();
public IList<string> GlobalOptions => Array.Empty<string>();
public IList<string> InputOptions(InputFile inputFile) => Array.Empty<string>();
public IList<string> FilterOptions => Arguments();
public IList<string> OutputOptions => Array.Empty<string>();
public IList<string> OutputOptions => _outputOptions;
public FrameState NextState(FrameState currentState) => currentState;
private IList<string> Arguments()
{
var state = _currentState;
var audioLabel = "0:a";
var videoLabel = "0:v";
string watermarkLabel;
@ -60,6 +68,7 @@ public class ComplexFilter : IPipelineStep @@ -60,6 +68,7 @@ public class ComplexFilter : IPipelineStep
string watermarkOverlayFilterComplex = string.Empty;
string subtitleFilterComplex = string.Empty;
string subtitleOverlayFilterComplex = string.Empty;
string pixelFormatFilterComplex = string.Empty;
var distinctPaths = new List<string>();
foreach ((string path, _) in _maybeVideoInputFile)
@ -152,54 +161,56 @@ public class ComplexFilter : IPipelineStep @@ -152,54 +161,56 @@ public class ComplexFilter : IPipelineStep
}
foreach (VideoInputFile videoInputFile in _maybeVideoInputFile)
foreach (VideoStream stream in videoInputFile.VideoStreams)
{
foreach (VideoStream stream in videoInputFile.VideoStreams)
IPipelineFilterStep overlayFilter = AvailableWatermarkOverlayFilters.ForAcceleration(
_ffmpegState.EncoderHardwareAccelerationMode,
watermarkInputFile.DesiredState,
_resolution,
stream.SquarePixelFrameSize(_resolution),
_logger);
if (overlayFilter.Filter != string.Empty)
{
IPipelineFilterStep overlayFilter = AvailableWatermarkOverlayFilters.ForAcceleration(
_ffmpegState.EncoderHardwareAccelerationMode,
watermarkInputFile.DesiredState,
_resolution,
stream.SquarePixelFrameSize(_resolution),
_logger);
if (overlayFilter.Filter != string.Empty)
state = overlayFilter.NextState(state);
string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex)
? $"[{videoLabel}]"
: videoLabel;
// vaapi uses software overlay and needs to upload
// videotoolbox seems to require a hwupload for hevc
// also wait to upload if a subtitle overlay is coming
string uploadDownloadFilter = string.Empty;
if (_maybeSubtitleInputFile.IsNone &&
(_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi ||
_ffmpegState.EncoderHardwareAccelerationMode ==
HardwareAccelerationMode.VideoToolbox &&
state.VideoFormat == VideoFormat.Hevc))
{
string tempVideoLabel = string.IsNullOrWhiteSpace(videoFilterComplex)
? $"[{videoLabel}]"
: videoLabel;
// vaapi uses software overlay and needs to upload
// videotoolbox seems to require a hwupload for hevc
// also wait to upload if a subtitle overlay is coming
string uploadDownloadFilter = string.Empty;
if (_maybeSubtitleInputFile.IsNone &&
(_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi ||
_ffmpegState.EncoderHardwareAccelerationMode ==
HardwareAccelerationMode.VideoToolbox &&
_currentState.VideoFormat == VideoFormat.Hevc))
{
uploadDownloadFilter = new HardwareUploadFilter(_ffmpegState).Filter;
}
if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) &&
_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi &&
_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox &&
_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Amf)
{
uploadDownloadFilter = new HardwareDownloadFilter(_currentState).Filter;
}
if (!string.IsNullOrWhiteSpace(uploadDownloadFilter))
{
uploadDownloadFilter = "," + uploadDownloadFilter;
}
watermarkOverlayFilterComplex =
$"{tempVideoLabel}{watermarkLabel}{overlayFilter.Filter}{uploadDownloadFilter}[vf]";
// change the mapped label
videoLabel = "[vf]";
uploadDownloadFilter = new HardwareUploadFilter(_ffmpegState).Filter;
state = state with { FrameDataLocation = FrameDataLocation.Hardware };
}
if (_maybeSubtitleInputFile.Map(s => !s.IsImageBased).IfNone(false) &&
_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Vaapi &&
_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.VideoToolbox &&
_ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Amf)
{
uploadDownloadFilter = new HardwareDownloadFilter(state).Filter;
state = state with { FrameDataLocation = FrameDataLocation.Software };
}
if (!string.IsNullOrWhiteSpace(uploadDownloadFilter))
{
uploadDownloadFilter = "," + uploadDownloadFilter;
}
watermarkOverlayFilterComplex =
$"{tempVideoLabel}{watermarkLabel}{overlayFilter.Filter}{uploadDownloadFilter}[vf]";
// change the mapped label
videoLabel = "[vf]";
}
}
}
@ -230,12 +241,14 @@ public class ComplexFilter : IPipelineStep @@ -230,12 +241,14 @@ public class ComplexFilter : IPipelineStep
{
IPipelineFilterStep overlayFilter = AvailableSubtitleOverlayFilters.ForAcceleration(
_ffmpegState.EncoderHardwareAccelerationMode);
state = overlayFilter.NextState(state);
filter = overlayFilter.Filter;
}
else
{
subtitleLabel = string.Empty;
var subtitlesFilter = new SubtitlesFilter(_fontsDir, subtitleInputFile);
state = subtitlesFilter.NextState(state);
filter = subtitlesFilter.Filter;
}
@ -250,9 +263,13 @@ public class ComplexFilter : IPipelineStep @@ -250,9 +263,13 @@ public class ComplexFilter : IPipelineStep
string uploadFilter = string.Empty;
if (_ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi
|| _ffmpegState.EncoderHardwareAccelerationMode == HardwareAccelerationMode.VideoToolbox &&
_currentState.VideoFormat == VideoFormat.Hevc)
state.VideoFormat == VideoFormat.Hevc)
{
uploadFilter = new HardwareUploadFilter(_ffmpegState).Filter;
if (!string.IsNullOrWhiteSpace(uploadFilter))
{
state = state with { FrameDataLocation = FrameDataLocation.Hardware };
}
}
if (!string.IsNullOrWhiteSpace(uploadFilter))
@ -268,6 +285,62 @@ public class ComplexFilter : IPipelineStep @@ -268,6 +285,62 @@ public class ComplexFilter : IPipelineStep
}
}
foreach (VideoStream videoStream in _maybeVideoInputFile.Map(vif => vif.VideoStreams).Flatten())
foreach (IPixelFormat pixelFormat in _desiredPixelFormat)
{
_logger.LogDebug("Desired pixel format {PixelFormat}", pixelFormat);
string tempVideoLabel = videoLabel.StartsWith("[") && videoLabel.EndsWith("]")
? videoLabel
: $"[{videoLabel}]";
// normalize pixel format and color params
string filter = string.Empty;
if (!videoStream.ColorParams.IsBt709)
{
_logger.LogDebug("Adding colorspace filter");
filter = new ColorspaceFilter(videoStream, pixelFormat).Filter;
}
if (state.PixelFormat.Map(f => f.FFmpegName) != pixelFormat.FFmpegName)
{
_logger.LogDebug(
"Format {A} doesn't equal {B}",
state.PixelFormat.Map(f => f.FFmpegName),
pixelFormat.FFmpegName);
if (videoStream.ColorParams.IsHdr)
{
_logger.LogWarning("HDR tone mapping is not implemented; colors may appear incorrect");
continue;
}
if (state.FrameDataLocation == FrameDataLocation.Software)
{
_logger.LogDebug("Frame data location is SOFTWARE");
_outputOptions.AddRange(new[] { "-pix_fmt", pixelFormat.FFmpegName });
}
else
{
_logger.LogDebug("Frame data location is HARDWARE");
filter = _ffmpegState.EncoderHardwareAccelerationMode switch
{
HardwareAccelerationMode.Nvenc => $"{filter},scale_cuda=format={pixelFormat.FFmpegName}",
_ => filter
};
}
}
if (!string.IsNullOrWhiteSpace(filter))
{
pixelFormatFilterComplex = $"{tempVideoLabel}{filter}[vpf]";
// change the mapped label
videoLabel = "[vpf]";
}
}
var filterComplex = string.Join(
";",
new[]
@ -277,7 +350,8 @@ public class ComplexFilter : IPipelineStep @@ -277,7 +350,8 @@ public class ComplexFilter : IPipelineStep
watermarkFilterComplex,
subtitleFilterComplex,
watermarkOverlayFilterComplex,
subtitleOverlayFilterComplex
subtitleOverlayFilterComplex,
pixelFormatFilterComplex
}.Where(
s => !string.IsNullOrWhiteSpace(s)));

3
ErsatzTV.FFmpeg/Filter/Cuda/OverlayWatermarkCudaFilter.cs

@ -19,5 +19,6 @@ public class OverlayWatermarkCudaFilter : OverlayWatermarkFilter @@ -19,5 +19,6 @@ public class OverlayWatermarkCudaFilter : OverlayWatermarkFilter
public override string Filter => $"overlay_cuda={Position}";
public override FrameState NextState(FrameState currentState) => currentState;
public override FrameState NextState(FrameState currentState) =>
currentState with { FrameDataLocation = FrameDataLocation.Hardware };
}

3
ErsatzTV.FFmpeg/Filter/OverlayWatermarkFilter.cs

@ -46,7 +46,8 @@ public class OverlayWatermarkFilter : BaseFilter @@ -46,7 +46,8 @@ public class OverlayWatermarkFilter : BaseFilter
}
}
public override FrameState NextState(FrameState currentState) => currentState;
public override FrameState NextState(FrameState currentState) =>
currentState with { FrameDataLocation = FrameDataLocation.Software };
private WatermarkMargins NormalMargins()
{

3
ErsatzTV.FFmpeg/Filter/Qsv/OverlayWatermarkQsvFilter.cs

@ -19,5 +19,6 @@ public class OverlayWatermarkQsvFilter : OverlayWatermarkFilter @@ -19,5 +19,6 @@ public class OverlayWatermarkQsvFilter : OverlayWatermarkFilter
public override string Filter => $"overlay_qsv={Position}";
public override FrameState NextState(FrameState currentState) => currentState;
public override FrameState NextState(FrameState currentState) =>
currentState with { FrameDataLocation = FrameDataLocation.Hardware };
}

3
ErsatzTV.FFmpeg/Filter/SubtitlesFilter.cs

@ -40,5 +40,6 @@ public class SubtitlesFilter : BaseFilter @@ -40,5 +40,6 @@ public class SubtitlesFilter : BaseFilter
}
}
public override FrameState NextState(FrameState currentState) => currentState;
public override FrameState NextState(FrameState currentState) =>
currentState with { FrameDataLocation = FrameDataLocation.Software };
}

45
ErsatzTV.FFmpeg/Filter/TonemapFilter.cs

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg.Filter;
public class TonemapFilter : BaseFilter
{
private readonly FrameState _currentState;
private readonly IPixelFormat _desiredPixelFormat;
public TonemapFilter(FrameState currentState, IPixelFormat desiredPixelFormat)
{
_currentState = currentState;
_desiredPixelFormat = desiredPixelFormat;
}
public override FrameState NextState(FrameState currentState) =>
currentState with
{
PixelFormat = Some(_desiredPixelFormat),
FrameDataLocation = FrameDataLocation.Software
};
public override string Filter
{
get
{
string pixelFormat = _currentState.PixelFormat.Match(pf => pf.FFmpegName, () => string.Empty);
var tonemap =
$"setparams=colorspace=bt2020c,zscale=transfer=linear,tonemap=hable,zscale=transfer=bt709,format={_desiredPixelFormat.FFmpegName}";
if (_currentState.FrameDataLocation == FrameDataLocation.Hardware)
{
if (!string.IsNullOrWhiteSpace(pixelFormat))
{
return $"hwdownload,format={pixelFormat},{tonemap}";
}
return $"hwdownload,{tonemap}";
}
return tonemap;
}
}
}

1
ErsatzTV.FFmpeg/InputFile.cs

@ -18,6 +18,7 @@ public record ConcatInputFile(string Url, FrameSize Resolution) : InputFile( @@ -18,6 +18,7 @@ public record ConcatInputFile(string Url, FrameSize Resolution) : InputFile(
0,
string.Empty,
Option<IPixelFormat>.None,
ColorParams.Default,
Resolution,
string.Empty,
string.Empty,

4
ErsatzTV.FFmpeg/MediaStream.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using System.ComponentModel.DataAnnotations;
using ErsatzTV.FFmpeg.Format;
using ErsatzTV.FFmpeg.Format;
namespace ErsatzTV.FFmpeg;
@ -14,6 +13,7 @@ public record VideoStream( @@ -14,6 +13,7 @@ public record VideoStream(
int Index,
string Codec,
Option<IPixelFormat> PixelFormat,
ColorParams ColorParams,
FrameSize FrameSize,
string SampleAspectRatio,
string DisplayAspectRatio,

17
ErsatzTV.FFmpeg/PipelineBuilder.cs

@ -123,6 +123,8 @@ public class PipelineBuilder @@ -123,6 +123,8 @@ public class PipelineBuilder
public FFmpegPipeline Build(FFmpegState ffmpegState, FrameState desiredState)
{
var originalDesiredPixelFormat = desiredState.PixelFormat;
if (ffmpegState.Start.Exists(s => s > TimeSpan.Zero) && desiredState.Realtime)
{
_logger.LogInformation(
@ -520,20 +522,6 @@ public class PipelineBuilder @@ -520,20 +522,6 @@ public class PipelineBuilder
_pipelineSteps.Add(step);
}
}
foreach (IPixelFormat desiredPixelFormat in desiredState.PixelFormat)
{
if (currentState.PixelFormat.Map(pf => pf.FFmpegName) != desiredPixelFormat.FFmpegName)
{
// qsv doesn't seem to like this
if (ffmpegState.EncoderHardwareAccelerationMode != HardwareAccelerationMode.Qsv)
{
IPipelineStep step = new PixelFormatOutputOption(desiredPixelFormat);
currentState = step.NextState(currentState);
_pipelineSteps.Add(step);
}
}
}
}
// TODO: if all video filters are software, use software pixel format for hwaccel output
@ -787,6 +775,7 @@ public class PipelineBuilder @@ -787,6 +775,7 @@ public class PipelineBuilder
_audioInputFile,
_watermarkInputFile,
_subtitleInputFile,
originalDesiredPixelFormat,
currentState.PaddedSize,
_fontsFolder,
_logger);

4
ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs

@ -170,6 +170,10 @@ public class MetadataRepository : IMetadataRepository @@ -170,6 +170,10 @@ public class MetadataRepository : IMetadataRepository
existingStream.Forced = incomingStream.Forced;
existingStream.AttachedPic = incomingStream.AttachedPic;
existingStream.PixelFormat = incomingStream.PixelFormat;
existingStream.ColorRange = incomingStream.ColorRange;
existingStream.ColorSpace = incomingStream.ColorSpace;
existingStream.ColorTransfer = incomingStream.ColorTransfer;
existingStream.ColorPrimaries = incomingStream.ColorPrimaries;
existingStream.BitsPerRawSample = incomingStream.BitsPerRawSample;
existingStream.FileName = incomingStream.FileName;
existingStream.MimeType = incomingStream.MimeType;

4315
ErsatzTV.Infrastructure/Migrations/20221029131840_Add_FFmpegProfileBitDepth.Designer.cs generated

File diff suppressed because it is too large Load Diff

26
ErsatzTV.Infrastructure/Migrations/20221029131840_Add_FFmpegProfileBitDepth.cs

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

4327
ErsatzTV.Infrastructure/Migrations/20221103002717_Add_MediaStreamColor.Designer.cs generated

File diff suppressed because it is too large Load Diff

55
ErsatzTV.Infrastructure/Migrations/20221103002717_Add_MediaStreamColor.cs

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Add_MediaStreamColor : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ColorPrimaries",
table: "MediaStream",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ColorRange",
table: "MediaStream",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ColorSpace",
table: "MediaStream",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "ColorTransfer",
table: "MediaStream",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ColorPrimaries",
table: "MediaStream");
migrationBuilder.DropColumn(
name: "ColorRange",
table: "MediaStream");
migrationBuilder.DropColumn(
name: "ColorSpace",
table: "MediaStream");
migrationBuilder.DropColumn(
name: "ColorTransfer",
table: "MediaStream");
}
}
}

4327
ErsatzTV.Infrastructure/Migrations/20221104021107_Reset_AllStatistics20221103.Designer.cs generated

File diff suppressed because it is too large Load Diff

31
ErsatzTV.Infrastructure/Migrations/20221104021107_Reset_AllStatistics20221103.cs

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Reset_AllStatistics20221103 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// reset all statistics EXCEPT songs since they don't contain video streams
migrationBuilder.Sql("UPDATE MediaVersion SET DateUpdated = '0001-01-01 00:00:00' WHERE SongId IS NULL");
migrationBuilder.Sql("UPDATE LibraryFolder SET Etag = NULL WHERE Id IN (SELECT LF.Id FROM LibraryFolder LF INNER JOIN LibraryPath LP on LP.Id = LF.LibraryPathId INNER JOIN Library L on L.Id = LP.LibraryId WHERE MediaKind != 5)");
migrationBuilder.Sql("UPDATE EmbyMovie SET Etag = NULL");
migrationBuilder.Sql("UPDATE EmbyShow SET Etag = NULL");
migrationBuilder.Sql("UPDATE EmbySeason SET Etag = NULL");
migrationBuilder.Sql("UPDATE EmbyEpisode SET Etag = NULL");
migrationBuilder.Sql("UPDATE JellyfinMovie SET Etag = NULL");
migrationBuilder.Sql("UPDATE JellyfinShow SET Etag = NULL");
migrationBuilder.Sql("UPDATE JellyfinSeason SET Etag = NULL");
migrationBuilder.Sql("UPDATE JellyfinEpisode SET Etag = NULL");
migrationBuilder.Sql("UPDATE LibraryPath SET LastScan = '0001-01-01 00:00:00' WHERE Id IN (SELECT LP.Id FROM LibraryPath LP INNER JOIN Library L on L.Id = LP.LibraryId WHERE MediaKind != 5)");
migrationBuilder.Sql("UPDATE Library SET LastScan = '0001-01-01 00:00:00' WHERE MediaKind != 5");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

17
ErsatzTV.Infrastructure/Migrations/TvContextModelSnapshot.cs

@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -15,7 +15,7 @@ namespace ErsatzTV.Infrastructure.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.HasAnnotation("ProductVersion", "6.0.10");
modelBuilder.Entity("ErsatzTV.Core.Domain.Actor", b =>
{
@ -548,6 +548,9 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -548,6 +548,9 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<int>("AudioSampleRate")
.HasColumnType("INTEGER");
b.Property<int>("BitDepth")
.HasColumnType("INTEGER");
b.Property<bool?>("DeinterlaceVideo")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
@ -961,6 +964,18 @@ namespace ErsatzTV.Infrastructure.Migrations @@ -961,6 +964,18 @@ namespace ErsatzTV.Infrastructure.Migrations
b.Property<string>("Codec")
.HasColumnType("TEXT");
b.Property<string>("ColorPrimaries")
.HasColumnType("TEXT");
b.Property<string>("ColorRange")
.HasColumnType("TEXT");
b.Property<string>("ColorSpace")
.HasColumnType("TEXT");
b.Property<string>("ColorTransfer")
.HasColumnType("TEXT");
b.Property<bool>("Default")
.HasColumnType("INTEGER");

4
ErsatzTV.sln

@ -27,8 +27,8 @@ Global @@ -27,8 +27,8 @@ Global
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Release|Any CPU.Build.0 = Release|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.ActiveCfg = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug No Sync|Any CPU.Build.0 = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.ActiveCfg = Debug No Sync|Any CPU
{E83551AD-27E4-46E5-AD06-5B0DF797B8FF}.Debug|Any CPU.Build.0 = Debug No Sync|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C56FC23D-B863-401E-8E7C-E92BC307AFC1}.Release|Any CPU.ActiveCfg = Release|Any CPU

1
ErsatzTV.sln.DotSettings

@ -60,6 +60,7 @@ @@ -60,6 +60,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=setsar/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=showtitle/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=strm/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Tonemap/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Trakt/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=transcoded/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=tvdb/@EntryIndexedValue">True</s:Boolean>

5
ErsatzTV/Pages/FFmpegEditor.razor

@ -4,7 +4,6 @@ @@ -4,7 +4,6 @@
@using ErsatzTV.Application.Resolutions
@using ErsatzTV.Core.FFmpeg
@using ErsatzTV.Application.FFmpegProfiles
@using ErsatzTV.Infrastructure.Runtime
@using System.Runtime.InteropServices
@implements IDisposable
@inject NavigationManager _navigationManager
@ -48,6 +47,10 @@ @@ -48,6 +47,10 @@
<MudSelectItem Value="@FFmpegProfileVideoFormat.Hevc">hevc</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileVideoFormat.Mpeg2Video">mpeg-2</MudSelectItem>
</MudSelect>
<MudSelect Label="Bit Depth" @bind-Value="_model.BitDepth" For="@(() => _model.BitDepth)">
<MudSelectItem Value="@FFmpegProfileBitDepth.EightBit">8-bit</MudSelectItem>
<MudSelectItem Value="@FFmpegProfileBitDepth.TenBit">10-bit</MudSelectItem>
</MudSelect>
<MudElement HtmlTag="div" Class="mt-3">
<MudTextField Label="Bitrate" @bind-Value="_model.VideoBitrate" For="@(() => _model.VideoBitrate)" Adornment="Adornment.End" AdornmentText="kBit/s"/>
</MudElement>

4
ErsatzTV/ViewModels/FFmpegProfileEditViewModel.cs

@ -32,6 +32,7 @@ public class FFmpegProfileEditViewModel @@ -32,6 +32,7 @@ public class FFmpegProfileEditViewModel
VideoBitrate = viewModel.VideoBitrate;
VideoBufferSize = viewModel.VideoBufferSize;
VideoFormat = viewModel.VideoFormat;
BitDepth = viewModel.BitDepth;
}
public int AudioBitrate { get; set; }
@ -53,6 +54,7 @@ public class FFmpegProfileEditViewModel @@ -53,6 +54,7 @@ public class FFmpegProfileEditViewModel
public int VideoBitrate { get; set; }
public int VideoBufferSize { get; set; }
public FFmpegProfileVideoFormat VideoFormat { get; set; }
public FFmpegProfileBitDepth BitDepth { get; set; }
public CreateFFmpegProfile ToCreate() =>
new(
@ -64,6 +66,7 @@ public class FFmpegProfileEditViewModel @@ -64,6 +66,7 @@ public class FFmpegProfileEditViewModel
QsvExtraHardwareFrames,
Resolution.Id,
VideoFormat,
BitDepth,
VideoBitrate,
VideoBufferSize,
AudioFormat,
@ -87,6 +90,7 @@ public class FFmpegProfileEditViewModel @@ -87,6 +90,7 @@ public class FFmpegProfileEditViewModel
QsvExtraHardwareFrames,
Resolution.Id,
VideoFormat,
BitDepth,
VideoBitrate,
VideoBufferSize,
AudioFormat,

Loading…
Cancel
Save