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         static readonly object syncObject = new object();\r
61 \r
62         #endregion\r
63 \r
64         /// <summary>\r
65         /// Initializes a new instance of the <see cref="Encode"/> class.\r
66         /// </summary>\r
67         public Encode()\r
68         {\r
69             this.EncodeStarted += this.EncodeEncodeStarted;\r
70             GrowlCommunicator.Register();\r
71         }\r
72 \r
73         #region Delegates and Event Handlers\r
74 \r
75         public delegate void ProcessEncodeEventsDelegate();\r
76 \r
77         /// <summary>\r
78         /// Fires when a new CLI QueueTask starts\r
79         /// </summary>\r
80         public event EventHandler EncodeStarted;\r
81 \r
82         /// <summary>\r
83         /// Fires when a CLI QueueTask finishes.\r
84         /// </summary>\r
85         public event EncodeCompletedStatus EncodeCompleted;\r
86 \r
87         /// <summary>\r
88         /// Encode process has progressed\r
89         /// </summary>\r
90         public event EncodeProgessStatus EncodeStatusChanged;\r
91         #endregion\r
92 \r
93         #region Properties\r
94 \r
95         /// <summary>\r
96         /// Gets or sets The HB Process\r
97         /// </summary>\r
98         protected Process HbProcess { get; set; }\r
99 \r
100         /// <summary>\r
101         /// Gets a value indicating whether IsEncoding.\r
102         /// </summary>\r
103         public bool IsEncoding { get; private set; }\r
104 \r
105         /// <summary>\r
106         /// Gets ActivityLog.\r
107         /// </summary>\r
108         public string ActivityLog\r
109         {\r
110             get\r
111             {\r
112                 if (this.IsEncoding == false)\r
113                 {\r
114                     try\r
115                     {\r
116                         ReadFile(); // Read the last log file back in if it exists\r
117                     }\r
118                     catch (Exception exc)\r
119                     {\r
120                         return exc.ToString();\r
121                     }\r
122                 }\r
123 \r
124                 return string.IsNullOrEmpty(this.logBuffer.ToString()) ? "No log data available..." : this.logBuffer.ToString();\r
125             }\r
126         }\r
127 \r
128         #endregion\r
129 \r
130         #region Public Methods\r
131 \r
132         /// <summary>\r
133         /// Execute a HandBrakeCLI process.\r
134         /// </summary>\r
135         /// <param name="encodeQueueTask">\r
136         /// The encodeQueueTask.\r
137         /// </param>\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
140         /// </param>\r
141         public void Start(QueueTask encodeQueueTask, bool enableLogging)\r
142         {\r
143             try\r
144             {\r
145                 QueueTask queueTask = encodeQueueTask;\r
146 \r
147                 if (queueTask == null)\r
148                 {\r
149                     throw new ArgumentNullException("QueueTask was null");\r
150                 }\r
151 \r
152                 if (IsEncoding)\r
153                 {\r
154                     throw new Exception("HandBrake is already encodeing.");\r
155                 }\r
156 \r
157                 IsEncoding = true;\r
158 \r
159                 if (enableLogging)\r
160                 {\r
161                     try\r
162                     {\r
163                         SetupLogging(queueTask);\r
164                     }\r
165                     catch (Exception)\r
166                     {\r
167                         IsEncoding = false;\r
168                         throw;\r
169                     }\r
170                 }\r
171 \r
172                 if (Init.PreventSleep)\r
173                 {\r
174                     Win32.PreventSleep();\r
175                 }\r
176 \r
177                 string handbrakeCLIPath = Path.Combine(Application.StartupPath, "HandBrakeCLI.exe");\r
178                 ProcessStartInfo cliStart = new ProcessStartInfo(handbrakeCLIPath, queueTask.Query)\r
179                 {\r
180                     RedirectStandardOutput = true,\r
181                     RedirectStandardError = enableLogging ? true : false,\r
182                     UseShellExecute = false,\r
183                     CreateNoWindow = !Init.ShowCliForInGuiEncodeStatus ? true : false\r
184                 };\r
185 \r
186                 this.HbProcess = new Process { StartInfo = cliStart };\r
187 \r
188                 this.HbProcess.Start();\r
189 \r
190                 if (enableLogging)\r
191                 {\r
192                     this.HbProcess.ErrorDataReceived += HbProcErrorDataReceived;\r
193                     this.HbProcess.BeginErrorReadLine();\r
194                 }\r
195 \r
196                 this.processId = HbProcess.Id;\r
197                 this.processHandle = HbProcess.Handle;\r
198 \r
199                 // Set the process Priority\r
200                 if (this.processId != -1)\r
201                 {\r
202                     this.HbProcess.EnableRaisingEvents = true;\r
203                     this.HbProcess.Exited += this.HbProcessExited;\r
204                 }\r
205 \r
206                 // Set the Process Priority\r
207                 switch (Init.ProcessPriority)\r
208                 {\r
209                     case "Realtime":\r
210                         this.HbProcess.PriorityClass = ProcessPriorityClass.RealTime;\r
211                         break;\r
212                     case "High":\r
213                         this.HbProcess.PriorityClass = ProcessPriorityClass.High;\r
214                         break;\r
215                     case "Above Normal":\r
216                         this.HbProcess.PriorityClass = ProcessPriorityClass.AboveNormal;\r
217                         break;\r
218                     case "Normal":\r
219                         this.HbProcess.PriorityClass = ProcessPriorityClass.Normal;\r
220                         break;\r
221                     case "Low":\r
222                         this.HbProcess.PriorityClass = ProcessPriorityClass.Idle;\r
223                         break;\r
224                     default:\r
225                         this.HbProcess.PriorityClass = ProcessPriorityClass.BelowNormal;\r
226                         break;\r
227                 }\r
228 \r
229                 // Fire the Encode Started Event\r
230                 if (this.EncodeStarted != null)\r
231                     this.EncodeStarted(this, new EventArgs());\r
232             }\r
233             catch (Exception exc)\r
234             {\r
235                 if (this.EncodeCompleted != null)\r
236                     this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured in EncodeService.Run()"));\r
237             }\r
238         }\r
239 \r
240         /// <summary>\r
241         /// Stop the Encode\r
242         /// </summary>\r
243         public void Stop()\r
244         {\r
245             this.Stop(null);\r
246         }\r
247 \r
248         /// <summary>\r
249         /// Kill the CLI process\r
250         /// </summary>\r
251         /// <param name="exc">\r
252         /// The Exception that has occured.\r
253         /// This will get bubbled up through the EncodeCompletedEventArgs\r
254         /// </param>\r
255         public void Stop(Exception exc)\r
256         {\r
257             try\r
258             {\r
259                 if (this.HbProcess != null && !this.HbProcess.HasExited)\r
260                 {\r
261                     this.HbProcess.Kill();\r
262                 }\r
263             }\r
264             catch (Exception)\r
265             {\r
266                 // No need to report anything to the user. If it fails, it's probably already stopped.\r
267             }\r
268 \r
269 \r
270             if (exc == null)\r
271             {\r
272                 if (this.EncodeCompleted != null)\r
273                     this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));\r
274             }\r
275             else\r
276             {\r
277                 if (this.EncodeCompleted != null)\r
278                     this.EncodeCompleted(this, new EncodeCompletedEventArgs(false, exc, "An Error has occured."));\r
279             }\r
280         }\r
281 \r
282         /// <summary>\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
286         /// </summary>\r
287         public void SafelyStop()\r
288         {\r
289             if ((int)this.processHandle == 0)\r
290                 return;\r
291 \r
292             // Allow the CLI to exit cleanly\r
293             Win32.SetForegroundWindow((int)this.processHandle);\r
294             SendKeys.Send("^C");\r
295             SendKeys.Flush();\r
296 \r
297             /*/if (HbProcess != null)\r
298             //{\r
299             //    HbProcess.StandardInput.AutoFlush = true;\r
300             //    HbProcess.StandardInput.WriteLine("^c^z");\r
301             //}*/\r
302         }\r
303 \r
304         /// <summary>\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
307         /// </summary>\r
308         /// <param name="destination">\r
309         /// The Destination File Path\r
310         /// </param>\r
311         public void ProcessLogs(string destination)\r
312         {\r
313             try\r
314             {\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
318 \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
323 \r
324                 // Make sure the log directory exists.\r
325                 if (!Directory.Exists(logDir))\r
326                     Directory.CreateDirectory(logDir);\r
327 \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
330 \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
334 \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
339             }\r
340             catch (Exception exc)\r
341             {\r
342                 // This exception doesn't warrent user interaction, but it should be logged (TODO)\r
343             }\r
344         }\r
345 \r
346         #endregion\r
347 \r
348         #region Private Helper Methods\r
349 \r
350         /// <summary>\r
351         /// The HandBrakeCLI process has exited.\r
352         /// </summary>\r
353         /// <param name="sender">\r
354         /// The sender.\r
355         /// </param>\r
356         /// <param name="e">\r
357         /// The EventArgs.\r
358         /// </param>\r
359         private void HbProcessExited(object sender, EventArgs e)\r
360         {\r
361             IsEncoding = false;\r
362             if (windowsSeven.IsWindowsSeven)\r
363             {\r
364                 windowsSeven.SetTaskBarProgressToNoProgress();\r
365             }\r
366 \r
367             if (Init.PreventSleep)\r
368             {\r
369                 Win32.AllowSleep();\r
370             }\r
371 \r
372             try\r
373             {\r
374                 lock (fileWriterLock)\r
375                 {\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
381 \r
382                     this.HbProcess.CancelErrorRead();\r
383                     this.HbProcess.CancelOutputRead();\r
384 \r
385                     if (fileWriter != null)\r
386                     {\r
387                         fileWriter.Close();\r
388                         fileWriter.Dispose();\r
389                     }\r
390 \r
391                     fileWriter = null;\r
392                 }\r
393             }\r
394             catch (Exception exc)\r
395             {\r
396                 // This exception doesn't warrent user interaction, but it should be logged (TODO)\r
397             }\r
398 \r
399             if (this.EncodeCompleted != null)\r
400                 this.EncodeCompleted(this, new EncodeCompletedEventArgs(true, null, string.Empty));\r
401         }\r
402 \r
403         /// <summary>\r
404         /// Read the log file\r
405         /// </summary>\r
406         private void ReadFile()\r
407         {\r
408             logBuffer = new StringBuilder();\r
409             lock (logBuffer)\r
410             {\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
417 \r
418                 try\r
419                 {\r
420                     // Copy the log file.\r
421                     if (File.Exists(logFile))\r
422                         File.Copy(logFile, logFile2, true);\r
423                     else\r
424                         return;\r
425 \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
429                     {\r
430                         string line;\r
431                         int i = 1;\r
432                         while ((line = sr.ReadLine()) != null)\r
433                         {\r
434                             if (i > logFilePosition)\r
435                             {\r
436                                 logBuffer.AppendLine(line);\r
437                                 logFilePosition++;\r
438                             }\r
439                             i++;\r
440                         }\r
441                         sr.Close();\r
442                     }\r
443                 }\r
444                 catch (Exception exc)\r
445                 {\r
446                     logBuffer.Append(\r
447                         Environment.NewLine + "Unable to read Log file..." + Environment.NewLine + exc +\r
448                         Environment.NewLine);\r
449                 }\r
450             }\r
451         }\r
452 \r
453         /// <summary>\r
454         /// Setup the logging.\r
455         /// </summary>\r
456         /// <param name="encodeQueueTask">\r
457         /// The encode QueueTask.\r
458         /// </param>\r
459         private void SetupLogging(QueueTask encodeQueueTask)\r
460         {\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
464 \r
465             try\r
466             {\r
467                 logBuffer = new StringBuilder();\r
468 \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
472 \r
473                 fileWriter = new StreamWriter(logFile) { AutoFlush = true };\r
474 \r
475                 fileWriter.WriteLine(UtilityService.CreateCliLogHeader(encodeQueueTask));\r
476                 logBuffer.AppendLine(UtilityService.CreateCliLogHeader(encodeQueueTask));\r
477             }\r
478             catch (Exception)\r
479             {\r
480                 if (fileWriter != null)\r
481                 {\r
482                     fileWriter.Close();\r
483                     fileWriter.Dispose();\r
484                 }\r
485                 throw;\r
486             }\r
487         }\r
488 \r
489         /// <summary>\r
490         /// Recieve the Standard Error information and process it\r
491         /// </summary>\r
492         /// <param name="sender">\r
493         /// The Sender Object\r
494         /// </param>\r
495         /// <param name="e">\r
496         /// DataReceived EventArgs\r
497         /// </param>\r
498         private void HbProcErrorDataReceived(object sender, DataReceivedEventArgs e)\r
499         {\r
500             if (!String.IsNullOrEmpty(e.Data))\r
501             {\r
502                 try\r
503                 {\r
504                     lock (logBuffer)\r
505                         logBuffer.AppendLine(e.Data);\r
506 \r
507                     lock (fileWriterLock)\r
508                     {\r
509                         if (fileWriter != null && fileWriter.BaseStream.CanWrite)\r
510                         {\r
511                             fileWriter.WriteLine(e.Data);\r
512 \r
513                             // If the logging grows past 100MB, kill the encode and stop.\r
514                             if (fileWriter.BaseStream.Length > 100000000)\r
515                             {\r
516                                 this.Stop(\r
517                                     new Exception(\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
520                             }\r
521                         }\r
522                     }\r
523                 }\r
524                 catch (Exception exc)\r
525                 {\r
526                     // Do Nothing.\r
527                 }\r
528             }\r
529         }\r
530 \r
531         /// <summary>\r
532         /// Encode Started\r
533         /// </summary>\r
534         /// <param name="sender">\r
535         /// The sender.\r
536         /// </param>\r
537         /// <param name="e">\r
538         /// The EventArgs.\r
539         /// </param>\r
540         private void EncodeEncodeStarted(object sender, EventArgs e)\r
541         {\r
542             Thread monitor = new Thread(EncodeMonitor);\r
543             monitor.Start();\r
544         }\r
545 \r
546         /// <summary>\r
547         /// Monitor the QueueTask\r
548         /// </summary>\r
549         private void EncodeMonitor()\r
550         {\r
551             try\r
552             {\r
553                 Parser encode = new Parser(HbProcess.StandardOutput.BaseStream);\r
554                 encode.OnEncodeProgress += EncodeOnEncodeProgress;\r
555                 while (!encode.EndOfStream)\r
556                     encode.ReadEncodeStatus();\r
557             }\r
558             catch (Exception exc)\r
559             {\r
560                 EncodeOnEncodeProgress(null, 0, 0, 0, 0, 0, "Unknown, status not available..");\r
561             }\r
562         }\r
563 \r
564         /// <summary>\r
565         /// Displays the Encode status in the GUI\r
566         /// </summary>\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
575         {\r
576             EncodeProgressEventArgs eventArgs = new EncodeProgressEventArgs\r
577             {\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
584             };\r
585 \r
586             if (this.EncodeStatusChanged != null)\r
587                 this.EncodeStatusChanged(this, eventArgs);\r
588 \r
589             if (windowsSeven.IsWindowsSeven)\r
590             {\r
591                 int percent;\r
592                 int.TryParse(Math.Round(percentComplete).ToString(), out percent);\r
593 \r
594                 windowsSeven.SetTaskBarProgress(percent);\r
595             }\r
596         }\r
597 \r
598         #endregion\r
599     }\r
600 }