OSDN Git Service

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