OSDN Git Service

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