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