Browse Source

fix cliwrap usage (#737)

pull/738/head
Jason Dove 3 years ago committed by GitHub
parent
commit
7e55681916
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 10
      ErsatzTV.Application/Images/Queries/GetCachedImagePathHandler.cs
  3. 22
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  4. 7
      ErsatzTV.Application/Streaming/PlayoutItemProcessModel.cs
  5. 4
      ErsatzTV.Application/Streaming/Queries/GetConcatProcessByChannelNumberHandler.cs
  6. 4
      ErsatzTV.Application/Streaming/Queries/GetErrorProcessHandler.cs
  7. 10
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  8. 4
      ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs
  9. 26
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  10. 54
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  11. 58
      ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs
  12. 16
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  13. 17
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  14. 5
      ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs
  15. 25
      ErsatzTV/Controllers/InternalController.cs
  16. 28
      ErsatzTV/Controllers/IptvController.cs

2
CHANGELOG.md

@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Only select picture subtitles (text subtitles are not yet supported)
- Supported picture subtitles are `hdmv_pgs_subtitle` and `dvd_subtitle`
- Fix subtitles using software encoders, videotoolbox, VAAPI
- Fix setting VAAPI driver name
- Fix ffmpeg troubleshooting reports
## [0.5.0-beta] - 2022-04-13
### Fixed

10
ErsatzTV.Application/Images/Queries/GetCachedImagePathHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using System.Diagnostics;
using CliWrap;
using CliWrap;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -70,16 +69,13 @@ public class @@ -70,16 +69,13 @@ public class
string originalPath = _imageCache.GetPathForImage(request.FileName, request.ArtworkKind, None);
Process process = _ffmpegProcessService.ResizeImage(
Command process = _ffmpegProcessService.ResizeImage(
ffmpegPath,
originalPath,
withExtension,
request.MaxHeight.Value);
CommandResult resize = await Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.ExecuteAsync();
CommandResult resize = await process.ExecuteAsync();
if (resize.ExitCode != 0)
{

22
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Text;
using System.Timers;
using Bugsnag;
using CliWrap;
@ -207,18 +207,15 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -207,18 +207,15 @@ public class HlsSessionWorker : IHlsSessionWorker
{
await TrimAndDelete(cancellationToken);
using Process process = processModel.Process;
Command process = processModel.Process;
_logger.LogInformation(
"ffmpeg hls arguments {FFmpegArguments}",
string.Join(" ", process.StartInfo.ArgumentList));
_logger.LogInformation("ffmpeg hls arguments {FFmpegArguments}", process.Arguments);
try
{
BufferedCommandResult commandResult = await Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
BufferedCommandResult commandResult = await process
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(cancellationToken);
.ExecuteBufferedAsync(Encoding.UTF8, cancellationToken);
if (commandResult.ExitCode == 0)
{
@ -256,16 +253,15 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -256,16 +253,15 @@ public class HlsSessionWorker : IHlsSessionWorker
foreach (PlayoutItemProcessModel errorProcessModel in maybeOfflineProcess.RightAsEnumerable())
{
Process errorProcess = errorProcessModel.Process;
Command errorProcess = errorProcessModel.Process;
_logger.LogInformation(
"ffmpeg hls error arguments {FFmpegArguments}",
string.Join(" ", errorProcess.StartInfo.ArgumentList));
errorProcess.Arguments);
commandResult = await Cli.Wrap(errorProcess.StartInfo.FileName)
.WithArguments(errorProcess.StartInfo.ArgumentList)
commandResult = await errorProcess
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(cancellationToken);
.ExecuteBufferedAsync(Encoding.UTF8, cancellationToken);
if (commandResult.ExitCode == 0)
{

7
ErsatzTV.Application/Streaming/PlayoutItemProcessModel.cs

@ -1,5 +1,8 @@ @@ -1,5 +1,8 @@
using System.Diagnostics;
using CliWrap;
namespace ErsatzTV.Application.Streaming;
public record PlayoutItemProcessModel(Process Process, Option<TimeSpan> MaybeDuration, DateTimeOffset Until);
public record PlayoutItemProcessModel(
Command Process,
Option<TimeSpan> MaybeDuration,
DateTimeOffset Until);

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

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -32,7 +32,7 @@ public class GetConcatProcessByChannelNumberHandler : FFmpegProcessHandler<GetCo @@ -32,7 +32,7 @@ public class GetConcatProcessByChannelNumberHandler : FFmpegProcessHandler<GetCo
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
Process process = _ffmpegProcessService.ConcatChannel(
Command process = _ffmpegProcessService.ConcatChannel(
ffmpegPath,
saveReports,
channel,

4
ErsatzTV.Application/Streaming/Queries/GetErrorProcessHandler.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -27,7 +27,7 @@ public class GetErrorProcessHandler : FFmpegProcessHandler<GetErrorProcess> @@ -27,7 +27,7 @@ public class GetErrorProcessHandler : FFmpegProcessHandler<GetErrorProcess>
string ffprobePath,
CancellationToken cancellationToken)
{
Process process = await _ffmpegProcessService.ForError(
Command process = await _ffmpegProcessService.ForError(
ffmpegPath,
channel,
request.MaybeDuration,

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

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
@ -139,7 +139,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -139,7 +139,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
Process process = await _ffmpegProcessService.ForPlayoutItem(
Command process = await _ffmpegProcessService.ForPlayoutItem(
ffmpegPath,
ffprobePath,
saveReports,
@ -185,7 +185,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -185,7 +185,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
switch (error)
{
case UnableToLocatePlayoutItem:
Process offlineProcess = await _ffmpegProcessService.ForError(
Command offlineProcess = await _ffmpegProcessService.ForError(
ffmpegPath,
channel,
maybeDuration,
@ -195,7 +195,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -195,7 +195,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
return new PlayoutItemProcessModel(offlineProcess, maybeDuration, finish);
case PlayoutItemDoesNotExistOnDisk:
Process doesNotExistProcess = await _ffmpegProcessService.ForError(
Command doesNotExistProcess = await _ffmpegProcessService.ForError(
ffmpegPath,
channel,
maybeDuration,
@ -205,7 +205,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -205,7 +205,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
return new PlayoutItemProcessModel(doesNotExistProcess, maybeDuration, finish);
default:
Process errorProcess = await _ffmpegProcessService.ForError(
Command errorProcess = await _ffmpegProcessService.ForError(
ffmpegPath,
channel,
maybeDuration,

4
ErsatzTV.Application/Streaming/Queries/GetWrappedProcessByChannelNumberHandler.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -32,7 +32,7 @@ public class GetWrappedProcessByChannelNumberHandler : FFmpegProcessHandler<GetW @@ -32,7 +32,7 @@ public class GetWrappedProcessByChannelNumberHandler : FFmpegProcessHandler<GetW
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)
.Map(result => result.IfNone(false));
Process process = _ffmpegProcessService.WrapSegmenter(
Command process = _ffmpegProcessService.WrapSegmenter(
ffmpegPath,
saveReports,
channel,

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

@ -172,8 +172,8 @@ public class TranscodingTests @@ -172,8 +172,8 @@ public class TranscodingTests
[ValueSource(typeof(TestData), nameof(TestData.Watermarks))] Watermark watermark,
[ValueSource(typeof(TestData), nameof(TestData.Subtitles))] Subtitle subtitle,
[ValueSource(typeof(TestData), nameof(TestData.VideoFormats))] FFmpegProfileVideoFormat profileVideoFormat,
[ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration)
// [ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration)
// [ValueSource(typeof(TestData), nameof(TestData.NoAcceleration))] HardwareAccelerationKind profileAcceleration)
[ValueSource(typeof(TestData), nameof(TestData.NvidiaAcceleration))] HardwareAccelerationKind profileAcceleration)
// [ValueSource(typeof(TestData), nameof(TestData.VaapiAcceleration))] HardwareAccelerationKind profileAcceleration)
// [ValueSource(typeof(TestData), nameof(TestData.QsvAcceleration))] HardwareAccelerationKind profileAcceleration)
// [ValueSource(typeof(TestData), nameof(TestData.VideoToolboxAcceleration))] HardwareAccelerationKind profileAcceleration)
@ -377,7 +377,7 @@ public class TranscodingTests @@ -377,7 +377,7 @@ public class TranscodingTests
_ => ChannelSubtitleMode.None
};
using Process process = await service.ForPlayoutItem(
Command process = await service.ForPlayoutItem(
ExecutableName("ffmpeg"),
ExecutableName("ffprobe"),
false,
@ -426,17 +426,14 @@ public class TranscodingTests @@ -426,17 +426,14 @@ public class TranscodingTests
var timeoutSignal = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
result = await Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
result = await process
.WithStandardOutputPipe(PipeTarget.ToStream(Stream.Null))
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(sb))
.ExecuteAsync(timeoutSignal.Token);
}
catch (OperationCanceledException)
{
IEnumerable<string> quotedArgs = process.StartInfo.ArgumentList.Map(a => $"\'{a}\'");
Assert.Fail($"Transcode failure (timeout): ffmpeg {string.Join(" ", quotedArgs)}");
Assert.Fail($"Transcode failure (timeout): ffmpeg {process.Arguments}");
return;
}
@ -445,22 +442,19 @@ public class TranscodingTests @@ -445,22 +442,19 @@ public class TranscodingTests
if (profileAcceleration != HardwareAccelerationKind.None && isUnsupported)
{
var quotedArgs = process.StartInfo.ArgumentList.Map(a => $"\'{a}\'").ToList();
result.ExitCode.Should().Be(1, $"Error message with successful exit code? {string.Join(" ", quotedArgs)}");
Assert.Warn($"Unsupported on this hardware: ffmpeg {string.Join(" ", quotedArgs)}");
result.ExitCode.Should().Be(1, $"Error message with successful exit code? {process.Arguments}");
Assert.Warn($"Unsupported on this hardware: ffmpeg {process.Arguments}");
}
else if (error.Contains("Impossible to convert between"))
{
IEnumerable<string> quotedArgs = process.StartInfo.ArgumentList.Map(a => $"\'{a}\'");
Assert.Fail($"Transcode failure: ffmpeg {string.Join(" ", quotedArgs)}");
Assert.Fail($"Transcode failure: ffmpeg {process.Arguments}");
}
else
{
var quotedArgs = process.StartInfo.ArgumentList.Map(a => $"\'{a}\'").ToList();
result.ExitCode.Should().Be(0, error + Environment.NewLine + string.Join(" ", quotedArgs));
result.ExitCode.Should().Be(0, error + Environment.NewLine + process.Arguments);
if (result.ExitCode == 0)
{
Console.WriteLine(string.Join(" ", quotedArgs));
Console.WriteLine(process.Arguments);
}
}
}

54
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Text;
using CliWrap;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Interfaces.FFmpeg;
@ -32,7 +31,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -32,7 +31,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
_logger = logger;
}
public async Task<Process> ForPlayoutItem(
public async Task<Command> ForPlayoutItem(
string ffmpegPath,
string ffprobePath,
bool saveReports,
@ -226,7 +225,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -226,7 +225,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
FFmpegPipeline pipeline = pipelineBuilder.Build(ffmpegState, desiredState);
return GetProcess(ffmpegPath, videoInputFile, audioInputFile, watermarkInputFile, None, pipeline);
return GetCommand(ffmpegPath, videoInputFile, audioInputFile, watermarkInputFile, None, pipeline);
}
private Option<WatermarkInputFile> GetWatermarkInputFile(
@ -290,7 +289,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -290,7 +289,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
return None;
}
public Task<Process> ForError(
public Task<Command> ForError(
string ffmpegPath,
Channel channel,
Option<TimeSpan> duration,
@ -299,7 +298,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -299,7 +298,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
long ptsOffset) =>
_ffmpegProcessService.ForError(ffmpegPath, channel, duration, errorMessage, hlsRealtime, ptsOffset);
public Process ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
public Command ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
{
var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height);
@ -319,13 +318,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -319,13 +318,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
concatInputFile,
FFmpegState.Concat(saveReports, channel.Name));
return GetProcess(ffmpegPath, None, None, None, concatInputFile, pipeline);
return GetCommand(ffmpegPath, None, None, None, concatInputFile, pipeline);
}
public Process WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host) =>
public Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host) =>
_ffmpegProcessService.WrapSegmenter(ffmpegPath, saveReports, channel, scheme, host);
public Process ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height)
public Command ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height)
{
var videoInputFile = new VideoInputFile(
inputFile,
@ -341,13 +340,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -341,13 +340,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
FFmpegPipeline pipeline = pipelineBuilder.Resize(outputFile, new FrameSize(-1, height));
return GetProcess(ffmpegPath, videoInputFile, None, None, None, pipeline, false);
return GetCommand(ffmpegPath, videoInputFile, None, None, None, pipeline, false);
}
public Process ConvertToPng(string ffmpegPath, string inputFile, string outputFile) =>
public Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile) =>
_ffmpegProcessService.ConvertToPng(ffmpegPath, inputFile, outputFile);
public Process ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile) =>
public Command ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile) =>
_ffmpegProcessService.ExtractAttachedPicAsPng(ffmpegPath, inputFile, streamIndex, outputFile);
public Task<Either<BaseError, string>> GenerateSongImage(
@ -383,7 +382,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -383,7 +382,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
watermarkWidthPercent,
cancellationToken);
private Process GetProcess(
private Command GetCommand(
string ffmpegPath,
Option<VideoInputFile> videoInputFile,
Option<AudioInputFile> audioInputFile,
@ -417,35 +416,16 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -417,35 +416,16 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
concatInputFile,
pipeline.PipelineSteps);
var startInfo = new ProcessStartInfo
{
FileName = ffmpegPath,
RedirectStandardOutput = true,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8
};
if (environmentVariables.Any())
{
_logger.LogDebug("FFmpeg environment variables {EnvVars}", environmentVariables);
}
foreach ((string key, string value) in environmentVariables)
{
startInfo.EnvironmentVariables[key] = value;
}
foreach (string argument in arguments)
{
startInfo.ArgumentList.Add(argument);
}
return new Process
{
StartInfo = startInfo
};
return Cli.Wrap(ffmpegPath)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null))
.WithEnvironmentVariables(environmentVariables.ToDictionary(e => e.Key, e => e.Value));
}
private static Option<string> VaapiDriverName(HardwareAccelerationMode accelerationMode, VaapiDriver driver)

58
ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Text;
using Bugsnag;
using CliWrap;
using CliWrap.Buffered;
@ -39,7 +40,7 @@ public class FFmpegProcessService @@ -39,7 +40,7 @@ public class FFmpegProcessService
_logger = logger;
}
public async Task<Process> ForError(
public async Task<Command> ForError(
string ffmpegPath,
Channel channel,
Option<TimeSpan> duration,
@ -102,29 +103,34 @@ public class FFmpegProcessService @@ -102,29 +103,34 @@ public class FFmpegProcessService
await duration.IfSomeAsync(d => builder = builder.WithDuration(d));
switch (channel.StreamingMode)
Process process = channel.StreamingMode switch
{
// HLS needs to segment and generate playlist
case StreamingMode.HttpLiveStreamingSegmenter:
return builder.WithHls(
StreamingMode.HttpLiveStreamingSegmenter =>
builder.WithHls(
channel.Number,
None,
ptsOffset,
playbackSettings.VideoTrackTimeScale,
playbackSettings.FrameRate)
.Build();
default:
return builder.WithFormat("mpegts")
.WithPipe()
.Build();
}
.Build(),
_ => builder.WithFormat("mpegts")
.WithPipe()
.Build()
};
return Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null));
}
public Process WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
public Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host)
{
FFmpegPlaybackSettings playbackSettings = _playbackSettingsCalculator.ConcatSettings;
return new FFmpegProcessBuilder(ffmpegPath, saveReports, _logger)
Process process = new FFmpegProcessBuilder(ffmpegPath, saveReports, _logger)
.WithThreads(1)
.WithQuiet()
.WithFormatFlags(playbackSettings.FormatFlags)
@ -136,27 +142,45 @@ public class FFmpegProcessService @@ -136,27 +142,45 @@ public class FFmpegProcessService
.WithFormat("mpegts")
.WithPipe()
.Build();
return Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null));
}
public Process ConvertToPng(string ffmpegPath, string inputFile, string outputFile)
public Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile)
{
return new FFmpegProcessBuilder(ffmpegPath, false, _logger)
Process process = new FFmpegProcessBuilder(ffmpegPath, false, _logger)
.WithThreads(1)
.WithQuiet()
.WithInput(inputFile)
.WithOutputFormat("apng", outputFile)
.Build();
return Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null));
}
public Process ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile)
public Command ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile)
{
return new FFmpegProcessBuilder(ffmpegPath, false, _logger)
Process process = new FFmpegProcessBuilder(ffmpegPath, false, _logger)
.WithThreads(1)
.WithQuiet()
.WithInput(inputFile)
.WithMap($"0:{streamIndex}")
.WithOutputFormat("apng", outputFile)
.Build();
return Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.WithEnvironmentVariables(process.StartInfo.Environment.ToDictionary(kvp => kvp.Key, kvp => kvp.Value))
.WithStandardErrorPipe(PipeTarget.ToStream(Stream.Null));
}
public async Task<Either<BaseError, string>> GenerateSongImage(
@ -414,7 +438,7 @@ public class FFmpegProcessService @@ -414,7 +438,7 @@ public class FFmpegProcessService
path
})
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync();
.ExecuteBufferedAsync(Encoding.UTF8);
if (result.ExitCode == 0)
{

16
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.FFmpeg;
@ -8,7 +8,7 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg; @@ -8,7 +8,7 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg;
public interface IFFmpegProcessService
{
Task<Process> ForPlayoutItem(
Task<Command> ForPlayoutItem(
string ffmpegPath,
string ffprobePath,
bool saveReports,
@ -31,7 +31,7 @@ public interface IFFmpegProcessService @@ -31,7 +31,7 @@ public interface IFFmpegProcessService
long ptsOffset,
Option<int> targetFramerate);
Task<Process> ForError(
Task<Command> ForError(
string ffmpegPath,
Channel channel,
Option<TimeSpan> duration,
@ -39,15 +39,15 @@ public interface IFFmpegProcessService @@ -39,15 +39,15 @@ public interface IFFmpegProcessService
bool hlsRealtime,
long ptsOffset);
Process ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Command ConcatChannel(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Process WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Command WrapSegmenter(string ffmpegPath, bool saveReports, Channel channel, string scheme, string host);
Process ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height);
Command ResizeImage(string ffmpegPath, string inputFile, string outputFile, int height);
Process ConvertToPng(string ffmpegPath, string inputFile, string outputFile);
Command ConvertToPng(string ffmpegPath, string inputFile, string outputFile);
Process ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile);
Command ExtractAttachedPicAsPng(string ffmpegPath, string inputFile, int streamIndex, string outputFile);
Task<Either<BaseError, string>> GenerateSongImage(
string ffmpegPath,

17
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using System.Diagnostics;
using Bugsnag;
using Bugsnag;
using CliWrap;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions;
@ -163,16 +162,13 @@ public abstract class LocalFolderScanner @@ -163,16 +162,13 @@ public abstract class LocalFolderScanner
{
// extract attached pic (and convert to png)
string tempName = _tempFilePool.GetNextTempFile(TempFileCategory.CoverArt);
using Process process = _ffmpegProcessService.ExtractAttachedPicAsPng(
Command process = _ffmpegProcessService.ExtractAttachedPicAsPng(
path,
artworkFile,
picIndex,
tempName);
await Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(cancellationToken);
await process.ExecuteAsync(cancellationToken);
return tempName;
},
@ -180,12 +176,9 @@ public abstract class LocalFolderScanner @@ -180,12 +176,9 @@ public abstract class LocalFolderScanner
{
// no attached pic index means convert to png
string tempName = _tempFilePool.GetNextTempFile(TempFileCategory.CoverArt);
using Process process = _ffmpegProcessService.ConvertToPng(path, artworkFile, tempName);
Command process = _ffmpegProcessService.ConvertToPng(path, artworkFile, tempName);
await Cli.Wrap(process.StartInfo.FileName)
.WithArguments(process.StartInfo.ArgumentList)
.WithValidation(CommandResultValidation.None)
.ExecuteAsync(cancellationToken);
await process.ExecuteAsync(cancellationToken);
return tempName;
});

5
ErsatzTV.Infrastructure/Health/Checks/BaseHealthCheck.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using CliWrap;
using System.Text;
using CliWrap;
using CliWrap.Buffered;
using ErsatzTV.Core.Health;
@ -37,7 +38,7 @@ public abstract class BaseHealthCheck @@ -37,7 +38,7 @@ public abstract class BaseHealthCheck
BufferedCommandResult result = await Cli.Wrap(path)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(cancellationToken);
.ExecuteBufferedAsync(Encoding.UTF8, cancellationToken);
return result.StandardOutput;
}

25
ErsatzTV/Controllers/InternalController.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Application.Streaming;
using ErsatzTV.Extensions;
using MediatR;
@ -43,11 +44,27 @@ public class InternalController : ControllerBase @@ -43,11 +44,27 @@ public class InternalController : ControllerBase
result.Match<IActionResult>(
processModel =>
{
Process process = processModel.Process;
Command command = processModel.Process;
_logger.LogInformation("ffmpeg arguments {FFmpegArguments}", command.Arguments);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = command.TargetFilePath,
Arguments = command.Arguments,
RedirectStandardOutput = true,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true
}
};
foreach ((string key, string value) in command.EnvironmentVariables)
{
process.StartInfo.Environment[key] = value;
}
_logger.LogInformation(
"ffmpeg arguments {FFmpegArguments}",
string.Join(" ", process.StartInfo.ArgumentList));
process.Start();
return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t");
},

28
ErsatzTV/Controllers/IptvController.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Diagnostics;
using CliWrap;
using ErsatzTV.Application.Channels;
using ErsatzTV.Application.Images;
using ErsatzTV.Application.Streaming;
@ -92,15 +93,30 @@ public class IptvController : ControllerBase @@ -92,15 +93,30 @@ public class IptvController : ControllerBase
result => result.Match<IActionResult>(
processModel =>
{
Process process = processModel.Process;
Command command = processModel.Process;
_logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber);
_logger.LogDebug(
"ffmpeg ts arguments {FFmpegArguments}",
string.Join(" ", process.StartInfo.ArgumentList));
_logger.LogInformation("ffmpeg arguments {FFmpegArguments}", command.Arguments);
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = command.TargetFilePath,
Arguments = command.Arguments,
RedirectStandardOutput = true,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true
}
};
foreach ((string key, string value) in command.EnvironmentVariables)
{
process.StartInfo.Environment[key] = value;
}
process.Start();
return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t");
},
return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t"); },
error => BadRequest(error.Value)));
}

Loading…
Cancel
Save