2 This file is part of the HandBrake source code.
\r
3 Homepage: <http://handbrake.fr/>.
\r
4 It may be used under the terms of the GNU General Public License. */
\r
6 namespace HandBrake.ApplicationServices.Services
\r
9 using System.ComponentModel;
\r
10 using System.Diagnostics;
\r
13 using System.Threading;
\r
14 using System.Windows.Forms;
\r
16 using HandBrake.ApplicationServices.EventArgs;
\r
17 using HandBrake.ApplicationServices.Functions;
\r
18 using HandBrake.ApplicationServices.Model;
\r
19 using HandBrake.ApplicationServices.Parsing;
\r
20 using HandBrake.ApplicationServices.Services.Interfaces;
\r
21 using HandBrake.ApplicationServices.Utilities;
\r
24 /// Class which handles the CLI
\r
26 public class Encode : IEncode
\r
28 #region Private Variables
\r
33 private StringBuilder logBuffer;
\r
36 /// The Log file writer
\r
38 private StreamWriter fileWriter;
\r
41 /// Gets The Process Handle
\r
43 private IntPtr processHandle;
\r
46 /// Gets the Process ID
\r
48 private int processId;
\r
51 /// Windows 7 API Pack wrapper
\r
53 private Win7 windowsSeven = new Win7();
\r
56 /// A Lock for the filewriter
\r
58 static readonly object fileWriterLock = new object();
\r
60 static readonly object syncObject = new object();
\r
65 /// Initializes a new instance of the <see cref="Encode"/> class.
\r
69 this.EncodeStarted += this.EncodeEncodeStarted;
\r
70 GrowlCommunicator.Register();
\r
73 #region Delegates and Event Handlers
\r
75 public delegate void ProcessEncodeEventsDelegate();
\r
78 /// Fires when a new CLI QueueTask starts
\r
80 public event EventHandler EncodeStarted;
\r
83 /// Fires when a CLI QueueTask finishes.
\r
85 public event EncodeCompletedStatus EncodeCompleted;
\r
88 /// Encode process has progressed
\r
90 public event EncodeProgessStatus EncodeStatusChanged;
\r
96 /// Gets or sets The HB Process
\r
98 protected Process HbProcess { get; set; }
\r
101 /// Gets a value indicating whether IsEncoding.
\r
103 public bool IsEncoding { get; private set; }
\r
106 /// Gets ActivityLog.
\r
108 public string ActivityLog
\r
112 if (this.IsEncoding == false)
\r
116 ReadFile(); // Read the last log file back in if it exists
\r
118 catch (Exception exc)
\r
120 return exc.ToString();
\r
124 return string.IsNullOrEmpty(this.logBuffer.ToString()) ? "No log data available..." : this.logBuffer.ToString();
\r
130 #region Public Methods
\r
133 /// Execute a HandBrakeCLI process.
\r
135 /// <param name="encodeQueueTask">
\r
136 /// The encodeQueueTask.
\r
138 /// <param name="enableLogging">
\r
139 /// Enable Logging. When Disabled we onlt parse Standard Ouput for progress info. Standard Error log data is ignored.
\r
141 public void Start(QueueTask encodeQueueTask, bool enableLogging)
\r
145 QueueTask queueTask = encodeQueueTask;
\r
147 if (queueTask == null)
\r
149 throw new ArgumentNullException("QueueTask was null");
\r
154 throw new Exception("HandBrake is already encodeing.");
\r
163 SetupLogging(queueTask);
\r
167 IsEncoding = false;
\r
172 if (Init.PreventSleep)
\r
174 Win32.PreventSleep();
\r
177 string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe");
\r
178 ProcessStartInfo cliStart = new ProcessStartInfo(handbrakeCLIPath, queueTask.Query)
\r
180 RedirectStandardOutput = true,
\r
181 RedirectStandardError = enableLogging ? true : false,
\r
182 UseShellExecute = false,
\r
183 CreateNoWindow = !Init.ShowCliForInGuiEncodeStatus ? true : false
\r
186 this.HbProcess = new Process { StartInfo = cliStart };
\r
188 this.HbProcess.Start();
\r
192 this.HbProcess.ErrorDataReceived += HbProcErrorDataReceived;
\r
193 this.HbProcess.BeginErrorReadLine();
\r
196 this.processId = HbProcess.Id;
\r
197 this.processHandle = HbProcess.Handle;
\r
199 // Set the process Priority
\r
200 if (this.processId != -1)
\r
202 this.HbProcess.EnableRaisingEvents = true;
\r
203 this.HbProcess.Exited += this.HbProcessExited;
\r
206 // Set the Process Priority
\r
207 switch (Init.ProcessPriority)
\r
210 this.HbProcess.PriorityClass = ProcessPriorityClass.RealTime;
\r
213 this.HbProcess.PriorityClass = ProcessPriorityClass.High;
\r
215 case "Above Normal":
\r
216 this.HbProcess.PriorityClass = ProcessPriorityClass.AboveNormal;
\r
219 this.HbProcess.PriorityClass = ProcessPriorityClass.Normal;
\r
222 this.HbProcess.PriorityClass = ProcessPriorityClass.Idle;
\r
225 this.HbProcess.PriorityClass = ProcessPriorityClass.BelowNormal;
\r
229 // Fire the Encode Started Event
\r
230 if (this.EncodeStarted != null)
\r
231 this.EncodeStarted(this, new EventArgs());
\r
233 catch (Exception exc)
\r
235 if (this.EncodeCompleted != null)
\r
236 this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured in EncodeService.Run()"));
\r
241 /// Stop the Encode
\r
249 /// Kill the CLI process
\r
251 /// <param name="exc">
\r
252 /// The Exception that has occured.
\r
253 /// This will get bubbled up through the EncodeCompletedEventArgs
\r
255 public void Stop(Exception exc)
\r
259 if (this.HbProcess != null && !this.HbProcess.HasExited)
\r
261 this.HbProcess.Kill();
\r
266 // No need to report anything to the user. If it fails, it's probably already stopped.
\r
272 if (this.EncodeCompleted != null)
\r
273 this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));
\r
277 if (this.EncodeCompleted != null)
\r
278 this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured."));
\r
283 /// Attempt to Safely kill a DirectRun() CLI
\r
284 /// NOTE: This will not work with a MinGW CLI
\r
285 /// Note: http://www.cygwin.com/ml/cygwin/2006-03/msg00330.html
\r
287 public void SafelyStop()
\r
289 if ((int)this.processHandle == 0)
\r
292 // Allow the CLI to exit cleanly
\r
293 Win32.SetForegroundWindow((int)this.processHandle);
\r
294 SendKeys.Send("^C");
\r
297 /*/if (HbProcess != null)
\r
299 // HbProcess.StandardInput.AutoFlush = true;
\r
300 // HbProcess.StandardInput.WriteLine("^c^z");
\r
305 /// Save a copy of the log to the users desired location or a default location
\r
306 /// if this feature is enabled in options.
\r
308 /// <param name="destination">
\r
309 /// The Destination File Path
\r
311 public void ProcessLogs(string destination)
\r
315 string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
\r
316 "\\HandBrake\\logs";
\r
317 string tempLogFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));
\r
319 string encodeDestinationPath = Path.GetDirectoryName(destination);
\r
320 string destinationFile = Path.GetFileName(destination);
\r
321 string encodeLogFile = destinationFile + " " +
\r
322 DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".txt";
\r
324 // Make sure the log directory exists.
\r
325 if (!Directory.Exists(logDir))
\r
326 Directory.CreateDirectory(logDir);
\r
328 // Copy the Log to HandBrakes log folder in the users applciation data folder.
\r
329 File.Copy(tempLogFile, Path.Combine(logDir, encodeLogFile));
\r
331 // Save a copy of the log file in the same location as the enocde.
\r
332 if (Init.SaveLogWithVideo)
\r
333 File.Copy(tempLogFile, Path.Combine(encodeDestinationPath, encodeLogFile));
\r
335 // Save a copy of the log file to a user specified location
\r
336 if (Directory.Exists(Init.SaveLogPath))
\r
337 if (Init.SaveLogPath != String.Empty && Init.SaveLogToSpecifiedPath)
\r
338 File.Copy(tempLogFile, Path.Combine(Init.SaveLogPath, encodeLogFile));
\r
340 catch (Exception exc)
\r
342 // This exception doesn't warrent user interaction, but it should be logged (TODO)
\r
348 #region Private Helper Methods
\r
351 /// The HandBrakeCLI process has exited.
\r
353 /// <param name="sender">
\r
356 /// <param name="e">
\r
359 private void HbProcessExited(object sender, EventArgs e)
\r
361 IsEncoding = false;
\r
362 if (windowsSeven.IsWindowsSeven)
\r
364 windowsSeven.SetTaskBarProgressToNoProgress();
\r
367 if (Init.PreventSleep)
\r
369 Win32.AllowSleep();
\r
374 lock (fileWriterLock)
\r
376 // This is just a quick hack to ensure that we are done processing the logging data.
\r
377 // Logging data comes in after the exit event has processed sometimes. We should really impliment ISyncronizingInvoke
\r
378 // and set the SyncObject on the process. I think this may resolve this properly.
\r
379 // For now, just wait 2.5 seconds to let any trailing log messages come in and be processed.
\r
380 Thread.Sleep(2500);
\r
382 this.HbProcess.CancelErrorRead();
\r
383 this.HbProcess.CancelOutputRead();
\r
385 if (fileWriter != null)
\r
387 fileWriter.Close();
\r
388 fileWriter.Dispose();
\r
394 catch (Exception exc)
\r
396 // This exception doesn't warrent user interaction, but it should be logged (TODO)
\r
399 if (this.EncodeCompleted != null)
\r
400 this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));
\r
404 /// Read the log file
\r
406 private void ReadFile()
\r
408 logBuffer = new StringBuilder();
\r
411 // 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),
\r
412 // we'll need to make a copy of it.
\r
413 string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";
\r
414 string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));
\r
415 string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId));
\r
416 int logFilePosition = 0;
\r
420 // Copy the log file.
\r
421 if (File.Exists(logFile))
\r
422 File.Copy(logFile, logFile2, true);
\r
426 // Start the Reader
\r
427 // Only use text which continues on from the last read line
\r
428 using (StreamReader sr = new StreamReader(logFile2))
\r
432 while ((line = sr.ReadLine()) != null)
\r
434 if (i > logFilePosition)
\r
436 logBuffer.AppendLine(line);
\r
444 catch (Exception exc)
\r
447 Environment.NewLine + "Unable to read Log file..." + Environment.NewLine + exc +
\r
448 Environment.NewLine);
\r
454 /// Setup the logging.
\r
456 /// <param name="encodeQueueTask">
\r
457 /// The encode QueueTask.
\r
459 private void SetupLogging(QueueTask encodeQueueTask)
\r
461 string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";
\r
462 string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));
\r
463 string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId));
\r
467 logBuffer = new StringBuilder();
\r
469 // Clear the current Encode Logs
\r
470 if (File.Exists(logFile)) File.Delete(logFile);
\r
471 if (File.Exists(logFile2)) File.Delete(logFile2);
\r
473 fileWriter = new StreamWriter(logFile) { AutoFlush = true };
\r
475 fileWriter.WriteLine(UtilityService.CreateCliLogHeader(encodeQueueTask));
\r
476 logBuffer.AppendLine(UtilityService.CreateCliLogHeader(encodeQueueTask));
\r
480 if (fileWriter != null)
\r
482 fileWriter.Close();
\r
483 fileWriter.Dispose();
\r
490 /// Recieve the Standard Error information and process it
\r
492 /// <param name="sender">
\r
493 /// The Sender Object
\r
495 /// <param name="e">
\r
496 /// DataReceived EventArgs
\r
498 private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e)
\r
500 if (!String.IsNullOrEmpty(e.Data))
\r
505 logBuffer.AppendLine(e.Data);
\r
507 lock (fileWriterLock)
\r
509 if (fileWriter != null && fileWriter.BaseStream.CanWrite)
\r
511 fileWriter.WriteLine(e.Data);
\r
513 // If the logging grows past 100MB, kill the encode and stop.
\r
514 if (fileWriter.BaseStream.Length > 100000000)
\r
518 "The encode has been stopped. The log file has grown to over 100MB which indicates a serious problem has occured with the encode." +
\r
519 "Please check the encode log for an indication of what the problem is."));
\r
524 catch (Exception exc)
\r
534 /// <param name="sender">
\r
537 /// <param name="e">
\r
540 private void EncodeEncodeStarted(object sender, EventArgs e)
\r
542 Thread monitor = new Thread(EncodeMonitor);
\r
547 /// Monitor the QueueTask
\r
549 private void EncodeMonitor()
\r
553 Parser encode = new Parser(HbProcess.StandardOutput.BaseStream);
\r
554 encode.OnEncodeProgress += EncodeOnEncodeProgress;
\r
555 while (!encode.EndOfStream)
\r
556 encode.ReadEncodeStatus();
\r
558 catch (Exception exc)
\r
560 EncodeOnEncodeProgress(null, 0, 0, 0, 0, 0, "Unknown, status not available..");
\r
565 /// Displays the Encode status in the GUI
\r
567 /// <param name="sender">The sender</param>
\r
568 /// <param name="currentTask">The current task</param>
\r
569 /// <param name="taskCount">Number of tasks</param>
\r
570 /// <param name="percentComplete">Percent complete</param>
\r
571 /// <param name="currentFps">Current encode speed in fps</param>
\r
572 /// <param name="avg">Avg encode speed</param>
\r
573 /// <param name="timeRemaining">Time Left</param>
\r
574 private void EncodeOnEncodeProgress(object sender, int currentTask, int taskCount, float percentComplete, float currentFps, float avg, string timeRemaining)
\r
576 EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs
\r
578 AverageFrameRate = avg,
\r
579 CurrentFrameRate = currentFps,
\r
580 EstimatedTimeLeft = Converters.EncodeToTimespan(timeRemaining),
\r
581 PercentComplete = percentComplete,
\r
582 Task = currentTask,
\r
583 TaskCount = taskCount
\r
586 if (this.EncodeStatusChanged != null)
\r
587 this.EncodeStatusChanged(this, eventArgs);
\r
589 if (windowsSeven.IsWindowsSeven)
\r
592 int.TryParse(Math.Round(percentComplete).ToString(), out percent);
\r
594 windowsSeven.SetTaskBarProgress(percent);
\r