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