Browse Source

ffmpeg tweaks (#121)

* save reports from ffmpeg concat process

* let ffmpeg determine thread count by default

* disable stdin for ffmpeg processes
pull/122/head
Jason Dove 5 years ago committed by GitHub
parent
commit
90f775aab4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs
  2. 2
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegProfileHandler.cs
  3. 28
      ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs
  4. 2
      ErsatzTV.CommandLine/Commands/FFmpegProfileCommand.cs
  5. 2
      ErsatzTV.Core/Domain/FFmpegProfile.cs
  6. 10
      ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs
  7. 4
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  8. 1829
      ErsatzTV.Infrastructure/Migrations/20210331010449_Update_FFmpegProfile_ThreadCount.Designer.cs
  9. 14
      ErsatzTV.Infrastructure/Migrations/20210331010449_Update_FFmpegProfile_ThreadCount.cs
  10. 3
      ErsatzTV/Controllers/IptvController.cs
  11. 2
      ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

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

@ -63,7 +63,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
.Bind(_ => createFFmpegProfile.NotLongerThan(50)(x => x.Name)); .Bind(_ => createFFmpegProfile.NotLongerThan(50)(x => x.Name));
private Validation<BaseError, int> ValidateThreadCount(CreateFFmpegProfile createFFmpegProfile) => private Validation<BaseError, int> ValidateThreadCount(CreateFFmpegProfile createFFmpegProfile) =>
createFFmpegProfile.AtLeast(1)(p => p.ThreadCount); createFFmpegProfile.AtLeast(0)(p => p.ThreadCount);
private async Task<Validation<BaseError, int>> ResolutionMustExist(CreateFFmpegProfile createFFmpegProfile) => private async Task<Validation<BaseError, int>> ResolutionMustExist(CreateFFmpegProfile createFFmpegProfile) =>
(await _resolutionRepository.Get(createFFmpegProfile.ResolutionId)) (await _resolutionRepository.Get(createFFmpegProfile.ResolutionId))

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

@ -69,7 +69,7 @@ namespace ErsatzTV.Application.FFmpegProfiles.Commands
.Bind(_ => updateFFmpegProfile.NotLongerThan(50)(x => x.Name)); .Bind(_ => updateFFmpegProfile.NotLongerThan(50)(x => x.Name));
private Validation<BaseError, int> ValidateThreadCount(UpdateFFmpegProfile updateFFmpegProfile) => private Validation<BaseError, int> ValidateThreadCount(UpdateFFmpegProfile updateFFmpegProfile) =>
updateFFmpegProfile.AtLeast(1)(p => p.ThreadCount); updateFFmpegProfile.AtLeast(0)(p => p.ThreadCount);
private async Task<Validation<BaseError, int>> ResolutionMustExist(UpdateFFmpegProfile updateFFmpegProfile) => private async Task<Validation<BaseError, int>> ResolutionMustExist(UpdateFFmpegProfile updateFFmpegProfile) =>
(await _resolutionRepository.Get(updateFFmpegProfile.ResolutionId)) (await _resolutionRepository.Get(updateFFmpegProfile.ResolutionId))

28
ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs

@ -5,30 +5,38 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using LanguageExt; using LanguageExt;
using static LanguageExt.Prelude;
namespace ErsatzTV.Application.Streaming.Queries namespace ErsatzTV.Application.Streaming.Queries
{ {
public class GetConcatProcessByChannelNumberHandler : FFmpegProcessHandler<GetConcatProcessByChannelNumber> public class GetConcatProcessByChannelNumberHandler : FFmpegProcessHandler<GetConcatProcessByChannelNumber>
{ {
private readonly IConfigElementRepository _configElementRepository;
private readonly FFmpegProcessService _ffmpegProcessService; private readonly FFmpegProcessService _ffmpegProcessService;
public GetConcatProcessByChannelNumberHandler( public GetConcatProcessByChannelNumberHandler(
IChannelRepository channelRepository, IChannelRepository channelRepository,
IConfigElementRepository configElementRepository, IConfigElementRepository configElementRepository,
FFmpegProcessService ffmpegProcessService) FFmpegProcessService ffmpegProcessService)
: base(channelRepository, configElementRepository) => : base(channelRepository, configElementRepository)
{
_configElementRepository = configElementRepository;
_ffmpegProcessService = ffmpegProcessService; _ffmpegProcessService = ffmpegProcessService;
}
protected override Task<Either<BaseError, Process>> GetProcess( protected override async Task<Either<BaseError, Process>> GetProcess(
GetConcatProcessByChannelNumber request, GetConcatProcessByChannelNumber request,
Channel channel, Channel channel,
string ffmpegPath) => string ffmpegPath)
Right<BaseError, Process>( {
_ffmpegProcessService.ConcatChannel( bool saveReports = await _configElementRepository.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
ffmpegPath, .Map(result => result.IfNone(false));
channel,
request.Scheme, return _ffmpegProcessService.ConcatChannel(
request.Host)).AsTask(); ffmpegPath,
saveReports,
channel,
request.Scheme,
request.Host);
}
} }
} }

2
ErsatzTV.CommandLine/Commands/FFmpegProfileCommand.cs

@ -28,7 +28,7 @@ namespace ErsatzTV.CommandLine.Commands
public string Name { get; set; } public string Name { get; set; }
[CommandOption("thread-count", Description = "The number of threads")] [CommandOption("thread-count", Description = "The number of threads")]
public int ThreadCount { get; set; } = 4; public int ThreadCount { get; set; } = 0;
[CommandOption("transcode", Description = "Whether to transcode all media")] [CommandOption("transcode", Description = "Whether to transcode all media")]
public bool Transcode { get; set; } = true; public bool Transcode { get; set; } = true;

2
ErsatzTV.Core/Domain/FFmpegProfile.cs

@ -27,7 +27,7 @@
new() new()
{ {
Name = name, Name = name,
ThreadCount = 4, ThreadCount = 0,
Transcode = true, Transcode = true,
ResolutionId = resolution.Id, ResolutionId = resolution.Id,
Resolution = resolution, Resolution = resolution,

10
ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs

@ -42,6 +42,7 @@ namespace ErsatzTV.Core.FFmpeg
private readonly string _ffmpegPath; private readonly string _ffmpegPath;
private readonly bool _saveReports; private readonly bool _saveReports;
private FFmpegComplexFilterBuilder _complexFilterBuilder = new(); private FFmpegComplexFilterBuilder _complexFilterBuilder = new();
private bool _isConcat;
public FFmpegProcessBuilder(string ffmpegPath, bool saveReports) public FFmpegProcessBuilder(string ffmpegPath, bool saveReports)
{ {
@ -186,6 +187,8 @@ namespace ErsatzTV.Core.FFmpeg
public FFmpegProcessBuilder WithConcat(string concatPlaylist) public FFmpegProcessBuilder WithConcat(string concatPlaylist)
{ {
_isConcat = true;
var arguments = new List<string> var arguments = new List<string>
{ {
"-f", "concat", "-f", "concat",
@ -193,8 +196,6 @@ namespace ErsatzTV.Core.FFmpeg
"-protocol_whitelist", "file,http,tcp,https,tcp,tls", "-protocol_whitelist", "file,http,tcp,https,tcp,tls",
"-probesize", "32", "-probesize", "32",
"-i", concatPlaylist, "-i", concatPlaylist,
"-map", "0:v",
"-map", "0:a",
"-c", "copy", "-c", "copy",
"-muxdelay", "0", "-muxdelay", "0",
"-muxpreload", "0" "-muxpreload", "0"
@ -373,10 +374,13 @@ namespace ErsatzTV.Core.FFmpeg
if (_saveReports) if (_saveReports)
{ {
string fileName = Path.Combine(FileSystemLayout.FFmpegReportsFolder, "%p-%t.log"); string fileName = _isConcat
? Path.Combine(FileSystemLayout.FFmpegReportsFolder, "ffmpeg-%t-concat.log")
: Path.Combine(FileSystemLayout.FFmpegReportsFolder, "ffmpeg-%t-transcode.log");
startInfo.EnvironmentVariables.Add("FFREPORT", $"file={fileName}:level=32"); startInfo.EnvironmentVariables.Add("FFREPORT", $"file={fileName}:level=32");
} }
startInfo.ArgumentList.Add("-nostdin");
foreach (string argument in _arguments) foreach (string argument in _arguments)
{ {
startInfo.ArgumentList.Add(argument); startInfo.ArgumentList.Add(argument);

4
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -124,11 +124,11 @@ namespace ErsatzTV.Core.FFmpeg
return builder.WithPipe().Build(); return builder.WithPipe().Build();
} }
public Process ConcatChannel(string ffmpegPath, Channel channel, string scheme, string host) public Process ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
{ {
FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.ConcatSettings; FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.ConcatSettings;
return new FFmpegProcessBuilder(ffmpegPath, false) return new FFmpegProcessBuilder(ffmpegPath, saveReports)
.WithThreads(1) .WithThreads(1)
.WithQuiet() .WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags) .WithFormatFlags(playbackSettings.FormatFlags)

1829
ErsatzTV.Infrastructure/Migrations/20210331010449_Update_FFmpegProfile_ThreadCount.Designer.cs generated

File diff suppressed because it is too large Load Diff

14
ErsatzTV.Infrastructure/Migrations/20210331010449_Update_FFmpegProfile_ThreadCount.cs

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace ErsatzTV.Infrastructure.Migrations
{
public partial class Update_FFmpegProfile_ThreadCount : Migration
{
protected override void Up(MigrationBuilder migrationBuilder) =>
migrationBuilder.Sql(@"UPDATE FFmpegProfile SET ThreadCount = 0 WHERE ThreadCount = 4");
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

3
ErsatzTV/Controllers/IptvController.cs

@ -44,6 +44,9 @@ namespace ErsatzTV.Controllers
process => process =>
{ {
_logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber); _logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber);
// _logger.LogDebug(
// "ffmpeg concat arguments {FFmpegArguments}",
// string.Join(" ", process.StartInfo.ArgumentList));
process.Start(); process.Start();
return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t"); return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t");
}, },

2
ErsatzTV/Validators/FFmpegProfileEditViewModelValidator.cs

@ -14,7 +14,7 @@ namespace ErsatzTV.Validators
public FFmpegProfileEditViewModelValidator() public FFmpegProfileEditViewModelValidator()
{ {
RuleFor(x => x.Name).NotEmpty(); RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.ThreadCount).GreaterThan(0); RuleFor(x => x.ThreadCount).GreaterThanOrEqualTo(0);
When( When(
x => x.Transcode, x => x.Transcode,

Loading…
Cancel
Save