// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // Class which handles the CLI // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrake.ApplicationServices.Services { using System; using System.Diagnostics; using System.IO; using System.Threading; using System.Windows.Forms; using HandBrake.ApplicationServices.EventArgs; using HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Services.Base; using HandBrake.ApplicationServices.Services.Interfaces; using HandBrake.ApplicationServices.Utilities; using Parser = HandBrake.ApplicationServices.Parsing.Parser; /// /// Class which handles the CLI /// public class Encode : EncodeBase, IEncode { #region Private Variables /// /// The User Setting Service /// private readonly IUserSettingService userSettingService; /// /// Gets The Process Handle /// private IntPtr processHandle; /// /// Gets the Process ID /// private int processId; /// /// The Start time of the current Encode; /// private DateTime startTime; /// /// The Current Task /// private QueueTask currentTask; #endregion /// /// Initializes a new instance of the class. /// /// /// The user Setting Service. /// public Encode(IUserSettingService userSettingService) : base(userSettingService) { this.userSettingService = userSettingService; this.EncodeStarted += this.EncodeEncodeStarted; } #region Properties /// /// Gets or sets The HB Process /// protected Process HbProcess { get; set; } #endregion #region Public Methods /// /// Execute a HandBrakeCLI process. /// /// /// The encodeQueueTask. /// /// /// Enable Logging. When Disabled we onlt parse Standard Ouput for progress info. Standard Error log data is ignored. /// public void Start(QueueTask encodeQueueTask, bool enableLogging) { try { this.currentTask = encodeQueueTask; if (this.IsEncoding) { throw new Exception("HandBrake is already encodeing."); } this.IsEncoding = true; if (enableLogging) { try { this.SetupLogging(currentTask); } catch (Exception) { this.IsEncoding = false; throw; } } if (this.userSettingService.GetUserSetting(ASUserSettingConstants.PreventSleep)) { Win32.PreventSleep(); } // Make sure the path exists, attempt to create it if it doesn't this.VerifyEncodeDestinationPath(currentTask); string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe"); // TODO tidy this code up, it's kinda messy. string query = this.currentTask.Task.IsPreviewEncode ? QueryGeneratorUtility.GeneratePreviewQuery( new EncodeTask(this.currentTask.Task), this.currentTask.Task.PreviewEncodeDuration, this.currentTask.Task.PreviewEncodeStartAt, userSettingService.GetUserSetting(ASUserSettingConstants.PreviewScanCount), userSettingService.GetUserSetting(ASUserSettingConstants.Verbosity), userSettingService.GetUserSetting(ASUserSettingConstants.DisableLibDvdNav)) : QueryGeneratorUtility.GenerateQuery(new EncodeTask(this.currentTask.Task), userSettingService.GetUserSetting(ASUserSettingConstants.PreviewScanCount), userSettingService.GetUserSetting(ASUserSettingConstants.Verbosity), userSettingService.GetUserSetting(ASUserSettingConstants.DisableLibDvdNav)); ProcessStartInfo cliStart = new ProcessStartInfo(handbrakeCLIPath, query) { RedirectStandardOutput = true, RedirectStandardError = enableLogging, UseShellExecute = false, CreateNoWindow = true }; this.HbProcess = new Process { StartInfo = cliStart }; this.HbProcess.Start(); this.startTime = DateTime.Now; if (enableLogging) { this.HbProcess.ErrorDataReceived += this.HbProcErrorDataReceived; this.HbProcess.BeginErrorReadLine(); } this.processId = this.HbProcess.Id; this.processHandle = this.HbProcess.Handle; // Set the process Priority if (this.processId != -1) { this.HbProcess.EnableRaisingEvents = true; this.HbProcess.Exited += this.HbProcessExited; } // Set the Process Priority switch (this.userSettingService.GetUserSetting(ASUserSettingConstants.ProcessPriority)) { case "Realtime": this.HbProcess.PriorityClass = ProcessPriorityClass.RealTime; break; case "High": this.HbProcess.PriorityClass = ProcessPriorityClass.High; break; case "Above Normal": this.HbProcess.PriorityClass = ProcessPriorityClass.AboveNormal; break; case "Normal": this.HbProcess.PriorityClass = ProcessPriorityClass.Normal; break; case "Low": this.HbProcess.PriorityClass = ProcessPriorityClass.Idle; break; default: this.HbProcess.PriorityClass = ProcessPriorityClass.BelowNormal; break; } // Fire the Encode Started Event this.InvokeEncodeStarted(EventArgs.Empty); } catch (Exception exc) { encodeQueueTask.Status = QueueItemStatus.Error; this.InvokeEncodeCompleted( new EncodeCompletedEventArgs( false, null, "An Error occured when trying to encode this source. ")); throw; } } /// /// Stop the Encode /// public void Stop() { this.Stop(null); } /// /// Kill the CLI process /// /// /// The Exception that has occured. /// This will get bubbled up through the EncodeCompletedEventArgs /// public override void Stop(Exception exc) { try { if (this.HbProcess != null && !this.HbProcess.HasExited) { this.HbProcess.Kill(); } } catch (Exception) { // No need to report anything to the user. If it fails, it's probably already stopped. } this.InvokeEncodeCompleted( exc == null ? new EncodeCompletedEventArgs(true, null, string.Empty) : new EncodeCompletedEventArgs(false, exc, "An Unknown Error has occured when trying to Stop this encode.")); } /// /// Shutdown the service. /// public void Shutdown() { // Nothing to do. } #endregion #region Private Helper Methods /// /// The HandBrakeCLI process has exited. /// /// /// The sender. /// /// /// The EventArgs. /// private void HbProcessExited(object sender, EventArgs e) { this.IsEncoding = false; if (this.WindowsSeven.IsWindowsSeven) { this.WindowsSeven.SetTaskBarProgressToNoProgress(); } if (this.userSettingService.GetUserSetting(ASUserSettingConstants.PreventSleep)) { Win32.AllowSleep(); } try { // This is just a quick hack to ensure that we are done processing the logging data. // Logging data comes in after the exit event has processed sometimes. We should really impliment ISyncronizingInvoke // and set the SyncObject on the process. I think this may resolve this properly. // For now, just wait 2.5 seconds to let any trailing log messages come in and be processed. Thread.Sleep(2500); this.HbProcess.CancelErrorRead(); this.ShutdownFileWriter(); } catch (Exception exc) { // This exception doesn't warrent user interaction, but it should be logged (TODO) } this.currentTask.Status = QueueItemStatus.Completed; this.InvokeEncodeCompleted(new EncodeCompletedEventArgs(true, null, string.Empty)); } /// /// Recieve the Standard Error information and process it /// /// /// The Sender Object /// /// /// DataReceived EventArgs /// private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e) { if (!String.IsNullOrEmpty(e.Data)) { this.ProcessLogMessage(e.Data); } } /// /// Encode Started /// /// /// The sender. /// /// /// The EventArgs. /// private void EncodeEncodeStarted(object sender, EventArgs e) { Thread monitor = new Thread(this.EncodeMonitor); monitor.Start(); } /// /// Monitor the QueueTask /// private void EncodeMonitor() { try { Parser encode = new Parser(this.HbProcess.StandardOutput.BaseStream); encode.OnEncodeProgress += this.EncodeOnEncodeProgress; while (!encode.EndOfStream) { encode.ReadEncodeStatus(); } } catch (Exception) { this.EncodeOnEncodeProgress(null, 0, 0, 0, 0, 0, "Unknown, status not available.."); } } /// /// Displays the Encode status in the GUI /// /// The sender /// The current task /// Number of tasks /// Percent complete /// Current encode speed in fps /// Avg encode speed /// Time Left private void EncodeOnEncodeProgress(object sender, int currentTask, int taskCount, float percentComplete, float currentFps, float avg, string timeRemaining) { if (!this.IsEncoding) { // We can get events out of order since the CLI progress is monitored on a background thread. // So make sure we don't send a status update after an encode complete event. return; } EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs { AverageFrameRate = avg, CurrentFrameRate = currentFps, EstimatedTimeLeft = Converters.EncodeToTimespan(timeRemaining), PercentComplete = percentComplete, Task = currentTask, TaskCount = taskCount, ElapsedTime = DateTime.Now - this.startTime, }; this.InvokeEncodeStatusChanged(eventArgs); if (this.WindowsSeven.IsWindowsSeven) { int percent; int.TryParse(Math.Round(percentComplete).ToString(), out percent); this.WindowsSeven.SetTaskBarProgress(percent); } } #endregion } }