|
|
@ -1,4 +1,6 @@ |
|
|
|
using System.Diagnostics; |
|
|
|
using System.Diagnostics; |
|
|
|
|
|
|
|
using System.IO.Pipelines; |
|
|
|
|
|
|
|
using System.Text; |
|
|
|
using CliWrap; |
|
|
|
using CliWrap; |
|
|
|
using ErsatzTV.Application.Emby; |
|
|
|
using ErsatzTV.Application.Emby; |
|
|
|
using ErsatzTV.Application.Jellyfin; |
|
|
|
using ErsatzTV.Application.Jellyfin; |
|
|
@ -9,6 +11,7 @@ using ErsatzTV.Application.Subtitles.Queries; |
|
|
|
using ErsatzTV.Core; |
|
|
|
using ErsatzTV.Core; |
|
|
|
using ErsatzTV.Core.FFmpeg; |
|
|
|
using ErsatzTV.Core.FFmpeg; |
|
|
|
using ErsatzTV.Core.Interfaces.FFmpeg; |
|
|
|
using ErsatzTV.Core.Interfaces.FFmpeg; |
|
|
|
|
|
|
|
using ErsatzTV.Core.Interfaces.Streaming; |
|
|
|
using ErsatzTV.Extensions; |
|
|
|
using ErsatzTV.Extensions; |
|
|
|
using Flurl; |
|
|
|
using Flurl; |
|
|
|
using MediatR; |
|
|
|
using MediatR; |
|
|
@ -21,15 +24,18 @@ namespace ErsatzTV.Controllers; |
|
|
|
public class InternalController : ControllerBase |
|
|
|
public class InternalController : ControllerBase |
|
|
|
{ |
|
|
|
{ |
|
|
|
private readonly IFFmpegSegmenterService _ffmpegSegmenterService; |
|
|
|
private readonly IFFmpegSegmenterService _ffmpegSegmenterService; |
|
|
|
|
|
|
|
private readonly IGraphicsEngine _graphicsEngine; |
|
|
|
private readonly ILogger<InternalController> _logger; |
|
|
|
private readonly ILogger<InternalController> _logger; |
|
|
|
private readonly IMediator _mediator; |
|
|
|
private readonly IMediator _mediator; |
|
|
|
|
|
|
|
|
|
|
|
public InternalController( |
|
|
|
public InternalController( |
|
|
|
IFFmpegSegmenterService ffmpegSegmenterService, |
|
|
|
IFFmpegSegmenterService ffmpegSegmenterService, |
|
|
|
|
|
|
|
IGraphicsEngine graphicsEngine, |
|
|
|
IMediator mediator, |
|
|
|
IMediator mediator, |
|
|
|
ILogger<InternalController> logger) |
|
|
|
ILogger<InternalController> logger) |
|
|
|
{ |
|
|
|
{ |
|
|
|
_ffmpegSegmenterService = ffmpegSegmenterService; |
|
|
|
_ffmpegSegmenterService = ffmpegSegmenterService; |
|
|
|
|
|
|
|
_graphicsEngine = graphicsEngine; |
|
|
|
_mediator = mediator; |
|
|
|
_mediator = mediator; |
|
|
|
_logger = logger; |
|
|
|
_logger = logger; |
|
|
|
} |
|
|
|
} |
|
|
@ -298,38 +304,55 @@ public class InternalController : ControllerBase |
|
|
|
|
|
|
|
|
|
|
|
foreach (PlayoutItemProcessModel processModel in result.RightToSeq()) |
|
|
|
foreach (PlayoutItemProcessModel processModel in result.RightToSeq()) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Command command = processModel.Process; |
|
|
|
// for process counter
|
|
|
|
|
|
|
|
var ffmpegProcess = new FFmpegProcess(); |
|
|
|
|
|
|
|
|
|
|
|
_logger.LogDebug("ffmpeg arguments {FFmpegArguments}", command.Arguments); |
|
|
|
Command process = processModel.Process; |
|
|
|
|
|
|
|
|
|
|
|
var process = new FFmpegProcess |
|
|
|
_logger.LogDebug("ffmpeg arguments {FFmpegArguments}", process.Arguments); |
|
|
|
{ |
|
|
|
|
|
|
|
StartInfo = new ProcessStartInfo |
|
|
|
var cts = new CancellationTokenSource(); |
|
|
|
|
|
|
|
HttpContext.Response.OnCompleted( |
|
|
|
|
|
|
|
async () => |
|
|
|
{ |
|
|
|
{ |
|
|
|
FileName = command.TargetFilePath, |
|
|
|
ffmpegProcess.Dispose(); |
|
|
|
Arguments = command.Arguments, |
|
|
|
await cts.CancelAsync(); |
|
|
|
RedirectStandardOutput = true, |
|
|
|
cts.Dispose(); |
|
|
|
RedirectStandardError = false, |
|
|
|
}); |
|
|
|
UseShellExecute = false, |
|
|
|
|
|
|
|
CreateNoWindow = true |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HttpContext.Response.RegisterForDispose(process); |
|
|
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource( |
|
|
|
|
|
|
|
cts.Token, |
|
|
|
|
|
|
|
HttpContext.RequestAborted); |
|
|
|
|
|
|
|
|
|
|
|
foreach ((string key, string value) in command.EnvironmentVariables) |
|
|
|
var pipe = new Pipe(); |
|
|
|
|
|
|
|
var stdErrBuffer = new StringBuilder(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var processWithPipe = process; |
|
|
|
|
|
|
|
foreach (var graphicsEngineContext in processModel.GraphicsEngineContext) |
|
|
|
{ |
|
|
|
{ |
|
|
|
process.StartInfo.Environment[key] = value; |
|
|
|
var gePipe = new Pipe(); |
|
|
|
|
|
|
|
processWithPipe = process.WithStandardInputPipe(PipeSource.FromStream(gePipe.Reader.AsStream())); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// fire and forget graphics engine task
|
|
|
|
|
|
|
|
_ = _graphicsEngine.Run( |
|
|
|
|
|
|
|
graphicsEngineContext, |
|
|
|
|
|
|
|
gePipe.Writer, |
|
|
|
|
|
|
|
linkedCts.Token); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var contentType = "video/mp2t"; |
|
|
|
_ = processWithPipe |
|
|
|
if (mode.Equals("hls-direct", StringComparison.OrdinalIgnoreCase)) |
|
|
|
.WithStandardOutputPipe(PipeTarget.ToStream(pipe.Writer.AsStream())) |
|
|
|
|
|
|
|
.WithStandardErrorPipe(PipeTarget.ToStringBuilder(stdErrBuffer)) |
|
|
|
|
|
|
|
.WithValidation(CommandResultValidation.None) |
|
|
|
|
|
|
|
.ExecuteAsync(linkedCts.Token); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var contentType = mode switch |
|
|
|
{ |
|
|
|
{ |
|
|
|
contentType = "video/mp4"; |
|
|
|
"segmenter-v2" => "video/x-matroska", |
|
|
|
} |
|
|
|
_ => "video/mp2t" |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
process.Start(); |
|
|
|
return new FileStreamResult(pipe.Reader.AsStream(), contentType); |
|
|
|
return new FileStreamResult(process.StandardOutput.BaseStream, contentType); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// this will never happen
|
|
|
|
// this will never happen
|
|
|
|