// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // A Base Class for the Encode Services. // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrake.ApplicationServices.Services.Encode { using System; using System.Globalization; using System.IO; using System.Text; using System.Text.RegularExpressions; using HandBrake.ApplicationServices.Exceptions; using HandBrake.ApplicationServices.Model; using HandBrake.ApplicationServices.Services.Encode.EventArgs; using HandBrake.ApplicationServices.Services.Encode.Interfaces; using HandBrake.ApplicationServices.Services.Encode.Model; using HandBrake.ApplicationServices.Utilities; /// /// A Base Class for the Encode Services. /// public class EncodeBase { #region Private Variables /// /// A Lock for the filewriter /// private static readonly object FileWriterLock = new object(); /// /// The Log File Header /// private readonly StringBuilder header; /// /// The Log Buffer /// private StringBuilder logBuffer; /// /// The Log file writer /// private StreamWriter fileWriter; #endregion /// /// Initializes a new instance of the class. /// public EncodeBase() { this.logBuffer = new StringBuilder(); this.header = GeneralUtilities.CreateCliLogHeader(); this.LogIndex = 0; } #region Events /// /// 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 a value indicating whether IsEncoding. /// public bool IsEncoding { get; protected set; } /// /// Gets ActivityLog. /// public string ActivityLog { get { string noLog = "There is no log information to display." + Environment.NewLine + Environment.NewLine + "This window will only display logging information after you have started an encode." + Environment.NewLine + Environment.NewLine + "You can find previous log files in the log directory or by clicking the 'Open Log Directory' button above."; return string.IsNullOrEmpty(this.logBuffer.ToString()) ? noLog : this.header + this.logBuffer.ToString(); } } /// /// Gets the log index. /// public int LogIndex { get; private set; } /// /// Gets LogBuffer. /// public StringBuilder LogBuffer { get { return this.logBuffer; } } #endregion #region Invoke Events /// /// Invoke the Encode Status Changed Event. /// /// /// The EncodeProgressEventArgs. /// public void InvokeEncodeStatusChanged(EncodeProgressEventArgs e) { EncodeProgessStatus handler = this.EncodeStatusChanged; if (handler != null) { handler(this, e); } } /// /// Invoke the Encode Completed Event /// /// /// The EncodeCompletedEventArgs. /// public void InvokeEncodeCompleted(EncodeCompletedEventArgs e) { EncodeCompletedStatus handler = this.EncodeCompleted; if (handler != null) { handler(this, e); } this.LogIndex = 0; // Reset } /// /// Invoke the Encode Started Event /// /// /// The EventArgs. /// public void InvokeEncodeStarted(System.EventArgs e) { EventHandler handler = this.EncodeStarted; if (handler != null) { handler(this, e); } } #endregion #region Methods /// /// A Stop Method to be implemeneted. /// public virtual void Stop() { // Do Nothing } /// /// 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 /// /// /// The configuration. /// public void ProcessLogs(string destination, HBConfiguration configuration) { try { string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs"; string tempLogFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", GeneralUtilities.ProcessId)); 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 (configuration.SaveLogWithVideo) { File.Copy(tempLogFile, Path.Combine(encodeDestinationPath, encodeLogFile)); } // Save a copy of the log file to a user specified location if (Directory.Exists(configuration.SaveLogCopyDirectory) && configuration.SaveLogToCopyDirectory) { File.Copy( tempLogFile, Path.Combine(configuration.SaveLogCopyDirectory, encodeLogFile)); } } catch (Exception) { // This exception doesn't warrent user interaction, but it should be logged (TODO) } } /// /// Pase the CLI status output (from standard output) /// /// /// The encode Status. /// /// /// The start Time. /// /// /// The . /// public EncodeProgressEventArgs ReadEncodeStatus(string encodeStatus, DateTime startTime) { try { Match m = Regex.Match( encodeStatus, @"^Encoding: task ([0-9]*) of ([0-9]*), ([0-9]*\.[0-9]*) %( \(([0-9]*\.[0-9]*) fps, avg ([0-9]*\.[0-9]*) fps, ETA ([0-9]{2})h([0-9]{2})m([0-9]{2})s\))?"); if (m.Success) { int currentTask = int.Parse(m.Groups[1].Value); int totalTasks = int.Parse(m.Groups[2].Value); float percent = float.Parse(m.Groups[3].Value, CultureInfo.InvariantCulture); float currentFps = m.Groups[5].Value == string.Empty ? 0.0F : float.Parse(m.Groups[5].Value, CultureInfo.InvariantCulture); float avgFps = m.Groups[6].Value == string.Empty ? 0.0F : float.Parse(m.Groups[6].Value, CultureInfo.InvariantCulture); string remaining = string.Empty; if (m.Groups[7].Value != string.Empty) { remaining = m.Groups[7].Value + ":" + m.Groups[8].Value + ":" + m.Groups[9].Value; } if (string.IsNullOrEmpty(remaining)) { remaining = "Calculating ..."; } EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs { AverageFrameRate = avgFps, CurrentFrameRate = currentFps, EstimatedTimeLeft = Converters.EncodeToTimespan( remaining), PercentComplete = percent, Task = currentTask, TaskCount = totalTasks, ElapsedTime = DateTime.Now - startTime, }; return eventArgs; } return null; } catch (Exception) { return null; } } /// /// Setup the logging. /// /// /// The encode QueueTask. /// /// /// Indicates if this is libhb that is encoding or not. /// protected void SetupLogging(QueueTask encodeQueueTask, bool isLibhb) { this.ShutdownFileWriter(); string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs"; string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", GeneralUtilities.ProcessId)); string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", GeneralUtilities.ProcessId)); try { string query = QueryGeneratorUtility.GenerateQuery(new EncodeTask(encodeQueueTask.Task), encodeQueueTask.Configuration); this.logBuffer = new StringBuilder(); if (!isLibhb) { this.logBuffer.AppendLine(String.Format("CLI Query: {0}", query)); } this.logBuffer.AppendLine(); // Clear the current Encode Logs) if (File.Exists(logFile)) { File.Delete(logFile); } if (File.Exists(logFile2)) { File.Delete(logFile2); } lock (FileWriterLock) { this.fileWriter = new StreamWriter(logFile) { AutoFlush = true }; this.fileWriter.WriteLine(this.header); if (!isLibhb) { this.fileWriter.WriteLine(string.Format("CLI Query: {0}", query)); } this.fileWriter.WriteLine(); } } catch (Exception) { if (this.fileWriter != null) { this.fileWriter.Close(); this.fileWriter.Dispose(); } throw; } } /// /// Process an Incomming Log Message. /// /// /// The message. /// protected void ProcessLogMessage(string message) { if (!String.IsNullOrEmpty(message)) { try { this.LogIndex = this.LogIndex + 1; lock (this.LogBuffer) { this.LogBuffer.AppendLine(message); } lock (FileWriterLock) { if (this.fileWriter != null && this.fileWriter.BaseStream.CanWrite) { this.fileWriter.WriteLine(message); } } } catch (Exception) { // Do Nothing. } } } /// /// Shutdown and Dispose of the File Writer. /// protected void ShutdownFileWriter() { try { lock (FileWriterLock) { if (this.fileWriter != null) { this.fileWriter.Close(); this.fileWriter.Dispose(); } this.fileWriter = null; } } catch (Exception) { // This exception doesn't warrent user interaction, but it should be logged (TODO) } } /// /// Verify the Encode Destination path exists and if not, create it. /// /// /// The task. /// /// /// If the creation fails, an exception is thrown. /// protected void VerifyEncodeDestinationPath(QueueTask task) { // Make sure the path exists, attempt to create it if it doesn't try { string path = Directory.GetParent(task.Task.Destination).ToString(); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } } catch (Exception exc) { throw new GeneralApplicationException( "Unable to create directory for the encoded output.", "Please verify that you have a valid path.", exc); } } #endregion } }