/* Encode.cs $ This file is part of the HandBrake source code. Homepage: . It may be used under the terms of the GNU General Public License. */ namespace HandBrake.ApplicationServices.Services { using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Text; using System.Threading; using System.Windows.Forms; using HandBrake.ApplicationServices.EventArgs; using HandBrake.ApplicationServices.Functions; using HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Parsing; using HandBrake.ApplicationServices.Services.Interfaces; using HandBrake.ApplicationServices.Utilities; /// /// Class which handles the CLI /// public class Encode : IEncode { #region Private Variables /// /// The Log Buffer /// private StringBuilder logBuffer; /// /// The Log file writer /// private StreamWriter fileWriter; /// /// Gets The Process Handle /// private IntPtr processHandle; /// /// Gets the Process ID /// private int processId; /// /// Windows 7 API Pack wrapper /// private Win7 windowsSeven = new Win7(); /// /// A Lock for the filewriter /// static readonly object fileWriterLock = new object(); static readonly object syncObject = new object(); #endregion /// /// Initializes a new instance of the class. /// public Encode() { this.EncodeStarted += this.EncodeEncodeStarted; GrowlCommunicator.Register(); } #region Delegates and Event Handlers public delegate void ProcessEncodeEventsDelegate(); /// /// Fires when a new CLI QueueTask starts /// public event EventHandler EncodeStarted; /// /// Fires when a CLI QueueTask finishes. /// public event EncodeCompletedStatus EncodeCompleted; /// /// Encode process has progressed /// public event EncodeProgessStatus EncodeStatusChanged; #endregion #region Properties /// /// Gets or sets The HB Process /// protected Process HbProcess { get; set; } /// /// Gets a value indicating whether IsEncoding. /// public bool IsEncoding { get; private set; } /// /// Gets ActivityLog. /// public string ActivityLog { get { if (this.IsEncoding == false) { try { ReadFile(); // Read the last log file back in if it exists } catch (Exception exc) { return exc.ToString(); } } return string.IsNullOrEmpty(this.logBuffer.ToString()) ? "No log data available..." : this.logBuffer.ToString(); } } #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 { QueueTask queueTask = encodeQueueTask; if (queueTask == null) { throw new ArgumentNullException("QueueTask was null"); } if (IsEncoding) { throw new Exception("HandBrake is already encodeing."); } IsEncoding = true; if (enableLogging) { try { SetupLogging(queueTask); } catch (Exception) { IsEncoding = false; throw; } } if (Init.PreventSleep) { Win32.PreventSleep(); } string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe"); ProcessStartInfo cliStart = new ProcessStartInfo(handbrakeCLIPath, queueTask.Query) { RedirectStandardOutput = true, RedirectStandardError = enableLogging ? true : false, UseShellExecute = false, CreateNoWindow = !Init.ShowCliForInGuiEncodeStatus ? true : false }; this.HbProcess = new Process { StartInfo = cliStart }; this.HbProcess.Start(); if (enableLogging) { this.HbProcess.ErrorDataReceived += HbProcErrorDataReceived; this.HbProcess.BeginErrorReadLine(); } this.processId = HbProcess.Id; this.processHandle = HbProcess.Handle; // Set the process Priority if (this.processId != -1) { this.HbProcess.EnableRaisingEvents = true; this.HbProcess.Exited += this.HbProcessExited; } // Set the Process Priority switch (Init.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 if (this.EncodeStarted != null) this.EncodeStarted(this, new EventArgs()); } catch (Exception exc) { if (this.EncodeCompleted != null) this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured in EncodeService.Run()")); } } /// /// 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 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. } if (exc == null) { if (this.EncodeCompleted != null) this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty)); } else { if (this.EncodeCompleted != null) this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured.")); } } /// /// Attempt to Safely kill a DirectRun() CLI /// NOTE: This will not work with a MinGW CLI /// Note: http://www.cygwin.com/ml/cygwin/2006-03/msg00330.html /// public void SafelyStop() { if ((int)this.processHandle == 0) return; // Allow the CLI to exit cleanly Win32.SetForegroundWindow((int)this.processHandle); SendKeys.Send("^C"); SendKeys.Flush(); /*/if (HbProcess != null) //{ // HbProcess.StandardInput.AutoFlush = true; // HbProcess.StandardInput.WriteLine("^c^z"); //}*/ } /// /// Save a copy of the log to the users desired location or a default location /// if this feature is enabled in options. /// /// /// The Destination File Path /// public void ProcessLogs(string destination) { try { string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs"; string tempLogFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId)); string encodeDestinationPath = Path.GetDirectoryName(destination); string destinationFile = Path.GetFileName(destination); string encodeLogFile = destinationFile + " " + DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".txt"; // Make sure the log directory exists. if (!Directory.Exists(logDir)) Directory.CreateDirectory(logDir); // Copy the Log to HandBrakes log folder in the users applciation data folder. File.Copy(tempLogFile, Path.Combine(logDir, encodeLogFile)); // Save a copy of the log file in the same location as the enocde. if (Init.SaveLogWithVideo) File.Copy(tempLogFile, Path.Combine(encodeDestinationPath, encodeLogFile)); // Save a copy of the log file to a user specified location if (Directory.Exists(Init.SaveLogPath)) if (Init.SaveLogPath != String.Empty && Init.SaveLogToSpecifiedPath) File.Copy(tempLogFile, Path.Combine(Init.SaveLogPath, encodeLogFile)); } catch (Exception exc) { // This exception doesn't warrent user interaction, but it should be logged (TODO) } } #endregion #region Private Helper Methods /// /// The HandBrakeCLI process has exited. /// /// /// The sender. /// /// /// The EventArgs. /// private void HbProcessExited(object sender, EventArgs e) { IsEncoding = false; if (windowsSeven.IsWindowsSeven) { windowsSeven.SetTaskBarProgressToNoProgress(); } if (Init.PreventSleep) { Win32.AllowSleep(); } try { lock (fileWriterLock) { // 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.HbProcess.CancelOutputRead(); if (fileWriter != null) { fileWriter.Close(); fileWriter.Dispose(); } fileWriter = null; } } catch (Exception exc) { // This exception doesn't warrent user interaction, but it should be logged (TODO) } if (this.EncodeCompleted != null) this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty)); } /// /// Read the log file /// private void ReadFile() { logBuffer = new StringBuilder(); lock (logBuffer) { // last_encode_log.txt is the primary log file. Since .NET can't read this file whilst the CLI is outputing to it (Not even in read only mode), // we'll need to make a copy of it. string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs"; string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId)); string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId)); int logFilePosition = 0; try { // Copy the log file. if (File.Exists(logFile)) File.Copy(logFile, logFile2, true); else return; // Start the Reader // Only use text which continues on from the last read line using (StreamReader sr = new StreamReader(logFile2)) { string line; int i = 1; while ((line = sr.ReadLine()) != null) { if (i > logFilePosition) { logBuffer.AppendLine(line); logFilePosition++; } i++; } sr.Close(); } } catch (Exception exc) { logBuffer.Append( Environment.NewLine + "Unable to read Log file..." + Environment.NewLine + exc + Environment.NewLine); } } } /// /// Setup the logging. /// /// /// The encode QueueTask. /// private void SetupLogging(QueueTask encodeQueueTask) { string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs"; string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId)); string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId)); try { logBuffer = new StringBuilder(); // Clear the current Encode Logs if (File.Exists(logFile)) File.Delete(logFile); if (File.Exists(logFile2)) File.Delete(logFile2); fileWriter = new StreamWriter(logFile) { AutoFlush = true }; fileWriter.WriteLine(UtilityService.CreateCliLogHeader(encodeQueueTask)); logBuffer.AppendLine(UtilityService.CreateCliLogHeader(encodeQueueTask)); } catch (Exception) { if (fileWriter != null) { fileWriter.Close(); fileWriter.Dispose(); } throw; } } /// /// 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)) { try { lock (logBuffer) logBuffer.AppendLine(e.Data); lock (fileWriterLock) { if (fileWriter != null && fileWriter.BaseStream.CanWrite) { fileWriter.WriteLine(e.Data); // If the logging grows past 100MB, kill the encode and stop. if (fileWriter.BaseStream.Length > 100000000) { this.Stop( new Exception( "The encode has been stopped. The log file has grown to over 100MB which indicates a serious problem has occured with the encode." + "Please check the encode log for an indication of what the problem is.")); } } } } catch (Exception exc) { // Do Nothing. } } } /// /// Encode Started /// /// /// The sender. /// /// /// The EventArgs. /// private void EncodeEncodeStarted(object sender, EventArgs e) { Thread monitor = new Thread(EncodeMonitor); monitor.Start(); } /// /// Monitor the QueueTask /// private void EncodeMonitor() { try { Parser encode = new Parser(HbProcess.StandardOutput.BaseStream); encode.OnEncodeProgress += EncodeOnEncodeProgress; while (!encode.EndOfStream) encode.ReadEncodeStatus(); } catch (Exception exc) { 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) { EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs { AverageFrameRate = avg, CurrentFrameRate = currentFps, EstimatedTimeLeft = Converters.EncodeToTimespan(timeRemaining), PercentComplete = percentComplete, Task = currentTask, TaskCount = taskCount }; if (this.EncodeStatusChanged != null) this.EncodeStatusChanged(this, eventArgs); if (windowsSeven.IsWindowsSeven) { int percent; int.TryParse(Math.Round(percentComplete).ToString(), out percent); windowsSeven.SetTaskBarProgress(percent); } } #endregion } }