OSDN Git Service

WinGui:
[handbrake-jp/handbrake-jp-git.git] / win / C# / interop / HandBrakeInstance.cs
1 // --------------------------------------------------------------------------------------------------------------------\r
2 // <copyright file="HandBrakeInstance.cs" company="HandBrake Project (http://handbrake.fr)">\r
3 //   This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.\r
4 // </copyright>\r
5 // <summary>\r
6 //   A wrapper for a HandBrake instance.\r
7 // </summary>\r
8 // --------------------------------------------------------------------------------------------------------------------\r
9 \r
10 namespace HandBrake.Interop\r
11 {\r
12     using System;\r
13     using System.Collections.Generic;\r
14     using System.IO;\r
15     using System.Linq;\r
16     using System.Runtime.InteropServices;\r
17     using System.Windows.Media.Imaging;\r
18     using Model;\r
19     using Model.Encoding;\r
20     using SourceData;\r
21 \r
22     /// <summary>\r
23     /// A wrapper for a HandBrake instance.\r
24     /// </summary>\r
25     public class HandBrakeInstance : IDisposable\r
26     {\r
27         /// <summary>\r
28         /// The number of MS between status polls when scanning.\r
29         /// </summary>\r
30         private const double ScanPollIntervalMs = 200;\r
31 \r
32         /// <summary>\r
33         /// The number of MS between status polls when encoding.\r
34         /// </summary>\r
35         private const double EncodePollIntervalMs = 200;\r
36 \r
37         /// <summary>\r
38         /// X264 options to add for a turbo first pass.\r
39         /// </summary>\r
40         private const string TurboX264Opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0";\r
41 \r
42         /// <summary>\r
43         /// The native handle to the HandBrake instance.\r
44         /// </summary>\r
45         private IntPtr hbHandle;\r
46 \r
47         /// <summary>\r
48         /// The timer to poll for scan status.\r
49         /// </summary>\r
50         private System.Timers.Timer scanPollTimer;\r
51 \r
52         /// <summary>\r
53         /// The timer to poll for encode status.\r
54         /// </summary>\r
55         private System.Timers.Timer encodePollTimer;\r
56 \r
57         /// <summary>\r
58         /// The list of original titles in native structure form.\r
59         /// </summary>\r
60         private List<hb_title_s> originalTitles;\r
61 \r
62         /// <summary>\r
63         /// The list of titles on this instance.\r
64         /// </summary>\r
65         private List<Title> titles;\r
66 \r
67         /// <summary>\r
68         /// A list of native memory locations allocated by this instance.\r
69         /// </summary>\r
70         private List<IntPtr> encodeAllocatedMemory;\r
71 \r
72         /// <summary>\r
73         /// The callback for log messages from HandBrake.\r
74         /// </summary>\r
75         private static LoggingCallback loggingCallback;\r
76 \r
77         /// <summary>\r
78         /// The callback for error messages from HandBrake.\r
79         /// </summary>\r
80         private static LoggingCallback errorCallback;\r
81 \r
82         /// <summary>\r
83         /// Fires for progress updates when scanning.\r
84         /// </summary>\r
85         public event EventHandler<ScanProgressEventArgs> ScanProgress;\r
86 \r
87         /// <summary>\r
88         /// Fires when a scan has completed.\r
89         /// </summary>\r
90         public event EventHandler<EventArgs> ScanCompleted;\r
91 \r
92         /// <summary>\r
93         /// Fires for progress updates when encoding.\r
94         /// </summary>\r
95         public event EventHandler<EncodeProgressEventArgs> EncodeProgress;\r
96 \r
97         /// <summary>\r
98         /// Fires when an encode has completed.\r
99         /// </summary>\r
100         public event EventHandler<EventArgs> EncodeCompleted;\r
101 \r
102         /// <summary>\r
103         /// Fires when HandBrake has logged a message.\r
104         /// </summary>\r
105         public static event EventHandler<MessageLoggedEventArgs> MessageLogged;\r
106 \r
107         /// <summary>\r
108         /// Fires when HandBrake has logged an error.\r
109         /// </summary>\r
110         public static event EventHandler<MessageLoggedEventArgs> ErrorLogged;\r
111 \r
112         /// <summary>\r
113         /// Destructor.\r
114         /// </summary>\r
115         ~HandBrakeInstance()\r
116         {\r
117             this.Dispose(false);\r
118         }\r
119 \r
120         /// <summary>\r
121         /// The list of titles on this instance.\r
122         /// </summary>\r
123         public List<Title> Titles\r
124         {\r
125             get\r
126             {\r
127                 return this.titles;\r
128             }\r
129         }\r
130 \r
131         /// <summary>\r
132         /// Initializes this instance.\r
133         /// </summary>\r
134         /// <param name="verbosity"></param>\r
135         public void Initialize(int verbosity)\r
136         {\r
137             // Register the logger if we have not already\r
138             if (loggingCallback == null)\r
139             {\r
140                 // Keep the callback as a member to prevent it from being garbage collected.\r
141                 loggingCallback = new LoggingCallback(HandBrakeInstance.LoggingHandler);\r
142                 errorCallback = new LoggingCallback(HandBrakeInstance.ErrorHandler);\r
143                 HbLib.hb_register_logger(loggingCallback);\r
144                 HbLib.hb_register_error_handler(errorCallback);\r
145             }\r
146 \r
147             this.hbHandle = HbLib.hb_init(verbosity, 0);\r
148         }\r
149 \r
150         /// <summary>\r
151         /// Handles log messages from HandBrake.\r
152         /// </summary>\r
153         /// <param name="message">The log message (including newline).</param>\r
154         public static void LoggingHandler(string message)\r
155         {\r
156             if (!string.IsNullOrEmpty(message))\r
157             {\r
158                 string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);\r
159 \r
160                 if (messageParts.Length > 0)\r
161                 {\r
162                     if (MessageLogged != null)\r
163                     {\r
164                         MessageLogged(null, new MessageLoggedEventArgs { Message = messageParts[0] });\r
165                     }\r
166 \r
167                     System.Diagnostics.Debug.WriteLine(messageParts[0]);\r
168                 }\r
169             }\r
170         }\r
171 \r
172         /// <summary>\r
173         /// Handles errors from HandBrake.\r
174         /// </summary>\r
175         /// <param name="message">The error message.</param>\r
176         public static void ErrorHandler(string message)\r
177         {\r
178             if (!string.IsNullOrEmpty(message))\r
179             {\r
180                 if (ErrorLogged != null)\r
181                 {\r
182                     ErrorLogged(null, new MessageLoggedEventArgs { Message = message });\r
183                 }\r
184 \r
185                 System.Diagnostics.Debug.WriteLine("ERROR: " + message);\r
186             }\r
187         }\r
188 \r
189         /// <summary>\r
190         /// Starts scanning the given path.\r
191         /// </summary>\r
192         /// <param name="path">The path to the video to scan.</param>\r
193         /// <param name="previewCount">The number of preview images to make.</param>\r
194         public void StartScan(string path, int previewCount)\r
195         {\r
196             this.StartScan(path, previewCount, 0);\r
197         }\r
198 \r
199         /// <summary>\r
200         /// Starts a scan of the given path.\r
201         /// </summary>\r
202         /// <param name="path">The path of the video to scan.</param>\r
203         /// <param name="previewCount">The number of previews to make on each title.</param>\r
204         /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param>\r
205         public void StartScan(string path, int previewCount, int titleIndex)\r
206         {\r
207             HbLib.hb_scan(hbHandle, path, titleIndex, previewCount, 1);\r
208             this.scanPollTimer = new System.Timers.Timer();\r
209             this.scanPollTimer.Interval = ScanPollIntervalMs;\r
210 \r
211             // Lambda notation used to make sure we can view any JIT exceptions the method throws\r
212             this.scanPollTimer.Elapsed += (o, e) =>\r
213             {\r
214                 this.PollScanProgress();\r
215             };\r
216             this.scanPollTimer.Start();\r
217         }\r
218 \r
219         /// <summary>\r
220         /// Gets an image for the given job and preview\r
221         /// </summary>\r
222         /// <remarks>\r
223         /// Only incorporates sizing and aspect ratio into preview image.\r
224         /// </remarks>\r
225         /// <param name="job">The encode job to preview.</param>\r
226         /// <param name="previewNumber">The index of the preview to get (0-based).</param>\r
227         /// <returns>An image with the requested preview.</returns>\r
228         public BitmapImage GetPreview(EncodeJob job, int previewNumber)\r
229         {\r
230             hb_title_s title = this.GetOriginalTitle(job.Title);\r
231 \r
232             hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);\r
233             List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);\r
234             \r
235             // Create a new job pointer from our modified job object\r
236             IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));\r
237             Marshal.StructureToPtr(nativeJob, newJob, false);\r
238             allocatedMemory.Add(newJob);\r
239 \r
240             int outputWidth = nativeJob.width;\r
241             int outputHeight = nativeJob.height;\r
242             int imageBufferSize = outputWidth * outputHeight * 4;\r
243             IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);\r
244             allocatedMemory.Add(nativeBuffer);\r
245             HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);\r
246             HbLib.hb_get_preview_by_index(this.hbHandle, job.Title, previewNumber, nativeBuffer);\r
247 \r
248             // Copy the filled image buffer to a managed array.\r
249             byte[] managedBuffer = new byte[imageBufferSize];\r
250             Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);\r
251 \r
252             InteropUtilities.FreeMemory(allocatedMemory);\r
253 \r
254             System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight);\r
255             System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, outputWidth, outputHeight), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);\r
256 \r
257             IntPtr ptr = bitmapData.Scan0;\r
258 \r
259             for (int i = 0; i < nativeJob.height; i++)\r
260             {\r
261                 Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4);\r
262                 ptr = AddOffset(ptr, bitmapData.Stride);\r
263             }\r
264 \r
265             bitmap.UnlockBits(bitmapData);\r
266             //bitmap.Save(@"d:\docs\test_" + previewNumber + ".png", System.Drawing.Imaging.ImageFormat.Png);\r
267 \r
268             using (MemoryStream memoryStream = new MemoryStream())\r
269             {\r
270                 bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);\r
271                 bitmap.Dispose();\r
272 \r
273                 BitmapImage wpfBitmap = new BitmapImage();\r
274                 wpfBitmap.BeginInit();\r
275                 wpfBitmap.CacheOption = BitmapCacheOption.OnLoad;\r
276                 wpfBitmap.StreamSource = memoryStream;\r
277                 wpfBitmap.EndInit();\r
278 \r
279                 return wpfBitmap;\r
280             }\r
281         }\r
282 \r
283         public static IntPtr AddOffset(IntPtr src, int offset)\r
284         {\r
285             return new IntPtr(src.ToInt64() + offset);\r
286         }\r
287 \r
288         /// <summary>\r
289         /// Starts an encode with the given job.\r
290         /// </summary>\r
291         /// <param name="job">The job to start.</param>\r
292         public void StartEncode(EncodeJob job)\r
293         {\r
294             this.StartEncode(job, false, 0, 0);\r
295         }\r
296 \r
297         /// <summary>\r
298         /// Starts an encode with the given job.\r
299         /// </summary>\r
300         /// <param name="job">The job to start.</param>\r
301         /// <param name="preview">True if this is a preview encode.</param>\r
302         /// <param name="previewNumber">The preview number to start the encode at (0-based).</param>\r
303         /// <param name="previewSeconds">The number of seconds in the preview.</param>\r
304         public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)\r
305         {\r
306             hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);\r
307             this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);\r
308 \r
309             if (!preview && job.EncodingProfile.IncludeChapterMarkers)\r
310             {\r
311                 Title title = this.GetTitle(job.Title);\r
312                 int numChapters = title.Chapters.Count;\r
313 \r
314                 if (job.UseDefaultChapterNames)\r
315                 {\r
316                     for (int i = 0; i < numChapters; i++)\r
317                     {\r
318                         HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));\r
319                     }\r
320                 }\r
321                 else\r
322                 {\r
323                     for (int i = 0; i < numChapters; i++)\r
324                     {\r
325                         HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);\r
326                     }\r
327                 }\r
328             }\r
329 \r
330             HbLib.hb_add(this.hbHandle, ref nativeJob);\r
331 \r
332             if (job.EncodingProfile.TwoPass)\r
333             {\r
334                 nativeJob.pass = 2;\r
335 \r
336                 string x264Opts = job.EncodingProfile.X264Options ?? string.Empty;\r
337                 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Opts);\r
338                 this.encodeAllocatedMemory.Add(nativeJob.x264opts);\r
339 \r
340                 HbLib.hb_add(this.hbHandle, ref nativeJob);\r
341             }\r
342 \r
343             HbLib.hb_start(this.hbHandle);\r
344 \r
345             this.encodePollTimer = new System.Timers.Timer();\r
346             this.encodePollTimer.Interval = EncodePollIntervalMs;\r
347 \r
348             this.encodePollTimer.Elapsed += (o, e) =>\r
349             {\r
350                 this.PollEncodeProgress();\r
351             };\r
352             this.encodePollTimer.Start();\r
353         }\r
354 \r
355         /// <summary>\r
356         /// Pauses the current encode.\r
357         /// </summary>\r
358         public void PauseEncode()\r
359         {\r
360             HbLib.hb_pause(this.hbHandle);\r
361         }\r
362 \r
363         /// <summary>\r
364         /// Resumes a paused encode.\r
365         /// </summary>\r
366         public void ResumeEncode()\r
367         {\r
368             HbLib.hb_resume(this.hbHandle);\r
369         }\r
370 \r
371         /// <summary>\r
372         /// Stops the current encode.\r
373         /// </summary>\r
374         public void StopEncode()\r
375         {\r
376             HbLib.hb_stop(this.hbHandle);\r
377 \r
378             // Also remove all jobs from the queue (in case we stopped a 2-pass encode)\r
379             var currentJobs = new List<IntPtr>();\r
380 \r
381             int jobs = HbLib.hb_count(this.hbHandle);\r
382             for (int i = 0; i < jobs; i++)\r
383             {\r
384                 currentJobs.Add(HbLib.hb_job(this.hbHandle, 0));\r
385             }\r
386 \r
387             foreach (IntPtr job in currentJobs)\r
388             {\r
389                 HbLib.hb_rem(this.hbHandle, job);\r
390             }\r
391         }\r
392 \r
393         /// <summary>\r
394         /// Gets the final size when using Anamorphic for a given encode job.\r
395         /// </summary>\r
396         /// <param name="job">The encode job to use.</param>\r
397         /// <param name="width">The storage width.</param>\r
398         /// <param name="height">The storage height.</param>\r
399         /// <param name="parWidth">The pixel aspect X number.</param>\r
400         /// <param name="parHeight">The pixel aspect Y number.</param>\r
401         public void GetAnamorphicSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight)\r
402         {\r
403             hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);\r
404             List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);\r
405 \r
406             int refWidth = 0;\r
407             int refHeight = 0;\r
408             int refParWidth = 0;\r
409             int refParHeight = 0;\r
410             HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);\r
411             HbLib.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);\r
412             //HbLib.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);\r
413             InteropUtilities.FreeMemory(allocatedMemory);\r
414 \r
415             width = refWidth;\r
416             height = refHeight;\r
417             parWidth = refParWidth;\r
418             parHeight = refParHeight;\r
419         }\r
420 \r
421         /// <summary>\r
422         /// Frees any resources associated with this object.\r
423         /// </summary>\r
424         public void Dispose()\r
425         {\r
426             this.Dispose(true);\r
427             GC.SuppressFinalize(this);\r
428         }\r
429 \r
430         /// <summary>\r
431         /// Call before app shutdown. Performs global cleanup.\r
432         /// </summary>\r
433         public static void DisposeGlobal()\r
434         {\r
435             HbLib.hb_global_close();\r
436         }\r
437 \r
438         /// <summary>\r
439         /// Frees any resources associated with this object.\r
440         /// </summary>\r
441         /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param>\r
442         protected virtual void Dispose(bool disposing)\r
443         {\r
444             if (disposing)\r
445             {\r
446                 // Free other state (managed objects).\r
447             }\r
448 \r
449             // Free unmanaged objects.\r
450             IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));\r
451             Marshal.WriteIntPtr(handlePtr, this.hbHandle);\r
452             HbLib.hb_close(handlePtr);\r
453             Marshal.FreeHGlobal(handlePtr);\r
454         }\r
455 \r
456         /// <summary>\r
457         /// Checks the status of the ongoing scan.\r
458         /// </summary>\r
459         private void PollScanProgress()\r
460         {\r
461             hb_state_s state = new hb_state_s();\r
462             HbLib.hb_get_state(this.hbHandle, ref state);\r
463 \r
464             if (state.state == NativeConstants.HB_STATE_SCANNING)\r
465             {\r
466                 if (this.ScanProgress != null)\r
467                 {\r
468                     int currentTitle = state.param.scanning.title_cur;\r
469                     int totalTitles = state.param.scanning.title_count;\r
470                     this.ScanProgress(this, new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = totalTitles });\r
471                 }\r
472             }\r
473             else if (state.state == NativeConstants.HB_STATE_SCANDONE)\r
474             {\r
475                 this.titles = new List<Title>();\r
476 \r
477                 IntPtr listPtr = HbLib.hb_get_titles(this.hbHandle);\r
478                 this.originalTitles = InteropUtilities.ConvertList<hb_title_s>(listPtr);\r
479 \r
480                 foreach (hb_title_s title in this.originalTitles)\r
481                 {\r
482                     var newTitle = this.ConvertTitle(title);\r
483                     this.titles.Add(newTitle);\r
484                 }\r
485 \r
486                 this.scanPollTimer.Stop();\r
487 \r
488                 if (this.ScanCompleted != null)\r
489                 {\r
490                     this.ScanCompleted(this, new EventArgs());\r
491                 }\r
492             }\r
493         }\r
494 \r
495         /// <summary>\r
496         /// Checks the status of the ongoing encode.\r
497         /// </summary>\r
498         private void PollEncodeProgress()\r
499         {\r
500             hb_state_s state = new hb_state_s();\r
501             HbLib.hb_get_state(this.hbHandle, ref state);\r
502 \r
503             if (state.state == NativeConstants.HB_STATE_WORKING)\r
504             {\r
505                 if (this.EncodeProgress != null)\r
506                 {\r
507                     var progressEventArgs = new EncodeProgressEventArgs\r
508                     {\r
509                         FractionComplete = state.param.working.progress,\r
510                         CurrentFrameRate = state.param.working.rate_cur,\r
511                         AverageFrameRate = state.param.working.rate_avg,\r
512                         EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds),\r
513                         Pass = state.param.working.job_cur\r
514                     };\r
515 \r
516                     this.EncodeProgress(this, progressEventArgs);\r
517                 }\r
518             }\r
519             else if (state.state == NativeConstants.HB_STATE_MUXING)\r
520             {\r
521                 //System.Diagnostics.Debug.WriteLine("Muxing...");\r
522             }\r
523             else if (state.state == NativeConstants.HB_STATE_WORKDONE)\r
524             {\r
525                 InteropUtilities.FreeMemory(this.encodeAllocatedMemory);\r
526                 this.encodePollTimer.Stop();\r
527 \r
528                 if (this.EncodeCompleted != null)\r
529                 {\r
530                     this.EncodeCompleted(this, new EventArgs());\r
531                 }\r
532             }\r
533         }\r
534 \r
535         /// <summary>\r
536         /// Applies the encoding job to the native memory structure and returns a list of memory\r
537         /// locations allocated during this.\r
538         /// </summary>\r
539         /// <param name="nativeJob">The native structure to apply to job info to.</param>\r
540         /// <param name="job">The job info to apply.</param>\r
541         /// <param name="preview">True if this is a preview encode.</param>\r
542         /// <param name="previewNumber">The preview number (0-based) to encode.</param>\r
543         /// <param name="previewSeconds">The number of seconds in the preview.</param>\r
544         /// <returns>The list of memory locations allocated for the job.</returns>\r
545         private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds)\r
546         {\r
547             var allocatedMemory = new List<IntPtr>();\r
548             Title title = this.GetTitle(job.Title);\r
549             hb_title_s originalTitle = this.GetOriginalTitle(job.Title);\r
550 \r
551             EncodingProfile profile = job.EncodingProfile;\r
552 \r
553             if (preview)\r
554             {\r
555                 nativeJob.start_at_preview = previewNumber + 1;\r
556                 nativeJob.seek_points = 10;\r
557 \r
558                 // There are 90,000 PTS per second.\r
559                 nativeJob.pts_to_stop = previewSeconds * 90000;\r
560             }\r
561             else if (job.ChapterStart > 0 && job.ChapterEnd > 0)\r
562             {\r
563                 nativeJob.chapter_start = job.ChapterStart;\r
564                 nativeJob.chapter_end = job.ChapterEnd;\r
565             }\r
566             else\r
567             {\r
568                 nativeJob.chapter_start = 1;\r
569                 nativeJob.chapter_end = title.Chapters.Count;\r
570             }\r
571 \r
572             nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;\r
573 \r
574             Cropping crop;\r
575 \r
576             if (profile.CustomCropping)\r
577             {\r
578                 crop = profile.Cropping;\r
579             }\r
580             else\r
581             {\r
582                 crop = title.AutoCropDimensions;\r
583             }\r
584 \r
585             nativeJob.crop[0] = crop.Top;\r
586             nativeJob.crop[1] = crop.Bottom;\r
587             nativeJob.crop[2] = crop.Left;\r
588             nativeJob.crop[3] = crop.Right;\r
589 \r
590             List<IntPtr> filterList = new List<IntPtr>();\r
591             if (profile.Deinterlace != Deinterlace.Off)\r
592             {\r
593                 nativeJob.deinterlace = 1;\r
594                 string settings = null;\r
595 \r
596                 switch (profile.Deinterlace)\r
597                 {\r
598                     case Deinterlace.Fast:\r
599                         settings = "-1";\r
600                         break;\r
601                     case Deinterlace.Slow:\r
602                         settings = "2";\r
603                         break;\r
604                     case Deinterlace.Slower:\r
605                         settings = "0";\r
606                         break;\r
607                     case Deinterlace.Custom:\r
608                         settings = profile.CustomDeinterlace;\r
609                         break;\r
610                     default:\r
611                         break;\r
612                 }\r
613 \r
614                 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);\r
615                 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEINTERLACE, settings));\r
616             }\r
617             else\r
618             {\r
619                 nativeJob.deinterlace = 0;\r
620             }\r
621 \r
622             if (profile.Detelecine != Detelecine.Off)\r
623             {\r
624                 string settings = null;\r
625                 if (profile.Detelecine == Detelecine.Custom)\r
626                 {\r
627                     settings = profile.CustomDetelecine;\r
628                 }\r
629 \r
630                 this.AddFilter(filterList, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);\r
631                 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DETELECINE, settings));\r
632             }\r
633 \r
634             if (profile.Decomb != Decomb.Off)\r
635             {\r
636                 string settings = null;\r
637                 if (profile.Decomb == Decomb.Custom)\r
638                 {\r
639                     settings = profile.CustomDecomb;\r
640                 }\r
641 \r
642                 this.AddFilter(filterList, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);\r
643                 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DECOMB, settings));\r
644             }\r
645 \r
646             if (profile.Deblock > 0)\r
647             {\r
648                 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);\r
649                 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString()));\r
650             }\r
651 \r
652             if (profile.Denoise != Denoise.Off)\r
653             {\r
654                 string settings = null;\r
655                 switch (profile.Denoise)\r
656                 {\r
657                     case Denoise.Weak:\r
658                         settings = "2:1:2:3";\r
659                         break;\r
660                     case Denoise.Medium:\r
661                         settings = "3:2:2:3";\r
662                         break;\r
663                     case Denoise.Strong:\r
664                         settings = "7:7:5:5";\r
665                         break;\r
666                     case Denoise.Custom:\r
667                         settings = profile.CustomDenoise;\r
668                         break;\r
669                     default:\r
670                         break;\r
671                 }\r
672 \r
673                 this.AddFilter(filterList, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);\r
674                 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DENOISE, settings));\r
675             }\r
676 \r
677             NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterList);\r
678             nativeJob.filters = filterListNative.ListPtr;\r
679             allocatedMemory.AddRange(filterListNative.AllocatedMemory);\r
680 \r
681             int width = profile.Width;\r
682             int height = profile.Height;\r
683 \r
684             if (width == 0)\r
685             {\r
686                 width = title.Resolution.Width;\r
687             }\r
688 \r
689             if (profile.MaxWidth > 0 && width > profile.MaxWidth)\r
690             {\r
691                 width = profile.MaxWidth;\r
692             }\r
693 \r
694             if (height == 0)\r
695             {\r
696                 height = title.Resolution.Height;\r
697             }\r
698 \r
699             if (profile.MaxHeight > 0 && height > profile.MaxHeight)\r
700             {\r
701                 height = profile.MaxHeight;\r
702             }\r
703 \r
704             nativeJob.grayscale = profile.Grayscale ? 1 : 0;\r
705 \r
706             switch (profile.Anamorphic)\r
707             {\r
708                 case Anamorphic.None:\r
709                     nativeJob.anamorphic.mode = 0;\r
710 \r
711                     if (profile.KeepDisplayAspect)\r
712                     {\r
713                         if (profile.Width == 0 && profile.Height == 0 || profile.Width == 0)\r
714                         {\r
715                             width = (int)((double)height * this.GetTitle(job.Title).AspectRatio);\r
716                         }\r
717                         else if (profile.Height == 0)\r
718                         {\r
719                             height = (int)((double)width / this.GetTitle(job.Title).AspectRatio);\r
720                         }\r
721                     }\r
722 \r
723                     nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;\r
724                     break;\r
725                 case Anamorphic.Strict:\r
726                     nativeJob.anamorphic.mode = 1;\r
727                     break;\r
728                 case Anamorphic.Loose:\r
729                     nativeJob.anamorphic.mode = 2;\r
730                     break;\r
731                 case Anamorphic.Custom:\r
732                     nativeJob.anamorphic.mode = 3;\r
733 \r
734                     nativeJob.modulus = profile.Modulus;\r
735 \r
736                     if (profile.UseDisplayWidth)\r
737                     {\r
738                         if (profile.KeepDisplayAspect)\r
739                         {\r
740                             height = (int)((double)profile.DisplayWidth / this.GetTitle(job.Title).AspectRatio);\r
741                         }\r
742 \r
743                         nativeJob.anamorphic.dar_width = profile.DisplayWidth;\r
744                         nativeJob.anamorphic.dar_height = height;\r
745                         nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;\r
746                     }\r
747                     else\r
748                     {\r
749                         nativeJob.anamorphic.par_width = profile.PixelAspectX;\r
750                         nativeJob.anamorphic.par_height = profile.PixelAspectY;\r
751                         nativeJob.anamorphic.keep_display_aspect = 0;\r
752                     }\r
753                     break;\r
754                 default:\r
755                     break;\r
756             }\r
757 \r
758             nativeJob.width = width;\r
759             nativeJob.height = height;\r
760 \r
761             nativeJob.maxWidth = profile.MaxWidth;\r
762             nativeJob.maxHeight = profile.MaxHeight;\r
763 \r
764             switch (profile.VideoEncoder)\r
765             {\r
766                 case VideoEncoder.X264:\r
767                     nativeJob.vcodec = NativeConstants.HB_VCODEC_X264;\r
768                     break;\r
769                 case VideoEncoder.Theora:\r
770                     nativeJob.vcodec = NativeConstants.HB_VCODEC_THEORA;\r
771                     break;\r
772                 case VideoEncoder.FFMpeg:\r
773                     nativeJob.vcodec = NativeConstants.HB_VCODEC_FFMPEG;\r
774                     break;\r
775                 default:\r
776                     break;\r
777             }\r
778 \r
779             if (profile.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality)\r
780             {\r
781                 nativeJob.vquality = (float)profile.Quality;\r
782                 nativeJob.vbitrate = 0;\r
783             }\r
784             else if (profile.VideoEncodeRateType == VideoEncodeRateType.AverageBitrate)\r
785             {\r
786                 nativeJob.vquality = -1;\r
787                 nativeJob.vbitrate = profile.VideoBitrate;\r
788             }\r
789 \r
790             // vrate\r
791             // vrate_base\r
792             // vfr\r
793             // cfr\r
794             // areBframes\r
795             // color_matrix\r
796             List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);\r
797             \r
798             List<hb_audio_s> audioList = new List<hb_audio_s>();\r
799             int numTracks = 0;\r
800             foreach (AudioEncoding encoding in profile.AudioEncodings)\r
801             {\r
802                 if (encoding.InputNumber == 0)\r
803                 {\r
804                     // Add this encoding for all chosen tracks\r
805                     foreach (int chosenTrack in job.ChosenAudioTracks)\r
806                     {\r
807                         if (titleAudio.Count >= chosenTrack)\r
808                         {\r
809                             audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++));\r
810                         }\r
811                     }\r
812                 }\r
813                 else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)\r
814                 {\r
815                     // Add this encoding for the specified track, if it exists\r
816                     int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];\r
817                     audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++));\r
818                 }\r
819             }\r
820 \r
821             NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);\r
822             nativeJob.list_audio = nativeAudioList.ListPtr;\r
823             allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);\r
824 \r
825             List<hb_subtitle_s> subtitleList = new List<hb_subtitle_s>();\r
826 \r
827             if (job.Subtitles != null)\r
828             {\r
829                 if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)\r
830                 {\r
831                     List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);\r
832 \r
833                     foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles)\r
834                     {\r
835                         if (sourceSubtitle.TrackNumber == 0)\r
836                         {\r
837                             // Use subtitle search.\r
838                             nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0;\r
839                             nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0;\r
840 \r
841                             if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv)\r
842                             {\r
843                                 nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;\r
844                             }\r
845 \r
846                             nativeJob.indepth_scan = 1;\r
847                         }\r
848                         else\r
849                         {\r
850                             // Use specified subtitle.\r
851                             hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1];\r
852                             nativeSubtitle.config.force = sourceSubtitle.Forced ? 1 : 0;\r
853                             nativeSubtitle.config.default_track = sourceSubtitle.Default ? 1 : 0;\r
854 \r
855                             if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv && nativeSubtitle.format == hb_subtitle_s_subtype.PICTURESUB)\r
856                             {\r
857                                 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;\r
858                             }\r
859 \r
860                             subtitleList.Add(nativeSubtitle);\r
861                         }\r
862                     }\r
863                 }\r
864 \r
865                 if (job.Subtitles.SrtSubtitles != null)\r
866                 {\r
867                     foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles)\r
868                     {\r
869                         hb_subtitle_s nativeSubtitle = new hb_subtitle_s();\r
870                         nativeSubtitle.id = subtitleList.Count << 8 | 0xFF;\r
871                         nativeSubtitle.iso639_2 = srtSubtitle.LanguageCode;\r
872                         nativeSubtitle.lang = LanguageCodes.Decode(srtSubtitle.LanguageCode);\r
873                         nativeSubtitle.source = hb_subtitle_s_subsource.SRTSUB;\r
874                         nativeSubtitle.format = hb_subtitle_s_subtype.TEXTSUB;\r
875 \r
876                         nativeSubtitle.config.src_codeset = srtSubtitle.CharacterCode;\r
877                         nativeSubtitle.config.src_filename = srtSubtitle.FileName;\r
878                         nativeSubtitle.config.offset = srtSubtitle.Offset;\r
879                         nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;\r
880                         nativeSubtitle.config.default_track = srtSubtitle.Default ? 1 : 0;\r
881 \r
882                         subtitleList.Add(nativeSubtitle);\r
883                     }\r
884                 }\r
885             }\r
886 \r
887             NativeList nativeSubtitleList = InteropUtilities.ConvertListBack<hb_subtitle_s>(subtitleList);\r
888             nativeJob.list_subtitle = nativeSubtitleList.ListPtr;\r
889             allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);\r
890 \r
891             if (profile.OutputFormat == OutputFormat.Mp4)\r
892             {\r
893                 nativeJob.mux = NativeConstants.HB_MUX_MP4;\r
894             }\r
895             else\r
896             {\r
897                 nativeJob.mux = NativeConstants.HB_MUX_MKV;\r
898             }\r
899 \r
900             nativeJob.file = job.OutputPath;\r
901 \r
902             nativeJob.largeFileSize = profile.LargeFile ? 1 : 0;\r
903             nativeJob.mp4_optimize = profile.Optimize ? 1 : 0;\r
904             nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0;\r
905 \r
906             string x264Options = profile.X264Options ?? string.Empty;\r
907             if (profile.TwoPass)\r
908             {\r
909                 nativeJob.pass = 1;\r
910 \r
911                 if (profile.TurboFirstPass)\r
912                 {\r
913                     if (x264Options == string.Empty)\r
914                     {\r
915                         x264Options = TurboX264Opts;\r
916                     }\r
917                     else\r
918                     {\r
919                         x264Options += ":" + TurboX264Opts;\r
920                     }\r
921                 }\r
922             }\r
923 \r
924             nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Options);\r
925             allocatedMemory.Add(nativeJob.x264opts);\r
926 \r
927             // indepth_scan\r
928 \r
929             if (title.AngleCount > 1)\r
930             {\r
931                 nativeJob.angle = job.Angle;\r
932             }\r
933 \r
934             // frames_to_skip\r
935 \r
936             return allocatedMemory;\r
937         }\r
938 \r
939         /// <summary>\r
940         /// Adds a filter to the given filter list.\r
941         /// </summary>\r
942         /// <param name="filterList">The filter list to add to.</param>\r
943         /// <param name="filterType">The type of filter.</param>\r
944         /// <param name="settings">Settings for the filter.</param>\r
945         /// <param name="allocatedMemory">The list of allocated memory.</param>\r
946         private void AddFilter(List<IntPtr> filterList, int filterType, string settings, List<IntPtr> allocatedMemory)\r
947         {\r
948             IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings);\r
949             filterList.Add(HbLib.hb_get_filter_object(filterType, settingsNativeString));\r
950 \r
951             allocatedMemory.Add(settingsNativeString);\r
952         }\r
953 \r
954         /// <summary>\r
955         /// Gets the title, given the 1-based index.\r
956         /// </summary>\r
957         /// <param name="titleIndex">The index of the title (1-based).</param>\r
958         /// <returns>The requested Title.</returns>\r
959         private Title GetTitle(int titleIndex)\r
960         {\r
961             return this.Titles.SingleOrDefault(title => title.TitleNumber == titleIndex);\r
962         }\r
963 \r
964         /// <summary>\r
965         /// Gets the native title object from the title index.\r
966         /// </summary>\r
967         /// <param name="titleIndex">The index of the title (1-based).</param>\r
968         /// <returns>Gets the native title object for the given index.</returns>\r
969         private hb_title_s GetOriginalTitle(int titleIndex)\r
970         {\r
971             List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList();\r
972             if (matchingTitles.Count == 0)\r
973             {\r
974                 throw new ArgumentException("Could not find specified title.");\r
975             }\r
976 \r
977             if (matchingTitles.Count > 1)\r
978             {\r
979                 throw new ArgumentException("Multiple titles matched.");\r
980             }\r
981 \r
982             return matchingTitles[0];\r
983         }\r
984 \r
985         /// <summary>\r
986         /// Applies an audio encoding to a native audio encoding base structure.\r
987         /// </summary>\r
988         /// <param name="encoding">The encoding to apply.</param>\r
989         /// <param name="baseStruct">The base native structure.</param>\r
990         /// <param name="track"></param>\r
991         /// <param name="outputTrack"></param>\r
992         /// <returns>The resulting native audio structure.</returns>\r
993         private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int track, int outputTrack)\r
994         {\r
995             hb_audio_s nativeAudio = baseStruct;\r
996 \r
997             //nativeAudio.config.input.track = track;\r
998             nativeAudio.config.output.track = outputTrack;\r
999 \r
1000             switch (encoding.Encoder)\r
1001             {\r
1002                 case AudioEncoder.Ac3Passthrough:\r
1003                     nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_AC3;\r
1004                     break;\r
1005                 case AudioEncoder.DtsPassthrough:\r
1006                     nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_DCA;\r
1007                     break;\r
1008                 case AudioEncoder.Faac:\r
1009                     nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_FAAC;\r
1010                     break;\r
1011                 case AudioEncoder.Lame:\r
1012                     nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_LAME;\r
1013                     break;\r
1014                 case AudioEncoder.Vorbis:\r
1015                     nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_VORBIS;\r
1016                     break;\r
1017                 default:\r
1018                     break;\r
1019             }\r
1020 \r
1021             nativeAudio.config.output.bitrate = encoding.Bitrate;\r
1022             nativeAudio.config.output.dynamic_range_compression = 0.0;\r
1023 \r
1024             switch (encoding.Mixdown)\r
1025             {\r
1026                 case Mixdown.DolbyProLogicII:\r
1027                     nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBYPLII;\r
1028                     break;\r
1029                 case Mixdown.DolbySurround:\r
1030                     nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBY;\r
1031                     break;\r
1032                 case Mixdown.Mono:\r
1033                     nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_MONO;\r
1034                     break;\r
1035                 case Mixdown.SixChannelDiscrete:\r
1036                     nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_6CH;\r
1037                     break;\r
1038                 case Mixdown.Stereo:\r
1039                     nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_STEREO;\r
1040                     break;\r
1041                 default:\r
1042                     break;\r
1043             }\r
1044 \r
1045             if (encoding.SampleRate != null)\r
1046             {\r
1047                 nativeAudio.config.output.samplerate = (int)(double.Parse(encoding.SampleRate) * 1000);\r
1048             }\r
1049 \r
1050             nativeAudio.padding = new byte[24600];\r
1051 \r
1052             return nativeAudio;\r
1053         }\r
1054 \r
1055         /// <summary>\r
1056         /// Converts a native title to a Title object.\r
1057         /// </summary>\r
1058         /// <param name="title">The native title structure.</param>\r
1059         /// <returns>The managed Title object.</returns>\r
1060         private Title ConvertTitle(hb_title_s title)\r
1061         {\r
1062             var newTitle = new Title\r
1063             {\r
1064                 TitleNumber = title.index,\r
1065                 Resolution = new Size(title.width, title.height),\r
1066                 ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height),\r
1067                 Duration = TimeSpan.FromSeconds(((double)title.duration) / 90000),\r
1068                 AutoCropDimensions = new Cropping\r
1069                 {\r
1070                     Top = title.crop[0],\r
1071                     Bottom = title.crop[1],\r
1072                     Left = title.crop[2],\r
1073                     Right = title.crop[3]\r
1074                 },\r
1075                 AspectRatio = title.aspect,\r
1076                 AngleCount = title.angle_count\r
1077             };\r
1078 \r
1079             int currentSubtitleTrack = 1;\r
1080             List<hb_subtitle_s> subtitleList = InteropUtilities.ConvertList<hb_subtitle_s>(title.list_subtitle);\r
1081             foreach (hb_subtitle_s subtitle in subtitleList)\r
1082             {\r
1083                 var newSubtitle = new Subtitle\r
1084                 {\r
1085                     TrackNumber = currentSubtitleTrack,\r
1086                     Language = subtitle.lang,\r
1087                     LanguageCode = subtitle.iso639_2\r
1088                 };\r
1089 \r
1090                 if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB)\r
1091                 {\r
1092                     newSubtitle.SubtitleType = SubtitleType.Picture;\r
1093                 }\r
1094                 else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB)\r
1095                 {\r
1096                     newSubtitle.SubtitleType = SubtitleType.Text;\r
1097                 }\r
1098 \r
1099                 newTitle.Subtitles.Add(newSubtitle);\r
1100 \r
1101                 currentSubtitleTrack++;\r
1102             }\r
1103 \r
1104             int currentAudioTrack = 1;\r
1105             List<hb_audio_s> audioList = InteropUtilities.ConvertList<hb_audio_s>(title.list_audio);\r
1106             foreach (hb_audio_s audio in audioList)\r
1107             {\r
1108                 var newAudio = new AudioTrack\r
1109                 {\r
1110                     TrackNumber = currentAudioTrack,\r
1111                     Language = audio.config.lang.simple,\r
1112                     LanguageCode = audio.config.lang.iso639_2,\r
1113                     Description = audio.config.lang.description\r
1114                 };\r
1115 \r
1116                 newTitle.AudioTracks.Add(newAudio);\r
1117 \r
1118                 currentAudioTrack++;\r
1119             }\r
1120 \r
1121             List<hb_chapter_s> chapterList = InteropUtilities.ConvertList<hb_chapter_s>(title.list_chapter);\r
1122             foreach (hb_chapter_s chapter in chapterList)\r
1123             {\r
1124                 var newChapter = new Chapter\r
1125                 {\r
1126                     ChapterNumber = chapter.index,\r
1127                     Duration = TimeSpan.FromSeconds(((double)chapter.duration) / 90000)\r
1128                 };\r
1129 \r
1130                 newTitle.Chapters.Add(newChapter);\r
1131             }\r
1132 \r
1133             return newTitle;\r
1134         }\r
1135     }\r
1136 }\r