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 @@
@@ -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 @@
@@ -0,0 +1,10 @@
|
||||
namespace ErsatzTV.Core.Domain |
||||
{ |
||||
public enum HardwareAccelerationKind |
||||
{ |
||||
None = 0, |
||||
Qsv = 1, |
||||
Nvenc = 2, |
||||
Vaapi = 3 |
||||
} |
||||
} |
||||
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Core.FFmpeg |
||||
{ |
||||
public record FFmpegComplexFilter(string ComplexFilter, string VideoLabel, string AudioLabel); |
||||
} |
||||
@ -0,0 +1,146 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,5 +1,8 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal-amd64 AS runtime-base |
||||
RUN apt-get update && apt-get install -y ffmpeg |
||||
FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal-amd64 AS dotnet-runtime |
||||
|
||||
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 |
||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build |
||||
Loading…
Reference in new issue