OSDN Git Service

0e9465d1854f155cd21bda8b07521568983a3515
[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                     this.HbProcess.CancelOutputRead();\r
380 \r
381                     if (fileWriter != null)\r
382                     {\r
383                         fileWriter.Close();\r
384                         fileWriter.Dispose();\r
385                     }\r
386 \r
387                     fileWriter = null;\r
388                 }\r
389             }\r
390             catch (Exception exc)\r
391             {\r
392                 // This exception doesn't warrent user interaction, but it should be logged (TODO)\r
393             }\r
394 \r
395             if (this.EncodeCompleted != null)\r
396                 this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));\r
397         }\r
398 \r
399         /// <summary>\r
400         /// Read the log file\r
401         /// </summary>\r
402         private void ReadFile()\r
403         {\r
404             logBuffer = new StringBuilder();\r
405             lock (logBuffer)\r
406             {\r
407                 // 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
408                 // we'll need to make a copy of it.\r
409                 string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";\r
410                 string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));\r
411                 string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId));\r
412                 int logFilePosition = 0;\r
413 \r
414                 try\r
415                 {\r
416                     // Copy the log file.\r
417                     if (File.Exists(logFile))\r
418                         File.Copy(logFile, logFile2, true);\r
419                     else\r
420                         return;\r
421 \r
422                     // Start the Reader\r
423                     // Only use text which continues on from the last read line\r
424                     using (StreamReader sr = new StreamReader(logFile2))\r
425                     {\r
426                         string line;\r
427                         int i = 1;\r
428                         while ((line = sr.ReadLine()) != null)\r
429                         {\r
430                             if (i > logFilePosition)\r
431                             {\r
432                                 logBuffer.AppendLine(line);\r
433                                 logFilePosition++;\r
434                             }\r
435                             i++;\r
436                         }\r
437                         sr.Close();\r
438                     }\r
439                 }\r
440                 catch (Exception exc)\r
441                 {\r
442                     logBuffer.Append(\r
443                         Environment.NewLine + "Unable to read Log file..." + Environment.NewLine + exc +\r
444                         Environment.NewLine);\r
445                 }\r
446             }\r
447         }\r
448 \r
449         /// <summary>\r
450         /// Setup the logging.\r
451         /// </summary>\r
452         /// <param name="encodeQueueTask">\r
453         /// The encode QueueTask.\r
454         /// </param>\r
455         private void SetupLogging(QueueTask encodeQueueTask)\r
456         {\r
457             string logDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\HandBrake\\logs";\r
458             string logFile = Path.Combine(logDir, string.Format("last_encode_log{0}.txt", Init.InstanceId));\r
459             string logFile2 = Path.Combine(logDir, string.Format("tmp_appReadable_log{0}.txt", Init.InstanceId));\r
460 \r
461             try\r
462             {\r
463                 logBuffer = new StringBuilder();\r
464 \r
465                 // Clear the current Encode Logs\r
466                 if (File.Exists(logFile)) File.Delete(logFile);\r
467                 if (File.Exists(logFile2)) File.Delete(logFile2);\r
468 \r
469                 fileWriter = new StreamWriter(logFile) { AutoFlush = true };\r
470 \r
471                 fileWriter.WriteLine(UtilityService.CreateCliLogHeader(encodeQueueTask));\r
472                 logBuffer.AppendLine(UtilityService.CreateCliLogHeader(encodeQueueTask));\r
473             }\r
474             catch (Exception)\r
475             {\r
476                 if (fileWriter != null)\r
477                 {\r
478                     fileWriter.Close();\r
479                     fileWriter.Dispose();\r
480                 }\r
481                 throw;\r
482             }\r
483         }\r
484 \r
485         /// <summary>\r
486         /// Recieve the Standard Error information and process it\r
487         /// </summary>\r
488         /// <param name="sender">\r
489         /// The Sender Object\r
490         /// </param>\r
491         /// <param name="e">\r
492         /// DataReceived EventArgs\r
493         /// </param>\r
494         private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e)\r
495         {\r
496             if (!String.IsNullOrEmpty(e.Data))\r
497             {\r
498                 try\r
499                 {\r
500                     lock (logBuffer)\r
501                         logBuffer.AppendLine(e.Data);\r
502 \r
503                     lock (fileWriterLock)\r
504                     {\r
505                         if (fileWriter != null && fileWriter.BaseStream.CanWrite)\r
506                         {\r
507                             fileWriter.WriteLine(e.Data);\r
508 \r
509                             // If the logging grows past 100MB, kill the encode and stop.\r
510                             if (fileWriter.BaseStream.Length > 100000000)\r
511                             {\r
512                                 this.Stop(\r
513                                     new Exception(\r
514                                         "The encode has been stopped. The log file has grown to over 100MB which indicates a serious problem has occured with the encode." +\r
515                                         "Please check the encode log for an indication of what the problem is."));\r
516                             }\r
517                         }\r
518                     }\r
519                 }\r
520                 catch (Exception exc)\r
521                 {\r
522                     // Do Nothing.\r
523                 }\r
524             }\r
525         }\r
526 \r
527         /// <summary>\r
528         /// Encode Started\r
529         /// </summary>\r
530         /// <param name="sender">\r
531         /// The sender.\r
532         /// </param>\r
533         /// <param name="e">\r
534         /// The EventArgs.\r
535         /// </param>\r
536         private void EncodeEncodeStarted(object sender, EventArgs e)\r
537         {\r
538             Thread monitor = new Thread(EncodeMonitor);\r
539             monitor.Start();\r
540         }\r
541 \r
542         /// <summary>\r
543         /// Monitor the QueueTask\r
544         /// </summary>\r
545         private void EncodeMonitor()\r
546         {\r
547             try\r
548             {\r
549                 Parser encode = new Parser(HbProcess.StandardOutput.BaseStream);\r
550                 encode.OnEncodeProgress += EncodeOnEncodeProgress;\r
551                 while (!encode.EndOfStream)\r
552                     encode.ReadEncodeStatus();\r
553             }\r
554             catch (Exception exc)\r
555             {\r
556                 EncodeOnEncodeProgress(null, 0, 0, 0, 0, 0, "Unknown, status not available..");\r
557             }\r
558         }\r
559 \r
560         /// <summary>\r
561         /// Displays the Encode status in the GUI\r
562         /// </summary>\r
563         /// <param name="sender">The sender</param>\r
564         /// <param name="currentTask">The current task</param>\r
565         /// <param name="taskCount">Number of tasks</param>\r
566         /// <param name="percentComplete">Percent complete</param>\r
567         /// <param name="currentFps">Current encode speed in fps</param>\r
568         /// <param name="avg">Avg encode speed</param>\r
569         /// <param name="timeRemaining">Time Left</param>\r
570         private void EncodeOnEncodeProgress(object sender, int currentTask, int taskCount, float percentComplete, float currentFps, float avg, string timeRemaining)\r
571         {\r
572             EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs\r
573             {\r
574                 AverageFrameRate = avg,\r
575                 CurrentFrameRate = currentFps,\r
576                 EstimatedTimeLeft = Converters.EncodeToTimespan(timeRemaining),\r
577                 PercentComplete = percentComplete,\r
578                 Task = currentTask,\r
579                 TaskCount = taskCount\r
580             };\r
581 \r
582             if (this.EncodeStatusChanged != null)\r
583                 this.EncodeStatusChanged(this, eventArgs);\r
584 \r
585             if (windowsSeven.IsWindowsSeven)\r
586             {\r
587                 int percent;\r
588                 int.TryParse(Math.Round(percentComplete).ToString(), out percent);\r
589 \r
590                 windowsSeven.SetTaskBarProgress(percent);\r
591             }\r
592         }\r
593 \r
594         #endregion\r
595     }\r
596 }