using csmic; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace butterflow_ui { /// A butterflow wrapper. Provides interaction with the butterflow executable. public class ButterflowWrapper : PropertyChangedAlerter { #region Members /// The RegEx string for matching probed resolution. private const string REGEX_RESOLUTION = @"Resolution\s*:\s(?\d+)x(?\d+)"; /// The RegEx string for matching the probed playback rate. private const string REGEX_RATE = @"Rate\s*:\s(?\d+\.\d+) fps"; /// The RegEx string for detecting progress made when rendering a video. private const string REGEX_PROGRESS = @"To write\:\s*\w*\s*\w*\,*\w*\s*(?\d+\.*\d*)%"; /// An alternative RegEx string for detecting progress made when rendering a video. private const string REGEX_PROGRESS_ALT = @"\\d+\.*\d*)%\>"; /// The RegEx string for determining available processing devices in butterflow.. private const string REGEX_DEVICE = @"\*\s*Device\s*(?\d+)\s*\:\s*(?(\w*\s*)*)"; /// Full pathname of the butterflow executable file. private Lazy executablePath = new Lazy(() => Path.Combine(Directory.GetCurrentDirectory(), "ThirdPartyCompiled", "butterflow.exe")); /// Queue of butterflow commands to run. private Queue runQueue = new Queue(); /// The console output from butterflow. private string consoleOutput = string.Empty; /// True if butterflow is running, false if not. private bool isRunning; /// The progress percentage as reported by butterflow. private double progressPercentage; /// The running butterflow process. private Process runningProcess; /// An input interpreter used for converting string values to numeric values. private InputInterpreter interpreter = new InputInterpreter(); /// Event queue for all listeners interested in ParsedConsoleOutputRecieved events. public event EventHandler ParsedConsoleOutputRecieved; /// Event queue for all listeners interested in ButterflowExited events. public event EventHandler ButterflowExited; #endregion #region Properties /// Gets the console output from butterflow. /// The console output from butterflow. public string ConsoleOutput { get { return this.consoleOutput; } private set { this.consoleOutput = value; OnPropertyChanged(); } } /// Gets a value indicating whether butterflow is currently running. /// True if butterflow is running, false if not. public bool IsRunning { get { return this.isRunning; } private set { this.isRunning = value; OnPropertyChanged(); } } /// Gets the progress percentage as reported by butterflow. /// The progress percentage as reported by butterflow. public double ProgressPercentage { get { return this.progressPercentage; } private set { this.progressPercentage = value; OnPropertyChanged(); } } /// Gets or sets the list of devices available for butterflow processing. /// The devices available for butterflow processing. public Dictionary Devices { get; private set; } = new Dictionary() { { 0, Localization.Localization.ForceCPU } }; #endregion #region Methods /// Runs butterflow with the given by adding it to the queue. /// The options configuration. public void Run(OptionsConfiguration optionsConfiguration) { for (int i = 0; i < optionsConfiguration.VideoInput.Count(); i++) { var arguments = optionsConfiguration.ToButterflowArguments(i); this.runQueue.Enqueue(arguments); } ProcessQueue(); } /// Process the queue of butterflow arguments. public void ProcessQueue() { if (!this.IsRunning && this.runQueue.Any()) { var arguments = this.runQueue.Dequeue(); var process = new Process(); process.StartInfo = new ProcessStartInfo(executablePath.Value, arguments); process.StartInfo.CreateNoWindow = true; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.EnableRaisingEvents = true; process.OutputDataReceived += Process_OutputDataReceived; process.ErrorDataReceived += Process_OutputDataReceived; process.Exited += Process_Exited; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); this.IsRunning = true; this.runningProcess = process; } } /// Kills the running instance of butterflow, cancelling its current operation. public void Cancel() { if (this.IsRunning && this.runningProcess != null) { this.runningProcess.Kill(); } this.runQueue.Clear(); } /// Probes a video file. /// The video file to be probed. public void Probe(string videoFile) { string arguments = string.Format("-prb \"{0}\"", videoFile); Run(arguments); } /// Gets the devices available for butterflow processing. public void GetDevices() { string arguments = "--show-devices"; Run(arguments); } /// Runs butterflow with the given by adding it to the queue. /// Options for controlling the operation. private void Run(string arguments) { this.runQueue.Enqueue(arguments); ProcessQueue(); } /// Event handler. Called by Process for exited events. /// Source of the event. /// Event information. private void Process_Exited(object sender, EventArgs e) { this.IsRunning = false; this.runningProcess = null; OnButterflowExited(); ProcessQueue(); } /// /// Parses console output and attempts to find known values. If a known value is found, the /// event is triggered. /// /// The console output from butterflow. private void ParseConsoleOutput(string consoleOutput) { if (string.IsNullOrWhiteSpace(consoleOutput)) { //Ignore null content and just escape. return; } // Test for resolution var regex = new Regex(REGEX_RESOLUTION); foreach (Match match in regex.Matches(consoleOutput)) { var width = match.Groups["Width"].Value; var height = match.Groups["Height"].Value; OnParsedConsoleOutputRecieved(ButterflowOutputType.Width, width, consoleOutput); OnParsedConsoleOutputRecieved(ButterflowOutputType.Height, height, consoleOutput); } // Test for playback rate regex = new Regex(REGEX_RATE); foreach (Match match in regex.Matches(consoleOutput)) { var rate = match.Groups["Rate"].Value; OnParsedConsoleOutputRecieved(ButterflowOutputType.Rate, rate, consoleOutput); } // Test for progress being made when rendering a video regex = new Regex(REGEX_PROGRESS); foreach (Match match in regex.Matches(consoleOutput)) { var progress = match.Groups["Progress"].Value; this.interpreter.Interpret(progress); this.ProgressPercentage = this.interpreter.Double; OnParsedConsoleOutputRecieved(ButterflowOutputType.Progress, progress, consoleOutput); } // An alternate test for progression on a rendering video regex = new Regex(REGEX_PROGRESS_ALT); foreach (Match match in regex.Matches(consoleOutput)) { var progress = match.Groups["Progress"].Value; this.interpreter.Interpret(progress); this.ProgressPercentage = this.interpreter.Double; OnParsedConsoleOutputRecieved(ButterflowOutputType.Progress, progress, consoleOutput); } // Test for device declaration regex = new Regex(REGEX_DEVICE); foreach (Match match in regex.Matches(consoleOutput)) { var deviceID = match.Groups["DeviceID"].Value; var deviceName = match.Groups["DeviceName"].Value.Trim(); //Add 1 to the index count. This accounts for the 0 index of the "Force CPU" option in the options window. this.interpreter.Interpret(string.Concat(deviceID,"+1")); if (!this.Devices.ContainsKey(this.interpreter.Int)) { this.Devices.Add(this.interpreter.Int, deviceName); OnPropertyChanged("Devices"); } OnParsedConsoleOutputRecieved(ButterflowOutputType.Device, deviceName, consoleOutput); } } /// Executes the parsed console output recieved action. /// Type of the output. /// The value. /// The console output from butterflow. private void OnParsedConsoleOutputRecieved(ButterflowOutputType outputType, string value, string consoleOutput) { this.ParsedConsoleOutputRecieved?.Invoke(this, new ButterflowOutputArgs(outputType, value, consoleOutput)); } /// Executes the butterflow exited action. private void OnButterflowExited() { this.ButterflowExited?.Invoke(this, new ButterflowExitArgs()); } /// Event handler. Called by Process for output data received events. /// Source of the event. /// Data received event information. private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) { this.ConsoleOutput += string.Format("{0}{1}", e.Data, Environment.NewLine); ParseConsoleOutput(e.Data); } #endregion #region Subclasses /// Arguments for butterflow output events where a known value of type can be parsed. public class ButterflowOutputArgs : ButterflowConsoleOutputArgs { #region Properties /// Gets or sets the type of the output detected from butterflow. /// The type of the output detected from butterflow. public ButterflowOutputType OutputType { get; private set; } /// Gets or sets the value detected from butterflow. /// The value detected from butterflow. public string Value { get; private set; } #endregion /// Constructor. /// The type of the output detected from butterflow. /// The value detected from butterflow. /// The console output. public ButterflowOutputArgs(ButterflowOutputType outputType, string value, string consoleOutput) : base(consoleOutput) { this.OutputType = outputType; this.Value = value; } } /// Arguments for butterflow console output events. public class ButterflowConsoleOutputArgs : EventArgs { #region Properties /// Gets the console output. /// The console output. public string ConsoleOutput { get; private set; } #endregion #region Constructors /// Constructor. /// The console output. public ButterflowConsoleOutputArgs(string consoleOutput) { this.ConsoleOutput = consoleOutput; } #endregion } /// Arguments for butterflow exiting. public class ButterflowExitArgs : EventArgs { #region Constructors public ButterflowExitArgs() { // } #endregion } /// Values that represent butterflow output types. public enum ButterflowOutputType { /// Video Width. Width, /// Video Height. Height, /// Video playback rate. Rate, /// Video processing progress. Progress, /// An available processing device. Device } #endregion } }