OSDN Git Service

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