/* 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
}
}