// --------------------------------------------------------------------------------------------------------------------
//
// This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
//
//
// LibHB Implementation of IEncode
//
// --------------------------------------------------------------------------------------------------------------------
namespace HandBrakeWPF.Services.Encode
{
using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using HandBrake.Interop.Interop.Interfaces;
using HandBrake.Interop.Interop.Interfaces.EventArgs;
using HandBrake.Interop.Interop.Interfaces.Model;
using HandBrake.Interop.Interop.Json.Encode;
using HandBrake.Interop.Interop.Json.State;
using HandBrakeWPF.Exceptions;
using HandBrakeWPF.Properties;
using HandBrakeWPF.Services.Encode.Factories;
using HandBrakeWPF.Services.Encode.Interfaces;
using HandBrakeWPF.Services.Interfaces;
using HandBrakeWPF.Services.Logging.Interfaces;
using HandBrakeWPF.Utilities;
using EncodeTask = Model.EncodeTask;
using HandBrakeInstanceManager = Instance.HandBrakeInstanceManager;
using IEncode = Interfaces.IEncode;
using LogService = Logging.LogService;
public class LibEncode : IEncode
{
private readonly IUserSettingService userSettingService;
private readonly ILogInstanceManager logInstanceManager;
private readonly IPortService portService;
private readonly object portLock = new object();
private readonly EncodeTaskFactory encodeTaskFactory;
private ILog encodeLogService;
private IEncodeInstance instance;
private DateTime startTime;
private EncodeTask currentTask;
private bool isPreviewInstance;
private bool isLoggingInitialised;
private bool isEncodeComplete;
private int encodeCounter;
public LibEncode(IUserSettingService userSettingService, ILogInstanceManager logInstanceManager, int encodeCounter, IPortService portService)
{
this.userSettingService = userSettingService;
this.logInstanceManager = logInstanceManager;
this.encodeCounter = encodeCounter;
this.portService = portService;
this.encodeTaskFactory = new EncodeTaskFactory(this.userSettingService);
}
public event EventHandler EncodeStarted;
public event EncodeCompletedStatus EncodeCompleted;
public event EncodeProgessStatus EncodeStatusChanged;
public bool IsPasued { get; private set; }
public bool IsEncoding { get; protected set; }
public void Start(EncodeTask task, HBConfiguration configuration, string basePresetName)
{
try
{
// Sanity Checking and Setup
if (this.IsEncoding)
{
throw new GeneralApplicationException(Resources.Queue_AlreadyEncoding, Resources.Queue_AlreadyEncodingSolution, null);
}
// Setup
this.startTime = DateTime.Now;
this.currentTask = task;
this.isPreviewInstance = task.IsPreviewEncode;
if (this.userSettingService.GetUserSetting(UserSettingConstants.ProcessIsolationEnabled))
{
this.InitLogging(task.Destination);
}
else
{
this.encodeLogService = this.logInstanceManager.ApplicationLogInstance;
this.encodeLogService.Reset();
}
if (this.instance != null)
{
// Cleanup
try
{
this.instance.EncodeCompleted -= this.InstanceEncodeCompleted;
this.instance.EncodeProgress -= this.InstanceEncodeProgress;
this.instance.Dispose();
this.instance = null;
}
catch (Exception exc)
{
this.ServiceLogMessage("Failed to cleanup previous instance: " + exc);
}
}
this.ServiceLogMessage("Starting Encode ...");
if (!string.IsNullOrEmpty(basePresetName))
{
this.TimedLogMessage(string.Format("base preset: {0}", basePresetName));
}
int verbosity = this.userSettingService.GetUserSetting(UserSettingConstants.Verbosity);
// Prevent port stealing if multiple jobs start at the same time.
lock (this.portLock)
{
this.instance = task.IsPreviewEncode ? HandBrakeInstanceManager.GetPreviewInstance(verbosity, this.userSettingService) : HandBrakeInstanceManager.GetEncodeInstance(verbosity, configuration, this.encodeLogService, this.userSettingService, this.portService);
this.instance.EncodeCompleted += this.InstanceEncodeCompleted;
this.instance.EncodeProgress += this.InstanceEncodeProgress;
this.IsEncoding = true;
// Verify the Destination Path Exists, and if not, create it.
this.VerifyEncodeDestinationPath(task);
// Get an EncodeJob object for the Interop Library
JsonEncodeObject work = this.encodeTaskFactory.Create(task, configuration);
this.instance.StartEncode(work);
}
// Fire the Encode Started Event
this.InvokeEncodeStarted(System.EventArgs.Empty);
}
catch (Exception exc)
{
this.IsEncoding = false;
this.ServiceLogMessage("Failed to start encoding ..." + Environment.NewLine + exc);
this.InvokeEncodeCompleted(new EventArgs.EncodeCompletedEventArgs(false, exc, "Unable to start encoding", this.currentTask.Source, this.currentTask.Destination, null, 0));
}
}
public void Pause()
{
if (this.instance != null)
{
this.instance.PauseEncode();
this.ServiceLogMessage("Encode Paused");
this.IsPasued = true;
}
}
public void Resume()
{
if (this.instance != null)
{
this.instance.ResumeEncode();
this.ServiceLogMessage("Encode Resumed");
this.IsPasued = false;
}
}
public void Stop()
{
try
{
this.IsEncoding = false;
if (this.instance != null)
{
this.instance.StopEncode();
this.ServiceLogMessage("Encode Stopped");
}
}
catch (Exception exc)
{
Debug.WriteLine(exc);
}
}
public EncodeTask GetActiveJob()
{
if (this.currentTask != null)
{
EncodeTask task = new EncodeTask(this.currentTask); // Shallow copy our current instance.
return task;
}
return null;
}
protected void ServiceLogMessage(string message)
{
this.encodeLogService.LogMessage(string.Format("{0}# {1}", Environment.NewLine, message));
}
protected void TimedLogMessage(string message)
{
this.encodeLogService.LogMessage(string.Format("[{0}] {1}", DateTime.Now.ToString("HH:mm:ss"), message));
}
private void InvokeEncodeStatusChanged(EventArgs.EncodeProgressEventArgs e)
{
EncodeProgessStatus handler = this.EncodeStatusChanged;
handler?.Invoke(this, e);
}
private void InvokeEncodeCompleted(EventArgs.EncodeCompletedEventArgs e)
{
EncodeCompletedStatus handler = this.EncodeCompleted;
handler?.Invoke(this, e);
}
private void InvokeEncodeStarted(System.EventArgs e)
{
EventHandler handler = this.EncodeStarted;
handler?.Invoke(this, e);
}
private void InstanceEncodeProgress(object sender, EncodeProgressEventArgs e)
{
EventArgs.EncodeProgressEventArgs args = new EventArgs.EncodeProgressEventArgs
{
AverageFrameRate = e.AverageFrameRate,
CurrentFrameRate = e.CurrentFrameRate,
EstimatedTimeLeft = e.EstimatedTimeLeft,
PercentComplete = e.FractionComplete * 100,
Task = e.Pass,
TaskCount = e.PassCount,
ElapsedTime = DateTime.Now - this.startTime,
PassId = e.PassId,
IsMuxing = e.StateCode == TaskState.Muxing.Code,
IsSearching = e.StateCode == TaskState.Searching.Code
};
this.InvokeEncodeStatusChanged(args);
}
private void InstanceEncodeCompleted(object sender, EncodeCompletedEventArgs e)
{
this.IsEncoding = false;
if (this.isEncodeComplete)
{
return; // Prevent phantom events bubbling up the stack.
}
this.isEncodeComplete = true;
string completeMessage = "Job Completed!";
switch (e.Error)
{
case 0:
break;
case 1:
completeMessage = "Job Cancelled!";
break;
case 2:
completeMessage = string.Format("Job Failed. Check log and input settings ({0})", e.Error);
break;
case 3:
completeMessage = string.Format("Job Failed to Initialise. Check log and input settings ({0})", e.Error);
break;
default:
completeMessage = string.Format("Job Failed ({0})", e.Error);
break;
}
this.ServiceLogMessage(completeMessage);
// Handling Log Data
string hbLog = this.ProcessLogs(this.currentTask.Destination);
long filesize = this.GetFilesize(this.currentTask.Destination);
this.logInstanceManager.Deregister(Path.GetFileName(hbLog));
// Raise the Encode Completed Event.
this.InvokeEncodeCompleted(
e.Error != 0
? new EventArgs.EncodeCompletedEventArgs(false, null, e.Error.ToString(), this.currentTask.Source, this.currentTask.Destination, hbLog, filesize)
: new EventArgs.EncodeCompletedEventArgs(true, null, string.Empty, this.currentTask.Source, this.currentTask.Destination, hbLog, filesize));
}
private long GetFilesize(string destination)
{
try
{
if (!string.IsNullOrEmpty(destination) && File.Exists(destination))
{
return new FileInfo(destination).Length;
}
return 0;
}
catch (Exception e)
{
this.ServiceLogMessage("Unable to get final filesize ..." + Environment.NewLine + e);
Debug.WriteLine(e);
}
return 0;
}
private void InitLogging(string destination)
{
if (!this.isLoggingInitialised)
{
string logType = this.isPreviewInstance ? "preview" : "encode";
string destinationFile = Path.GetFileNameWithoutExtension(destination);
string logFileName = string.Format("{0}_{1}_{2}.txt", DateTime.Now.ToString(CultureInfo.InvariantCulture).Replace("/", ".").Replace(":", "-"), logType, destinationFile);
string fullLogPath = Path.Combine(DirectoryUtilities.GetLogDirectory(), logFileName);
this.encodeLogService = new LogService();
this.encodeLogService.ConfigureLogging(logFileName, fullLogPath);
this.encodeLogService.SetId(this.encodeCounter);
this.logInstanceManager.Register(logFileName, this.encodeLogService, false);
this.isLoggingInitialised = true;
}
}
private string ProcessLogs(string destination)
{
try
{
string logDir = DirectoryUtilities.GetLogDirectory();
string filename = this.encodeLogService.FileName;
string logContent = this.encodeLogService.GetFullLog();
// Make sure the log directory exists.
if (!Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
// Copy the Log to HandBrakes log folder in the users application data folder.
// Only needed for process isolation mode. Worker will handle it's own logging.
if (!this.userSettingService.GetUserSetting(UserSettingConstants.ProcessIsolationEnabled))
{
string logType = this.isPreviewInstance ? "preview" : "encode";
string destinationFile = Path.GetFileNameWithoutExtension(destination);
string logFileName = string.Format("{0}_{1}_{2}.txt", DateTime.Now.ToString(CultureInfo.InvariantCulture).Replace("/", ".").Replace(":", "-"), logType, destinationFile);
this.WriteFile(logContent, Path.Combine(logDir, logFileName));
filename = logFileName;
}
// Save a copy of the log file in the same location as the encode.
if (this.userSettingService.GetUserSetting(UserSettingConstants.SaveLogWithVideo))
{
this.WriteFile(logContent, Path.Combine(Path.GetDirectoryName(destination), filename));
}
// Save a copy of the log file to a user specified location
if (Directory.Exists(this.userSettingService.GetUserSetting(UserSettingConstants.SaveLogCopyDirectory)) && this.userSettingService.GetUserSetting(UserSettingConstants.SaveLogToCopyDirectory))
{
this.WriteFile(logContent, Path.Combine(this.userSettingService.GetUserSetting(UserSettingConstants.SaveLogCopyDirectory), filename));
}
return Path.Combine(logDir, filename);
}
catch (Exception exc)
{
Debug.WriteLine(exc); // This exception doesn't warrant user interaction, but it should be logged
}
return null;
}
private void VerifyEncodeDestinationPath(EncodeTask task)
{
// Make sure the path exists, attempt to create it if it doesn't
try
{
string path = Directory.GetParent(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);
}
}
private void WriteFile(string content, string fileName)
{
try
{
using (StreamWriter fileWriter = new StreamWriter(fileName) { AutoFlush = true })
{
fileWriter.Write(content);
}
}
catch (Exception exc)
{
Debug.WriteLine(exc);
}
}
}
}