/// <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; }