/// <summary>
        /// Converts to low-quality, small video
        /// </summary>
        /// <param name="maxSeconds">0 if you don't want to truncate at all</param>
        /// <returns>log of the run</returns>
        public static ExecutionResult MakeLowQualitySmallVideo(string inputPath, string outputPath, int maxSeconds, IProgress progress)
        {
            if (string.IsNullOrEmpty(LocateAndRememberFFmpeg()))
            {
                return new ExecutionResult() { StandardError = "Could not locate FFMpeg" };
            }

            // isn't working: var arguments = "-i \"" + inputPath + "\" -vcodec mpeg4 -s 160x120 -b 800  -acodec libmp3lame -ar 22050 -ab 32k -ac 1 \"" + outputPath + "\"";
            var arguments = "-i \"" + inputPath +
                            "\" -vcodec mpeg4 -s 160x120 -b 800 -acodec libmp3lame -ar 22050 -ab 32k -ac 1 ";
            if (maxSeconds > 0)
                arguments += " -t " + maxSeconds + " ";
            arguments += "\"" + outputPath + "\"";

            progress.WriteMessage("ffmpeg " + arguments);

            var result = CommandLineProcessing.CommandLineRunner.Run(LocateAndRememberFFmpeg(),
                                                        arguments,
                                                        Environment.CurrentDirectory,
                                                        60 * 10, //10 minutes
                                                        progress
                );

            progress.WriteVerbose(result.StandardOutput);


            //hide a meaningless error produced by some versions of liblame
            if (result.StandardError.Contains("lame: output buffer too small")
                && File.Exists(outputPath))
            {
                result = new ExecutionResult
                {
                    ExitCode = 0,
                    StandardOutput = result.StandardOutput,
                    StandardError = string.Empty
                };

            }
            if (result.StandardError.ToLower().Contains("error") //ffmpeg always outputs config info to standarderror
                || result.StandardError.ToLower().Contains("unable to")
                || result.StandardError.ToLower().Contains("invalid")
                || result.StandardError.ToLower().Contains("could not"))
                progress.WriteWarning(result.StandardError);

            return result;
        }
        /// <summary>
        /// Converts to low-quality, mono mp3
        /// </summary>
        /// <returns>log of the run</returns>
        public static ExecutionResult MakeLowQualityCompressedAudio(string inputPath, string outputPath, IProgress progress)
        {
            if (string.IsNullOrEmpty(LocateAndRememberFFmpeg()))
            {
                return new ExecutionResult() { StandardError = "Could not locate FFMpeg" };
            }

            var arguments = "-i \"" + inputPath + "\" -acodec libmp3lame -ac 1 -ar 8000 \"" + outputPath + "\"";


            progress.WriteMessage("ffmpeg " + arguments);


            var result = CommandLineProcessing.CommandLineRunner.Run(LocateAndRememberFFmpeg(),
                                                        arguments,
                                                        Environment.CurrentDirectory,
                                                        60 * 10, //10 minutes
                                                        progress
                );

            progress.WriteVerbose(result.StandardOutput);


            //hide a meaningless error produced by some versions of liblame
            if (result.StandardError.Contains("lame: output buffer too small")
                && File.Exists(outputPath))
            {
                result = new ExecutionResult
                {
                    ExitCode = 0,
                    StandardOutput = result.StandardOutput,
                    StandardError = string.Empty
                };
            }
            if (result.StandardError.ToLower().Contains("error")
                || result.StandardError.ToLower().Contains("unable to")
                || result.StandardError.ToLower().Contains("invalid")
                || result.StandardError.ToLower().Contains("could not")
                ) //ffmpeg always outputs config info to standarderror
                progress.WriteError(result.StandardError);

            return result;
        }
        /// <summary>
        /// Extracts the audio from a video. Note, it will fail if the file exists, so the client
        /// is responsible for verifying with the user and deleting the file before calling this.
        /// </summary>
        /// <param name="inputPath"></param>
        /// <param name="outputPath"></param>
        /// <param name="audioCodec">e.g. copy, pcm_s16le, pcm_s32le, etc.</param>
        /// <param name="sampleRate">e.g. 22050, 44100, 4800. Use 0 to use ffmpeg's default</param>
        /// <param name="channels">0 for same, 1 for mono, 2 for stereo</param>
        /// <param name="progress"></param>
        /// <returns>log of the run</returns>
        private static ExecutionResult ExtractAudio(string inputPath, string outputPath,
            string audioCodec, int sampleRate, int channels, IProgress progress)
        {
            if (string.IsNullOrEmpty(LocateFFmpeg()))
            {
                return new ExecutionResult() { StandardError = "Could not locate FFMpeg" };
            }

            var sampleRateArg = "";
            if (sampleRate > 0)
                sampleRateArg = string.Format("-ar {0}", sampleRate);

            //TODO: this will output whatever mp3 or wav or whatever is in the video... might not be wav at all!
            var channelsArg = "";
            if (channels > 0)
                channelsArg = string.Format(" -ac {0}", channels);

            var arguments = string.Format("-i \"{0}\" -vn -acodec {1}  {2} {3} \"{4}\"",
                inputPath, audioCodec, sampleRateArg, channelsArg, outputPath);

            progress.WriteMessage("ffmpeg " + arguments);

            var result = CommandLineProcessing.CommandLineRunner.Run(LocateAndRememberFFmpeg(),
                                                        arguments,
                                                        Environment.CurrentDirectory,
                                                        60 * 10, //10 minutes
                                                        progress);

            progress.WriteVerbose(result.StandardOutput);

            //hide a meaningless error produced by some versions of liblame
            if (result.StandardError.Contains("lame: output buffer too small")
                && File.Exists(outputPath))
            {
                var doctoredResult = new ExecutionResult
                {
                    ExitCode = 0,
                    StandardOutput = result.StandardOutput,
                    StandardError = string.Empty
                };
                return doctoredResult;
            }
            if (result.StandardError.ToLower().Contains("error")) //ffmpeg always outputs config info to standarderror
                progress.WriteError(result.StandardError);

            return result;
        }
        /// <summary>
        /// Creates an audio file, using the received one as the bases, with the specified number
        /// of channels. For example, this can be used to convert a 2-channel audio file to a
        /// single channel audio file.
        /// </summary>
        /// <returns>log of the run</returns>
        public static ExecutionResult ChangeNumberOfAudioChannels(string inputPath,
            string outputPath, int channels, IProgress progress)
        {
            if (string.IsNullOrEmpty(LocateFFmpeg()))
                return new ExecutionResult { StandardError = "Could not locate FFMpeg" };

            var arguments = string.Format("-i \"{0}\" -vn -ac {1} \"{2}\"",
                inputPath, channels, outputPath);

            var result = CommandLineRunner.Run(LocateAndRememberFFmpeg(),
                            arguments,
                            Environment.CurrentDirectory,
                            60 * 10, //10 minutes
                            progress);

            progress.WriteVerbose(result.StandardOutput);

            //hide a meaningless error produced by some versions of liblame
            if (result.StandardError.Contains("lame: output buffer too small") && File.Exists(outputPath))
            {
                var doctoredResult = new ExecutionResult
                {
                    ExitCode = 0,
                    StandardOutput = result.StandardOutput,
                    StandardError = string.Empty
                };

                return doctoredResult;
            }

            // ffmpeg always outputs config info to standarderror
            if (result.StandardError.ToLower().Contains("error"))
                progress.WriteError(result.StandardError);

            return result;
        }
		/// <summary>
		/// use this one if you're doing a long running task that you'll have running in a thread, so that you need a way to abort it
		/// </summary>
        public ExecutionResult Start(string exePath, string arguments, Encoding encoding, string fromDirectory, int secondsBeforeTimeOut, IProgress progress, Action<string> actionForReportingProgress, string standardInputPath = null)
		{
			progress.WriteVerbose("running '{0} {1}' from '{2}'", exePath, arguments, fromDirectory);
			ExecutionResult result = new ExecutionResult();
			result.Arguments = arguments;
			result.ExePath = exePath;

		    using (_process = new Process())
		    {
		        _process.StartInfo.RedirectStandardError = true;
		        _process.StartInfo.RedirectStandardOutput = true;
		        _process.StartInfo.UseShellExecute = false;
		        _process.StartInfo.CreateNoWindow = true;
		        _process.StartInfo.WorkingDirectory = fromDirectory;
		        _process.StartInfo.FileName = exePath;
		        _process.StartInfo.Arguments = arguments;
		        if (encoding != null)
		        {
		            _process.StartInfo.StandardOutputEncoding = encoding;
		        }
		        if (actionForReportingProgress != null)
		            _processReader = new AsyncProcessOutputReader(_process, progress, actionForReportingProgress);
		        else
		            _processReader = new SynchronousProcessOutputReader(_process, progress);
		        if (standardInputPath != null)
		            _process.StartInfo.RedirectStandardInput = true;

		        try
		        {
		            Debug.WriteLine("CommandLineRunner Starting at " + DateTime.Now.ToString());
		            _process.Start();
		            if (standardInputPath != null)
		            {
		                var myWriter = _process.StandardInput.BaseStream;
		                var input = File.ReadAllBytes(standardInputPath);
		                myWriter.Write(input, 0, input.Length);
		                myWriter.Close(); // no more input
		            }
		        }
		        catch (Win32Exception error)
		        {
		            throw;
		        }

		        if (secondsBeforeTimeOut > TimeoutSecondsOverrideForUnitTests)
		            secondsBeforeTimeOut = TimeoutSecondsOverrideForUnitTests;

		        bool timedOut = false;
		        Debug.WriteLine("CommandLineRunner Reading at " + DateTime.Now.ToString("HH:mm:ss.ffff"));
		        if (!_processReader.Read(secondsBeforeTimeOut))
		        {
		            timedOut = !progress.CancelRequested;
		            try
		            {
		                if (_process.HasExited)
		                {
		                    progress.WriteWarning("Process exited, cancelRequested was {0}", progress.CancelRequested);
		                }
		                else
		                {
		                    if (timedOut)
		                        progress.WriteWarning("({0}) Timed Out...", exePath);
		                    progress.WriteWarning("Killing Process ({0})", exePath);
		                    _process.Kill();
		                }
		            }
		            catch (Exception e)
		            {
		                progress.WriteWarning(
		                    "Exception while killing process, as though the process reader failed to notice that the process was over: {0}",
		                    e.Message);
		                progress.WriteWarning("Process.HasExited={0}", _process.HasExited.ToString());
		            }
		        }
		        result.StandardOutput = _processReader.StandardOutput;
		        result.StandardError = _processReader.StandardError;

		        if (timedOut)
		        {
		            result.StandardError += Environment.NewLine + "Timed Out after waiting " + secondsBeforeTimeOut +
		                                    " seconds.";
		            result.ExitCode = ExecutionResult.kTimedOut;
		        }

		        else if (progress.CancelRequested)
		        {
		            result.StandardError += Environment.NewLine + "User Cancelled.";
		            result.ExitCode = ExecutionResult.kCancelled;
		        }
		        else
		        {
		            result.ExitCode = _process.ExitCode;
		        }
		    }
		    return result;
		}