From 893646a52c0824e55a8f3a9ff3d4da5e4d50cf9a Mon Sep 17 00:00:00 2001
From: Tobias Faller <fallert@tf.uni-freiburg.de>
Date: Fri, 5 Jan 2024 17:38:47 +0100
Subject: [PATCH 1/3] Clean-up code handling pipes in Engine class; Fixes #69

---
 src/FFmpeg.NET/Engine.cs | 70 +++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 30 deletions(-)
 mode change 100644 => 100755 src/FFmpeg.NET/Engine.cs

diff --git a/src/FFmpeg.NET/Engine.cs b/src/FFmpeg.NET/Engine.cs
old mode 100644
new mode 100755
index 6b7bffa..a3fea1a
--- a/src/FFmpeg.NET/Engine.cs
+++ b/src/FFmpeg.NET/Engine.cs
@@ -87,28 +87,35 @@ public async Task<Stream> ConvertAsync(IInputArgument input, ConversionOptions o
 
         public async Task ConvertAsync(IInputArgument input, Stream output, ConversionOptions options, CancellationToken cancellationToken)
         {
-            var pipeName = $"{_pipePrefix}{Guid.NewGuid()}";
+            var outputPipeName = $"{_pipePrefix}{Guid.NewGuid()}";
+            var outputArgument = new OutputPipe(GetPipePath(outputPipeName));
+            var pipe = new NamedPipeServerStream(outputPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
+
             var parameters = new FFmpegParameters
             {
                 Task = FFmpegTask.Convert,
                 Input = input,
-                Output = new OutputPipe(GetPipePath(pipeName)),
+                Output = outputArgument,
                 ConversionOptions = options
             };
 
-            var process = CreateProcess(parameters);
-            var pipe = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
-
-            await pipe.WaitForConnectionAsync(cancellationToken);
-            await Task.WhenAll(
-                pipe.CopyToAsync(output, cancellationToken),
-                process.ExecuteAsync(cancellationToken).ContinueWith(x =>
-                {
-                    pipe.Disconnect();
-                    pipe.Dispose();
-                }, cancellationToken)
-            ).ConfigureAwait(false);
-            Cleanup(process);
+            var ffmpegProcess = CreateProcess(parameters);
+            try
+            {
+                var executeProcess = ffmpegProcess.ExecuteAsync(cancellationToken);
+                var copyData = pipe.WaitForConnectionAsync(cancellationToken)
+                    .ContinueWith(async x =>
+                    {
+                        await pipe.CopyToAsync(output, cancellationToken);
+                    }, cancellationToken).Unwrap();
+                await Task.WhenAll(executeProcess, copyData).ConfigureAwait(false);
+                pipe.Disconnect();
+            }
+            finally
+            {
+                pipe.Dispose();
+                Cleanup(ffmpegProcess);
+            }
         }
 
         public async Task ConvertAsync(IArgument argument, Stream output, CancellationToken cancellationToken)
@@ -119,21 +126,24 @@ public async Task ConvertAsync(IArgument argument, Stream output, CancellationTo
 
             var arguments = argument.Argument + $" {outputArgument.Argument}";
             var parameters = new FFmpegParameters { CustomArguments = arguments };
-            var process = CreateProcess(parameters);
-
-            await Task.WhenAll(
-                pipe.WaitForConnectionAsync(cancellationToken).ContinueWith(async x =>
-                {
-                    await pipe.CopyToAsync(output, cancellationToken);
-                }),
-                process.ExecuteAsync(cancellationToken).ContinueWith(x =>
-                {
-                    pipe.Disconnect();
-                    pipe.Dispose();
-                }, cancellationToken)
-            ).ConfigureAwait(false);
-
-            Cleanup(process);
+
+            var ffmpegProcess = CreateProcess(parameters);
+            try
+            {
+                var executeProcess = ffmpegProcess.ExecuteAsync(cancellationToken);
+                var copyData = pipe.WaitForConnectionAsync(cancellationToken)
+                    .ContinueWith(async x =>
+                    {
+                        await pipe.CopyToAsync(output, cancellationToken);
+                    }, cancellationToken).Unwrap();
+                await Task.WhenAll(executeProcess, copyData).ConfigureAwait(false);
+                pipe.Disconnect();
+            }
+            finally
+            {
+                pipe.Dispose();
+                Cleanup(ffmpegProcess);
+            }
         }
 
         private async Task ExecuteAsync(FFmpegParameters parameters, CancellationToken cancellationToken)

From 0bfc55ca845038887f9a30dc1e515c4f4905a319 Mon Sep 17 00:00:00 2001
From: Tobias Faller <fallert@tf.uni-freiburg.de>
Date: Fri, 5 Jan 2024 19:19:21 +0100
Subject: [PATCH 2/3] Made ExecuteAsync in Engine public and added Execute task
 type

---
 src/FFmpeg.NET/Engine.cs                | 100 +++++++++++-------------
 src/FFmpeg.NET/FFmpegArgumentBuilder.cs |  26 +++++-
 src/FFmpeg.NET/FFmpegTask.cs            |   3 +-
 3 files changed, 72 insertions(+), 57 deletions(-)

diff --git a/src/FFmpeg.NET/Engine.cs b/src/FFmpeg.NET/Engine.cs
index a3fea1a..69f75f7 100755
--- a/src/FFmpeg.NET/Engine.cs
+++ b/src/FFmpeg.NET/Engine.cs
@@ -31,6 +31,11 @@ public Engine(string ffmpegPath = null)
         public event EventHandler<ConversionCompleteEventArgs> Complete;
         public event EventHandler<ConversionDataEventArgs> Data;
 
+
+        // ---------------------------------------------------------
+        // Wrapper methods for ease of use
+        // ---------------------------------------------------------
+
         public async Task<MetaData> GetMetaDataAsync(IInputArgument mediaFile, CancellationToken cancellationToken)
         {
             var parameters = new FFmpegParameters
@@ -87,46 +92,64 @@ public async Task<Stream> ConvertAsync(IInputArgument input, ConversionOptions o
 
         public async Task ConvertAsync(IInputArgument input, Stream output, ConversionOptions options, CancellationToken cancellationToken)
         {
-            var outputPipeName = $"{_pipePrefix}{Guid.NewGuid()}";
-            var outputArgument = new OutputPipe(GetPipePath(outputPipeName));
-            var pipe = new NamedPipeServerStream(outputPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
-
             var parameters = new FFmpegParameters
             {
                 Task = FFmpegTask.Convert,
                 Input = input,
-                Output = outputArgument,
                 ConversionOptions = options
             };
+            await ExecuteAsync(parameters, output, cancellationToken).ConfigureAwait(false);
+        }
 
-            var ffmpegProcess = CreateProcess(parameters);
-            try
+        public async Task ConvertAsync(IArgument argument, Stream output, CancellationToken cancellationToken)
+        {
+            var parameters = new FFmpegParameters
             {
-                var executeProcess = ffmpegProcess.ExecuteAsync(cancellationToken);
-                var copyData = pipe.WaitForConnectionAsync(cancellationToken)
-                    .ContinueWith(async x =>
-                    {
-                        await pipe.CopyToAsync(output, cancellationToken);
-                    }, cancellationToken).Unwrap();
-                await Task.WhenAll(executeProcess, copyData).ConfigureAwait(false);
-                pipe.Disconnect();
-            }
-            finally
+                Task = FFmpegTask.Execute,
+                CustomArguments = argument.Argument
+            };
+            await ExecuteAsync(parameters, output, cancellationToken).ConfigureAwait(false);
+        }
+
+        public async Task ExecuteAsync(string arguments, CancellationToken cancellationToken)
+        {
+            var parameters = new FFmpegParameters
             {
-                pipe.Dispose();
-                Cleanup(ffmpegProcess);
-            }
+                Task = FFmpegTask.Execute,
+                CustomArguments = arguments,
+            };
+            await ExecuteAsync(parameters, cancellationToken).ConfigureAwait(false);
+        }
+        
+        public async Task ExecuteAsync(string arguments, string workingDirectory, CancellationToken cancellationToken)
+        {
+            var parameters = new FFmpegParameters
+            {
+                Task = FFmpegTask.Execute,
+                CustomArguments = arguments,
+                WorkingDirectory = workingDirectory
+            };
+            await ExecuteAsync(parameters, cancellationToken).ConfigureAwait(false);
         }
 
-        public async Task ConvertAsync(IArgument argument, Stream output, CancellationToken cancellationToken)
+        // ---------------------------------------------------------
+        // Basic API to call ffmpeg
+        // ---------------------------------------------------------
+
+        public async Task ExecuteAsync(FFmpegParameters parameters, CancellationToken cancellationToken)
+        {
+            var ffmpegProcess = CreateProcess(parameters);
+            await ffmpegProcess.ExecuteAsync(cancellationToken).ConfigureAwait(false);
+            Cleanup(ffmpegProcess);
+        }
+
+        public async Task ExecuteAsync(FFmpegParameters parameters, Stream output, CancellationToken cancellationToken)
         {
             var outputPipeName = $"{_pipePrefix}{Guid.NewGuid()}";
             var outputArgument = new OutputPipe(GetPipePath(outputPipeName));
             var pipe = new NamedPipeServerStream(outputPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
 
-            var arguments = argument.Argument + $" {outputArgument.Argument}";
-            var parameters = new FFmpegParameters { CustomArguments = arguments };
-
+            parameters.Output = outputArgument;
             var ffmpegProcess = CreateProcess(parameters);
             try
             {
@@ -146,35 +169,6 @@ public async Task ConvertAsync(IArgument argument, Stream output, CancellationTo
             }
         }
 
-        private async Task ExecuteAsync(FFmpegParameters parameters, CancellationToken cancellationToken)
-        {
-            var ffmpegProcess = CreateProcess(parameters);
-            await ffmpegProcess.ExecuteAsync(cancellationToken).ConfigureAwait(false);
-            Cleanup(ffmpegProcess);
-        }
-
-        public async Task ExecuteAsync(string arguments, CancellationToken cancellationToken)
-        {
-            var parameters = new FFmpegParameters
-            {
-                CustomArguments = arguments,
-            };
-            await ExecuteAsync(parameters, cancellationToken).ConfigureAwait(false);
-        }
-        
-        // if further overloads are needed
-        // it should be considered if ExecuteAsync(FFmpegParameters parameters, CancellationToken cancellationToken) should be made public
-        public async Task ExecuteAsync(string arguments, string workingDirectory, CancellationToken cancellationToken)
-        {
-            var parameters = new FFmpegParameters
-            {
-                CustomArguments = arguments,
-                WorkingDirectory = workingDirectory
-            };
-            await ExecuteAsync(parameters, cancellationToken).ConfigureAwait(false);
-        }
-
-
         private FFmpegProcess CreateProcess(FFmpegParameters parameters)
         {
             var ffmpegProcess = new FFmpegProcess(parameters, _ffmpegPath);
diff --git a/src/FFmpeg.NET/FFmpegArgumentBuilder.cs b/src/FFmpeg.NET/FFmpegArgumentBuilder.cs
index baa6867..5f9e5ff 100644
--- a/src/FFmpeg.NET/FFmpegArgumentBuilder.cs
+++ b/src/FFmpeg.NET/FFmpegArgumentBuilder.cs
@@ -9,14 +9,12 @@ internal static class FFmpegArgumentBuilder
     {
         public static string Build(FFmpegParameters parameters)
         {
-            if (parameters.HasCustomArguments)
-                return parameters.CustomArguments;
-
             return parameters.Task switch
             {
                 FFmpegTask.Convert => Convert(parameters.Input, parameters.Output, parameters.ConversionOptions),
                 FFmpegTask.GetMetaData => GetMetadata(parameters.Input),
                 FFmpegTask.GetThumbnail => GetThumbnail(parameters.Input, parameters.Output, parameters.ConversionOptions),
+                FFmpegTask.Execute => Execute(parameters.Input, parameters.Output, parameters.CustomArguments),
                 _ => throw new ArgumentOutOfRangeException(),
             };
         }
@@ -182,6 +180,28 @@ private static string Convert(IInputArgument input, IOutputArgument output, Conv
             return commandBuilder.AppendFormat(" {0} ", output.Argument).ToString();
         }
 
+        private static string Execute(IInputArgument input, IOutputArgument output, string customArguments)
+        {
+            StringBuilder commandBuilder = new StringBuilder();
+            commandBuilder.Append(customArguments);
+
+            if (input != null)
+            {
+                if (input.UseStandardInput)
+                {
+                    commandBuilder.Append(" -nostdin ");
+                }
+                commandBuilder.Append($" -i {input.Argument}");
+            }
+
+            if (output != null)
+            {
+                commandBuilder.Append($"{output.Argument}");
+            }
+
+            return commandBuilder.ToString();
+        }
+
         private static void AppendHWAccelOutputFormat(StringBuilder commandBuilder, ConversionOptions conversionOptions)
         {
             if (conversionOptions.HWAccel != HWAccel.None && conversionOptions.HWAccelOutputFormatCopy)
diff --git a/src/FFmpeg.NET/FFmpegTask.cs b/src/FFmpeg.NET/FFmpegTask.cs
index b30634d..7e2d8a8 100644
--- a/src/FFmpeg.NET/FFmpegTask.cs
+++ b/src/FFmpeg.NET/FFmpegTask.cs
@@ -4,6 +4,7 @@ public enum FFmpegTask
     {
         Convert,
         GetMetaData,
-        GetThumbnail
+        GetThumbnail,
+        Execute
     }
 }
\ No newline at end of file

From aed8734ad5adf6c7f5231ba109a58b33a326e274 Mon Sep 17 00:00:00 2001
From: Tobias Faller <fallert@tf.uni-freiburg.de>
Date: Fri, 5 Jan 2024 19:42:12 +0100
Subject: [PATCH 3/3] Added spaces around string in argument builder

---
 src/FFmpeg.NET/FFmpegArgumentBuilder.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/FFmpeg.NET/FFmpegArgumentBuilder.cs b/src/FFmpeg.NET/FFmpegArgumentBuilder.cs
index 5f9e5ff..621b1cc 100644
--- a/src/FFmpeg.NET/FFmpegArgumentBuilder.cs
+++ b/src/FFmpeg.NET/FFmpegArgumentBuilder.cs
@@ -191,12 +191,12 @@ private static string Execute(IInputArgument input, IOutputArgument output, stri
                 {
                     commandBuilder.Append(" -nostdin ");
                 }
-                commandBuilder.Append($" -i {input.Argument}");
+                commandBuilder.Append($" -i {input.Argument} ");
             }
 
             if (output != null)
             {
-                commandBuilder.Append($"{output.Argument}");
+                commandBuilder.Append($" {output.Argument} ");
             }
 
             return commandBuilder.ToString();