mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-12-14 10:05:44 +00:00
639 lines
No EOL
26 KiB
C#
639 lines
No EOL
26 KiB
C#
using FFMpegCore.Enums;
|
|
using FFMpegCore.FFMPEG.Argument;
|
|
using FFMpegCore.FFMPEG.Enums;
|
|
using FFMpegCore.FFMPEG.Exceptions;
|
|
using FFMpegCore.Helpers;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using Instances;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
namespace FFMpegCore.FFMPEG
|
|
{
|
|
public delegate void ConversionHandler(double percentage);
|
|
|
|
public class FFMpeg
|
|
{
|
|
IArgumentBuilder ArgumentBuilder { get; set; } = new FFArgumentBuilder();
|
|
|
|
/// <summary>
|
|
/// Intializes the FFMPEG encoder.
|
|
/// </summary>
|
|
public FFMpeg() : base()
|
|
{
|
|
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
|
_ffmpegPath = FFMpegOptions.Options.FFmpegBinary;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the percentage of the current conversion progress.
|
|
/// </summary>
|
|
public event ConversionHandler OnProgress;
|
|
|
|
/// <summary>
|
|
/// Saves a 'png' thumbnail from the input video.
|
|
/// </summary>
|
|
/// <param name="source">Source video file.</param>
|
|
/// <param name="output">Output video file</param>
|
|
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
|
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
|
/// <param name="persistSnapshotOnFileSystem">By default, it deletes the created image on disk. If set to true, it won't delete the image</param>
|
|
/// <returns>Bitmap with the requested snapshot.</returns>
|
|
public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, TimeSpan? captureTime = null,
|
|
bool persistSnapshotOnFileSystem = false)
|
|
{
|
|
if (captureTime == null)
|
|
captureTime = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3);
|
|
|
|
if (output.Extension.ToLower() != FileExtension.Png)
|
|
output = new FileInfo(output.FullName.Replace(output.Extension, FileExtension.Png));
|
|
|
|
if (size == null || (size.Value.Height == 0 && size.Value.Width == 0))
|
|
{
|
|
size = new Size(source.Width, source.Height);
|
|
}
|
|
|
|
if (size.Value.Width != size.Value.Height)
|
|
{
|
|
if (size.Value.Width == 0)
|
|
{
|
|
var ratio = source.Width / (double)size.Value.Width;
|
|
|
|
size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio));
|
|
}
|
|
|
|
if (size.Value.Height == 0)
|
|
{
|
|
var ratio = source.Height / (double)size.Value.Height;
|
|
|
|
size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio));
|
|
}
|
|
}
|
|
|
|
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
var container = new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new VideoCodecArgument(VideoCodec.Png),
|
|
new FrameOutputCountArgument(1),
|
|
new SeekArgument(captureTime),
|
|
new SizeArgument(size),
|
|
new OutputArgument(output)
|
|
);
|
|
|
|
if (!RunProcess(container, output, false))
|
|
{
|
|
throw new OperationCanceledException("Could not take snapshot!");
|
|
}
|
|
|
|
output.Refresh();
|
|
|
|
Bitmap result;
|
|
using (var bmp = (Bitmap)Image.FromFile(output.FullName))
|
|
{
|
|
using var ms = new MemoryStream();
|
|
bmp.Save(ms, ImageFormat.Png);
|
|
result = new Bitmap(ms);
|
|
}
|
|
|
|
if (output.Exists && !persistSnapshotOnFileSystem)
|
|
{
|
|
output.Delete();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Convert a video do a different format.
|
|
/// </summary>
|
|
/// <param name="source">Input video source.</param>
|
|
/// <param name="output">Output information.</param>
|
|
/// <param name="type">Target conversion video type.</param>
|
|
/// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param>
|
|
/// <param name="size">Video size.</param>
|
|
/// <param name="audioQuality">Conversion target audio quality.</param>
|
|
/// <param name="multithreaded">Is encoding multithreaded.</param>
|
|
/// <returns>Output video information.</returns>
|
|
public VideoInfo Convert(
|
|
VideoInfo source,
|
|
FileInfo output,
|
|
VideoType type = VideoType.Mp4,
|
|
Speed speed = Speed.SuperFast,
|
|
VideoSize size = VideoSize.Original,
|
|
AudioQuality audioQuality = AudioQuality.Normal,
|
|
bool multithreaded = false)
|
|
{
|
|
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type));
|
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
|
|
|
var scale = VideoSize.Original == size ? 1 : (double)source.Height / (int)size;
|
|
var outputSize = new Size((int)(source.Width / scale), (int)(source.Height / scale));
|
|
|
|
if (outputSize.Width % 2 != 0)
|
|
outputSize.Width += 1;
|
|
|
|
return type switch
|
|
{
|
|
VideoType.Mp4 => Convert(new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new ThreadsArgument(multithreaded),
|
|
new ScaleArgument(outputSize),
|
|
new VideoCodecArgument(VideoCodec.LibX264, 2400),
|
|
new SpeedArgument(speed),
|
|
new AudioCodecArgument(AudioCodec.Aac),
|
|
new AudioBitrateArgument(audioQuality),
|
|
new OutputArgument(output))),
|
|
VideoType.Ogv => Convert(new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new ThreadsArgument(multithreaded),
|
|
new ScaleArgument(outputSize),
|
|
new VideoCodecArgument(VideoCodec.LibTheora, 2400),
|
|
new SpeedArgument(speed),
|
|
new AudioCodecArgument(AudioCodec.LibVorbis),
|
|
new AudioBitrateArgument(audioQuality),
|
|
new OutputArgument(output))),
|
|
VideoType.Ts => Convert(new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new CopyArgument(),
|
|
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
|
new ForceFormatArgument(VideoCodec.MpegTs),
|
|
new OutputArgument(output))),
|
|
VideoType.WebM => Convert(new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new ThreadsArgument(multithreaded),
|
|
new ScaleArgument(outputSize),
|
|
new VideoCodecArgument(VideoCodec.LibVpx, 2400),
|
|
new SpeedArgument(speed),
|
|
new AudioCodecArgument(AudioCodec.LibVorbis),
|
|
new AudioBitrateArgument(audioQuality),
|
|
new OutputArgument(output))),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a poster image to an audio file.
|
|
/// </summary>
|
|
/// <param name="image">Source image file.</param>
|
|
/// <param name="audio">Source audio file.</param>
|
|
/// <param name="output">Output video file.</param>
|
|
/// <returns></returns>
|
|
public VideoInfo PosterWithAudio(FileInfo image, FileInfo audio, FileInfo output)
|
|
{
|
|
FFMpegHelper.InputsExistExceptionCheck(image, audio);
|
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
|
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
|
|
|
var container = new ArgumentContainer(
|
|
new InputArgument(image.FullName, audio.FullName),
|
|
new LoopArgument(1),
|
|
new VideoCodecArgument(VideoCodec.LibX264, 2400),
|
|
new AudioCodecArgument(AudioCodec.Aac),
|
|
new AudioBitrateArgument(AudioQuality.Normal),
|
|
new ShortestArgument(true),
|
|
new OutputArgument(output)
|
|
);
|
|
if (!RunProcess(container, output, false))
|
|
{
|
|
throw new FFMpegException(FFMpegExceptionType.Operation,
|
|
"An error occured while adding the audio file to the image.");
|
|
}
|
|
|
|
return new VideoInfo(output);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Joins a list of video files.
|
|
/// </summary>
|
|
/// <param name="output">Output video file.</param>
|
|
/// <param name="videos">List of vides that need to be joined together.</param>
|
|
/// <returns>Output video information.</returns>
|
|
public VideoInfo Join(FileInfo output, params VideoInfo[] videos)
|
|
{
|
|
FFMpegHelper.OutputExistsExceptionCheck(output);
|
|
FFMpegHelper.InputsExistExceptionCheck(videos.Select(video => video.ToFileInfo()).ToArray());
|
|
|
|
var temporaryVideoParts = videos.Select(video =>
|
|
{
|
|
FFMpegHelper.ConversionSizeExceptionCheck(video);
|
|
var destinationPath = video.FullName.Replace(video.Extension, FileExtension.Ts);
|
|
Convert(video, new FileInfo(destinationPath), VideoType.Ts);
|
|
return destinationPath;
|
|
}).ToList();
|
|
|
|
try
|
|
{
|
|
return Convert(new ArgumentContainer(
|
|
new ConcatArgument(temporaryVideoParts),
|
|
new CopyArgument(),
|
|
new BitStreamFilterArgument(Channel.Audio, Filter.Aac_AdtstoAsc),
|
|
new OutputArgument(output)
|
|
));
|
|
}
|
|
finally
|
|
{
|
|
Cleanup(temporaryVideoParts);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts an image sequence to a video.
|
|
/// </summary>
|
|
/// <param name="output">Output video file.</param>
|
|
/// <param name="frameRate">FPS</param>
|
|
/// <param name="images">Image sequence collection</param>
|
|
/// <returns>Output video information.</returns>
|
|
public VideoInfo JoinImageSequence(FileInfo output, double frameRate = 30, params ImageInfo[] images)
|
|
{
|
|
var temporaryImageFiles = images.Select((image, index) =>
|
|
{
|
|
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
|
var destinationPath =
|
|
image.FullName.Replace(image.Name, $"{index.ToString().PadLeft(9, '0')}{image.Extension}");
|
|
File.Copy(image.FullName, destinationPath);
|
|
|
|
return destinationPath;
|
|
}).ToList();
|
|
|
|
var firstImage = images.First();
|
|
|
|
var container = new ArgumentContainer(
|
|
new FrameRateArgument(frameRate),
|
|
new SizeArgument(firstImage.Width, firstImage.Height),
|
|
new StartNumberArgument(0),
|
|
new InputArgument($"{firstImage.Directory}{Path.DirectorySeparatorChar}%09d.png"),
|
|
new FrameOutputCountArgument(images.Length),
|
|
new VideoCodecArgument(VideoCodec.LibX264),
|
|
new OutputArgument(output)
|
|
);
|
|
|
|
try
|
|
{
|
|
if (!RunProcess(container, output, false))
|
|
{
|
|
throw new FFMpegException(FFMpegExceptionType.Operation,
|
|
"Could not join the provided image sequence.");
|
|
}
|
|
|
|
return new VideoInfo(output);
|
|
}
|
|
finally
|
|
{
|
|
Cleanup(temporaryImageFiles);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Records M3U8 streams to the specified output.
|
|
/// </summary>
|
|
/// <param name="uri">URI to pointing towards stream.</param>
|
|
/// <param name="output">Output file</param>
|
|
/// <returns>Success state.</returns>
|
|
public VideoInfo SaveM3U8Stream(Uri uri, FileInfo output)
|
|
{
|
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
|
|
|
if (uri.Scheme == "http" || uri.Scheme == "https")
|
|
{
|
|
return Convert(new ArgumentContainer(
|
|
new InputArgument(uri),
|
|
new OutputArgument(output)
|
|
));
|
|
}
|
|
|
|
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Strips a video file of audio.
|
|
/// </summary>
|
|
/// <param name="source">Source video file.</param>
|
|
/// <param name="output">Output video file.</param>
|
|
/// <returns></returns>
|
|
public VideoInfo Mute(VideoInfo source, FileInfo output)
|
|
{
|
|
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
|
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
|
|
|
return Convert(new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new CopyArgument(Channel.Video),
|
|
new DisableChannelArgument(Channel.Audio),
|
|
new OutputArgument(output)
|
|
));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves audio from a specific video file to disk.
|
|
/// </summary>
|
|
/// <param name="source">Source video file.</param>
|
|
/// <param name="output">Output audio file.</param>
|
|
/// <returns>Success state.</returns>
|
|
public FileInfo ExtractAudio(VideoInfo source, FileInfo output)
|
|
{
|
|
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3);
|
|
|
|
var container = new ArgumentContainer(
|
|
new InputArgument(source),
|
|
new DisableChannelArgument(Channel.Video),
|
|
new OutputArgument(output)
|
|
);
|
|
|
|
if (!RunProcess(container, output, false))
|
|
{
|
|
throw new FFMpegException(FFMpegExceptionType.Operation,
|
|
"Could not extract the audio from the requested video.");
|
|
}
|
|
|
|
output.Refresh();
|
|
|
|
return output;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds audio to a video file.
|
|
/// </summary>
|
|
/// <param name="source">Source video file.</param>
|
|
/// <param name="audio">Source audio file.</param>
|
|
/// <param name="output">Output video file.</param>
|
|
/// <param name="stopAtShortest">Indicates if the encoding should stop at the shortest input file.</param>
|
|
/// <returns>Success state</returns>
|
|
public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output, bool stopAtShortest = false)
|
|
{
|
|
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
FFMpegHelper.InputsExistExceptionCheck(audio);
|
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
|
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
|
|
|
return Convert(new ArgumentContainer(
|
|
new InputArgument(source.FullName, audio.FullName),
|
|
new CopyArgument(),
|
|
new AudioCodecArgument(AudioCodec.Aac),
|
|
new AudioBitrateArgument(AudioQuality.Hd),
|
|
new ShortestArgument(stopAtShortest),
|
|
new OutputArgument(output)
|
|
));
|
|
}
|
|
|
|
public VideoInfo Convert(ArgumentContainer arguments, bool skipExistsCheck = false)
|
|
{
|
|
var (sources, output) = GetInputOutput(arguments);
|
|
if (sources != null)
|
|
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
|
|
|
if (!RunProcess(arguments, output, skipExistsCheck))
|
|
throw new FFMpegException(FFMpegExceptionType.Conversion, "Could not process file without error");
|
|
|
|
_totalTime = TimeSpan.MinValue;
|
|
|
|
return output != null && output.Exists ? new VideoInfo(output) : null;
|
|
}
|
|
public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments, bool skipExistsCheck = false)
|
|
{
|
|
var (sources, output) = GetInputOutput(arguments);
|
|
if (sources != null)
|
|
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
|
|
|
if (!await RunProcessAsync(arguments, output, skipExistsCheck))
|
|
throw new FFMpegException(FFMpegExceptionType.Conversion, "Could not process file without error");
|
|
|
|
_totalTime = TimeSpan.MinValue;
|
|
|
|
return output != null && output.Exists ? new VideoInfo(output) : null;
|
|
}
|
|
|
|
private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentContainer arguments)
|
|
{
|
|
FileInfo output;
|
|
if (arguments.TryGetArgument<OutputArgument>(out var outputArg))
|
|
output = outputArg.GetAsFileInfo();
|
|
else if (arguments.TryGetArgument<OutputPipeArgument>(out var outputPipeArg))
|
|
output = null;
|
|
else
|
|
throw new FFMpegException(FFMpegExceptionType.Operation, "No output argument found");
|
|
|
|
VideoInfo[] sources;
|
|
if (arguments.TryGetArgument<InputArgument>(out var input))
|
|
sources = input.GetAsVideoInfo();
|
|
else if (arguments.TryGetArgument<ConcatArgument>(out var concat))
|
|
sources = concat.GetAsVideoInfo();
|
|
else if (arguments.TryGetArgument<InputPipeArgument>(out var pipe))
|
|
sources = null;
|
|
else
|
|
throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found");
|
|
return (sources, output);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the associated process is still alive/running.
|
|
/// </summary>
|
|
public bool IsWorking => _instance.Started;
|
|
|
|
/// <summary>
|
|
/// Stops any current job that FFMpeg is running.
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
if (IsWorking)
|
|
{
|
|
_instance.SendInput("q").Wait();
|
|
}
|
|
}
|
|
|
|
#region Private Members & Methods
|
|
|
|
private readonly string _ffmpegPath;
|
|
private TimeSpan _totalTime;
|
|
|
|
private bool RunProcess(ArgumentContainer container, FileInfo output, bool skipExistsCheck)
|
|
{
|
|
_instance?.Dispose();
|
|
var arguments = ArgumentBuilder.BuildArguments(container);
|
|
var exitCode = -1;
|
|
|
|
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
|
|
{
|
|
inputPipeArgument.OpenPipe();
|
|
}
|
|
if (container.TryGetArgument<OutputPipeArgument>(out var outputPipeArgument))
|
|
{
|
|
outputPipeArgument.OpenPipe();
|
|
}
|
|
|
|
|
|
_instance = new Instance(_ffmpegPath, arguments);
|
|
_instance.DataReceived += OutputData;
|
|
|
|
if (inputPipeArgument != null || outputPipeArgument != null)
|
|
{
|
|
try
|
|
{
|
|
using (var tokenSource = new CancellationTokenSource())
|
|
{
|
|
var concurrentTasks = new List<Task>();
|
|
concurrentTasks.Add(_instance.FinishedRunning()
|
|
.ContinueWith((t =>
|
|
{
|
|
exitCode = t.Result;
|
|
if (exitCode != 0)
|
|
tokenSource.Cancel();
|
|
})));
|
|
if (inputPipeArgument != null)
|
|
concurrentTasks.Add(inputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
.ContinueWith((t) =>
|
|
{
|
|
inputPipeArgument.ClosePipe();
|
|
if (t.Exception != null)
|
|
throw t.Exception;
|
|
}));
|
|
if (outputPipeArgument != null)
|
|
concurrentTasks.Add(outputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
.ContinueWith((t) =>
|
|
{
|
|
outputPipeArgument.ClosePipe();
|
|
if (t.Exception != null)
|
|
throw t.Exception;
|
|
}));
|
|
|
|
Task.WaitAll(concurrentTasks.ToArray()/*, tokenSource.Token*/);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
inputPipeArgument?.ClosePipe();
|
|
outputPipeArgument?.ClosePipe();
|
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
exitCode = _instance.BlockUntilFinished();
|
|
}
|
|
|
|
if(exitCode != 0)
|
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
|
|
if (outputPipeArgument == null && !skipExistsCheck && (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0))
|
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
|
|
return exitCode == 0;
|
|
}
|
|
private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo output, bool skipExistsCheck)
|
|
{
|
|
_instance?.Dispose();
|
|
var arguments = ArgumentBuilder.BuildArguments(container);
|
|
var exitCode = -1;
|
|
|
|
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
|
|
{
|
|
inputPipeArgument.OpenPipe();
|
|
}
|
|
if (container.TryGetArgument<OutputPipeArgument>(out var outputPipeArgument))
|
|
{
|
|
outputPipeArgument.OpenPipe();
|
|
}
|
|
|
|
|
|
_instance = new Instance(_ffmpegPath, arguments);
|
|
_instance.DataReceived += OutputData;
|
|
|
|
if (inputPipeArgument != null || outputPipeArgument != null)
|
|
{
|
|
try
|
|
{
|
|
using (var tokenSource = new CancellationTokenSource())
|
|
{
|
|
var concurrentTasks = new List<Task>();
|
|
concurrentTasks.Add(_instance.FinishedRunning()
|
|
.ContinueWith((t =>
|
|
{
|
|
exitCode = t.Result;
|
|
if (exitCode != 0)
|
|
tokenSource.Cancel();
|
|
})));
|
|
if (inputPipeArgument != null)
|
|
concurrentTasks.Add(inputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
.ContinueWith((t) =>
|
|
{
|
|
inputPipeArgument.ClosePipe();
|
|
if (t.Exception != null)
|
|
throw t.Exception;
|
|
}));
|
|
if (outputPipeArgument != null)
|
|
concurrentTasks.Add(outputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
.ContinueWith((t) =>
|
|
{
|
|
outputPipeArgument.ClosePipe();
|
|
if (t.Exception != null)
|
|
throw t.Exception;
|
|
}));
|
|
|
|
await Task.WhenAll(concurrentTasks);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
inputPipeArgument?.ClosePipe();
|
|
outputPipeArgument?.ClosePipe();
|
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
exitCode = await _instance.FinishedRunning();
|
|
}
|
|
|
|
if (exitCode != 0)
|
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
|
|
if (outputPipeArgument == null && !skipExistsCheck && (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0))
|
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
|
|
return exitCode == 0;
|
|
}
|
|
|
|
private void Cleanup(IEnumerable<string> pathList)
|
|
{
|
|
foreach (var path in pathList)
|
|
{
|
|
if (File.Exists(path))
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static readonly Regex ProgressRegex = new Regex(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
|
|
private Instance _instance;
|
|
|
|
private void OutputData(object sender, (DataType Type, string Data) msg)
|
|
{
|
|
#if DEBUG
|
|
Trace.WriteLine(msg.Data);
|
|
#endif
|
|
if (OnProgress == null) return;
|
|
|
|
var match = ProgressRegex.Match(msg.Data);
|
|
if (!match.Success) return;
|
|
|
|
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
|
var percentage = Math.Round(processed.TotalSeconds / _totalTime.TotalSeconds * 100, 2);
|
|
OnProgress(percentage);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |