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