mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* add qsv transcoding support * add basic nvenc support * add nvenc to docker-compose * add vaapi hardware acceleration * add vaapi driver to dockerfile * raise ffmpeg log level * lots of progress with nvenc, qsv and vaapi remain untested * qsv fixes * code cleanuppull/1213/head
29 changed files with 2321 additions and 80 deletions
@ -0,0 +1,344 @@ |
|||||||
|
using System; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.FFmpeg; |
||||||
|
using FluentAssertions; |
||||||
|
using LanguageExt; |
||||||
|
using NUnit.Framework; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.Tests.FFmpeg |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class FFmpegComplexFilterBuilderTests |
||||||
|
{ |
||||||
|
[TestFixture] |
||||||
|
public class Build |
||||||
|
{ |
||||||
|
[Test] |
||||||
|
public void Should_Return_None_With_No_Filters() |
||||||
|
{ |
||||||
|
var builder = new FFmpegComplexFilterBuilder(); |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsNone.Should().BeTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Should_Return_Audio_Filter_With_AudioDuration() |
||||||
|
{ |
||||||
|
var duration = TimeSpan.FromMinutes(54); |
||||||
|
FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() |
||||||
|
.WithAlignedAudio(duration); |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsSome.Should().BeTrue(); |
||||||
|
result.IfSome( |
||||||
|
filter => |
||||||
|
{ |
||||||
|
filter.ComplexFilter.Should().Be($"[0:a]apad=whole_dur={duration.TotalMilliseconds}ms[a]"); |
||||||
|
filter.AudioLabel.Should().Be("[a]"); |
||||||
|
filter.VideoLabel.Should().Be("0:v"); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
public void Should_Return_Audio_And_Video_Filter() |
||||||
|
{ |
||||||
|
var duration = TimeSpan.FromMinutes(54); |
||||||
|
FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() |
||||||
|
.WithAlignedAudio(duration) |
||||||
|
.WithDeinterlace(true); |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsSome.Should().BeTrue(); |
||||||
|
result.IfSome( |
||||||
|
filter => |
||||||
|
{ |
||||||
|
filter.ComplexFilter.Should().Be( |
||||||
|
$"[0:a]apad=whole_dur={duration.TotalMilliseconds}ms[a];[0:v]yadif=1[v]"); |
||||||
|
filter.AudioLabel.Should().Be("[a]"); |
||||||
|
filter.VideoLabel.Should().Be("[v]"); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[TestCase(true, false, false, "[0:v]yadif=1[v]", "[v]")]
|
||||||
|
[TestCase(true, true, false, "[0:v]yadif=1,scale=1920:1000:flags=fast_bilinear,setsar=1[v]", "[v]")]
|
||||||
|
[TestCase(true, false, true, "[0:v]yadif=1,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2[v]", "[v]")]
|
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]yadif=1,scale=1920:1000:flags=fast_bilinear,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase(false, true, false, "[0:v]scale=1920:1000:flags=fast_bilinear,setsar=1[v]", "[v]")]
|
||||||
|
[TestCase(false, false, true, "[0:v]setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2[v]", "[v]")]
|
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]scale=1920:1000:flags=fast_bilinear,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2[v]", |
||||||
|
"[v]")] |
||||||
|
public void Should_Return_Software_Video_Filter( |
||||||
|
bool deinterlace, |
||||||
|
bool scale, |
||||||
|
bool pad, |
||||||
|
string expectedVideoFilter, |
||||||
|
string expectedVideoLabel) |
||||||
|
{ |
||||||
|
FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() |
||||||
|
.WithDeinterlace(deinterlace); |
||||||
|
|
||||||
|
if (scale) |
||||||
|
{ |
||||||
|
builder = builder.WithScaling(new Resolution { Width = 1920, Height = 1000 }); |
||||||
|
} |
||||||
|
|
||||||
|
if (pad) |
||||||
|
{ |
||||||
|
builder = builder.WithBlackBars(new Resolution { Width = 1920, Height = 1080 }); |
||||||
|
} |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsSome.Should().BeTrue(); |
||||||
|
result.IfSome( |
||||||
|
filter => |
||||||
|
{ |
||||||
|
filter.ComplexFilter.Should().Be(expectedVideoFilter); |
||||||
|
filter.AudioLabel.Should().Be("0:a"); |
||||||
|
filter.VideoLabel.Should().Be(expectedVideoLabel); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[TestCase(true, false, false, "[0:v]deinterlace_qsv[v]", "[v]")]
|
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
false, |
||||||
|
"[0:v]deinterlace_qsv,scale_qsv=w=1920:h=1000,hwdownload,format=nv12,setsar=1,hwupload=extra_hw_frames=64[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
false, |
||||||
|
true, |
||||||
|
"[0:v]deinterlace_qsv,hwdownload,format=nv12,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload=extra_hw_frames=64[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]deinterlace_qsv,scale_qsv=w=1920:h=1000,hwdownload,format=nv12,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload=extra_hw_frames=64[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
true, |
||||||
|
false, |
||||||
|
"[0:v]scale_qsv=w=1920:h=1000,hwdownload,format=nv12,setsar=1,hwupload=extra_hw_frames=64[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
false, |
||||||
|
true, |
||||||
|
"[0:v]hwdownload,format=nv12,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload=extra_hw_frames=64[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]scale_qsv=w=1920:h=1000,hwdownload,format=nv12,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload=extra_hw_frames=64[v]", |
||||||
|
"[v]")] |
||||||
|
public void Should_Return_QSV_Video_Filter( |
||||||
|
bool deinterlace, |
||||||
|
bool scale, |
||||||
|
bool pad, |
||||||
|
string expectedVideoFilter, |
||||||
|
string expectedVideoLabel) |
||||||
|
{ |
||||||
|
FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() |
||||||
|
.WithHardwareAcceleration(HardwareAccelerationKind.Qsv) |
||||||
|
.WithDeinterlace(deinterlace); |
||||||
|
|
||||||
|
if (scale) |
||||||
|
{ |
||||||
|
builder = builder.WithScaling(new Resolution { Width = 1920, Height = 1000 }); |
||||||
|
} |
||||||
|
|
||||||
|
if (pad) |
||||||
|
{ |
||||||
|
builder = builder.WithBlackBars(new Resolution { Width = 1920, Height = 1080 }); |
||||||
|
} |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsSome.Should().BeTrue(); |
||||||
|
result.IfSome( |
||||||
|
filter => |
||||||
|
{ |
||||||
|
filter.ComplexFilter.Should().Be(expectedVideoFilter); |
||||||
|
filter.AudioLabel.Should().Be("0:a"); |
||||||
|
filter.VideoLabel.Should().Be(expectedVideoLabel); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
// TODO: get yadif_cuda working in docker
|
||||||
|
// [TestCase(true, false, false, "[0:v]yadif_cuda[v]", "[v]")]
|
||||||
|
// [TestCase(
|
||||||
|
// true,
|
||||||
|
// true,
|
||||||
|
// false,
|
||||||
|
// "[0:v]yadif_cuda,scale_npp=1920:1000:format=yuv420p,hwdownload,setsar=1,hwupload[v]",
|
||||||
|
// "[v]")]
|
||||||
|
// [TestCase(
|
||||||
|
// true,
|
||||||
|
// false,
|
||||||
|
// true,
|
||||||
|
// "[0:v]yadif_cuda,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]",
|
||||||
|
// "[v]")]
|
||||||
|
// [TestCase(
|
||||||
|
// true,
|
||||||
|
// true,
|
||||||
|
// true,
|
||||||
|
// "[0:v]yadif_cuda,scale_npp=1920:1000:format=yuv420p,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]",
|
||||||
|
// "[v]")]
|
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
false, |
||||||
|
"[0:v]scale_npp=1920:1000:format=yuv420p,hwdownload,setsar=1,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
false, |
||||||
|
true, |
||||||
|
"[0:v]hwdownload,format=nv12,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]scale_npp=1920:1000:format=yuv420p,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
true, |
||||||
|
false, |
||||||
|
"[0:v]scale_npp=1920:1000:format=yuv420p,hwdownload,setsar=1,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
false, |
||||||
|
true, |
||||||
|
"[0:v]hwdownload,format=nv12,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]scale_npp=1920:1000:format=yuv420p,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
public void Should_Return_NVENC_Video_Filter( |
||||||
|
bool deinterlace, |
||||||
|
bool scale, |
||||||
|
bool pad, |
||||||
|
string expectedVideoFilter, |
||||||
|
string expectedVideoLabel) |
||||||
|
{ |
||||||
|
FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() |
||||||
|
.WithHardwareAcceleration(HardwareAccelerationKind.Nvenc) |
||||||
|
.WithDeinterlace(deinterlace); |
||||||
|
|
||||||
|
if (scale) |
||||||
|
{ |
||||||
|
builder = builder.WithScaling(new Resolution { Width = 1920, Height = 1000 }); |
||||||
|
} |
||||||
|
|
||||||
|
if (pad) |
||||||
|
{ |
||||||
|
builder = builder.WithBlackBars(new Resolution { Width = 1920, Height = 1080 }); |
||||||
|
} |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsSome.Should().BeTrue(); |
||||||
|
result.IfSome( |
||||||
|
filter => |
||||||
|
{ |
||||||
|
filter.ComplexFilter.Should().Be(expectedVideoFilter); |
||||||
|
filter.AudioLabel.Should().Be("0:a"); |
||||||
|
filter.VideoLabel.Should().Be(expectedVideoLabel); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
[Test] |
||||||
|
[TestCase(true, false, false, "[0:v]deinterlace_vaapi[v]", "[v]")]
|
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
false, |
||||||
|
"[0:v]deinterlace_vaapi,scale_vaapi=w=1920:h=1000,hwdownload,setsar=1,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
false, |
||||||
|
true, |
||||||
|
"[0:v]deinterlace_vaapi,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
true, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]deinterlace_vaapi,scale_vaapi=w=1920:h=1000,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase(false, true, false, "[0:v]scale_vaapi=w=1920:h=1000,hwdownload,setsar=1,hwupload[v]", "[v]")]
|
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
false, |
||||||
|
true, |
||||||
|
"[0:v]hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
[TestCase( |
||||||
|
false, |
||||||
|
true, |
||||||
|
true, |
||||||
|
"[0:v]scale_vaapi=w=1920:h=1000,hwdownload,setsar=1,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,hwupload[v]", |
||||||
|
"[v]")] |
||||||
|
public void Should_Return_VAAPI_Video_Filter( |
||||||
|
bool deinterlace, |
||||||
|
bool scale, |
||||||
|
bool pad, |
||||||
|
string expectedVideoFilter, |
||||||
|
string expectedVideoLabel) |
||||||
|
{ |
||||||
|
FFmpegComplexFilterBuilder builder = new FFmpegComplexFilterBuilder() |
||||||
|
.WithHardwareAcceleration(HardwareAccelerationKind.Vaapi) |
||||||
|
.WithDeinterlace(deinterlace); |
||||||
|
|
||||||
|
if (scale) |
||||||
|
{ |
||||||
|
builder = builder.WithScaling(new Resolution { Width = 1920, Height = 1000 }); |
||||||
|
} |
||||||
|
|
||||||
|
if (pad) |
||||||
|
{ |
||||||
|
builder = builder.WithBlackBars(new Resolution { Width = 1920, Height = 1080 }); |
||||||
|
} |
||||||
|
|
||||||
|
Option<FFmpegComplexFilter> result = builder.Build(); |
||||||
|
|
||||||
|
result.IsSome.Should().BeTrue(); |
||||||
|
result.IfSome( |
||||||
|
filter => |
||||||
|
{ |
||||||
|
filter.ComplexFilter.Should().Be(expectedVideoFilter); |
||||||
|
filter.AudioLabel.Should().Be("0:a"); |
||||||
|
filter.VideoLabel.Should().Be(expectedVideoLabel); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,10 @@ |
|||||||
|
namespace ErsatzTV.Core.Domain |
||||||
|
{ |
||||||
|
public enum HardwareAccelerationKind |
||||||
|
{ |
||||||
|
None = 0, |
||||||
|
Qsv = 1, |
||||||
|
Nvenc = 2, |
||||||
|
Vaapi = 3 |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
namespace ErsatzTV.Core.FFmpeg |
||||||
|
{ |
||||||
|
public record FFmpegComplexFilter(string ComplexFilter, string VideoLabel, string AudioLabel); |
||||||
|
} |
||||||
@ -0,0 +1,146 @@ |
|||||||
|
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using System.Text; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.FFmpeg; |
||||||
|
using LanguageExt; |
||||||
|
using static LanguageExt.Prelude; |
||||||
|
|
||||||
|
namespace ErsatzTV.Core.FFmpeg |
||||||
|
{ |
||||||
|
public class FFmpegComplexFilterBuilder |
||||||
|
{ |
||||||
|
private Option<TimeSpan> _audioDuration = None; |
||||||
|
private bool _deinterlace; |
||||||
|
private Option<HardwareAccelerationKind> _hardwareAccelerationKind = None; |
||||||
|
private Option<IDisplaySize> _padToSize = None; |
||||||
|
private Option<IDisplaySize> _scaleToSize = None; |
||||||
|
|
||||||
|
public FFmpegComplexFilterBuilder WithHardwareAcceleration(HardwareAccelerationKind hardwareAccelerationKind) |
||||||
|
{ |
||||||
|
_hardwareAccelerationKind = Some(hardwareAccelerationKind); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public FFmpegComplexFilterBuilder WithScaling(IDisplaySize scaleToSize) |
||||||
|
{ |
||||||
|
_scaleToSize = Some(scaleToSize); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public FFmpegComplexFilterBuilder WithBlackBars(IDisplaySize padToSize) |
||||||
|
{ |
||||||
|
_padToSize = Some(padToSize); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public FFmpegComplexFilterBuilder WithDeinterlace(bool deinterlace) |
||||||
|
{ |
||||||
|
_deinterlace = deinterlace; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public FFmpegComplexFilterBuilder WithAlignedAudio(Option<TimeSpan> audioDuration) |
||||||
|
{ |
||||||
|
_audioDuration = audioDuration; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
public Option<FFmpegComplexFilter> Build() |
||||||
|
{ |
||||||
|
var complexFilter = new StringBuilder(); |
||||||
|
|
||||||
|
var videoLabel = "0:v"; |
||||||
|
var audioLabel = "0:a"; |
||||||
|
|
||||||
|
HardwareAccelerationKind acceleration = _hardwareAccelerationKind.IfNone(HardwareAccelerationKind.None); |
||||||
|
|
||||||
|
_audioDuration.IfSome( |
||||||
|
audioDuration => |
||||||
|
{ |
||||||
|
complexFilter.Append($"[{audioLabel}]"); |
||||||
|
complexFilter.Append($"apad=whole_dur={audioDuration.TotalMilliseconds}ms"); |
||||||
|
audioLabel = "[a]"; |
||||||
|
complexFilter.Append(audioLabel); |
||||||
|
}); |
||||||
|
|
||||||
|
var filterQueue = new List<string>(); |
||||||
|
|
||||||
|
if (_deinterlace) |
||||||
|
{ |
||||||
|
string filter = acceleration switch |
||||||
|
{ |
||||||
|
HardwareAccelerationKind.Qsv => "deinterlace_qsv", |
||||||
|
HardwareAccelerationKind.Nvenc => "", // TODO: yadif_cuda support in docker
|
||||||
|
HardwareAccelerationKind.Vaapi => "deinterlace_vaapi", |
||||||
|
_ => "yadif=1" |
||||||
|
}; |
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter)) |
||||||
|
{ |
||||||
|
filterQueue.Add(filter); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
_scaleToSize.IfSome( |
||||||
|
size => |
||||||
|
{ |
||||||
|
string filter = acceleration switch |
||||||
|
{ |
||||||
|
HardwareAccelerationKind.Qsv => $"scale_qsv=w={size.Width}:h={size.Height}", |
||||||
|
HardwareAccelerationKind.Nvenc => $"scale_npp={size.Width}:{size.Height}:format=yuv420p", |
||||||
|
HardwareAccelerationKind.Vaapi => $"scale_vaapi=w={size.Width}:h={size.Height}", |
||||||
|
_ => $"scale={size.Width}:{size.Height}:flags=fast_bilinear" |
||||||
|
}; |
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(filter)) |
||||||
|
{ |
||||||
|
filterQueue.Add(filter); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
if (_scaleToSize.IsSome || _padToSize.IsSome) |
||||||
|
{ |
||||||
|
if (acceleration != HardwareAccelerationKind.None) |
||||||
|
{ |
||||||
|
filterQueue.Add("hwdownload"); |
||||||
|
if (_scaleToSize.IsNone && acceleration == HardwareAccelerationKind.Nvenc || |
||||||
|
acceleration == HardwareAccelerationKind.Qsv) |
||||||
|
{ |
||||||
|
filterQueue.Add("format=nv12"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
filterQueue.Add("setsar=1"); |
||||||
|
} |
||||||
|
|
||||||
|
_padToSize.IfSome(size => filterQueue.Add($"pad={size.Width}:{size.Height}:(ow-iw)/2:(oh-ih)/2")); |
||||||
|
|
||||||
|
if ((_scaleToSize.IsSome || _padToSize.IsSome) && acceleration != HardwareAccelerationKind.None) |
||||||
|
{ |
||||||
|
filterQueue.Add( |
||||||
|
acceleration == HardwareAccelerationKind.Qsv ? "hwupload=extra_hw_frames=64" : "hwupload"); |
||||||
|
} |
||||||
|
|
||||||
|
if (filterQueue.Any()) |
||||||
|
{ |
||||||
|
// TODO: any audio filter
|
||||||
|
if (_audioDuration.IsSome) |
||||||
|
{ |
||||||
|
complexFilter.Append(";"); |
||||||
|
} |
||||||
|
|
||||||
|
complexFilter.Append($"[{videoLabel}]"); |
||||||
|
complexFilter.Append(string.Join(",", filterQueue)); |
||||||
|
videoLabel = "[v]"; |
||||||
|
complexFilter.Append(videoLabel); |
||||||
|
} |
||||||
|
|
||||||
|
var filterResult = complexFilter.ToString(); |
||||||
|
return string.IsNullOrWhiteSpace(filterResult) |
||||||
|
? Option<FFmpegComplexFilter>.None |
||||||
|
: new FFmpegComplexFilter(filterResult, videoLabel, audioLabel); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@ |
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations |
||||||
|
{ |
||||||
|
public partial class Add_FFmpegProfileHardwareAcceleration : Migration |
||||||
|
{ |
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) => |
||||||
|
migrationBuilder.AddColumn<int>( |
||||||
|
"HardwareAcceleration", |
||||||
|
"FFmpegProfile", |
||||||
|
"INTEGER", |
||||||
|
nullable: false, |
||||||
|
defaultValue: 0); |
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) => |
||||||
|
migrationBuilder.DropColumn( |
||||||
|
"HardwareAcceleration", |
||||||
|
"FFmpegProfile"); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal-amd64 AS dotnet-runtime |
||||||
|
|
||||||
|
FROM jrottenberg/ffmpeg:4.3-ubuntu2004 AS runtime-base |
||||||
|
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet |
||||||
|
RUN apt-get update && apt-get install -y libicu-dev |
||||||
|
|
||||||
|
# https://hub.docker.com/_/microsoft-dotnet |
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build |
||||||
|
RUN apt-get update && apt-get install -y ca-certificates |
||||||
|
WORKDIR /source |
||||||
|
|
||||||
|
# copy csproj and restore as distinct layers |
||||||
|
COPY *.sln . |
||||||
|
COPY ErsatzTV/*.csproj ./ErsatzTV/ |
||||||
|
COPY generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/*.csproj ./generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/ |
||||||
|
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/ |
||||||
|
COPY ErsatzTV.CommandLine/*.csproj ./ErsatzTV.CommandLine/ |
||||||
|
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/ |
||||||
|
COPY ErsatzTV.Core.Tests/*.csproj ./ErsatzTV.Core.Tests/ |
||||||
|
COPY ErsatzTV.Infrastructure/*.csproj ./ErsatzTV.Infrastructure/ |
||||||
|
RUN dotnet restore -r linux-x64 |
||||||
|
|
||||||
|
# copy everything else and build app |
||||||
|
COPY ErsatzTV/. ./ErsatzTV/ |
||||||
|
COPY generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/. ./generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/ |
||||||
|
COPY ErsatzTV.Application/. ./ErsatzTV.Application/ |
||||||
|
COPY ErsatzTV.CommandLine/. ./ErsatzTV.CommandLine/ |
||||||
|
COPY ErsatzTV.Core/. ./ErsatzTV.Core/ |
||||||
|
COPY ErsatzTV.Core.Tests/. ./ErsatzTV.Core.Tests/ |
||||||
|
COPY ErsatzTV.Infrastructure/. ./ErsatzTV.Infrastructure/ |
||||||
|
WORKDIR /source/ErsatzTV |
||||||
|
ARG INFO_VERSION="unknown" |
||||||
|
RUN dotnet publish -c release -o /app -r linux-x64 --self-contained false --no-restore /p:InformationalVersion=${INFO_VERSION} |
||||||
|
|
||||||
|
# final stage/image |
||||||
|
FROM runtime-base |
||||||
|
WORKDIR /app |
||||||
|
EXPOSE 8409 |
||||||
|
COPY --from=build /app ./ |
||||||
|
ENTRYPOINT ["./ErsatzTV"] |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
version: "3.1" |
||||||
|
|
||||||
|
services: |
||||||
|
ersatztv: |
||||||
|
build: |
||||||
|
context: .. |
||||||
|
dockerfile: docker/nvidia/Dockerfile |
||||||
|
environment: |
||||||
|
NVIDIA_VISIBLE_DEVICES: all |
||||||
|
NVIDIA_DRIVER_CAPABILITIES: compute,utility,video |
||||||
|
deploy: |
||||||
|
resources: |
||||||
|
reservations: |
||||||
|
devices: |
||||||
|
- capabilities: [ gpu ] |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
version: "3.1" |
||||||
|
|
||||||
|
services: |
||||||
|
ersatztv: |
||||||
|
build: |
||||||
|
dockerfile: docker/vaapi/Dockerfile |
||||||
|
devices: |
||||||
|
- /dev/dri/renderD128:/dev/dri/renderD128 |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal-amd64 AS dotnet-runtime |
||||||
|
|
||||||
|
FROM jrottenberg/ffmpeg:4.3-nvidia1804 AS runtime-base |
||||||
|
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet |
||||||
|
RUN apt-get update && apt-get install -y libicu-dev |
||||||
|
|
||||||
|
# https://hub.docker.com/_/microsoft-dotnet |
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build |
||||||
|
RUN apt-get update && apt-get install -y ca-certificates |
||||||
|
WORKDIR /source |
||||||
|
|
||||||
|
# copy csproj and restore as distinct layers |
||||||
|
COPY *.sln . |
||||||
|
COPY ErsatzTV/*.csproj ./ErsatzTV/ |
||||||
|
COPY generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/*.csproj ./generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/ |
||||||
|
COPY ErsatzTV.Application/*.csproj ./ErsatzTV.Application/ |
||||||
|
COPY ErsatzTV.CommandLine/*.csproj ./ErsatzTV.CommandLine/ |
||||||
|
COPY ErsatzTV.Core/*.csproj ./ErsatzTV.Core/ |
||||||
|
COPY ErsatzTV.Core.Tests/*.csproj ./ErsatzTV.Core.Tests/ |
||||||
|
COPY ErsatzTV.Infrastructure/*.csproj ./ErsatzTV.Infrastructure/ |
||||||
|
RUN dotnet restore -r linux-x64 |
||||||
|
|
||||||
|
# copy everything else and build app |
||||||
|
COPY ErsatzTV/. ./ErsatzTV/ |
||||||
|
COPY generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/. ./generated/ErsatzTV.Api.Sdk/src/ErsatzTV.Api.Sdk/ |
||||||
|
COPY ErsatzTV.Application/. ./ErsatzTV.Application/ |
||||||
|
COPY ErsatzTV.CommandLine/. ./ErsatzTV.CommandLine/ |
||||||
|
COPY ErsatzTV.Core/. ./ErsatzTV.Core/ |
||||||
|
COPY ErsatzTV.Core.Tests/. ./ErsatzTV.Core.Tests/ |
||||||
|
COPY ErsatzTV.Infrastructure/. ./ErsatzTV.Infrastructure/ |
||||||
|
WORKDIR /source/ErsatzTV |
||||||
|
ARG INFO_VERSION="unknown" |
||||||
|
RUN dotnet publish -c release -o /app -r linux-x64 --self-contained false --no-restore /p:InformationalVersion=${INFO_VERSION} |
||||||
|
|
||||||
|
# final stage/image |
||||||
|
FROM runtime-base |
||||||
|
WORKDIR /app |
||||||
|
EXPOSE 8409 |
||||||
|
COPY --from=build /app ./ |
||||||
|
ENTRYPOINT ["./ErsatzTV"] |
||||||
@ -1,5 +1,8 @@ |
|||||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal-amd64 AS runtime-base |
FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal-amd64 AS dotnet-runtime |
||||||
RUN apt-get update && apt-get install -y ffmpeg |
|
||||||
|
FROM jrottenberg/ffmpeg:4.3-vaapi1804 AS runtime-base |
||||||
|
COPY --from=dotnet-runtime /usr/share/dotnet /usr/share/dotnet |
||||||
|
RUN apt-get update && apt-get install -y libicu-dev |
||||||
|
|
||||||
# https://hub.docker.com/_/microsoft-dotnet |
# https://hub.docker.com/_/microsoft-dotnet |
||||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build |
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build |
||||||
Loading…
Reference in new issue