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