OSDN Git Service

WinGui:
[handbrake-jp/handbrake-jp-git.git] / win / C# / HandBrake.ApplicationServices / Services / Encode.cs
1 /*  Encode.cs $\r
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
5 \r
6 namespace HandBrake.ApplicationServices.Services\r
7 {\r
8     using System;\r
9     using System.ComponentModel;\r
10     using System.Diagnostics;\r
11     using System.IO;\r
12     using System.Text;\r
13     using System.Threading;\r
14     using System.Windows.Forms;\r
15 \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
22 \r
23     /// <summary>\r
24     /// Class which handles the CLI\r
25     /// </summary>\r
26     public class Encode : IEncode\r
27     {\r
28         #region Private Variables\r
29 \r
30         /// <summary>\r
31         /// The Log Buffer\r
32         /// </summary>\r
33         private StringBuilder logBuffer;\r
34 \r
35         /// <summary>\r
36         /// The Log file writer\r
37         /// </summary>\r
38         private StreamWriter fileWriter;\r
39 \r
40         /// <summary>\r
41         /// Gets The Process Handle\r
42         /// </summary>\r
43         private IntPtr processHandle;\r
44 \r
45         /// <summary>\r
46         /// Gets the Process ID\r
47         /// </summary>\r
48         private int processId;\r
49 \r
50         /// <summary>\r
51         /// Windows 7 API Pack wrapper\r
52         /// </summary>\r
53         private Win7 windowsSeven = new Win7();\r
54 \r
55         /// <summary>\r
56         /// A Lock for the filewriter\r
57         /// </summary>\r
58         static readonly object fileWriterLock = new object();\r
59 \r
60         #endregion\r
61 \r
62         /// <summary>\r
63         /// Initializes a new instance of the <see cref="Encode"/> class.\r
64         /// </summary>\r
65         public Encode()\r
66         {\r
67             this.EncodeStarted += this.EncodeEncodeStarted;\r
68             GrowlCommunicator.Register();\r
69         }\r
70 \r
71         #region Delegates and Event Handlers\r
72 \r
73         /// <summary>\r
74         /// Fires when a new CLI QueueTask starts\r
75         /// </summary>\r
76         public event EventHandler EncodeStarted;\r
77 \r
78         /// <summary>\r
79         /// Fires when a CLI QueueTask finishes.\r
80         /// </summary>\r
81         public event EncodeCompletedStatus EncodeCompleted;\r
82 \r
83         /// <summary>\r
84         /// Encode process has progressed\r
85         /// </summary>\r
86         public event EncodeProgessStatus EncodeStatusChanged;\r
87         #endregion\r
88 \r
89         #region Properties\r
90 \r
91         /// <summary>\r
92         /// Gets or sets The HB Process\r
93         /// </summary>\r
94         protected Process HbProcess { get; set; }\r
95 \r
96         /// <summary>\r
97         /// Gets a value indicating whether IsEncoding.\r
98         /// </summary>\r
99         public bool IsEncoding { get; private set; }\r
100 \r
101         /// <summary>\r
102         /// Gets ActivityLog.\r
103         /// </summary>\r
104         public string ActivityLog\r
105         {\r
106             get\r
107             {\r
108                 if (this.IsEncoding == false)\r
109                 {\r
110                     try\r
111                     {\r
112                         ReadFile(); // Read the last log file back in if it exists\r
113                     }\r
114                     catch (Exception exc)\r
115                     {\r
116                         return exc.ToString();\r
117                     }\r
118                 }\r
119 \r
120                 return string.IsNullOrEmpty(this.logBuffer.ToString()) ? "No log data available..." : this.logBuffer.ToString();\r
121             }\r
122         }\r
123 \r
124         #endregion\r
125 \r
126         #region Public Methods\r
127 \r
128         /// <summary>\r
129         /// Execute a HandBrakeCLI process.\r
130         /// </summary>\r
131         /// <param name="encodeQueueTask">\r
132         /// The encodeQueueTask.\r
133         /// </param>\r
134         /// <param name="enableLogging">\r
135         /// Enable Logging. When Disabled we onlt parse Standard Ouput for progress info. Standard Error log data is ignored.\r
136         /// </param>\r
137         public void Start(QueueTask encodeQueueTask, bool enableLogging)\r
138         {\r
139             try\r
140             {\r
141                 QueueTask queueTask = encodeQueueTask;\r
142 \r
143                 if (queueTask == null)\r
144                 {\r
145                     throw new ArgumentNullException("QueueTask was null");\r
146                 }\r
147 \r
148                 if (IsEncoding)\r
149                 {\r
150                     throw new Exception("HandBrake is already encodeing.");\r
151                 }\r
152 \r
153                 IsEncoding = true;\r
154 \r
155                 if (enableLogging)\r
156                 {\r
157                     try\r
158                     {\r
159                         SetupLogging(queueTask);\r
160                     }\r
161                     catch (Exception)\r
162                     {\r
163                         IsEncoding = false;\r
164                         throw;\r
165                     }\r
166                 }\r
167 \r
168                 if (Init.PreventSleep)\r
169                 {\r
170                     Win32.PreventSleep();\r
171                 }\r
172 \r
173                 string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe");\r
174                 ProcessStartInfo cliStart = new ProcessStartInfo(handbrakeCLIPath, queueTask.Query)\r
175                 {\r
176                     RedirectStandardOutput = true,\r
177                     RedirectStandardError = enableLogging ? true : false,\r
178                     UseShellExecute = false,\r
179                     CreateNoWindow = !Init.ShowCliForInGuiEncodeStatus ? true : false\r
180                 };\r
181 \r
182                 this.HbProcess = new Process { StartInfo = cliStart };\r
183 \r
184                 this.HbProcess.Start();\r
185 \r
186                 if (enableLogging)\r
187                 {\r
188                     this.HbProcess.ErrorDataReceived += HbProcErrorDataReceived;\r
189                     this.HbProcess.BeginErrorReadLine();\r
190                 }\r
191 \r
192                 this.processId = HbProcess.Id;\r
193                 this.processHandle = HbProcess.Handle;\r
194 \r
195                 // Set the process Priority\r
196                 if (this.processId != -1)\r
197                 {\r
198                     this.HbProcess.EnableRaisingEvents = true;\r
199                     this.HbProcess.Exited += this.HbProcessExited;\r
200                 }\r
201 \r
202                 // Set the Process Priority\r
203                 switch (Init.ProcessPriority)\r
204                 {\r
205                     case "Realtime":\r
206                         this.HbProcess.PriorityClass = ProcessPriorityClass.RealTime;\r
207                         break;\r
208                     case "High":\r
209                         this.HbProcess.PriorityClass = ProcessPriorityClass.High;\r
210                         break;\r
211                     case "Above Normal":\r
212                         this.HbProcess.PriorityClass = ProcessPriorityClass.AboveNormal;\r
213                         break;\r
214                     case "Normal":\r
215                         this.HbProcess.PriorityClass = ProcessPriorityClass.Normal;\r
216                         break;\r
217                     case "Low":\r
218                         this.HbProcess.PriorityClass = ProcessPriorityClass.Idle;\r
219                         break;\r
220                     default:\r
221                         this.HbProcess.PriorityClass = ProcessPriorityClass.BelowNormal;\r
222                         break;\r
223                 }\r
224 \r
225                 // Fire the Encode Started Event\r
226                 if (this.EncodeStarted != null)\r
227                     this.EncodeStarted(this, new EventArgs());\r
228             }\r
229             catch (Exception exc)\r
230             {\r
231                 if (this.EncodeCompleted != null)\r
232                     this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured in EncodeService.Run()"));\r
233             }\r
234         }\r
235 \r
236         /// <summary>\r
237         /// Stop the Encode\r
238         /// </summary>\r
239         public void Stop()\r
240         {\r
241             this.Stop(null);\r
242         }\r
243 \r
244         /// <summary>\r
245         /// Kill the CLI process\r
246         /// </summary>\r
247         /// <param name="exc">\r
248         /// The Exception that has occured.\r
249         /// This will get bubbled up through the EncodeCompletedEventArgs\r
250         /// </param>\r
251         public void Stop(Exception exc)\r
252         {\r
253             try\r
254             {\r
255                 if (this.HbProcess != null && !this.HbProcess.HasExited)\r
256                 {\r
257                     this.HbProcess.Kill();\r
258                 }\r
259             }\r
260             catch (Exception)\r
261             {\r
262                 // No need to report anything to the user. If it fails, it's probably already stopped.\r
263             }\r
264 \r
265 \r
266             if (exc == null)\r
267             {\r
268                 if (this.EncodeCompleted != null)\r
269                     this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));\r
270             }\r
271             else\r
272             {\r
273                 if (this.EncodeCompleted != null)\r
274                     this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured."));\r
275             }\r
276         }\r
277 \r
278         /// <summary>\r
279         /// Attempt to Safely kill a DirectRun() CLI\r
280         /// NOTE: This will not work with a MinGW CLI\r
281         /// Note: http://www.cygwin.com/ml/cygwin/2006-03/msg00330.html\r
282         /// </summary>\r
283         public void SafelyStop()\r
284         {\r
285             if ((int)this.processHandle == 0)\r
286                 return;\r
287 \r
288             // Allow the CLI to exit cleanly\r
289             Win32.SetForegroundWindow((int)this.processHandle);\r
290             SendKeys.Send("^C");\r
291             SendKeys.Flush();\r
292 \r
293             /*/if (HbProcess != null)\r
294             //{\r
295             //    HbProcess.StandardInput.AutoFlush = true;\r
296             //    HbProcess.StandardInput.WriteLine("^c^z");\r
297             //}*/\r
298         }\r
299 \r
300         /// <summary>\r
301         /// Save a copy of the log to the users desired location or a default location\r
302         /// if this feature is enabled in options.\r
303         /// </summary>\r
304         /// <param name="destination">\r
305         /// The Destination File Path\r
306         /// </param>\r
307         public void ProcessLogs(string destination)\r
308         {\r
309             try\r
310             {\r
311                 string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +\r
312                                 "\\HandBrake\\logs";\r
313                 string tempLogFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));\r
314 \r
315                 string encodeDestinationPath = Path.GetDirectoryName(destination);\r
316                 string destinationFile = Path.GetFileName(destination);\r
317                 string encodeLogFile = destinationFile + " " +\r
318                                        DateTime.Now.ToString().Replace("/", "-").Replace(":", "-") + ".txt";\r
319 \r
320                 // Make sure the log directory exists.\r
321                 if (!Directory.Exists(logDir))\r
322                     Directory.CreateDirectory(logDir);\r
323 \r
324                 // Copy the Log to HandBrakes log folder in the users applciation data folder.\r
325                 File.Copy(tempLogFile, Path.Combine(logDir, encodeLogFile));\r
326 \r
327                 // Save a copy of the log file in the same location as the enocde.\r
328                 if (Init.SaveLogWithVideo)\r
329                     File.Copy(tempLogFile, Path.Combine(encodeDestinationPath, encodeLogFile));\r
330 \r
331                 // Save a copy of the log file to a user specified location\r
332                 if (Directory.Exists(Init.SaveLogPath))\r
333                     if (Init.SaveLogPath != String.Empty && Init.SaveLogToSpecifiedPath)\r
334                         File.Copy(tempLogFile, Path.Combine(Init.SaveLogPath, encodeLogFile));\r
335             }\r
336             catch (Exception exc)\r
337             {\r
338                 // This exception doesn't warrent user interaction, but it should be logged (TODO)\r
339             }\r
340         }\r
341 \r
342         #endregion\r
343 \r
344         #region Private Helper Methods\r
345 \r
346         /// <summary>\r
347         /// The HandBrakeCLI process has exited.\r
348         /// </summary>\r
349         /// <param name="sender">\r
350         /// The sender.\r
351         /// </param>\r
352         /// <param name="e">\r
353         /// The EventArgs.\r
354         /// </param>\r
355         private void HbProcessExited(object sender, EventArgs e)\r
356         {\r
357             IsEncoding = false;\r
358             if (windowsSeven.IsWindowsSeven)\r
359             {\r
360                 windowsSeven.SetTaskBarProgressToNoProgress();\r
361             }\r
362 \r
363             if (Init.PreventSleep)\r
364             {\r
365                 Win32.AllowSleep();\r
366             }\r
367 \r
368             try\r
369             {\r
370                 lock (fileWriterLock)\r
371                 {\r
372                     // This is just a quick hack to ensure that we are done processing the logging data.\r
373                     // Logging data comes in after the exit event has processed sometimes. We should really impliment ISyncronizingInvoke\r
374                     // and set the SyncObject on the process. I think this may resolve this properly.\r
375                     // For now, just wait 2.5 seconds to let any trailing log messages come in and be processed.\r
376                     Thread.Sleep(2500);\r
377 \r
378                     this.HbProcess.CancelErrorRead();\r
379 \r
380                     if (fileWriter != null)\r
381                     {\r
382                         fileWriter.Close();\r
383                         fileWriter.Dispose();\r
384                     }\r
385 \r
386                     fileWriter = null;\r
387                 }\r
388             }\r
389             catch (Exception exc)\r
390             {\r
391                 // This exception doesn't warrent user interaction, but it should be logged (TODO)\r
392             }\r
393 \r
394             if (this.EncodeCompleted != null)\r
395                 this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));\r
396         }\r
397 \r
398         /// <summary>\r
399         /// Read the log file\r
400         /// </summary>\r
401         private void ReadFile()\r
402         {\r
403             logBuffer = new StringBuilder();\r
404             lock (logBuffer)\r
405             {\r
406                 // 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
407                 // we'll need to make a copy of it.\r
408                 string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";\r
409                 string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));\r
410                 string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId));\r
411                 int logFilePosition = 0;\r
412 \r
413                 try\r
414                 {\r
415                     // Copy the log file.\r
416                     if (File.Exists(logFile))\r
417                         File.Copy(logFile, logFile2, true);\r
418                     else\r
419                         return;\r
420 \r
421                     // Start the Reader\r
422                     // Only use text which continues on from the last read line\r
423                     using (StreamReader sr = new StreamReader(logFile2))\r
424                     {\r
425                         string line;\r
426                         int i = 1;\r
427                         while ((line = sr.ReadLine()) != null)\r
428                         {\r
429                             if (i > logFilePosition)\r
430                             {\r
431                                 logBuffer.AppendLine(line);\r
432                                 logFilePosition++;\r
433                             }\r
434                             i++;\r
435                         }\r
436                         sr.Close();\r
437                     }\r
438                 }\r
439                 catch (Exception exc)\r
440                 {\r
441                     logBuffer.Append(\r
442                         Environment.NewLine + "Unable to read Log file..." + Environment.NewLine + exc +\r
443                         Environment.NewLine);\r
444                 }\r
445             }\r
446         }\r
447 \r
448         /// <summary>\r
449         /// Setup the logging.\r
450         /// </summary>\r
451         /// <param name="encodeQueueTask">\r
452         /// The encode QueueTask.\r
453         /// </param>\r
454         private void SetupLogging(QueueTask encodeQueueTask)\r
455         {\r
456             string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";\r
457             string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));\r
458             string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId));\r
459 \r
460             try\r
461             {\r
462                 logBuffer = new StringBuilder();\r
463 \r
464                 // Clear the current Encode Logs\r
465                 if (File.Exists(logFile)) File.Delete(logFile);\r
466                 if (File.Exists(logFile2)) File.Delete(logFile2);\r
467 \r
468                 fileWriter = new StreamWriter(logFile) { AutoFlush = true };\r
469 \r
470                 fileWriter.WriteLine(UtilityService.CreateCliLogHeader(encodeQueueTask));\r
471                 logBuffer.AppendLine(UtilityService.CreateCliLogHeader(encodeQueueTask));\r
472             }\r
473             catch (Exception)\r
474             {\r
475                 if (fileWriter != null)\r
476                 {\r
477                     fileWriter.Close();\r
478                     fileWriter.Dispose();\r
479                 }\r
480                 throw;\r
481             }\r
482         }\r
483 \r
484         /// <summary>\r
485         /// Recieve the Standard Error information and process it\r
486         /// </summary>\r
487         /// <param name="sender">\r
488         /// The Sender Object\r
489         /// </param>\r
490         /// <param name="e">\r
491         /// DataReceived EventArgs\r
492         /// </param>\r
493         private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e)\r
494         {\r
495             if (!String.IsNullOrEmpty(e.Data))\r
496             {\r
497                 try\r
498                 {\r
499                     lock (logBuffer)\r
500                         logBuffer.AppendLine(e.Data);\r
501 \r
502                     lock (fileWriterLock)\r
503                     {\r
504                         if (fileWriter != null && fileWriter.BaseStream.CanWrite)\r
505                         {\r
506                             fileWriter.WriteLine(e.Data);\r
507 \r
508                             // If the logging grows past 100MB, kill the encode and stop.\r
509                             if (fileWriter.BaseStream.Length > 100000000)\r
510                             {\r
511                                 this.Stop(\r
512                                     new Exception(\r
513                                         "The encode has been stopped. The log file has grown to over 100MB which indicates a serious problem has occured with the encode." +\r
514                                         "Please check the encode log for an indication of what the problem is."));\r
515                             }\r
516                         }\r
517                     }\r
518                 }\r
519                 catch (Exception exc)\r
520                 {\r
521                     // Do Nothing.\r
522                 }\r
523             }\r
524         }\r
525 \r
526         /// <summary>\r
527         /// Encode Started\r
528         /// </summary>\r
529         /// <param name="sender">\r
530         /// The sender.\r
531         /// </param>\r
532         /// <param name="e">\r
533         /// The EventArgs.\r
534         /// </param>\r
535         private void EncodeEncodeStarted(object sender, EventArgs e)\r
536         {\r
537             Thread monitor = new Thread(EncodeMonitor);\r
538             monitor.Start();\r
539         }\r
540 \r
541         /// <summary>\r
542         /// Monitor the QueueTask\r
543         /// </summary>\r
544         private void EncodeMonitor()\r
545         {\r
546             try\r
547             {\r
548                 Parser encode = new Parser(HbProcess.StandardOutput.BaseStream);\r
549                 encode.OnEncodeProgress += EncodeOnEncodeProgress;\r
550                 while (!encode.EndOfStream)\r
551                     encode.ReadEncodeStatus();\r
552             }\r
553             catch (Exception exc)\r
554             {\r
555                 EncodeOnEncodeProgress(null, 0, 0, 0, 0, 0, "Unknown, status not available..");\r
556             }\r
557         }\r
558 \r
559         /// <summary>\r
560         /// Displays the Encode status in the GUI\r
561         /// </summary>\r
562         /// <param name="sender">The sender</param>\r
563         /// <param name="currentTask">The current task</param>\r
564         /// <param name="taskCount">Number of tasks</param>\r
565         /// <param name="percentComplete">Percent complete</param>\r
566         /// <param name="currentFps">Current encode speed in fps</param>\r
567         /// <param name="avg">Avg encode speed</param>\r
568         /// <param name="timeRemaining">Time Left</param>\r
569         private void EncodeOnEncodeProgress(object sender, int currentTask, int taskCount, float percentComplete, float currentFps, float avg, string timeRemaining)\r
570         {\r
571             EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs\r
572             {\r
573                 AverageFrameRate = avg,\r
574                 CurrentFrameRate = currentFps,\r
575                 EstimatedTimeLeft = Converters.EncodeToTimespan(timeRemaining),\r
576                 PercentComplete = percentComplete,\r
577                 Task = currentTask,\r
578                 TaskCount = taskCount\r
579             };\r
580 \r
581             if (this.EncodeStatusChanged != null)\r
582                 this.EncodeStatusChanged(this, eventArgs);\r
583 \r
584             if (windowsSeven.IsWindowsSeven)\r
585             {\r
586                 int percent;\r
587                 int.TryParse(Math.Round(percentComplete).ToString(), out percent);\r
588 \r
589                 windowsSeven.SetTaskBarProgress(percent);\r
590             }\r
591         }\r
592 \r
593         #endregion\r
594     }\r
595 }