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