// -------------------------------------------------------------------------------------------------------------------- // // 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.Windows.Forms; using HandBrake.ApplicationServices.EventArgs; using HandBrake.ApplicationServices.Exceptions; using HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Services.Base; using HandBrake.ApplicationServices.Services.Interfaces; using HandBrake.ApplicationServices.Utilities; /// /// Class which handles the CLI /// public class Encode : EncodeBase, IEncode { #region Private Variables /// /// The User Setting Service /// private readonly IUserSettingService userSettingService; /// /// Gets the Process ID /// private int processId; /// /// The Start time of the current Encode; /// private DateTime startTime; /// /// The Current Task /// private QueueTask currentTask; /// /// The init shutdown. /// private bool initShutdown; #endregion /// /// Initializes a new instance of the class. /// /// /// The user Setting Service. /// public Encode(IUserSettingService userSettingService) : base(userSettingService) { this.userSettingService = userSettingService; } #region Properties /// /// Gets or sets The HB Process /// protected Process HbProcess { get; set; } /// /// Gets a value indicating whether can pause. /// public bool CanPause { get { return false; } } #endregion #region Public Methods /// /// Execute a HandBrakeCLI process. /// This should only be called from the UI thread. /// /// /// The encodeQueueTask. /// public void Start(QueueTask encodeQueueTask) { try { if (this.IsEncoding) { throw new GeneralApplicationException("HandBrake is already encodeing.", "Please try again in a minute", null); } this.IsEncoding = true; this.currentTask = encodeQueueTask; if (encodeQueueTask.Configuration.IsLoggingEnabled) { try { this.SetupLogging(currentTask); } catch (Exception) { this.IsEncoding = false; throw; } } // 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), encodeQueueTask.Configuration.IsDvdNavDisabled, userSettingService.GetUserSetting(ASUserSettingConstants.DisableQuickSyncDecoding)) : QueryGeneratorUtility.GenerateQuery(new EncodeTask(this.currentTask.Task), userSettingService.GetUserSetting(ASUserSettingConstants.PreviewScanCount), userSettingService.GetUserSetting(ASUserSettingConstants.Verbosity), encodeQueueTask.Configuration.IsDvdNavDisabled, userSettingService.GetUserSetting(ASUserSettingConstants.DisableQuickSyncDecoding), userSettingService.GetUserSetting(ASUserSettingConstants.EnableDxva), userSettingService.GetUserSetting(ASUserSettingConstants.ScalingMode) == VideoScaler.BicubicCl); ProcessStartInfo cliStart = new ProcessStartInfo(handbrakeCLIPath, query) { RedirectStandardOutput = true, RedirectStandardError = encodeQueueTask.Configuration.IsLoggingEnabled, UseShellExecute = false, CreateNoWindow = true }; this.HbProcess = new Process { StartInfo = cliStart }; this.HbProcess.Start(); this.startTime = DateTime.Now; if (encodeQueueTask.Configuration.IsLoggingEnabled) { this.HbProcess.ErrorDataReceived += this.HbProcErrorDataReceived; this.HbProcess.BeginErrorReadLine(); } this.HbProcess.OutputDataReceived += HbProcess_OutputDataReceived; this.HbProcess.BeginOutputReadLine(); this.processId = this.HbProcess.Id; // 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.IsEncoding = false; this.InvokeEncodeCompleted( new EncodeCompletedEventArgs( false, exc, "An Error occured when trying to encode this source. ", this.currentTask.Task.Destination)); throw; } } /// /// The pause. /// /// /// This feature is not available for CLI based encoding. /// public void Pause() { throw new NotImplementedException("This feature is not available for CLI based encoding."); } /// /// The resume. /// /// /// This feature is not available for CLI based encoding. /// public void Resume() { throw new NotImplementedException("This feature is not available for CLI based encoding."); } /// /// Kill the CLI process /// public override void Stop() { 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. } } /// /// 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) { HbProcess.WaitForExit(); try { this.HbProcess.CancelErrorRead(); this.HbProcess.CancelOutputRead(); this.ShutdownFileWriter(); } catch (Exception exc) { // This exception doesn't warrent user interaction, but it should be logged (TODO) } this.currentTask.Status = QueueItemStatus.Completed; this.IsEncoding = false; this.InvokeEncodeCompleted(new EncodeCompletedEventArgs(true, null, string.Empty, this.currentTask.Task.Destination)); } /// /// Recieve the Standard Error information and process it /// /// /// The Sender Object /// /// /// DataReceived EventArgs /// /// /// Worker Thread. /// private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e) { if (!String.IsNullOrEmpty(e.Data)) { if (initShutdown && this.LogBuffer.Length < 25000000) { initShutdown = false; // Reset this flag. } if (this.LogBuffer.Length > 25000000 && !initShutdown) // Approx 23.8MB and make sure it's only printed once { this.ProcessLogMessage("ERROR: Initiating automatic shutdown of encode process. The size of the log file indicates that there is an error! "); initShutdown = true; this.Stop(); } this.ProcessLogMessage(e.Data); } } /// /// The hb process output data received. /// /// /// The sender. /// /// /// The e. /// private void HbProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) { if (!String.IsNullOrEmpty(e.Data) && this.IsEncoding) { EncodeProgressEventArgs eventArgs = this.ReadEncodeStatus(e.Data, this.startTime); if (eventArgs != null) { 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; } this.InvokeEncodeStatusChanged(eventArgs); } } } #endregion } }