mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
parent
74c1222b9c
commit
2c2ceacb41
10 changed files with 389 additions and 38 deletions
89
FFMpegCore/Extend/BitmapVideoFrameWrapper.cs
Normal file
89
FFMpegCore/Extend/BitmapVideoFrameWrapper.cs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
using FFMpegCore.FFMPEG.Pipes;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Extend
|
||||||
|
{
|
||||||
|
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||||
|
{
|
||||||
|
public int Width => Source.Width;
|
||||||
|
|
||||||
|
public int Height => Source.Height;
|
||||||
|
|
||||||
|
public string Format { get; private set; }
|
||||||
|
|
||||||
|
public Bitmap Source { get; private set; }
|
||||||
|
|
||||||
|
public BitmapVideoFrameWrapper(Bitmap bitmap)
|
||||||
|
{
|
||||||
|
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||||
|
Format = ConvertStreamFormat(bitmap.PixelFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Serialize(IInputPipe pipe)
|
||||||
|
{
|
||||||
|
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var buffer = new byte[data.Stride * data.Height];
|
||||||
|
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||||
|
pipe.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Source.UnlockBits(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SerializeAsync(IInputPipe pipe)
|
||||||
|
{
|
||||||
|
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var buffer = new byte[data.Stride * data.Height];
|
||||||
|
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||||
|
await pipe.WriteAsync(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Source.UnlockBits(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Source.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ConvertStreamFormat(PixelFormat fmt)
|
||||||
|
{
|
||||||
|
switch (fmt)
|
||||||
|
{
|
||||||
|
case PixelFormat.Format16bppGrayScale:
|
||||||
|
return "gray16le";
|
||||||
|
case PixelFormat.Format16bppRgb565:
|
||||||
|
return "bgr565le";
|
||||||
|
case PixelFormat.Format24bppRgb:
|
||||||
|
return "rgb24";
|
||||||
|
case PixelFormat.Format32bppArgb:
|
||||||
|
return "rgba";
|
||||||
|
case PixelFormat.Format32bppPArgb:
|
||||||
|
//This is not really same as argb32
|
||||||
|
return "argb";
|
||||||
|
case PixelFormat.Format32bppRgb:
|
||||||
|
return "rgba";
|
||||||
|
case PixelFormat.Format48bppRgb:
|
||||||
|
return "rgb48le";
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ public ArgumentContainer(params Argument[] arguments)
|
||||||
{
|
{
|
||||||
_args = new Dictionary<Type, Argument>();
|
_args = new Dictionary<Type, Argument>();
|
||||||
|
|
||||||
foreach(var argument in arguments)
|
foreach (var argument in arguments)
|
||||||
{
|
{
|
||||||
Add(argument);
|
Add(argument);
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ public bool TryGetArgument<T>(out T output)
|
||||||
{
|
{
|
||||||
if (_args.TryGetValue(typeof(T), out var arg))
|
if (_args.TryGetValue(typeof(T), out var arg))
|
||||||
{
|
{
|
||||||
output = (T) arg;
|
output = (T)arg;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ public bool Contains(KeyValuePair<Type, Argument> item)
|
||||||
/// <param name="value">Argument that should be added to collection</param>
|
/// <param name="value">Argument that should be added to collection</param>
|
||||||
public void Add(params Argument[] values)
|
public void Add(params Argument[] values)
|
||||||
{
|
{
|
||||||
foreach(var value in values)
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
_args.Add(value.GetType(), value);
|
_args.Add(value.GetType(), value);
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,9 @@ public void Add(params Argument[] values)
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool ContainsInputOutput()
|
public bool ContainsInputOutput()
|
||||||
{
|
{
|
||||||
return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument))) ||
|
return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) ||
|
||||||
(!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument))))
|
(!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) ||
|
||||||
|
(!ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && ContainsKey(typeof(InputPipeArgument))))
|
||||||
&& ContainsKey(typeof(OutputArgument));
|
&& ContainsKey(typeof(OutputArgument));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
74
FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs
Normal file
74
FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using FFMpegCore.FFMPEG.Pipes;
|
||||||
|
using Instances;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.FFMPEG.Argument
|
||||||
|
{
|
||||||
|
public class InputPipeArgument : Argument, IInputPipe
|
||||||
|
{
|
||||||
|
public string PipeName { get; private set; }
|
||||||
|
public IPipeSource Source { get; private set; }
|
||||||
|
|
||||||
|
private NamedPipeServerStream pipe;
|
||||||
|
|
||||||
|
public InputPipeArgument(IPipeSource source)
|
||||||
|
{
|
||||||
|
Source = source;
|
||||||
|
PipeName = "FFMpegCore_Pipe_" + Guid.NewGuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenPipe()
|
||||||
|
{
|
||||||
|
if (pipe != null)
|
||||||
|
throw new InvalidOperationException("Pipe already has been opened");
|
||||||
|
|
||||||
|
pipe = new NamedPipeServerStream(PipeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClosePipe()
|
||||||
|
{
|
||||||
|
pipe?.Dispose();
|
||||||
|
pipe = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if(pipe == null)
|
||||||
|
throw new InvalidOperationException("Pipe shouled be opened before");
|
||||||
|
|
||||||
|
pipe.Write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteAsync(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (pipe == null)
|
||||||
|
throw new InvalidOperationException("Pipe shouled be opened before");
|
||||||
|
|
||||||
|
return pipe.WriteAsync(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetStringValue()
|
||||||
|
{
|
||||||
|
return $"-y {Source.GetFormat()} -i \\\\.\\pipe\\{PipeName}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FlushPipe()
|
||||||
|
{
|
||||||
|
pipe.WaitForConnection();
|
||||||
|
Source.FlushData(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task FlushPipeAsync()
|
||||||
|
{
|
||||||
|
await pipe.WaitForConnectionAsync();
|
||||||
|
await Source.FlushDataAsync(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -65,16 +65,16 @@ public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, Tim
|
||||||
{
|
{
|
||||||
if (size.Value.Width == 0)
|
if (size.Value.Width == 0)
|
||||||
{
|
{
|
||||||
var ratio = source.Width / (double) size.Value.Width;
|
var ratio = source.Width / (double)size.Value.Width;
|
||||||
|
|
||||||
size = new Size((int) (source.Width * ratio), (int) (source.Height * ratio));
|
size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size.Value.Height == 0)
|
if (size.Value.Height == 0)
|
||||||
{
|
{
|
||||||
var ratio = source.Height / (double) size.Value.Height;
|
var ratio = source.Height / (double)size.Value.Height;
|
||||||
|
|
||||||
size = new Size((int) (source.Width * ratio), (int) (source.Height * ratio));
|
size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, Tim
|
||||||
output.Refresh();
|
output.Refresh();
|
||||||
|
|
||||||
Bitmap result;
|
Bitmap result;
|
||||||
using (var bmp = (Bitmap) Image.FromFile(output.FullName))
|
using (var bmp = (Bitmap)Image.FromFile(output.FullName))
|
||||||
{
|
{
|
||||||
using var ms = new MemoryStream();
|
using var ms = new MemoryStream();
|
||||||
bmp.Save(ms, ImageFormat.Png);
|
bmp.Save(ms, ImageFormat.Png);
|
||||||
|
@ -135,8 +135,8 @@ public VideoInfo Convert(
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type));
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type));
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||||
|
|
||||||
var scale = VideoSize.Original == size ? 1 : (double) source.Height / (int) size;
|
var scale = VideoSize.Original == size ? 1 : (double)source.Height / (int)size;
|
||||||
var outputSize = new Size((int) (source.Width / scale), (int) (source.Height / scale));
|
var outputSize = new Size((int)(source.Width / scale), (int)(source.Height / scale));
|
||||||
|
|
||||||
if (outputSize.Width % 2 != 0)
|
if (outputSize.Width % 2 != 0)
|
||||||
outputSize.Width += 1;
|
outputSize.Width += 1;
|
||||||
|
@ -384,6 +384,7 @@ public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output,
|
||||||
public VideoInfo Convert(ArgumentContainer arguments)
|
public VideoInfo Convert(ArgumentContainer arguments)
|
||||||
{
|
{
|
||||||
var (sources, output) = GetInputOutput(arguments);
|
var (sources, output) = GetInputOutput(arguments);
|
||||||
|
if (sources != null)
|
||||||
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
||||||
|
|
||||||
if (!RunProcess(arguments, output))
|
if (!RunProcess(arguments, output))
|
||||||
|
@ -395,6 +396,7 @@ public VideoInfo Convert(ArgumentContainer arguments)
|
||||||
public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments)
|
public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments)
|
||||||
{
|
{
|
||||||
var (sources, output) = GetInputOutput(arguments);
|
var (sources, output) = GetInputOutput(arguments);
|
||||||
|
if (sources != null)
|
||||||
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
||||||
|
|
||||||
if (!await RunProcessAsync(arguments, output))
|
if (!await RunProcessAsync(arguments, output))
|
||||||
|
@ -406,12 +408,14 @@ public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments)
|
||||||
|
|
||||||
private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentContainer arguments)
|
private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentContainer arguments)
|
||||||
{
|
{
|
||||||
var output = ((OutputArgument) arguments[typeof(OutputArgument)]).GetAsFileInfo();
|
var output = ((OutputArgument)arguments[typeof(OutputArgument)]).GetAsFileInfo();
|
||||||
VideoInfo[] sources;
|
VideoInfo[] sources;
|
||||||
if (arguments.TryGetArgument<InputArgument>(out var input))
|
if (arguments.TryGetArgument<InputArgument>(out var input))
|
||||||
sources = input.GetAsVideoInfo();
|
sources = input.GetAsVideoInfo();
|
||||||
else if (arguments.TryGetArgument<ConcatArgument>(out var concat))
|
else if (arguments.TryGetArgument<ConcatArgument>(out var concat))
|
||||||
sources = concat.GetAsVideoInfo();
|
sources = concat.GetAsVideoInfo();
|
||||||
|
else if (arguments.TryGetArgument<InputPipeArgument>(out var pipe))
|
||||||
|
sources = null;
|
||||||
else
|
else
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found");
|
throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found");
|
||||||
return (sources, output);
|
return (sources, output);
|
||||||
|
@ -442,23 +446,68 @@ private bool RunProcess(ArgumentContainer container, FileInfo output)
|
||||||
{
|
{
|
||||||
_instance?.Dispose();
|
_instance?.Dispose();
|
||||||
var arguments = ArgumentBuilder.BuildArguments(container);
|
var arguments = ArgumentBuilder.BuildArguments(container);
|
||||||
|
var exitCode = -1;
|
||||||
|
|
||||||
|
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
|
||||||
|
{
|
||||||
|
inputPipeArgument.OpenPipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_instance = new Instance(_ffmpegPath, arguments);
|
_instance = new Instance(_ffmpegPath, arguments);
|
||||||
_instance.DataReceived += OutputData;
|
_instance.DataReceived += OutputData;
|
||||||
var exitCode = _instance.BlockUntilFinished();
|
|
||||||
|
if (inputPipeArgument != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var task = _instance.FinishedRunning();
|
||||||
|
inputPipeArgument.FlushPipe();
|
||||||
|
inputPipeArgument.ClosePipe();
|
||||||
|
task.Wait();
|
||||||
|
exitCode = task.Result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exitCode = _instance.BlockUntilFinished();
|
||||||
|
}
|
||||||
|
|
||||||
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
|
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
||||||
|
|
||||||
return exitCode == 0;
|
return exitCode == 0;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (inputPipeArgument != null)
|
||||||
|
inputPipeArgument.ClosePipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo output)
|
private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo output)
|
||||||
{
|
{
|
||||||
_instance?.Dispose();
|
_instance?.Dispose();
|
||||||
var arguments = ArgumentBuilder.BuildArguments(container);
|
var arguments = ArgumentBuilder.BuildArguments(container);
|
||||||
|
|
||||||
|
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
|
||||||
|
{
|
||||||
|
inputPipeArgument.OpenPipe();
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
_instance = new Instance(_ffmpegPath, arguments);
|
_instance = new Instance(_ffmpegPath, arguments);
|
||||||
_instance.DataReceived += OutputData;
|
_instance.DataReceived += OutputData;
|
||||||
|
|
||||||
|
if (inputPipeArgument != null)
|
||||||
|
{
|
||||||
|
await inputPipeArgument.FlushPipeAsync();
|
||||||
|
}
|
||||||
var exitCode = await _instance.FinishedRunning();
|
var exitCode = await _instance.FinishedRunning();
|
||||||
|
|
||||||
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
|
if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)
|
||||||
|
@ -466,6 +515,14 @@ private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo o
|
||||||
|
|
||||||
return exitCode == 0;
|
return exitCode == 0;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (inputPipeArgument != null)
|
||||||
|
{
|
||||||
|
inputPipeArgument.ClosePipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Cleanup(IEnumerable<string> pathList)
|
private void Cleanup(IEnumerable<string> pathList)
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,7 +47,7 @@ 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 instance = new Instance(_ffprobePath, BuildFFProbeArguments(info)) {DataBufferCapacity = _outputCapacity};
|
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info.FullName)) {DataBufferCapacity = _outputCapacity};
|
||||||
instance.BlockUntilFinished();
|
instance.BlockUntilFinished();
|
||||||
var output = string.Join("", instance.OutputData);
|
var output = string.Join("", instance.OutputData);
|
||||||
return ParseVideoInfoInternal(info, output);
|
return ParseVideoInfoInternal(info, output);
|
||||||
|
@ -59,14 +59,14 @@ 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 instance = new Instance(_ffprobePath, BuildFFProbeArguments(info)) {DataBufferCapacity = _outputCapacity};
|
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info.FullName)) {DataBufferCapacity = _outputCapacity};
|
||||||
await instance.FinishedRunning();
|
await instance.FinishedRunning();
|
||||||
var output = string.Join("", instance.OutputData);
|
var output = string.Join("", instance.OutputData);
|
||||||
return ParseVideoInfoInternal(info, output);
|
return ParseVideoInfoInternal(info, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildFFProbeArguments(VideoInfo info) =>
|
private static string BuildFFProbeArguments(string fullPath) =>
|
||||||
$"-v quiet -print_format json -show_streams \"{info.FullName}\"";
|
$"-v quiet -print_format json -show_streams \"{fullPath}\"";
|
||||||
|
|
||||||
private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
|
private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
|
||||||
{
|
{
|
||||||
|
@ -133,5 +133,21 @@ private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal FFMpegStreamMetadata GetMetadata(string path)
|
||||||
|
{
|
||||||
|
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity };
|
||||||
|
instance.BlockUntilFinished();
|
||||||
|
var output = string.Join("", instance.OutputData);
|
||||||
|
return JsonConvert.DeserializeObject<FFMpegStreamMetadata>(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal async Task<FFMpegStreamMetadata> GetMetadataAsync(string path)
|
||||||
|
{
|
||||||
|
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity };
|
||||||
|
await instance.FinishedRunning();
|
||||||
|
var output = string.Join("", instance.OutputData);
|
||||||
|
return JsonConvert.DeserializeObject<FFMpegStreamMetadata>(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
FFMpegCore/FFMPEG/Pipes/IInputPipe.cs
Normal file
13
FFMpegCore/FFMPEG/Pipes/IInputPipe.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.FFMPEG.Pipes
|
||||||
|
{
|
||||||
|
public interface IInputPipe
|
||||||
|
{
|
||||||
|
void Write(byte[] buffer, int offset, int count);
|
||||||
|
Task WriteAsync(byte[] buffer, int offset, int count);
|
||||||
|
}
|
||||||
|
}
|
15
FFMpegCore/FFMPEG/Pipes/IPipeSource.cs
Normal file
15
FFMpegCore/FFMPEG/Pipes/IPipeSource.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using FFMpegCore.FFMPEG.Argument;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.FFMPEG.Pipes
|
||||||
|
{
|
||||||
|
public interface IPipeSource
|
||||||
|
{
|
||||||
|
string GetFormat();
|
||||||
|
void FlushData(IInputPipe pipe);
|
||||||
|
Task FlushDataAsync(IInputPipe pipe);
|
||||||
|
}
|
||||||
|
}
|
17
FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs
Normal file
17
FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.FFMPEG.Pipes
|
||||||
|
{
|
||||||
|
public interface IVideoFrame
|
||||||
|
{
|
||||||
|
int Width { get; }
|
||||||
|
int Height { get; }
|
||||||
|
string Format { get; }
|
||||||
|
|
||||||
|
void Serialize(IInputPipe pipe);
|
||||||
|
Task SerializeAsync(IInputPipe pipe);
|
||||||
|
}
|
||||||
|
}
|
67
FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs
Normal file
67
FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
using FFMpegCore.FFMPEG.Argument;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.FFMPEG.Pipes
|
||||||
|
{
|
||||||
|
public class RawVideoPipeSource : IPipeSource
|
||||||
|
{
|
||||||
|
public string StreamFormat { get; set; }
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public int FrameRate { get; set; } = 25;
|
||||||
|
private IEnumerator<IVideoFrame> framesEnumerator;
|
||||||
|
|
||||||
|
public RawVideoPipeSource(IEnumerator<IVideoFrame> framesEnumerator)
|
||||||
|
{
|
||||||
|
this.framesEnumerator = framesEnumerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RawVideoPipeSource(IEnumerable<IVideoFrame> framesEnumerator) : this(framesEnumerator.GetEnumerator()) { }
|
||||||
|
|
||||||
|
public string GetFormat()
|
||||||
|
{
|
||||||
|
//see input format references https://lists.ffmpeg.org/pipermail/ffmpeg-user/2012-July/007742.html
|
||||||
|
if (framesEnumerator.Current == null)
|
||||||
|
{
|
||||||
|
if (!framesEnumerator.MoveNext())
|
||||||
|
throw new InvalidOperationException("Enumerator is empty, unable to get frame");
|
||||||
|
|
||||||
|
StreamFormat = framesEnumerator.Current.Format;
|
||||||
|
Width = framesEnumerator.Current.Width;
|
||||||
|
Height = framesEnumerator.Current.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FlushData(IInputPipe pipe)
|
||||||
|
{
|
||||||
|
if (framesEnumerator.Current != null)
|
||||||
|
{
|
||||||
|
framesEnumerator.Current.Serialize(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (framesEnumerator.MoveNext())
|
||||||
|
{
|
||||||
|
framesEnumerator.Current.Serialize(pipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task FlushDataAsync(IInputPipe pipe)
|
||||||
|
{
|
||||||
|
if (framesEnumerator.Current != null)
|
||||||
|
{
|
||||||
|
await framesEnumerator.Current.SerializeAsync(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (framesEnumerator.MoveNext())
|
||||||
|
{
|
||||||
|
await framesEnumerator.Current.SerializeAsync(pipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
using FFMpegCore.FFMPEG;
|
using FFMpegCore.FFMPEG;
|
||||||
|
using FFMpegCore.FFMPEG.Argument;
|
||||||
|
using FFMpegCore.FFMPEG.Pipes;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue