mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-12-14 18:15:44 +00:00
Merge pull request #584 from rosenbjerg/improve-cancellation-handling
Improve cancellation handling
This commit is contained in:
commit
0d07456c6e
2 changed files with 54 additions and 17 deletions
|
|
@ -1043,6 +1043,28 @@ public class VideoTest
|
||||||
Assert.ThrowsExactly<OperationCanceledException>(() => task.ProcessSynchronously());
|
Assert.ThrowsExactly<OperationCanceledException>(() => task.ProcessSynchronously());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||||
|
public void Video_Cancel_CancellationToken_Before_Throws()
|
||||||
|
{
|
||||||
|
using var outputFile = new TemporaryFile("out.mp4");
|
||||||
|
|
||||||
|
var cts = new CancellationTokenSource();
|
||||||
|
|
||||||
|
cts.Cancel();
|
||||||
|
var task = FFMpegArguments
|
||||||
|
.FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
|
||||||
|
.WithCustomArgument("-re")
|
||||||
|
.ForceFormat("lavfi"))
|
||||||
|
.OutputToFile(outputFile, false, opt => opt
|
||||||
|
.WithAudioCodec(AudioCodec.Aac)
|
||||||
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
|
.WithSpeedPreset(Speed.VeryFast))
|
||||||
|
.CancellableThrough(cts.Token);
|
||||||
|
|
||||||
|
Assert.ThrowsExactly<OperationCanceledException>(() => task.ProcessSynchronously());
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||||
public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
|
public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,11 @@ namespace FFMpegCore;
|
||||||
public class FFMpegArgumentProcessor
|
public class FFMpegArgumentProcessor
|
||||||
{
|
{
|
||||||
private static readonly Regex ProgressRegex = new(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
|
private static readonly Regex ProgressRegex = new(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
|
||||||
|
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||||
private readonly List<Action<FFOptions>> _configurations;
|
private readonly List<Action<FFOptions>> _configurations;
|
||||||
private readonly FFMpegArguments _ffMpegArguments;
|
private readonly FFMpegArguments _ffMpegArguments;
|
||||||
|
private CancellationTokenRegistration? _cancellationTokenRegistration;
|
||||||
|
private bool _cancelled;
|
||||||
private FFMpegLogLevel? _logLevel;
|
private FFMpegLogLevel? _logLevel;
|
||||||
private Action<string>? _onError;
|
private Action<string>? _onError;
|
||||||
private Action<string>? _onOutput;
|
private Action<string>? _onOutput;
|
||||||
|
|
@ -29,6 +32,12 @@ public class FFMpegArgumentProcessor
|
||||||
|
|
||||||
private event EventHandler<int> CancelEvent = null!;
|
private event EventHandler<int> CancelEvent = null!;
|
||||||
|
|
||||||
|
~FFMpegArgumentProcessor()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Dispose();
|
||||||
|
_cancellationTokenRegistration?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Register action that will be invoked during the ffmpeg processing, when a progress time is output and parsed and progress percentage is
|
/// Register action that will be invoked during the ffmpeg processing, when a progress time is output and parsed and progress percentage is
|
||||||
/// calculated.
|
/// calculated.
|
||||||
|
|
@ -69,15 +78,21 @@ public class FFMpegArgumentProcessor
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Cancel(int timeout)
|
||||||
|
{
|
||||||
|
_cancelled = true;
|
||||||
|
CancelEvent?.Invoke(this, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0)
|
public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0)
|
||||||
{
|
{
|
||||||
cancel = () => CancelEvent?.Invoke(this, timeout);
|
cancel = () => Cancel(timeout);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FFMpegArgumentProcessor CancellableThrough(CancellationToken token, int timeout = 0)
|
public FFMpegArgumentProcessor CancellableThrough(CancellationToken token, int timeout = 0)
|
||||||
{
|
{
|
||||||
token.Register(() => CancelEvent?.Invoke(this, timeout));
|
_cancellationTokenRegistration = token.Register(() => Cancel(timeout));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,12 +116,12 @@ public class FFMpegArgumentProcessor
|
||||||
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
||||||
{
|
{
|
||||||
var options = GetConfiguredOptions(ffMpegOptions);
|
var options = GetConfiguredOptions(ffMpegOptions);
|
||||||
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
|
var processArguments = PrepareProcessArguments(options);
|
||||||
|
|
||||||
IProcessResult? processResult = null;
|
IProcessResult? processResult = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
processResult = Process(processArguments, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult();
|
processResult = Process(processArguments).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|
@ -122,12 +137,12 @@ public class FFMpegArgumentProcessor
|
||||||
public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
||||||
{
|
{
|
||||||
var options = GetConfiguredOptions(ffMpegOptions);
|
var options = GetConfiguredOptions(ffMpegOptions);
|
||||||
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
|
var processArguments = PrepareProcessArguments(options);
|
||||||
|
|
||||||
IProcessResult? processResult = null;
|
IProcessResult? processResult = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false);
|
processResult = await Process(processArguments).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|
@ -140,23 +155,25 @@ public class FFMpegArgumentProcessor
|
||||||
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IProcessResult> Process(ProcessArguments processArguments, CancellationTokenSource cancellationTokenSource)
|
private async Task<IProcessResult> Process(ProcessArguments processArguments)
|
||||||
{
|
{
|
||||||
IProcessResult processResult = null!;
|
IProcessResult processResult = null!;
|
||||||
|
if (_cancelled)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException("cancelled before starting processing");
|
||||||
|
}
|
||||||
|
|
||||||
_ffMpegArguments.Pre();
|
_ffMpegArguments.Pre();
|
||||||
|
|
||||||
using var instance = processArguments.Start();
|
using var instance = processArguments.Start();
|
||||||
var cancelled = false;
|
|
||||||
|
|
||||||
void OnCancelEvent(object sender, int timeout)
|
void OnCancelEvent(object sender, int timeout)
|
||||||
{
|
{
|
||||||
cancelled = true;
|
|
||||||
instance.SendInput("q");
|
instance.SendInput("q");
|
||||||
|
|
||||||
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
if (!_cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
||||||
{
|
{
|
||||||
cancellationTokenSource.Cancel();
|
_cancellationTokenSource.Cancel();
|
||||||
instance.Kill();
|
instance.Kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -168,11 +185,11 @@ public class FFMpegArgumentProcessor
|
||||||
await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t =>
|
await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t =>
|
||||||
{
|
{
|
||||||
processResult = t.Result;
|
processResult = t.Result;
|
||||||
cancellationTokenSource.Cancel();
|
_cancellationTokenSource.Cancel();
|
||||||
_ffMpegArguments.Post();
|
_ffMpegArguments.Post();
|
||||||
}), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false);
|
}), _ffMpegArguments.During(_cancellationTokenSource.Token)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (cancelled)
|
if (_cancelled)
|
||||||
{
|
{
|
||||||
throw new OperationCanceledException("ffmpeg processing was cancelled");
|
throw new OperationCanceledException("ffmpeg processing was cancelled");
|
||||||
}
|
}
|
||||||
|
|
@ -214,8 +231,7 @@ public class FFMpegArgumentProcessor
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProcessArguments PrepareProcessArguments(FFOptions ffOptions,
|
private ProcessArguments PrepareProcessArguments(FFOptions ffOptions)
|
||||||
out CancellationTokenSource cancellationTokenSource)
|
|
||||||
{
|
{
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
FFMpegHelper.VerifyFFMpegExists(ffOptions);
|
FFMpegHelper.VerifyFFMpegExists(ffOptions);
|
||||||
|
|
@ -245,7 +261,6 @@ public class FFMpegArgumentProcessor
|
||||||
WorkingDirectory = ffOptions.WorkingDirectory
|
WorkingDirectory = ffOptions.WorkingDirectory
|
||||||
};
|
};
|
||||||
var processArguments = new ProcessArguments(startInfo);
|
var processArguments = new ProcessArguments(startInfo);
|
||||||
cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
if (_onOutput != null)
|
if (_onOutput != null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue