From 13d5e3d1910868610c3add8af6d24f69879ef43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Mon, 27 Apr 2020 19:23:31 +0300 Subject: [PATCH 01/27] Added input piping --- FFMpegCore/Extend/BitmapVideoFrameWrapper.cs | 89 ++++++++++++++ .../FFMPEG/Argument/ArgumentContainer.cs | 11 +- .../Argument/Atoms/InputPipeArgument.cs | 74 +++++++++++ FFMpegCore/FFMPEG/FFMpeg.cs | 115 +++++++++++++----- FFMpegCore/FFMPEG/FFProbe.cs | 24 +++- FFMpegCore/FFMPEG/Pipes/IInputPipe.cs | 13 ++ FFMpegCore/FFMPEG/Pipes/IPipeSource.cs | 15 +++ FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs | 17 +++ FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs | 67 ++++++++++ FFMpegCore/VideoInfo.cs | 2 + 10 files changed, 389 insertions(+), 38 deletions(-) create mode 100644 FFMpegCore/Extend/BitmapVideoFrameWrapper.cs create mode 100644 FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/IInputPipe.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/IPipeSource.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs new file mode 100644 index 0000000..8b86461 --- /dev/null +++ b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs @@ -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}"); + } + } + } +} diff --git a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs index d831904..d2e5532 100644 --- a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs +++ b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs @@ -15,7 +15,7 @@ public ArgumentContainer(params Argument[] arguments) { _args = new Dictionary(); - foreach(var argument in arguments) + foreach (var argument in arguments) { Add(argument); } @@ -28,7 +28,7 @@ public bool TryGetArgument(out T output) { if (_args.TryGetValue(typeof(T), out var arg)) { - output = (T) arg; + output = (T)arg; return true; } @@ -90,7 +90,7 @@ public bool Contains(KeyValuePair item) /// Argument that should be added to collection public void Add(params Argument[] values) { - foreach(var value in values) + foreach (var value in values) { _args.Add(value.GetType(), value); } @@ -102,8 +102,9 @@ public void Add(params Argument[] values) /// public bool ContainsInputOutput() { - return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument))) || - (!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument)))) + return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) || + (!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) || + (!ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && ContainsKey(typeof(InputPipeArgument)))) && ContainsKey(typeof(OutputArgument)); } diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs new file mode 100644 index 0000000..1d715a1 --- /dev/null +++ b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs @@ -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); + } + } +} diff --git a/FFMpegCore/FFMPEG/FFMpeg.cs b/FFMpegCore/FFMPEG/FFMpeg.cs index d98f24b..f1bd402 100644 --- a/FFMpegCore/FFMPEG/FFMpeg.cs +++ b/FFMpegCore/FFMPEG/FFMpeg.cs @@ -65,16 +65,16 @@ public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, Tim { 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) { - 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(); Bitmap result; - using (var bmp = (Bitmap) Image.FromFile(output.FullName)) + using (var bmp = (Bitmap)Image.FromFile(output.FullName)) { using var ms = new MemoryStream(); bmp.Save(ms, ImageFormat.Png); @@ -135,8 +135,8 @@ public VideoInfo Convert( 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)); + 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; @@ -279,7 +279,7 @@ public VideoInfo JoinImageSequence(FileInfo output, double frameRate = 30, param throw new FFMpegException(FFMpegExceptionType.Operation, "Could not join the provided image sequence."); } - + return new VideoInfo(output); } finally @@ -380,11 +380,12 @@ public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output, new OutputArgument(output) )); } - + public VideoInfo Convert(ArgumentContainer arguments) { var (sources, output) = GetInputOutput(arguments); - _totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds)); + if (sources != null) + _totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds)); if (!RunProcess(arguments, output)) throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio."); @@ -395,7 +396,8 @@ public VideoInfo Convert(ArgumentContainer arguments) public async Task ConvertAsync(ArgumentContainer arguments) { var (sources, output) = GetInputOutput(arguments); - _totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds)); + if (sources != null) + _totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds)); if (!await RunProcessAsync(arguments, output)) throw new FFMpegException(FFMpegExceptionType.Operation, "Could not replace the video audio."); @@ -406,12 +408,14 @@ public async Task ConvertAsync(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; if (arguments.TryGetArgument(out var input)) sources = input.GetAsVideoInfo(); else if (arguments.TryGetArgument(out var concat)) sources = concat.GetAsVideoInfo(); + else if (arguments.TryGetArgument(out var pipe)) + sources = null; else throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found"); return (sources, output); @@ -442,29 +446,82 @@ private bool RunProcess(ArgumentContainer container, FileInfo output) { _instance?.Dispose(); var arguments = ArgumentBuilder.BuildArguments(container); - - _instance = new Instance(_ffmpegPath, arguments); - _instance.DataReceived += OutputData; - var exitCode = _instance.BlockUntilFinished(); - - if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0) - throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); + var exitCode = -1; - return exitCode == 0; + if (container.TryGetArgument(out var inputPipeArgument)) + { + inputPipeArgument.OpenPipe(); + } + + try + { + _instance = new Instance(_ffmpegPath, arguments); + _instance.DataReceived += OutputData; + + 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) + throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); + + return exitCode == 0; + } + finally + { + if (inputPipeArgument != null) + inputPipeArgument.ClosePipe(); + } } private async Task RunProcessAsync(ArgumentContainer container, FileInfo output) { _instance?.Dispose(); var arguments = ArgumentBuilder.BuildArguments(container); - - _instance = new Instance(_ffmpegPath, arguments); - _instance.DataReceived += OutputData; - var exitCode = await _instance.FinishedRunning(); - - if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0) - throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); - return exitCode == 0; + if (container.TryGetArgument(out var inputPipeArgument)) + { + inputPipeArgument.OpenPipe(); + } + try + { + + _instance = new Instance(_ffmpegPath, arguments); + _instance.DataReceived += OutputData; + + if (inputPipeArgument != null) + { + await inputPipeArgument.FlushPipeAsync(); + } + var exitCode = await _instance.FinishedRunning(); + + if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0) + throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); + + return exitCode == 0; + } + finally + { + if (inputPipeArgument != null) + { + inputPipeArgument.ClosePipe(); + } + } } private void Cleanup(IEnumerable pathList) @@ -487,7 +544,7 @@ private void OutputData(object sender, (DataType Type, string Data) msg) Trace.WriteLine(msg.Data); #endif if (OnProgress == null) return; - + var match = ProgressRegex.Match(msg.Data); if (!match.Success) return; diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs index 52cb0b8..827ea7f 100644 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ b/FFMpegCore/FFMPEG/FFProbe.cs @@ -47,7 +47,7 @@ public Task ParseVideoInfoAsync(string source) /// A video info object containing all details necessary. 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(); var output = string.Join("", instance.OutputData); return ParseVideoInfoInternal(info, output); @@ -59,14 +59,14 @@ public VideoInfo ParseVideoInfo(VideoInfo info) /// A video info object containing all details necessary. public async Task 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(); var output = string.Join("", instance.OutputData); return ParseVideoInfoInternal(info, output); } - private static string BuildFFProbeArguments(VideoInfo info) => - $"-v quiet -print_format json -show_streams \"{info.FullName}\""; + private static string BuildFFProbeArguments(string fullPath) => + $"-v quiet -print_format json -show_streams \"{fullPath}\""; private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput) { @@ -133,5 +133,21 @@ private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput) 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(output); + } + + internal async Task GetMetadataAsync(string path) + { + var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity }; + await instance.FinishedRunning(); + var output = string.Join("", instance.OutputData); + return JsonConvert.DeserializeObject(output); + } } } diff --git a/FFMpegCore/FFMPEG/Pipes/IInputPipe.cs b/FFMpegCore/FFMPEG/Pipes/IInputPipe.cs new file mode 100644 index 0000000..d31d047 --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/IInputPipe.cs @@ -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); + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs new file mode 100644 index 0000000..943bec6 --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs @@ -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); + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs b/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs new file mode 100644 index 0000000..2458c30 --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs @@ -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); + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs new file mode 100644 index 0000000..586ec0e --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs @@ -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 framesEnumerator; + + public RawVideoPipeSource(IEnumerator framesEnumerator) + { + this.framesEnumerator = framesEnumerator; + } + + public RawVideoPipeSource(IEnumerable 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); + } + } + + } +} diff --git a/FFMpegCore/VideoInfo.cs b/FFMpegCore/VideoInfo.cs index b6f97d7..f56145c 100644 --- a/FFMpegCore/VideoInfo.cs +++ b/FFMpegCore/VideoInfo.cs @@ -1,4 +1,6 @@ using FFMpegCore.FFMPEG; +using FFMpegCore.FFMPEG.Argument; +using FFMpegCore.FFMPEG.Pipes; using System; using System.IO; From 3c3b11cec6b9032c905fc99d2009549c6306fd41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Mon, 27 Apr 2020 19:24:26 +0300 Subject: [PATCH 02/27] Added input piping tests --- FFMpegCore.Test/BitmapSources.cs | 219 +++++++++++++++++++++++++ FFMpegCore.Test/FFMpegCore.Test.csproj | 2 +- FFMpegCore.Test/VideoTest.cs | 133 +++++++++++++-- 3 files changed, 340 insertions(+), 14 deletions(-) create mode 100644 FFMpegCore.Test/BitmapSources.cs diff --git a/FFMpegCore.Test/BitmapSources.cs b/FFMpegCore.Test/BitmapSources.cs new file mode 100644 index 0000000..33c8035 --- /dev/null +++ b/FFMpegCore.Test/BitmapSources.cs @@ -0,0 +1,219 @@ +using FFMpegCore.Extend; +using FFMpegCore.FFMPEG.Pipes; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Numerics; +using System.Text; + +namespace FFMpegCore.Test +{ + static class BitmapSource + { + public static IEnumerable CreateBitmaps(int count, PixelFormat fmt, int w, int h) + { + for (int i = 0; i < count; i++) + { + using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f)) + { + yield return frame; + } + } + } + + private static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset) + { + var bitmap = new Bitmap(w, h, fmt); + + offset = offset * index; + + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + { + var nx = x * scaleNoise + offset; + var ny = y * scaleNoise + offset; + + var value = (int)((Perlin.Noise(nx, ny) + 1.0f) / 2.0f * 255); + + var color = Color.FromArgb(value, value, value); + + bitmap.SetPixel(x, y, color); + } + + return new BitmapVideoFrameWrapper(bitmap); + } + + // + // Perlin noise generator for Unity + // Keijiro Takahashi, 2013, 2015 + // https://github.com/keijiro/PerlinNoise + // + // Based on the original implementation by Ken Perlin + // http://mrl.nyu.edu/~perlin/noise/ + // + static class Perlin + { + #region Noise functions + + public static float Noise(float x) + { + var X = (int)MathF.Floor(x) & 0xff; + x -= MathF.Floor(x); + var u = Fade(x); + return Lerp(u, Grad(perm[X], x), Grad(perm[X + 1], x - 1)) * 2; + } + + public static float Noise(float x, float y) + { + var X = (int)MathF.Floor(x) & 0xff; + var Y = (int)MathF.Floor(y) & 0xff; + x -= MathF.Floor(x); + y -= MathF.Floor(y); + var u = Fade(x); + var v = Fade(y); + var A = (perm[X] + Y) & 0xff; + var B = (perm[X + 1] + Y) & 0xff; + return Lerp(v, Lerp(u, Grad(perm[A], x, y), Grad(perm[B], x - 1, y)), + Lerp(u, Grad(perm[A + 1], x, y - 1), Grad(perm[B + 1], x - 1, y - 1))); + } + + public static float Noise(Vector2 coord) + { + return Noise(coord.X, coord.Y); + } + + public static float Noise(float x, float y, float z) + { + var X = (int)MathF.Floor(x) & 0xff; + var Y = (int)MathF.Floor(y) & 0xff; + var Z = (int)MathF.Floor(z) & 0xff; + x -= MathF.Floor(x); + y -= MathF.Floor(y); + z -= MathF.Floor(z); + var u = Fade(x); + var v = Fade(y); + var w = Fade(z); + var A = (perm[X] + Y) & 0xff; + var B = (perm[X + 1] + Y) & 0xff; + var AA = (perm[A] + Z) & 0xff; + var BA = (perm[B] + Z) & 0xff; + var AB = (perm[A + 1] + Z) & 0xff; + var BB = (perm[B + 1] + Z) & 0xff; + return Lerp(w, Lerp(v, Lerp(u, Grad(perm[AA], x, y, z), Grad(perm[BA], x - 1, y, z)), + Lerp(u, Grad(perm[AB], x, y - 1, z), Grad(perm[BB], x - 1, y - 1, z))), + Lerp(v, Lerp(u, Grad(perm[AA + 1], x, y, z - 1), Grad(perm[BA + 1], x - 1, y, z - 1)), + Lerp(u, Grad(perm[AB + 1], x, y - 1, z - 1), Grad(perm[BB + 1], x - 1, y - 1, z - 1)))); + } + + public static float Noise(Vector3 coord) + { + return Noise(coord.X, coord.Y, coord.Z); + } + + #endregion + + #region fBm functions + + public static float Fbm(float x, int octave) + { + var f = 0.0f; + var w = 0.5f; + for (var i = 0; i < octave; i++) + { + f += w * Noise(x); + x *= 2.0f; + w *= 0.5f; + } + return f; + } + + public static float Fbm(Vector2 coord, int octave) + { + var f = 0.0f; + var w = 0.5f; + for (var i = 0; i < octave; i++) + { + f += w * Noise(coord); + coord *= 2.0f; + w *= 0.5f; + } + return f; + } + + public static float Fbm(float x, float y, int octave) + { + return Fbm(new Vector2(x, y), octave); + } + + public static float Fbm(Vector3 coord, int octave) + { + var f = 0.0f; + var w = 0.5f; + for (var i = 0; i < octave; i++) + { + f += w * Noise(coord); + coord *= 2.0f; + w *= 0.5f; + } + return f; + } + + public static float Fbm(float x, float y, float z, int octave) + { + return Fbm(new Vector3(x, y, z), octave); + } + + #endregion + + #region Private functions + + static float Fade(float t) + { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + static float Lerp(float t, float a, float b) + { + return a + t * (b - a); + } + + static float Grad(int hash, float x) + { + return (hash & 1) == 0 ? x : -x; + } + + static float Grad(int hash, float x, float y) + { + return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y); + } + + static float Grad(int hash, float x, float y, float z) + { + var h = hash & 15; + var u = h < 8 ? x : y; + var v = h < 4 ? y : (h == 12 || h == 14 ? x : z); + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + static int[] perm = { + 151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180, + 151 + }; + + #endregion + } + } +} diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index 8e749dd..1c729a9 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -12,7 +12,7 @@ - Always + PreserveNewest diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index eff0d44..5de10b1 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -1,6 +1,7 @@ using FFMpegCore.Enums; using FFMpegCore.FFMPEG.Argument; using FFMpegCore.FFMPEG.Enums; +using FFMpegCore.FFMPEG.Pipes; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -70,7 +71,7 @@ public void Convert(VideoType type, ArgumentContainer container) { var input = VideoInfo.FromFileInfo(Input); - var arguments = new ArgumentContainer {new InputArgument(input)}; + var arguments = new ArgumentContainer { new InputArgument(input) }; foreach (var arg in container) { arguments.Add(arg.Value); @@ -114,6 +115,64 @@ public void Convert(VideoType type, ArgumentContainer container) } } + public void ConvertFromPipe(VideoType type, ArgumentContainer container) + { + ConvertFromPipe(type, container, PixelFormat.Format24bppRgb); + ConvertFromPipe(type, container, PixelFormat.Format32bppArgb); + ConvertFromPipe(type, container, PixelFormat.Format48bppRgb); + } + + public void ConvertFromPipe(VideoType type, ArgumentContainer container, PixelFormat fmt) + { + var output = Input.OutputLocation(type); + + try + { + var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, fmt, 256, 256)); + var arguments = new ArgumentContainer { new InputPipeArgument(videoFramesSource) }; + foreach (var arg in container) + { + arguments.Add(arg.Value); + } + arguments.Add(new OutputArgument(output)); + + var scaling = container.Find(); + + Encoder.Convert(arguments); + + var outputVideo = new VideoInfo(output.FullName); + + Assert.IsTrue(File.Exists(output.FullName)); + + if (scaling == null) + { + Assert.AreEqual(outputVideo.Width, videoFramesSource.Width); + Assert.AreEqual(outputVideo.Height, videoFramesSource.Height); + } + else + { + if (scaling.Value.Width != -1) + { + Assert.AreEqual(outputVideo.Width, scaling.Value.Width); + } + + if (scaling.Value.Height != -1) + { + Assert.AreEqual(outputVideo.Height, scaling.Value.Height); + } + + Assert.AreNotEqual(outputVideo.Width, videoFramesSource.Width); + Assert.AreNotEqual(outputVideo.Height, videoFramesSource.Height); + } + } + finally + { + if (File.Exists(output.FullName)) + File.Delete(output.FullName); + } + + } + [TestMethod] public void Video_ToMP4() { @@ -123,10 +182,17 @@ public void Video_ToMP4() [TestMethod] public void Video_ToMP4_Args() { - var container = new ArgumentContainer {new VideoCodecArgument(VideoCodec.LibX264)}; + var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) }; Convert(VideoType.Mp4, container); } + [TestMethod] + public void Video_ToMP4_Args_Pipe() + { + var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) }; + ConvertFromPipe(VideoType.Mp4, container); + } + [TestMethod] public void Video_ToTS() { @@ -145,6 +211,17 @@ public void Video_ToTS_Args() Convert(VideoType.Ts, container); } + [TestMethod] + public void Video_ToTS_Args_Pipe() + { + var container = new ArgumentContainer + { + new CopyArgument(), + new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB), + new ForceFormatArgument(VideoCodec.MpegTs) + }; + ConvertFromPipe(VideoType.Ts, container); + } [TestMethod] public void Video_ToOGV_Resize() @@ -157,12 +234,23 @@ public void Video_ToOGV_Resize_Args() { var container = new ArgumentContainer { - new ScaleArgument(VideoSize.Ed), + new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora) }; Convert(VideoType.Ogv, container); } + [TestMethod] + public void Video_ToOGV_Resize_Args_Pipe() + { + var container = new ArgumentContainer + { + new ScaleArgument(VideoSize.Ed), + new VideoCodecArgument(VideoCodec.LibTheora) + }; + ConvertFromPipe(VideoType.Ogv, container); + } + [TestMethod] public void Video_ToMP4_Resize() { @@ -174,12 +262,23 @@ public void Video_ToMP4_Resize_Args() { var container = new ArgumentContainer { - new ScaleArgument(VideoSize.Ld), + new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264) }; Convert(VideoType.Mp4, container); } + [TestMethod] + public void Video_ToMP4_Resize_Args_Pipe() + { + var container = new ArgumentContainer + { + new ScaleArgument(VideoSize.Ld), + new VideoCodecArgument(VideoCodec.LibX264) + }; + ConvertFromPipe(VideoType.Mp4, container); + } + [TestMethod] public void Video_ToOGV() { @@ -323,9 +422,10 @@ public void Video_With_Only_Audio_Should_Extract_Metadata() Assert.AreEqual(79.5, video.Duration.TotalSeconds, 0.5); Assert.AreEqual(1.25, video.Size); } - + [TestMethod] - public void Video_Duration() { + public void Video_Duration() + { var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo); var output = Input.OutputLocation(VideoType.Mp4); @@ -336,7 +436,8 @@ public void Video_Duration() { new OutputArgument(output) }; - try { + try + { Encoder.Convert(arguments); Assert.IsTrue(File.Exists(output.FullName)); @@ -346,14 +447,17 @@ public void Video_Duration() { Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours); Assert.AreEqual(video.Duration.Minutes, outputVideo.Duration.Minutes); Assert.AreEqual(video.Duration.Seconds - 5, outputVideo.Duration.Seconds); - } finally { + } + finally + { if (File.Exists(output.FullName)) output.Delete(); } } - + [TestMethod] - public void Video_UpdatesProgress() { + public void Video_UpdatesProgress() + { var output = Input.OutputLocation(VideoType.Mp4); var percentageDone = 0.0; @@ -367,13 +471,16 @@ public void Video_UpdatesProgress() { new OutputArgument(output) }; - try { + try + { Encoder.Convert(arguments); Encoder.OnProgress -= OnProgess; - + Assert.IsTrue(File.Exists(output.FullName)); Assert.AreNotEqual(0.0, percentageDone); - } finally { + } + finally + { if (File.Exists(output.FullName)) output.Delete(); } From 4f51c3d32f6a2d9d4872b1914593e4969a8f797b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Mon, 27 Apr 2020 19:35:53 +0300 Subject: [PATCH 03/27] Replaced IInputPipe interface with System.IO.Stream --- FFMpegCore.Test/VideoTest.cs | 1 - FFMpegCore/Extend/BitmapVideoFrameWrapper.cs | 8 +++--- .../Argument/Atoms/InputPipeArgument.cs | 25 +++++-------------- FFMpegCore/FFMPEG/FFMpeg.cs | 13 +++++++--- FFMpegCore/FFMPEG/Pipes/IInputPipe.cs | 13 ---------- FFMpegCore/FFMPEG/Pipes/IPipeSource.cs | 4 +-- FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs | 4 +-- FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs | 12 ++++----- 8 files changed, 30 insertions(+), 50 deletions(-) delete mode 100644 FFMpegCore/FFMPEG/Pipes/IInputPipe.cs diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 5de10b1..da6a971 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -217,7 +217,6 @@ public void Video_ToTS_Args_Pipe() var container = new ArgumentContainer { new CopyArgument(), - new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB), new ForceFormatArgument(VideoCodec.MpegTs) }; ConvertFromPipe(VideoType.Ts, container); diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs index 8b86461..bcfdab7 100644 --- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs +++ b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs @@ -25,7 +25,7 @@ public BitmapVideoFrameWrapper(Bitmap bitmap) Format = ConvertStreamFormat(bitmap.PixelFormat); } - public void Serialize(IInputPipe pipe) + public void Serialize(System.IO.Stream stream) { var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat); @@ -33,7 +33,7 @@ public void Serialize(IInputPipe pipe) { var buffer = new byte[data.Stride * data.Height]; Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); - pipe.Write(buffer, 0, buffer.Length); + stream.Write(buffer, 0, buffer.Length); } finally { @@ -41,7 +41,7 @@ public void Serialize(IInputPipe pipe) } } - public async Task SerializeAsync(IInputPipe pipe) + public async Task SerializeAsync(System.IO.Stream stream) { var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat); @@ -49,7 +49,7 @@ public async Task SerializeAsync(IInputPipe pipe) { var buffer = new byte[data.Stride * data.Height]; Marshal.Copy(data.Scan0, buffer, 0, buffer.Length); - await pipe.WriteAsync(buffer, 0, buffer.Length); + await stream.WriteAsync(buffer, 0, buffer.Length); } finally { diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs index 1d715a1..bed5897 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs @@ -10,7 +10,10 @@ namespace FFMpegCore.FFMPEG.Argument { - public class InputPipeArgument : Argument, IInputPipe + /// + /// Represents input parameter for a named pipe + /// + public class InputPipeArgument : Argument { public string PipeName { get; private set; } public IPipeSource Source { get; private set; } @@ -37,22 +40,6 @@ public void ClosePipe() 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}"; @@ -61,14 +48,14 @@ public override string GetStringValue() public void FlushPipe() { pipe.WaitForConnection(); - Source.FlushData(this); + Source.FlushData(pipe); } public async Task FlushPipeAsync() { await pipe.WaitForConnectionAsync(); - await Source.FlushDataAsync(this); + await Source.FlushDataAsync(pipe); } } } diff --git a/FFMpegCore/FFMPEG/FFMpeg.cs b/FFMpegCore/FFMPEG/FFMpeg.cs index f1bd402..b13dbbd 100644 --- a/FFMpegCore/FFMPEG/FFMpeg.cs +++ b/FFMpegCore/FFMPEG/FFMpeg.cs @@ -493,7 +493,7 @@ private async Task RunProcessAsync(ArgumentContainer container, FileInfo o { _instance?.Dispose(); var arguments = ArgumentBuilder.BuildArguments(container); - + int exitCode = -1; if (container.TryGetArgument(out var inputPipeArgument)) { inputPipeArgument.OpenPipe(); @@ -506,9 +506,16 @@ private async Task RunProcessAsync(ArgumentContainer container, FileInfo o if (inputPipeArgument != null) { - await inputPipeArgument.FlushPipeAsync(); + var task = _instance.FinishedRunning(); + inputPipeArgument.FlushPipe(); + inputPipeArgument.ClosePipe(); + + exitCode = await task; + } + else + { + exitCode = await _instance.FinishedRunning(); } - var exitCode = await _instance.FinishedRunning(); if (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); diff --git a/FFMpegCore/FFMPEG/Pipes/IInputPipe.cs b/FFMpegCore/FFMPEG/Pipes/IInputPipe.cs deleted file mode 100644 index d31d047..0000000 --- a/FFMpegCore/FFMPEG/Pipes/IInputPipe.cs +++ /dev/null @@ -1,13 +0,0 @@ -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); - } -} diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs index 943bec6..6768f83 100644 --- a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs @@ -9,7 +9,7 @@ namespace FFMpegCore.FFMPEG.Pipes public interface IPipeSource { string GetFormat(); - void FlushData(IInputPipe pipe); - Task FlushDataAsync(IInputPipe pipe); + void FlushData(System.IO.Stream pipe); + Task FlushDataAsync(System.IO.Stream pipe); } } diff --git a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs b/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs index 2458c30..960a36a 100644 --- a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs +++ b/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs @@ -11,7 +11,7 @@ public interface IVideoFrame int Height { get; } string Format { get; } - void Serialize(IInputPipe pipe); - Task SerializeAsync(IInputPipe pipe); + void Serialize(System.IO.Stream pipe); + Task SerializeAsync(System.IO.Stream pipe); } } diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs index 586ec0e..27df6c0 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs @@ -37,29 +37,29 @@ public string GetFormat() return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}"; } - public void FlushData(IInputPipe pipe) + public void FlushData(System.IO.Stream stream) { if (framesEnumerator.Current != null) { - framesEnumerator.Current.Serialize(pipe); + framesEnumerator.Current.Serialize(stream); } while (framesEnumerator.MoveNext()) { - framesEnumerator.Current.Serialize(pipe); + framesEnumerator.Current.Serialize(stream); } } - public async Task FlushDataAsync(IInputPipe pipe) + public async Task FlushDataAsync(System.IO.Stream stream) { if (framesEnumerator.Current != null) { - await framesEnumerator.Current.SerializeAsync(pipe); + await framesEnumerator.Current.SerializeAsync(stream); } while (framesEnumerator.MoveNext()) { - await framesEnumerator.Current.SerializeAsync(pipe); + await framesEnumerator.Current.SerializeAsync(stream); } } From 6f8c7915f8e47f8346745230b3ebdf7b4a02c25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Mon, 27 Apr 2020 19:46:58 +0300 Subject: [PATCH 04/27] Added some summaries --- FFMpegCore/FFMPEG/Pipes/IPipeSource.cs | 3 +++ FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs | 3 +++ FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs | 3 +++ 3 files changed, 9 insertions(+) diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs index 6768f83..80d3ddf 100644 --- a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs @@ -6,6 +6,9 @@ namespace FFMpegCore.FFMPEG.Pipes { + /// + /// Interface for ffmpeg pipe source data IO + /// public interface IPipeSource { string GetFormat(); diff --git a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs b/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs index 960a36a..60de429 100644 --- a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs +++ b/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs @@ -5,6 +5,9 @@ namespace FFMpegCore.FFMPEG.Pipes { + /// + /// Interface for Video frame + /// public interface IVideoFrame { int Width { get; } diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs index 27df6c0..5e28647 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs @@ -6,6 +6,9 @@ namespace FFMpegCore.FFMPEG.Pipes { + /// + /// Implementation of for a raw video stream that is gathered from + /// public class RawVideoPipeSource : IPipeSource { public string StreamFormat { get; set; } From 9a4d20048348442654f8f6a0743424c7eda3977d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Mon, 27 Apr 2020 19:47:56 +0300 Subject: [PATCH 05/27] Updated RawVideoPipeSource.cs --- FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs index 5e28647..c6f3b27 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs @@ -31,11 +31,11 @@ public string GetFormat() { 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; } + StreamFormat = framesEnumerator.Current.Format; + Width = framesEnumerator.Current.Width; + Height = framesEnumerator.Current.Height; + return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}"; } From ca89dfcddd65e0aa993ec09a81d4e5dd5c732e51 Mon Sep 17 00:00:00 2001 From: Max Bagryantsev Date: Mon, 27 Apr 2020 20:16:08 +0300 Subject: [PATCH 06/27] Update README.md --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 6fddd05..d5d7462 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,42 @@ public class OverrideArgument : Argument } } ``` +### Input piping +With input piping it is possible to write video frames directly from program memory without saving them to jpeg or png and then passing path to input of ffmpeg. This feature also allows us to convert video on-the-fly while frames are beeing generated/created/processed. + +`IPipeSource` interface is used as source of data. It could be represented as encoded video stream or raw frames stream. Currently `IPipeSource` interface has single implementation, `RawVideoPipeSource` that is used for raw stream encoding. + +For example: + +Method that is generate bitmap frames: +```csharp +IEnumerable CreateFrames(int count) +{ + for(int i = 0; i < count; i++) + { + yield return GetNextFrame(); //method of generating new frames + } +} +``` +Then create `ArgumentsContainer` with `InputPipeArgument` +```csharp +var videoFramesSource = new RawVideoPipeSource(CreateFrames(64)) //pass IEnumerable or IEnumerator to constructor of RawVideoPipeSource +{ + FrameRate = 30 //set source frame rate +}; +var container = new ArgumentsContainer +{ + new InputPipeArgument(videoFramesSource), + ... //Other encoding arguments + new OutputArgument("temporary.mp4") +}; + +var ffmpeg = new FFMpeg(); +var result = ffmpeg.Convert(arguments); +``` + +if you want to use `System.Drawing.Bitmap` as `IVideoFrame`, there is `BitmapVideoFrameWrapper` wrapper class. + ## Contributors From 9903e333e6e3f6f689ad487d45ef9f359dac45a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Mon, 27 Apr 2020 21:22:05 +0300 Subject: [PATCH 07/27] Added StreamPipeSource --- FFMpegCore.Test/Resources/VideoLibrary.cs | 1 + FFMpegCore.Test/VideoTest.cs | 62 +++++++++++++++++++++ FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs | 46 +++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs diff --git a/FFMpegCore.Test/Resources/VideoLibrary.cs b/FFMpegCore.Test/Resources/VideoLibrary.cs index 90280f8..f630273 100644 --- a/FFMpegCore.Test/Resources/VideoLibrary.cs +++ b/FFMpegCore.Test/Resources/VideoLibrary.cs @@ -17,6 +17,7 @@ public enum ImageType public static class VideoLibrary { public static readonly FileInfo LocalVideo = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input.mp4"); + public static readonly FileInfo LocalVideoWebm = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}input.webm"); public static readonly FileInfo LocalVideoAudioOnly = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}audio_only.mp4"); public static readonly FileInfo LocalVideoNoAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}mute.mp4"); public static readonly FileInfo LocalAudio = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}audio.mp3"); diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index da6a971..9d7009d 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -62,6 +62,61 @@ public bool Convert(VideoType type, bool multithreaded = false, VideoSize size = File.Delete(output.FullName); } } + + private void ConvertFromStreamPipe(VideoType type, ArgumentContainer container) + { + var output = Input.OutputLocation(type); + + try + { + var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); + using (var inputStream = System.IO.File.OpenRead(input.FullName)) + { + var pipeSource = new StreamPipeSource(inputStream); + var arguments = new ArgumentContainer { new InputPipeArgument(pipeSource) }; + foreach (var arg in container) + { + arguments.Add(arg.Value); + } + arguments.Add(new OutputArgument(output)); + + var scaling = container.Find(); + + Encoder.Convert(arguments); + + var outputVideo = new VideoInfo(output.FullName); + + Assert.IsTrue(File.Exists(output.FullName)); + Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate); + + if (scaling == null) + { + Assert.AreEqual(outputVideo.Width, input.Width); + Assert.AreEqual(outputVideo.Height, input.Height); + } + else + { + if (scaling.Value.Width != -1) + { + Assert.AreEqual(outputVideo.Width, scaling.Value.Width); + } + + if (scaling.Value.Height != -1) + { + Assert.AreEqual(outputVideo.Height, scaling.Value.Height); + } + + Assert.AreNotEqual(outputVideo.Width, input.Width); + Assert.AreNotEqual(outputVideo.Height, input.Height); + } + } + } + finally + { + if (File.Exists(output.FullName)) + File.Delete(output.FullName); + } + } public void Convert(VideoType type, ArgumentContainer container) { @@ -193,6 +248,13 @@ public void Video_ToMP4_Args_Pipe() ConvertFromPipe(VideoType.Mp4, container); } + [TestMethod] + public void Video_ToMP4_Args_StreamPipe() + { + var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) }; + ConvertFromStreamPipe(VideoType.Mp4, container); + } + [TestMethod] public void Video_ToTS() { diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs new file mode 100644 index 0000000..1028562 --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace FFMpegCore.FFMPEG.Pipes +{ + /// + /// Implementation of used for stream redirection + /// + public class StreamPipeSource : IPipeSource + { + public System.IO.Stream Source { get; private set; } + public int BlockSize { get; set; } = 4096; + + public StreamPipeSource(System.IO.Stream stream) + { + Source = stream; + } + + public void FlushData(System.IO.Stream pipe) + { + var buffer = new byte[BlockSize]; + int read; + while ((read = Source.Read(buffer, 0, buffer.Length)) != 0) + { + pipe.Write(buffer, 0, read); + } + } + + public async Task FlushDataAsync(System.IO.Stream pipe) + { + var buffer = new byte[BlockSize]; + int read; + while ((read = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0) + { + await pipe.WriteAsync(buffer, 0, read); + } + } + + public string GetFormat() + { + return ""; + } + } +} From cfda0fc9ae87ac5aaa398175b9a109b7454b5312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 15:21:48 +0300 Subject: [PATCH 08/27] Added ffprobe stream input --- FFMpegCore.Test/FFProbeTests.cs | 25 +++++++++ FFMpegCore.Test/TasksExtensions.cs | 16 ++++++ .../Argument/Atoms/InputPipeArgument.cs | 3 +- FFMpegCore/FFMPEG/FFProbe.cs | 52 +++++++++++++++++++ FFMpegCore/VideoInfo.cs | 18 ++++--- 5 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 FFMpegCore.Test/TasksExtensions.cs diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index d66d561..5e877ff 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -31,5 +31,30 @@ public void Probe_Success() Assert.AreEqual(13, info.Duration.Seconds); } + + [TestMethod] + public void Probe_Success_FromStream() + { + var output = new FFProbe(); + + using (var stream = File.OpenRead(VideoLibrary.LocalVideo.FullName)) + { + var info = output.ParseVideoInfo(stream); + Assert.AreEqual(13, info.Duration.Seconds); + } + } + + [TestMethod] + public void Probe_Success_FromStream_Async() + { + var output = new FFProbe(); + + using (var stream = File.OpenRead(VideoLibrary.LocalVideo.FullName)) + { + var info = output.ParseVideoInfoAsync(stream).WaitForResult(); + + Assert.AreEqual(13, info.Duration.Seconds); + } + } } } \ No newline at end of file diff --git a/FFMpegCore.Test/TasksExtensions.cs b/FFMpegCore.Test/TasksExtensions.cs new file mode 100644 index 0000000..7958ec9 --- /dev/null +++ b/FFMpegCore.Test/TasksExtensions.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace FFMpegCore.Test +{ + static class TasksExtensions + { + public static T WaitForResult(this Task task) + { + task.Wait(); + return task.Result; + } + } +} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs index bed5897..a45937b 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs @@ -16,6 +16,7 @@ namespace FFMpegCore.FFMPEG.Argument public class InputPipeArgument : Argument { public string PipeName { get; private set; } + public string PipePath => $@"\\.\pipe\{PipeName}"; public IPipeSource Source { get; private set; } private NamedPipeServerStream pipe; @@ -42,7 +43,7 @@ public void ClosePipe() public override string GetStringValue() { - return $"-y {Source.GetFormat()} -i \\\\.\\pipe\\{PipeName}"; + return $"-y {Source.GetFormat()} -i {PipePath}"; } public void FlushPipe() diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs index 827ea7f..7c683bf 100644 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ b/FFMpegCore/FFMPEG/FFProbe.cs @@ -5,6 +5,8 @@ using System.Globalization; using System.Threading.Tasks; using Instances; +using FFMpegCore.FFMPEG.Argument; +using FFMpegCore.FFMPEG.Pipes; namespace FFMpegCore.FFMPEG { @@ -65,6 +67,56 @@ public async Task ParseVideoInfoAsync(VideoInfo info) return ParseVideoInfoInternal(info, output); } + public VideoInfo ParseVideoInfo(System.IO.Stream stream) + { + var info = new VideoInfo(); + var streamPipeSource = new StreamPipeSource(stream); + var pipeArgument = new InputPipeArgument(streamPipeSource); + + var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity }; + pipeArgument.OpenPipe(); + + try + { + var task = instance.FinishedRunning(); + pipeArgument.FlushPipe(); + pipeArgument.ClosePipe(); + task.Wait(); + } + finally + { + pipeArgument.ClosePipe(); + } + + var output = string.Join("", instance.OutputData); + return ParseVideoInfoInternal(info, output); + } + + public async Task ParseVideoInfoAsync(System.IO.Stream stream) + { + var info = new VideoInfo(); + var streamPipeSource = new StreamPipeSource(stream); + var pipeArgument = new InputPipeArgument(streamPipeSource); + + var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity }; + pipeArgument.OpenPipe(); + + try + { + var task = instance.FinishedRunning(); + await pipeArgument.FlushPipeAsync(); + pipeArgument.ClosePipe(); + await task; + } + finally + { + pipeArgument.ClosePipe(); + } + + var output = string.Join("", instance.OutputData); + return ParseVideoInfoInternal(info, output); + } + private static string BuildFFProbeArguments(string fullPath) => $"-v quiet -print_format json -show_streams \"{fullPath}\""; diff --git a/FFMpegCore/VideoInfo.cs b/FFMpegCore/VideoInfo.cs index f56145c..4e6c226 100644 --- a/FFMpegCore/VideoInfo.cs +++ b/FFMpegCore/VideoInfo.cs @@ -10,6 +10,10 @@ public class VideoInfo { private FileInfo _file; + internal VideoInfo() + { + + } /// /// Create a video information object from a file information object. /// @@ -76,37 +80,37 @@ public VideoInfo(string path, int outputCapacity = int.MaxValue) : this(new File /// /// Gets the name of the file. /// - public string Name => _file.Name; + public string Name => _file != null ? _file.Name : throw new FileNotFoundException(); /// /// Gets the full path of the file. /// - public string FullName => _file.FullName; + public string FullName => _file != null ? _file.FullName : throw new FileNotFoundException(); /// /// Gets the file extension. /// - public string Extension => _file.Extension; + public string Extension => _file != null ? _file.Extension : throw new FileNotFoundException(); /// /// Gets a flag indicating if the file is read-only. /// - public bool IsReadOnly => _file.IsReadOnly; + public bool IsReadOnly => _file != null ? _file.IsReadOnly : throw new FileNotFoundException(); /// /// Gets a flag indicating if the file exists (no cache, per call verification). /// - public bool Exists => File.Exists(FullName); + public bool Exists => _file != null ? File.Exists(FullName) : false; /// /// Gets the creation date. /// - public DateTime CreationTime => _file.CreationTime; + public DateTime CreationTime => _file != null ? _file.CreationTime : throw new FileNotFoundException(); /// /// Gets the parent directory information. /// - public DirectoryInfo Directory => _file.Directory; + public DirectoryInfo Directory => _file != null ? _file.Directory : throw new FileNotFoundException(); /// /// Create a video information object from a file information object. From f56ea098a5afe05861dc008aee7836ab1726a925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 15:44:27 +0300 Subject: [PATCH 09/27] FromStream method to Video Info & added comments to FFprobe methods with stream --- FFMpegCore/FFMPEG/FFProbe.cs | 10 ++++++++++ FFMpegCore/VideoInfo.cs | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs index 7c683bf..a7aec84 100644 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ b/FFMpegCore/FFMPEG/FFProbe.cs @@ -67,6 +67,11 @@ public async Task ParseVideoInfoAsync(VideoInfo info) return ParseVideoInfoInternal(info, output); } + /// + /// Probes the targeted video stream and retrieves all available details. + /// + /// Encoded video stream. + /// A video info object containing all details necessary. public VideoInfo ParseVideoInfo(System.IO.Stream stream) { var info = new VideoInfo(); @@ -92,6 +97,11 @@ public VideoInfo ParseVideoInfo(System.IO.Stream stream) return ParseVideoInfoInternal(info, output); } + /// + /// Probes the targeted video stream asynchronously and retrieves all available details. + /// + /// Encoded video stream. + /// A video info object containing all details necessary. public async Task ParseVideoInfoAsync(System.IO.Stream stream) { var info = new VideoInfo(); diff --git a/FFMpegCore/VideoInfo.cs b/FFMpegCore/VideoInfo.cs index 4e6c226..3bacdb9 100644 --- a/FFMpegCore/VideoInfo.cs +++ b/FFMpegCore/VideoInfo.cs @@ -132,6 +132,16 @@ public static VideoInfo FromPath(string path) return new VideoInfo(path); } + /// + /// Create a video information object from a encoded stream. + /// + /// Encoded video stream. + /// + public static VideoInfo FromStream(System.IO.Stream stream) + { + return new FFProbe().ParseVideoInfo(stream); + } + /// /// Pretty prints the video information. /// From ea7acf2140258c87f08981cc1ffefe6db642b6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 16:08:02 +0300 Subject: [PATCH 10/27] Updated TasksExtensions --- FFMpegCore.Test/TasksExtensions.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/FFMpegCore.Test/TasksExtensions.cs b/FFMpegCore.Test/TasksExtensions.cs index 7958ec9..67163a7 100644 --- a/FFMpegCore.Test/TasksExtensions.cs +++ b/FFMpegCore.Test/TasksExtensions.cs @@ -7,10 +7,7 @@ namespace FFMpegCore.Test { static class TasksExtensions { - public static T WaitForResult(this Task task) - { - task.Wait(); - return task.Result; - } + public static T WaitForResult(this Task task) => + task.ConfigureAwait(false).GetAwaiter().GetResult(); } } From 8434ffbba67e93ac917f5db14bf75822c190f5a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 18:39:39 +0300 Subject: [PATCH 11/27] Added OutputPipeArgument --- .../Argument/Atoms/InputPipeArgument.cs | 6 +-- .../Argument/Atoms/OutputPipeArgument.cs | 53 +++++++++++++++++++ FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs | 13 +++++ FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs | 16 ++++++ .../FFMPEG/Pipes/StreamPipeDataReader.cs | 34 ++++++++++++ 5 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs create mode 100644 FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs index a45937b..ee6b19b 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs @@ -16,7 +16,7 @@ namespace FFMpegCore.FFMPEG.Argument public class InputPipeArgument : Argument { public string PipeName { get; private set; } - public string PipePath => $@"\\.\pipe\{PipeName}"; + public string PipePath => PipeHelpers.GetPipePath(PipeName); public IPipeSource Source { get; private set; } private NamedPipeServerStream pipe; @@ -24,7 +24,7 @@ public class InputPipeArgument : Argument public InputPipeArgument(IPipeSource source) { Source = source; - PipeName = "FFMpegCore_Pipe_" + Guid.NewGuid(); + PipeName = PipeHelpers.GetUnqiuePipeName(); } public void OpenPipe() @@ -43,7 +43,7 @@ public void ClosePipe() public override string GetStringValue() { - return $"-y {Source.GetFormat()} -i {PipePath}"; + return $"-y {Source.GetFormat()} -i \"{PipePath}\""; } public void FlushPipe() diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs new file mode 100644 index 0000000..201e3d0 --- /dev/null +++ b/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs @@ -0,0 +1,53 @@ +using FFMpegCore.FFMPEG.Pipes; +using System; +using System.Collections.Generic; +using System.IO.Pipes; +using System.Text; +using System.Threading.Tasks; + +namespace FFMpegCore.FFMPEG.Argument +{ + public class OutputPipeArgument : Argument + { + public string PipeName { get; private set; } + public string PipePath => PipeHelpers.GetPipePath(PipeName); + public IPipeDataReader Reader { get; private set; } + + private NamedPipeClientStream pipe; + + public OutputPipeArgument(IPipeDataReader reader) + { + Reader = reader; + PipeName = PipeHelpers.GetUnqiuePipeName(); + } + + public void OpenPipe() + { + if(pipe != null) + throw new InvalidOperationException("Pipe already has been opened"); + + pipe = new NamedPipeClientStream(PipePath); + } + + public void ReadData() + { + Reader.ReadData(pipe); + } + + public Task ReadDataAsync() + { + return Reader.ReadDataAsync(pipe); + } + + public void Close() + { + pipe?.Dispose(); + pipe = null; + } + + public override string GetStringValue() + { + return $"\"{PipePath}\""; + } + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs b/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs new file mode 100644 index 0000000..eeb5734 --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace FFMpegCore.FFMPEG.Pipes +{ + public interface IPipeDataReader + { + void ReadData(System.IO.Stream stream); + Task ReadDataAsync(System.IO.Stream stream); + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs b/FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs new file mode 100644 index 0000000..6717dac --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace FFMpegCore.FFMPEG.Pipes +{ + static class PipeHelpers + { + public static string GetUnqiuePipeName() => "FFMpegCore_Pipe_" + Guid.NewGuid(); + + public static string GetPipePath(string pipeName) + { + return $@"\\.\pipe\{pipeName}"; + } + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs new file mode 100644 index 0000000..c59a475 --- /dev/null +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace FFMpegCore.FFMPEG.Pipes +{ + public class StreamPipedataReader : IPipeDataReader + { + public System.IO.Stream DestanationStream { get; private set; } + public int BlockSize { get; set; } = 4096; + + public StreamPipedataReader(System.IO. Stream destanationStream) + { + DestanationStream = destanationStream; + } + + public void ReadData(System.IO.Stream stream) + { + int read; + var buffer = new byte[BlockSize]; + while((read = stream.Read(buffer, 0, buffer.Length)) != 0) + DestanationStream.Write(buffer, 0, buffer.Length); + } + + public async Task ReadDataAsync(System.IO.Stream stream) + { + int read; + var buffer = new byte[BlockSize]; + while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0) + await DestanationStream.WriteAsync(buffer, 0, buffer.Length); + } + } +} From b7099f6709c86907b480f834c1ca26deb36503aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 18:42:50 +0300 Subject: [PATCH 12/27] Renamed IPipeSource to IpipeDataWriter --- .../FFMPEG/Argument/Atoms/InputPipeArgument.cs | 8 ++++---- .../Pipes/{IPipeSource.cs => IPipeDataWriter.cs} | 6 +++--- ...wVideoPipeSource.cs => RawVideoPipeDataWriter.cs} | 12 ++++++------ FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs | 4 ++-- .../{StreamPipeSource.cs => StreamPipeDataWriter.cs} | 10 +++++----- 5 files changed, 20 insertions(+), 20 deletions(-) rename FFMpegCore/FFMPEG/Pipes/{IPipeSource.cs => IPipeDataWriter.cs} (68%) rename FFMpegCore/FFMPEG/Pipes/{RawVideoPipeSource.cs => RawVideoPipeDataWriter.cs} (77%) rename FFMpegCore/FFMPEG/Pipes/{StreamPipeSource.cs => StreamPipeDataWriter.cs} (74%) diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs index ee6b19b..cd818d8 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs @@ -17,11 +17,11 @@ public class InputPipeArgument : Argument { public string PipeName { get; private set; } public string PipePath => PipeHelpers.GetPipePath(PipeName); - public IPipeSource Source { get; private set; } + public IPipeDataWriter Source { get; private set; } private NamedPipeServerStream pipe; - public InputPipeArgument(IPipeSource source) + public InputPipeArgument(IPipeDataWriter source) { Source = source; PipeName = PipeHelpers.GetUnqiuePipeName(); @@ -49,14 +49,14 @@ public override string GetStringValue() public void FlushPipe() { pipe.WaitForConnection(); - Source.FlushData(pipe); + Source.WriteData(pipe); } public async Task FlushPipeAsync() { await pipe.WaitForConnectionAsync(); - await Source.FlushDataAsync(pipe); + await Source.WriteDataAsync(pipe); } } } diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/IPipeDataWriter.cs similarity index 68% rename from FFMpegCore/FFMPEG/Pipes/IPipeSource.cs rename to FFMpegCore/FFMPEG/Pipes/IPipeDataWriter.cs index 80d3ddf..aa4bbc8 100644 --- a/FFMpegCore/FFMPEG/Pipes/IPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/IPipeDataWriter.cs @@ -9,10 +9,10 @@ namespace FFMpegCore.FFMPEG.Pipes /// /// Interface for ffmpeg pipe source data IO /// - public interface IPipeSource + public interface IPipeDataWriter { string GetFormat(); - void FlushData(System.IO.Stream pipe); - Task FlushDataAsync(System.IO.Stream pipe); + void WriteData(System.IO.Stream pipe); + Task WriteDataAsync(System.IO.Stream pipe); } } diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs similarity index 77% rename from FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs rename to FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs index c6f3b27..c7d3df0 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs @@ -7,9 +7,9 @@ namespace FFMpegCore.FFMPEG.Pipes { /// - /// Implementation of for a raw video stream that is gathered from + /// Implementation of for a raw video stream that is gathered from /// - public class RawVideoPipeSource : IPipeSource + public class RawVideoPipeDataWriter : IPipeDataWriter { public string StreamFormat { get; set; } public int Width { get; set; } @@ -17,12 +17,12 @@ public class RawVideoPipeSource : IPipeSource public int FrameRate { get; set; } = 25; private IEnumerator framesEnumerator; - public RawVideoPipeSource(IEnumerator framesEnumerator) + public RawVideoPipeDataWriter(IEnumerator framesEnumerator) { this.framesEnumerator = framesEnumerator; } - public RawVideoPipeSource(IEnumerable framesEnumerator) : this(framesEnumerator.GetEnumerator()) { } + public RawVideoPipeDataWriter(IEnumerable framesEnumerator) : this(framesEnumerator.GetEnumerator()) { } public string GetFormat() { @@ -40,7 +40,7 @@ public string GetFormat() return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}"; } - public void FlushData(System.IO.Stream stream) + public void WriteData(System.IO.Stream stream) { if (framesEnumerator.Current != null) { @@ -53,7 +53,7 @@ public void FlushData(System.IO.Stream stream) } } - public async Task FlushDataAsync(System.IO.Stream stream) + public async Task WriteDataAsync(System.IO.Stream stream) { if (framesEnumerator.Current != null) { diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs index c59a475..d080806 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs @@ -5,12 +5,12 @@ namespace FFMpegCore.FFMPEG.Pipes { - public class StreamPipedataReader : IPipeDataReader + public class StreamPipeDataReader : IPipeDataReader { public System.IO.Stream DestanationStream { get; private set; } public int BlockSize { get; set; } = 4096; - public StreamPipedataReader(System.IO. Stream destanationStream) + public StreamPipeDataReader(System.IO. Stream destanationStream) { DestanationStream = destanationStream; } diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs similarity index 74% rename from FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs rename to FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs index 1028562..441a28f 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeSource.cs +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs @@ -6,19 +6,19 @@ namespace FFMpegCore.FFMPEG.Pipes { /// - /// Implementation of used for stream redirection + /// Implementation of used for stream redirection /// - public class StreamPipeSource : IPipeSource + public class StreamPipeDataWriter : IPipeDataWriter { public System.IO.Stream Source { get; private set; } public int BlockSize { get; set; } = 4096; - public StreamPipeSource(System.IO.Stream stream) + public StreamPipeDataWriter(System.IO.Stream stream) { Source = stream; } - public void FlushData(System.IO.Stream pipe) + public void WriteData(System.IO.Stream pipe) { var buffer = new byte[BlockSize]; int read; @@ -28,7 +28,7 @@ public void FlushData(System.IO.Stream pipe) } } - public async Task FlushDataAsync(System.IO.Stream pipe) + public async Task WriteDataAsync(System.IO.Stream pipe) { var buffer = new byte[BlockSize]; int read; From 6845fe3bc76d272ed0cc31a074344304269ca04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 18:43:54 +0300 Subject: [PATCH 13/27] Added StreamFormat property to StreamPipeDataWriter --- FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs index 441a28f..8db19eb 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs @@ -12,6 +12,7 @@ public class StreamPipeDataWriter : IPipeDataWriter { public System.IO.Stream Source { get; private set; } public int BlockSize { get; set; } = 4096; + public string StreamFormat { get; set; } = string.Empty; public StreamPipeDataWriter(System.IO.Stream stream) { @@ -40,7 +41,7 @@ public async Task WriteDataAsync(System.IO.Stream pipe) public string GetFormat() { - return ""; + return StreamFormat; } } } From 1d51163a05e6b8ad1b1567be02907827bdd01574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 18:50:29 +0300 Subject: [PATCH 14/27] Simplified ContainsInputOutput implementation --- FFMpegCore.Test/VideoTest.cs | 4 ++-- FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs | 16 ++++++++++++---- FFMpegCore/FFMPEG/FFProbe.cs | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 9d7009d..63c40d0 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -72,7 +72,7 @@ private void ConvertFromStreamPipe(VideoType type, ArgumentContainer container) var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); using (var inputStream = System.IO.File.OpenRead(input.FullName)) { - var pipeSource = new StreamPipeSource(inputStream); + var pipeSource = new StreamPipeDataWriter(inputStream); var arguments = new ArgumentContainer { new InputPipeArgument(pipeSource) }; foreach (var arg in container) { @@ -183,7 +183,7 @@ public void ConvertFromPipe(VideoType type, ArgumentContainer container, PixelFo try { - var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, fmt, 256, 256)); + var videoFramesSource = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, fmt, 256, 256)); var arguments = new ArgumentContainer { new InputPipeArgument(videoFramesSource) }; foreach (var arg in container) { diff --git a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs index d2e5532..03e2014 100644 --- a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs +++ b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs @@ -102,10 +102,8 @@ public void Add(params Argument[] values) /// public bool ContainsInputOutput() { - return ((ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) || - (!ContainsKey(typeof(InputArgument)) && ContainsKey(typeof(ConcatArgument)) && !ContainsKey(typeof(InputPipeArgument))) || - (!ContainsKey(typeof(InputArgument)) && !ContainsKey(typeof(ConcatArgument)) && ContainsKey(typeof(InputPipeArgument)))) - && ContainsKey(typeof(OutputArgument)); + return CountExistedKeys(typeof(InputArgument), typeof(ConcatArgument), typeof(InputPipeArgument)) == 1 && + CountExistedKeys(typeof(OutputArgument), typeof(OutputPipeArgument)) == 1; } /// @@ -118,6 +116,16 @@ public bool ContainsKey(Type key) return _args.ContainsKey(key); } + public int CountExistedKeys(params Type[] types) + { + int count = 0; + for(int i =0; i < types.Length; i++) + if (_args.ContainsKey(types[i])) + count++; + + return count; + } + public void CopyTo(KeyValuePair[] array, int arrayIndex) { _args.CopyTo(array, arrayIndex); diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs index a7aec84..53ee313 100644 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ b/FFMpegCore/FFMPEG/FFProbe.cs @@ -75,7 +75,7 @@ public async Task ParseVideoInfoAsync(VideoInfo info) public VideoInfo ParseVideoInfo(System.IO.Stream stream) { var info = new VideoInfo(); - var streamPipeSource = new StreamPipeSource(stream); + var streamPipeSource = new StreamPipeDataWriter(stream); var pipeArgument = new InputPipeArgument(streamPipeSource); var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity }; @@ -105,7 +105,7 @@ public VideoInfo ParseVideoInfo(System.IO.Stream stream) public async Task ParseVideoInfoAsync(System.IO.Stream stream) { var info = new VideoInfo(); - var streamPipeSource = new StreamPipeSource(stream); + var streamPipeSource = new StreamPipeDataWriter(stream); var pipeArgument = new InputPipeArgument(streamPipeSource); var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity }; From b83479e1b6700d69af2aa530b90a82e8ab538ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:34:04 +0300 Subject: [PATCH 15/27] Updated Input and Output Pipe arguments. Derived them from PipieArgument --- .../Argument/Atoms/InputPipeArgument.cs | 45 +++++-------------- .../Argument/Atoms/OutputPipeArgument.cs | 44 +++++------------- .../FFMPEG/Argument/Atoms/PipeArgument.cs | 45 +++++++++++++++++++ FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs | 1 + .../FFMPEG/Pipes/StreamPipeDataReader.cs | 10 ++++- 5 files changed, 77 insertions(+), 68 deletions(-) create mode 100644 FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs index cd818d8..6197a23 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs @@ -6,6 +6,7 @@ using System.IO; using System.IO.Pipes; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace FFMpegCore.FFMPEG.Argument @@ -13,50 +14,26 @@ namespace FFMpegCore.FFMPEG.Argument /// /// Represents input parameter for a named pipe /// - public class InputPipeArgument : Argument + public class InputPipeArgument : PipeArgument { - public string PipeName { get; private set; } - public string PipePath => PipeHelpers.GetPipePath(PipeName); - public IPipeDataWriter Source { get; private set; } + public IPipeDataWriter Writer { get; private set; } - private NamedPipeServerStream pipe; - - public InputPipeArgument(IPipeDataWriter source) + public InputPipeArgument(IPipeDataWriter writer) : base(PipeDirection.Out) { - Source = source; - PipeName = PipeHelpers.GetUnqiuePipeName(); - } - - 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; + Writer = writer; } public override string GetStringValue() { - return $"-y {Source.GetFormat()} -i \"{PipePath}\""; + return $"-y {Writer.GetFormat()} -i \"{PipePath}\""; } - public void FlushPipe() + public override async Task ProcessDataAsync(CancellationToken token) { - pipe.WaitForConnection(); - Source.WriteData(pipe); - } - - - public async Task FlushPipeAsync() - { - await pipe.WaitForConnectionAsync(); - await Source.WriteDataAsync(pipe); + await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false); + if (!Pipe.IsConnected) + throw new TaskCanceledException(); + await Writer.WriteDataAsync(Pipe).ConfigureAwait(false); } } } diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs index 201e3d0..fd02df2 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs @@ -3,51 +3,31 @@ using System.Collections.Generic; using System.IO.Pipes; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace FFMpegCore.FFMPEG.Argument { - public class OutputPipeArgument : Argument + public class OutputPipeArgument : PipeArgument { - public string PipeName { get; private set; } - public string PipePath => PipeHelpers.GetPipePath(PipeName); public IPipeDataReader Reader { get; private set; } - private NamedPipeClientStream pipe; - - public OutputPipeArgument(IPipeDataReader reader) + public OutputPipeArgument(IPipeDataReader reader) : base(PipeDirection.In) { Reader = reader; - PipeName = PipeHelpers.GetUnqiuePipeName(); - } - - public void OpenPipe() - { - if(pipe != null) - throw new InvalidOperationException("Pipe already has been opened"); - - pipe = new NamedPipeClientStream(PipePath); - } - - public void ReadData() - { - Reader.ReadData(pipe); - } - - public Task ReadDataAsync() - { - return Reader.ReadDataAsync(pipe); - } - - public void Close() - { - pipe?.Dispose(); - pipe = null; } public override string GetStringValue() { - return $"\"{PipePath}\""; + return $"\"{PipePath}\" -y"; + } + + public override async Task ProcessDataAsync(CancellationToken token) + { + await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false); + if (!Pipe.IsConnected) + throw new TaskCanceledException(); + await Reader.ReadDataAsync(Pipe).ConfigureAwait(false); } } } diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs new file mode 100644 index 0000000..81fb872 --- /dev/null +++ b/FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs @@ -0,0 +1,45 @@ +using FFMpegCore.FFMPEG.Pipes; +using System; +using System.Collections.Generic; +using System.IO.Pipes; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace FFMpegCore.FFMPEG.Argument +{ + public abstract class PipeArgument : Argument + { + public string PipeName { get; private set; } + public string PipePath => PipeHelpers.GetPipePath(PipeName); + + protected NamedPipeServerStream Pipe { get; private set; } + private PipeDirection direction; + + protected PipeArgument(PipeDirection direction) + { + PipeName = PipeHelpers.GetUnqiuePipeName(); + this.direction = direction; + } + + public void OpenPipe() + { + if (Pipe != null) + throw new InvalidOperationException("Pipe already has been opened"); + + Pipe = new NamedPipeServerStream(PipeName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + } + + public void ClosePipe() + { + Pipe?.Dispose(); + Pipe = null; + } + public Task ProcessDataAsync() + { + return ProcessDataAsync(CancellationToken.None); + } + + public abstract Task ProcessDataAsync(CancellationToken token); + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs b/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs index eeb5734..3912cb3 100644 --- a/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs +++ b/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs @@ -9,5 +9,6 @@ public interface IPipeDataReader { void ReadData(System.IO.Stream stream); Task ReadDataAsync(System.IO.Stream stream); + string GetFormat(); } } diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs index d080806..372d227 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs @@ -9,8 +9,9 @@ public class StreamPipeDataReader : IPipeDataReader { public System.IO.Stream DestanationStream { get; private set; } public int BlockSize { get; set; } = 4096; + public string Format { get; set; } = string.Empty; - public StreamPipeDataReader(System.IO. Stream destanationStream) + public StreamPipeDataReader(System.IO.Stream destanationStream) { DestanationStream = destanationStream; } @@ -19,7 +20,7 @@ public void ReadData(System.IO.Stream stream) { int read; var buffer = new byte[BlockSize]; - while((read = stream.Read(buffer, 0, buffer.Length)) != 0) + while ((read = stream.Read(buffer, 0, buffer.Length)) != 0) DestanationStream.Write(buffer, 0, buffer.Length); } @@ -30,5 +31,10 @@ public async Task ReadDataAsync(System.IO.Stream stream) while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0) await DestanationStream.WriteAsync(buffer, 0, buffer.Length); } + + public string GetFormat() + { + return Format; + } } } From 06f5c319adfb63d31b5a0e6556fef2796eb6fc65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:36:03 +0300 Subject: [PATCH 16/27] Changed ForceFormatArgument and VideoCodecArgument to string arguments --- .../FFMPEG/Argument/Atoms/ForceFormatArgument.cs | 7 ++++--- FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs | 10 ++++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs index 700d320..c2322e0 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs @@ -5,16 +5,17 @@ namespace FFMpegCore.FFMPEG.Argument /// /// Represents force format parameter /// - public class ForceFormatArgument : Argument + public class ForceFormatArgument : Argument { public ForceFormatArgument() { } + public ForceFormatArgument(string format) : base(format) { } - public ForceFormatArgument(VideoCodec value) : base(value) { } + public ForceFormatArgument(VideoCodec value) : base(value.ToString().ToLower()) { } /// public override string GetStringValue() { - return $"-f {Value.ToString().ToLower()}"; + return $"-f {Value}"; } } } diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs index e8296ab..ac35f35 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs +++ b/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs @@ -5,15 +5,17 @@ namespace FFMpegCore.FFMPEG.Argument /// /// Represents video codec parameter /// - public class VideoCodecArgument : Argument + public class VideoCodecArgument : Argument { public int Bitrate { get; protected set; } = 0; public VideoCodecArgument() { } - public VideoCodecArgument(VideoCodec value) : base(value) { } + public VideoCodecArgument(string codec) : base(codec) { } - public VideoCodecArgument(VideoCodec value, int bitrate) : base(value) + public VideoCodecArgument(VideoCodec value) : base(value.ToString().ToLower()) { } + + public VideoCodecArgument(VideoCodec value, int bitrate) : base(value.ToString().ToLower()) { Bitrate = bitrate; } @@ -21,7 +23,7 @@ public VideoCodecArgument(VideoCodec value, int bitrate) : base(value) /// public override string GetStringValue() { - var video = $"-c:v {Value.ToString().ToLower()} -pix_fmt yuv420p"; + var video = $"-c:v {Value} -pix_fmt yuv420p"; if (Bitrate != default) { From 11edbbea2bd3a9b6822d877c41f875c9c1e1b409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:39:26 +0300 Subject: [PATCH 17/27] Updated FFProbe --- FFMpegCore/FFMPEG/FFProbe.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs index 53ee313..a9033c4 100644 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ b/FFMpegCore/FFMPEG/FFProbe.cs @@ -7,6 +7,7 @@ using Instances; using FFMpegCore.FFMPEG.Argument; using FFMpegCore.FFMPEG.Pipes; +using System.IO; namespace FFMpegCore.FFMPEG { @@ -81,18 +82,23 @@ public VideoInfo ParseVideoInfo(System.IO.Stream stream) var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity }; pipeArgument.OpenPipe(); + var task = instance.FinishedRunning(); try { - var task = instance.FinishedRunning(); - pipeArgument.FlushPipe(); + pipeArgument.ProcessDataAsync().ConfigureAwait(false).GetAwaiter().GetResult(); pipeArgument.ClosePipe(); - task.Wait(); + } + catch(IOException) + { } finally { pipeArgument.ClosePipe(); } + var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult(); + if (exitCode != 0) + throw new FFMpegException(FFMpegExceptionType.Process, "FFProbe process returned exit status " + exitCode); var output = string.Join("", instance.OutputData); return ParseVideoInfoInternal(info, output); } @@ -111,17 +117,20 @@ public async Task ParseVideoInfoAsync(System.IO.Stream stream) var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity }; pipeArgument.OpenPipe(); + var task = instance.FinishedRunning(); try { - var task = instance.FinishedRunning(); - await pipeArgument.FlushPipeAsync(); + await pipeArgument.ProcessDataAsync(); pipeArgument.ClosePipe(); - await task; + } + catch (IOException) + { } finally { pipeArgument.ClosePipe(); } + var exitCode = await task; var output = string.Join("", instance.OutputData); return ParseVideoInfoInternal(info, output); From 412456857f26db5c77b608a5c7b0a124b3578545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:40:00 +0300 Subject: [PATCH 18/27] Updated FFmpeg RunProcess and RunProcessAsyncFunctions --- FFMpegCore/FFMPEG/FFMpeg.cs | 175 +++++++++++++++++++++++++----------- 1 file changed, 123 insertions(+), 52 deletions(-) diff --git a/FFMpegCore/FFMPEG/FFMpeg.cs b/FFMpegCore/FFMPEG/FFMpeg.cs index e43486f..b99e564 100644 --- a/FFMpegCore/FFMPEG/FFMpeg.cs +++ b/FFMpegCore/FFMPEG/FFMpeg.cs @@ -15,6 +15,8 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Instances; +using System.Runtime.CompilerServices; +using System.Threading; namespace FFMpegCore.FFMPEG { @@ -391,6 +393,9 @@ public VideoInfo Convert(ArgumentContainer arguments, bool skipExistsCheck = fal throw new FFMpegException(FFMpegExceptionType.Conversion, "Could not process file without error"); _totalTime = TimeSpan.MinValue; + + if (output == null) + return null; return new VideoInfo(output); } public async Task ConvertAsync(ArgumentContainer arguments, bool skipExistsCheck = false) @@ -403,12 +408,21 @@ public async Task ConvertAsync(ArgumentContainer arguments, bool skip throw new FFMpegException(FFMpegExceptionType.Conversion, "Could not process file without error"); _totalTime = TimeSpan.MinValue; + if (output == null) + return null; return new VideoInfo(output); } private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentContainer arguments) { - var output = ((OutputArgument)arguments[typeof(OutputArgument)]).GetAsFileInfo(); + FileInfo output; + if (arguments.TryGetArgument(out var outputArg)) + output = outputArg.GetAsFileInfo(); + else if (arguments.TryGetArgument(out var outputPipeArg)) + output = null; + else + throw new FFMpegException(FFMpegExceptionType.Operation, "No output argument found"); + VideoInfo[] sources; if (arguments.TryGetArgument(out var input)) sources = input.GetAsVideoInfo(); @@ -452,84 +466,141 @@ private bool RunProcess(ArgumentContainer container, FileInfo output, bool skipE { inputPipeArgument.OpenPipe(); } - - try + if (container.TryGetArgument(out var outputPipeArgument)) { - _instance = new Instance(_ffmpegPath, arguments); - _instance.DataReceived += OutputData; + outputPipeArgument.OpenPipe(); + } - if (inputPipeArgument != null) + + _instance = new Instance(_ffmpegPath, arguments); + _instance.DataReceived += OutputData; + + if (inputPipeArgument != null || outputPipeArgument != null) + { + try { - try + using (var tokenSource = new CancellationTokenSource()) { - 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); + var concurrentTasks = new List(); + 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*/); } } - else + catch (Exception ex) { - exitCode = _instance.BlockUntilFinished(); + inputPipeArgument?.ClosePipe(); + outputPipeArgument?.ClosePipe(); + throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex); } - - if (!skipExistsCheck && (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)) - throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); - - return exitCode == 0; } - finally + else { - if (inputPipeArgument != null) - inputPipeArgument.ClosePipe(); + 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 RunProcessAsync(ArgumentContainer container, FileInfo output, bool skipExistsCheck) { _instance?.Dispose(); var arguments = ArgumentBuilder.BuildArguments(container); + var exitCode = -1; - int exitCode = -1; if (container.TryGetArgument(out var inputPipeArgument)) { inputPipeArgument.OpenPipe(); } - try + if (container.TryGetArgument(out var outputPipeArgument)) { - - _instance = new Instance(_ffmpegPath, arguments); - _instance.DataReceived += OutputData; - - if (inputPipeArgument != null) - { - var task = _instance.FinishedRunning(); - inputPipeArgument.FlushPipe(); - inputPipeArgument.ClosePipe(); - - exitCode = await task; - } - else - { - exitCode = await _instance.FinishedRunning(); - } - - if (!skipExistsCheck && (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0)) - throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData)); - - return exitCode == 0; + outputPipeArgument.OpenPipe(); } - finally + + + _instance = new Instance(_ffmpegPath, arguments); + _instance.DataReceived += OutputData; + + if (inputPipeArgument != null || outputPipeArgument != null) { - if (inputPipeArgument != null) + try { - inputPipeArgument.ClosePipe(); + using (var tokenSource = new CancellationTokenSource()) + { + var concurrentTasks = new List(); + 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 pathList) From 5991cc99f007822f02266be6840b55631642e14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:40:33 +0300 Subject: [PATCH 19/27] Added Unit tests for input and output pipes --- FFMpegCore.Test/VideoTest.cs | 124 ++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 2 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 63c40d0..25ff2f6 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -1,6 +1,7 @@ using FFMpegCore.Enums; using FFMpegCore.FFMPEG.Argument; using FFMpegCore.FFMPEG.Enums; +using FFMpegCore.FFMPEG.Exceptions; using FFMpegCore.FFMPEG.Pipes; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -62,7 +63,7 @@ public bool Convert(VideoType type, bool multithreaded = false, VideoSize size = File.Delete(output.FullName); } } - + private void ConvertFromStreamPipe(VideoType type, ArgumentContainer container) { var output = Input.OutputLocation(type); @@ -118,6 +119,54 @@ private void ConvertFromStreamPipe(VideoType type, ArgumentContainer container) } } + private void ConvertToStreamPipe(VideoType type, ArgumentContainer container) + { + using (var ms = new MemoryStream()) + { + var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo); + var arguments = new ArgumentContainer { new InputArgument(input) }; + + foreach (var arg in container) + { + arguments.Add(arg.Value); + } + + var streamPipeDataReader = new StreamPipeDataReader(ms); + streamPipeDataReader.BlockSize = streamPipeDataReader.BlockSize * 16; + arguments.Add(new OutputPipeArgument(streamPipeDataReader)); + + var scaling = container.Find(); + + Encoder.Convert(arguments); + + ms.Position = 0; + var outputVideo = VideoInfo.FromStream(ms); + + Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate); + + if (scaling == null) + { + Assert.AreEqual(outputVideo.Width, input.Width); + Assert.AreEqual(outputVideo.Height, input.Height); + } + else + { + if (scaling.Value.Width != -1) + { + Assert.AreEqual(outputVideo.Width, scaling.Value.Width); + } + + if (scaling.Value.Height != -1) + { + Assert.AreEqual(outputVideo.Height, scaling.Value.Height); + } + + Assert.AreNotEqual(outputVideo.Width, input.Width); + Assert.AreNotEqual(outputVideo.Height, input.Height); + } + } + } + public void Convert(VideoType type, ArgumentContainer container) { var output = Input.OutputLocation(type); @@ -255,6 +304,78 @@ public void Video_ToMP4_Args_StreamPipe() ConvertFromStreamPipe(VideoType.Mp4, container); } + [TestMethod] + public void Video_ToMP4_Args_StreamOutputPipe_Async_Failure() + { + Assert.ThrowsException(() => + { + using (var ms = new MemoryStream()) + { + var pipeSource = new StreamPipeDataReader(ms); + var container = new ArgumentContainer + { + new InputArgument(VideoLibrary.LocalVideo), + new VideoCodecArgument(VideoCodec.LibX264), + new ForceFormatArgument("mkv"), + new OutputPipeArgument(pipeSource) + }; + + var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); + Encoder.ConvertAsync(container).WaitForResult(); + } + }); + } + + [TestMethod] + public void Video_ToMP4_Args_StreamOutputPipe_Failure() + { + Assert.ThrowsException(() => + { + var container = new ArgumentContainer + { + new ForceFormatArgument("mkv") + }; + ConvertToStreamPipe(VideoType.Mp4, container); + }); + } + + + [TestMethod] + public void Video_ToMP4_Args_StreamOutputPipe_Async() + { + Assert.ThrowsException(() => + { + using (var ms = new MemoryStream()) + { + var pipeSource = new StreamPipeDataReader(ms); + var container = new ArgumentContainer + { + new InputArgument(VideoLibrary.LocalVideo), + new VideoCodecArgument(VideoCodec.LibX264), + new ForceFormatArgument("mp4"), + new OutputPipeArgument(pipeSource) + }; + + var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); + Encoder.ConvertAsync(container).WaitForResult(); + } + }); + } + + [TestMethod] + public void Video_ToMP4_Args_StreamOutputPipe() + { + Assert.ThrowsException(() => + { + var container = new ArgumentContainer + { + new VideoCodecArgument(VideoCodec.LibX264), + new ForceFormatArgument("mp4") + }; + ConvertToStreamPipe(VideoType.Mp4, container); + }); + } + [TestMethod] public void Video_ToTS() { @@ -278,7 +399,6 @@ public void Video_ToTS_Args_Pipe() { var container = new ArgumentContainer { - new CopyArgument(), new ForceFormatArgument(VideoCodec.MpegTs) }; ConvertFromPipe(VideoType.Ts, container); From 883185b5ec0a54c68fe27fe9a0d2480d15fb19c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:43:53 +0300 Subject: [PATCH 20/27] Added exit code checking to FFProbe stream methods --- FFMpegCore/FFMPEG/FFProbe.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs index a9033c4..3030344 100644 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ b/FFMpegCore/FFMPEG/FFProbe.cs @@ -99,6 +99,7 @@ public VideoInfo ParseVideoInfo(System.IO.Stream stream) if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, "FFProbe process returned exit status " + exitCode); + var output = string.Join("", instance.OutputData); return ParseVideoInfoInternal(info, output); } @@ -132,6 +133,9 @@ public async Task ParseVideoInfoAsync(System.IO.Stream stream) } var exitCode = await task; + if (exitCode != 0) + throw new FFMpegException(FFMpegExceptionType.Process, "FFProbe process returned exit status " + exitCode); + var output = string.Join("", instance.OutputData); return ParseVideoInfoInternal(info, output); } From 8cc1791d25c32ff48d3dfa58d5eaa86c858052f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Tue, 28 Apr 2020 22:54:39 +0300 Subject: [PATCH 21/27] Added Video_TranscodeInMemory test --- FFMpegCore.Test/VideoTest.cs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 25ff2f6..1c8ae3f 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -666,5 +666,30 @@ public void Video_UpdatesProgress() output.Delete(); } } + + [TestMethod] + public void Video_TranscodeInMemory() + { + using (var resStream = new MemoryStream()) + { + var reader = new StreamPipeDataReader(resStream); + var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, PixelFormat.Format24bppRgb, 128, 128)); + + var container = new ArgumentContainer + { + new InputPipeArgument(writer), + new VideoCodecArgument("vp9"), + new ForceFormatArgument("webm"), + new OutputPipeArgument(reader) + }; + + Encoder.Convert(container); + + resStream.Position = 0; + var vi = VideoInfo.FromStream(resStream); + Assert.AreEqual(vi.Width, 128); + Assert.AreEqual(vi.Height, 128); + } + } } } From b4dcd9ffb13819fd9671e6c60166133aa6702371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Sat, 2 May 2020 12:41:47 +0300 Subject: [PATCH 22/27] Added ContainsOnlyOneOf insted of CountExistedKeys --- FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs index 03e2014..829b511 100644 --- a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs +++ b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; namespace FFMpegCore.FFMPEG.Argument { @@ -102,8 +103,8 @@ public void Add(params Argument[] values) /// public bool ContainsInputOutput() { - return CountExistedKeys(typeof(InputArgument), typeof(ConcatArgument), typeof(InputPipeArgument)) == 1 && - CountExistedKeys(typeof(OutputArgument), typeof(OutputPipeArgument)) == 1; + return ContainsOnlyOneOf(typeof(InputArgument), typeof(ConcatArgument), typeof(InputPipeArgument)) && + ContainsOnlyOneOf(typeof(OutputArgument), typeof(OutputPipeArgument)); } /// @@ -116,14 +117,9 @@ public bool ContainsKey(Type key) return _args.ContainsKey(key); } - public int CountExistedKeys(params Type[] types) + public bool ContainsOnlyOneOf(params Type[] types) { - int count = 0; - for(int i =0; i < types.Length; i++) - if (_args.ContainsKey(types[i])) - count++; - - return count; + return types.Count(t => _args.ContainsKey(t)) == 1; } public void CopyTo(KeyValuePair[] array, int arrayIndex) From e4f4dd3def60eb3bf6055c11eece51b14d544976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Sat, 2 May 2020 13:02:22 +0300 Subject: [PATCH 23/27] Updated tests --- FFMpegCore.Test/VideoTest.cs | 40 +++++++++++++++--------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 1c8ae3f..0f4fc7f 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -142,7 +142,7 @@ private void ConvertToStreamPipe(VideoType type, ArgumentContainer container) ms.Position = 0; var outputVideo = VideoInfo.FromStream(ms); - Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate); + //Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate); if (scaling == null) { @@ -343,37 +343,31 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure() [TestMethod] public void Video_ToMP4_Args_StreamOutputPipe_Async() { - Assert.ThrowsException(() => + using (var ms = new MemoryStream()) { - using (var ms = new MemoryStream()) + var pipeSource = new StreamPipeDataReader(ms); + var container = new ArgumentContainer { - var pipeSource = new StreamPipeDataReader(ms); - var container = new ArgumentContainer - { - new InputArgument(VideoLibrary.LocalVideo), - new VideoCodecArgument(VideoCodec.LibX264), - new ForceFormatArgument("mp4"), - new OutputPipeArgument(pipeSource) - }; + new InputArgument(VideoLibrary.LocalVideo), + new VideoCodecArgument(VideoCodec.LibX264), + new ForceFormatArgument("matroska"), + new OutputPipeArgument(pipeSource) + }; - var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); - Encoder.ConvertAsync(container).WaitForResult(); - } - }); + var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); + Encoder.ConvertAsync(container).WaitForResult(); + } } [TestMethod] public void Video_ToMP4_Args_StreamOutputPipe() { - Assert.ThrowsException(() => + var container = new ArgumentContainer { - var container = new ArgumentContainer - { - new VideoCodecArgument(VideoCodec.LibX264), - new ForceFormatArgument("mp4") - }; - ConvertToStreamPipe(VideoType.Mp4, container); - }); + new VideoCodecArgument(VideoCodec.LibX264), + new ForceFormatArgument("matroska") + }; + ConvertToStreamPipe(VideoType.Mp4, container); } [TestMethod] From b007e9105a61161d6166c437a5754c45b1176a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Sat, 2 May 2020 13:07:34 +0300 Subject: [PATCH 24/27] Fixed VideoInfo ToString when no File is specified --- FFMpegCore/VideoInfo.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/FFMpegCore/VideoInfo.cs b/FFMpegCore/VideoInfo.cs index 3bacdb9..3a4cb75 100644 --- a/FFMpegCore/VideoInfo.cs +++ b/FFMpegCore/VideoInfo.cs @@ -8,6 +8,7 @@ namespace FFMpegCore { public class VideoInfo { + private const string NoVideoPlaceholder = "NULL"; private FileInfo _file; internal VideoInfo() @@ -148,10 +149,10 @@ public static VideoInfo FromStream(System.IO.Stream stream) /// public override string ToString() { - return "Video Path : " + FullName + Environment.NewLine + - "Video Root : " + Directory.FullName + Environment.NewLine + - "Video Name: " + Name + Environment.NewLine + - "Video Extension : " + Extension + Environment.NewLine + + return "Video Path : " + (_file != null ? FullName : NoVideoPlaceholder) + Environment.NewLine + + "Video Root : " + (_file != null ? Directory.FullName : NoVideoPlaceholder) + Environment.NewLine + + "Video Name: " + (_file != null ? Name : NoVideoPlaceholder) + Environment.NewLine + + "Video Extension : " + (_file != null ? Extension : NoVideoPlaceholder) + Environment.NewLine + "Video Duration : " + Duration + Environment.NewLine + "Audio Format : " + AudioFormat + Environment.NewLine + "Video Format : " + VideoFormat + Environment.NewLine + From f132a3b731159caea291df50fae229a74e0d9ee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Sat, 2 May 2020 13:13:22 +0300 Subject: [PATCH 25/27] StreamPipeDataWriter & StreamPipeDataReader using Stream.CopyTo --- .../FFMPEG/Pipes/StreamPipeDataReader.cs | 18 ++++----------- .../FFMPEG/Pipes/StreamPipeDataWriter.cs | 22 ++++--------------- 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs index 372d227..1c43dd2 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs @@ -16,21 +16,11 @@ public StreamPipeDataReader(System.IO.Stream destanationStream) DestanationStream = destanationStream; } - public void ReadData(System.IO.Stream stream) - { - int read; - var buffer = new byte[BlockSize]; - while ((read = stream.Read(buffer, 0, buffer.Length)) != 0) - DestanationStream.Write(buffer, 0, buffer.Length); - } + public void ReadData(System.IO.Stream stream) => + stream.CopyTo(DestanationStream, BlockSize); - public async Task ReadDataAsync(System.IO.Stream stream) - { - int read; - var buffer = new byte[BlockSize]; - while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0) - await DestanationStream.WriteAsync(buffer, 0, buffer.Length); - } + public Task ReadDataAsync(System.IO.Stream stream) => + stream.CopyToAsync(DestanationStream, BlockSize); public string GetFormat() { diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs index 8db19eb..e2b5120 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs +++ b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs @@ -19,25 +19,11 @@ public StreamPipeDataWriter(System.IO.Stream stream) Source = stream; } - public void WriteData(System.IO.Stream pipe) - { - var buffer = new byte[BlockSize]; - int read; - while ((read = Source.Read(buffer, 0, buffer.Length)) != 0) - { - pipe.Write(buffer, 0, read); - } - } + public void WriteData(System.IO.Stream pipe)=> + Source.CopyTo(pipe, BlockSize); - public async Task WriteDataAsync(System.IO.Stream pipe) - { - var buffer = new byte[BlockSize]; - int read; - while ((read = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0) - { - await pipe.WriteAsync(buffer, 0, read); - } - } + public Task WriteDataAsync(System.IO.Stream pipe) => + Source.CopyToAsync(pipe, BlockSize); public string GetFormat() { From df63417e110c16d1974d2f9b56a2eb27a37d56f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Sat, 2 May 2020 13:33:44 +0300 Subject: [PATCH 26/27] Changed RawVideoPipeDataWriter StreamFormat, Width, Height to be readonly --- FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs index c7d3df0..fd62f23 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs @@ -11,9 +11,9 @@ namespace FFMpegCore.FFMPEG.Pipes /// public class RawVideoPipeDataWriter : IPipeDataWriter { - public string StreamFormat { get; set; } - public int Width { get; set; } - public int Height { get; set; } + public string StreamFormat { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } public int FrameRate { get; set; } = 25; private IEnumerator framesEnumerator; From 5ad1a3931afc26884e9a4d7f20dcc1777185b0b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=91=D0=B0=D0=B3?= =?UTF-8?q?=D1=80=D1=8F=D0=BD=D1=86=D0=B5=D0=B2?= Date: Sat, 2 May 2020 13:39:48 +0300 Subject: [PATCH 27/27] RawVideoPipeDataWriter updated --- .../FFMPEG/Pipes/RawVideoPipeDataWriter.cs | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs index fd62f23..ce6bcdf 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs +++ b/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs @@ -1,4 +1,5 @@ using FFMpegCore.FFMPEG.Argument; +using FFMpegCore.FFMPEG.Exceptions; using System; using System.Collections.Generic; using System.Text; @@ -15,6 +16,7 @@ public class RawVideoPipeDataWriter : IPipeDataWriter public int Width { get; private set; } public int Height { get; private set; } public int FrameRate { get; set; } = 25; + private bool formatInitialized = false; private IEnumerator framesEnumerator; public RawVideoPipeDataWriter(IEnumerator framesEnumerator) @@ -26,16 +28,20 @@ public RawVideoPipeDataWriter(IEnumerable framesEnumerator) : this( public string GetFormat() { - //see input format references https://lists.ffmpeg.org/pipermail/ffmpeg-user/2012-July/007742.html - if (framesEnumerator.Current == null) + if (!formatInitialized) { - 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; + //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; + formatInitialized = true; + } return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}"; } @@ -44,11 +50,13 @@ public void WriteData(System.IO.Stream stream) { if (framesEnumerator.Current != null) { + CheckFrameAndThrow(framesEnumerator.Current); framesEnumerator.Current.Serialize(stream); } while (framesEnumerator.MoveNext()) { + CheckFrameAndThrow(framesEnumerator.Current); framesEnumerator.Current.Serialize(stream); } } @@ -66,5 +74,12 @@ public async Task WriteDataAsync(System.IO.Stream stream) } } + private void CheckFrameAndThrow(IVideoFrame frame) + { + if (frame.Width != Width || frame.Height != Height || frame.Format != StreamFormat) + throw new FFMpegException(FFMpegExceptionType.Operation, "Video frame is not the same format as created raw video stream\r\n" + + $"Frame format: {frame.Width}x{frame.Height} pix_fmt: {frame.Format}\r\n" + + $"Stream format: {Width}x{Height} pix_fmt: {StreamFormat}"); + } } }