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