mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
parent
662377903b
commit
4dd179c2d4
5 changed files with 46 additions and 190 deletions
|
@ -1,103 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using RunProcessAsTask;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
|
||||||
{
|
|
||||||
public abstract class FFBase : IDisposable
|
|
||||||
{
|
|
||||||
protected Process Process;
|
|
||||||
|
|
||||||
protected FFBase()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Is 'true' when an exception is thrown during process kill (for paranoia level users).
|
|
||||||
/// </summary>
|
|
||||||
public bool IsKillFaulty { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the associated process is still alive/running.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsWorking
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
bool processHasExited;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
processHasExited = Process.HasExited;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
processHasExited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !processHasExited && Process.GetProcesses().Any(x => x.Id == Process.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Process?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void CreateProcess(string args, string processPath, bool rStandardInput = false,
|
|
||||||
bool rStandardOutput = false, bool rStandardError = false)
|
|
||||||
{
|
|
||||||
if (IsWorking)
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"The current FFMpeg process is busy with another operation. Create a new object for parallel executions.");
|
|
||||||
|
|
||||||
Process = new Process();
|
|
||||||
IsKillFaulty = false;
|
|
||||||
Process.StartInfo.FileName = processPath;
|
|
||||||
Process.StartInfo.Arguments = args;
|
|
||||||
Process.StartInfo.UseShellExecute = false;
|
|
||||||
Process.StartInfo.CreateNoWindow = true;
|
|
||||||
|
|
||||||
Process.StartInfo.RedirectStandardInput = rStandardInput;
|
|
||||||
Process.StartInfo.RedirectStandardOutput = rStandardOutput;
|
|
||||||
Process.StartInfo.RedirectStandardError = rStandardError;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Kill()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (IsWorking)
|
|
||||||
Process.Kill();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
IsKillFaulty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected async Task<string> RunProcessAsync(string filePath, string arguments)
|
|
||||||
{
|
|
||||||
var result = await ProcessEx.RunAsync(filePath, arguments);
|
|
||||||
return string.Join("", result.StandardOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ProcessHelpers
|
|
||||||
{
|
|
||||||
public static void RunProcess(string filePath, string arguments)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
public static async Task<string> RunProcessAsync(string fileName, string arguments)
|
|
||||||
{
|
|
||||||
var startInfo = new ProcessStartInfo(fileName, arguments);
|
|
||||||
startInfo.RedirectStandardOutput = true;
|
|
||||||
startInfo.RedirectStandardError = true;
|
|
||||||
|
|
||||||
var result = await ProcessEx.RunAsync(startInfo);
|
|
||||||
return string.Join("", result.StandardOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,12 +13,13 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Instances;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
namespace FFMpegCore.FFMPEG
|
||||||
{
|
{
|
||||||
public delegate void ConversionHandler(double percentage);
|
public delegate void ConversionHandler(double percentage);
|
||||||
|
|
||||||
public class FFMpeg : FFBase
|
public class FFMpeg
|
||||||
{
|
{
|
||||||
IArgumentBuilder ArgumentBuilder { get; set; }
|
IArgumentBuilder ArgumentBuilder { get; set; }
|
||||||
|
|
||||||
|
@ -457,6 +458,11 @@ public VideoInfo Convert(ArgumentContainer arguments)
|
||||||
return new VideoInfo(output);
|
return new VideoInfo(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the associated process is still alive/running.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsWorking => _instance.Started;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stops any current job that FFMpeg is running.
|
/// Stops any current job that FFMpeg is running.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -464,53 +470,31 @@ public void Stop()
|
||||||
{
|
{
|
||||||
if (IsWorking)
|
if (IsWorking)
|
||||||
{
|
{
|
||||||
Process.StandardInput.Write('q');
|
_instance.SendInput("q").Wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Private Members & Methods
|
#region Private Members & Methods
|
||||||
|
|
||||||
private string _ffmpegPath;
|
private readonly string _ffmpegPath;
|
||||||
private TimeSpan _totalTime;
|
private TimeSpan _totalTime;
|
||||||
|
|
||||||
private volatile StringBuilder _errorOutput = new StringBuilder();
|
private volatile StringBuilder _errorOutput = new StringBuilder();
|
||||||
|
|
||||||
private bool RunProcess(ArgumentContainer container, FileInfo output)
|
private bool RunProcess(ArgumentContainer container, FileInfo output)
|
||||||
{
|
{
|
||||||
var successState = true;
|
_instance?.Dispose();
|
||||||
|
var arguments = ArgumentBuilder.BuildArguments(container);
|
||||||
|
_instance = new Instance(_ffmpegPath, arguments);
|
||||||
|
_instance.DataReceived += OutputData;
|
||||||
|
var exitCode = _instance.BlockUntilFinished();
|
||||||
|
|
||||||
CreateProcess(ArgumentBuilder.BuildArguments(container), _ffmpegPath, true, rStandardError: true);
|
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
Process.Start();
|
throw new FFMpegException(FFMpegExceptionType.Process, _errorOutput);
|
||||||
Process.ErrorDataReceived += OutputData;
|
|
||||||
Process.BeginErrorReadLine();
|
|
||||||
Process.WaitForExit();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
successState = false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Process.Close();
|
|
||||||
|
|
||||||
if (File.Exists(output.FullName))
|
|
||||||
{
|
|
||||||
using var file = File.Open(output.FullName, FileMode.Open);
|
|
||||||
if (file.Length == 0)
|
|
||||||
{
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, _errorOutput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, _errorOutput);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return successState;
|
return exitCode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Cleanup(IEnumerable<string> pathList)
|
private void Cleanup(IEnumerable<string> pathList)
|
||||||
|
@ -524,23 +508,28 @@ private void Cleanup(IEnumerable<string> pathList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Regex _progressRegex = new Regex(@"\w\w:\w\w:\w\w", RegexOptions.Compiled);
|
private static readonly Regex ProgressRegex = new Regex(@"\w\w:\w\w:\w\w", RegexOptions.Compiled);
|
||||||
private void OutputData(object sender, DataReceivedEventArgs e)
|
private Instance _instance;
|
||||||
{
|
|
||||||
if (e.Data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_errorOutput.AppendLine(e.Data);
|
private void OutputData(object sender, (DataType Type, string Data) msg)
|
||||||
|
{
|
||||||
|
var (type, data) = msg;
|
||||||
|
|
||||||
|
if (data == null) return;
|
||||||
|
if (type == DataType.Error)
|
||||||
|
{
|
||||||
|
_errorOutput.AppendLine(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Trace.WriteLine(e.Data);
|
Trace.WriteLine(data);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (OnProgress == null || !IsWorking) return;
|
if (OnProgress == null) return;
|
||||||
|
if (!data.Contains("frame")) return;
|
||||||
|
|
||||||
if (!e.Data.Contains("frame")) return;
|
|
||||||
|
|
||||||
var match = _progressRegex.Match(e.Data);
|
var match = ProgressRegex.Match(data);
|
||||||
if (!match.Success) return;
|
if (!match.Success) return;
|
||||||
|
|
||||||
var processed = TimeSpan.Parse(match.Value, CultureInfo.InvariantCulture);
|
var processed = TimeSpan.Parse(match.Value, CultureInfo.InvariantCulture);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Instances;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
namespace FFMpegCore.FFMPEG
|
||||||
{
|
{
|
||||||
|
@ -44,13 +45,7 @@ private static string FFBinary(string name)
|
||||||
ffName = Path.Combine(target, ffName);
|
ffName = Path.Combine(target, ffName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var path = Path.Combine(Options.RootDirectory, ffName);
|
return Path.Combine(Options.RootDirectory, ffName);
|
||||||
|
|
||||||
if (!File.Exists(path))
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Dependency,
|
|
||||||
$"{name} cannot be found @ {path}");
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,18 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Instances;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
namespace FFMpegCore.FFMPEG
|
||||||
{
|
{
|
||||||
public sealed class FFProbe : FFBase
|
public sealed class FFProbe
|
||||||
{
|
{
|
||||||
static readonly double BITS_TO_MB = 1024 * 1024 * 8;
|
static readonly double BITS_TO_MB = 1024 * 1024 * 8;
|
||||||
|
private readonly string _ffprobePath;
|
||||||
|
|
||||||
public FFProbe(): base()
|
public FFProbe(): base()
|
||||||
{
|
{
|
||||||
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
||||||
|
|
||||||
_ffprobePath = FFMpegOptions.Options.FFProbeBinary;
|
_ffprobePath = FFMpegOptions.Options.FFProbeBinary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,9 @@ public Task<VideoInfo> ParseVideoInfoAsync(string source)
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
/// <returns>A video info object containing all details necessary.</returns>
|
||||||
public VideoInfo ParseVideoInfo(VideoInfo info)
|
public VideoInfo ParseVideoInfo(VideoInfo info)
|
||||||
{
|
{
|
||||||
var output = RunProcess(BuildFFProbeArguments(info));
|
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info));
|
||||||
|
instance.BlockUntilFinished();
|
||||||
|
var output = string.Join("", instance.OutputData);
|
||||||
return ParseVideoInfoInternal(info, output);
|
return ParseVideoInfoInternal(info, output);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -54,7 +57,9 @@ public VideoInfo ParseVideoInfo(VideoInfo info)
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
/// <returns>A video info object containing all details necessary.</returns>
|
||||||
public async Task<VideoInfo> ParseVideoInfoAsync(VideoInfo info)
|
public async Task<VideoInfo> ParseVideoInfoAsync(VideoInfo info)
|
||||||
{
|
{
|
||||||
var output = await RunProcessAsync(_ffprobePath, BuildFFProbeArguments(info));
|
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info));
|
||||||
|
await instance.FinishedRunning();
|
||||||
|
var output = string.Join("", instance.OutputData);
|
||||||
return ParseVideoInfoInternal(info, output);
|
return ParseVideoInfoInternal(info, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,35 +120,5 @@ private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Private Members & Methods
|
|
||||||
|
|
||||||
private readonly string _ffprobePath;
|
|
||||||
|
|
||||||
private string RunProcess(string args)
|
|
||||||
{
|
|
||||||
CreateProcess(args, _ffprobePath, rStandardOutput: true);
|
|
||||||
|
|
||||||
string output;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Process.Start();
|
|
||||||
output = Process.StandardOutput.ReadToEnd();
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
output = "";
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Process.WaitForExit();
|
|
||||||
Process.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Instances" Version="1.3.3" />
|
<PackageReference Include="Instances" Version="1.4.0" />
|
||||||
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
<PackageReference Include="Microsoft.CSharp" Version="4.5.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||||
<PackageReference Include="RunProcessAsTask" Version="1.2.4" />
|
<PackageReference Include="RunProcessAsTask" Version="1.2.4" />
|
||||||
|
|
Loading…
Reference in a new issue