1 using HandBrake.Interop.Model;
\r
2 using HandBrake.Interop.Model.Encoding;
\r
3 using HandBrake.Interop.SourceData;
\r
5 namespace HandBrake.Interop
\r
8 using System.Collections.Generic;
\r
11 using System.Runtime.InteropServices;
\r
12 using System.Windows.Media.Imaging;
\r
15 /// A wrapper for a HandBrake instance.
\r
17 public class HandBrakeInstance : IDisposable
\r
20 /// The number of MS between status polls when scanning.
\r
22 private const double ScanPollIntervalMs = 200;
\r
25 /// The number of MS between status polls when encoding.
\r
27 private const double EncodePollIntervalMs = 200;
\r
30 /// X264 options to add for a turbo first pass.
\r
32 private const string TurboX264Opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0";
\r
35 /// The native handle to the HandBrake instance.
\r
37 private IntPtr hbHandle;
\r
40 /// The timer to poll for scan status.
\r
42 private System.Timers.Timer scanPollTimer;
\r
45 /// The timer to poll for encode status.
\r
47 private System.Timers.Timer encodePollTimer;
\r
50 /// The list of original titles in native structure form.
\r
52 private List<hb_title_s> originalTitles;
\r
55 /// The list of titles on this instance.
\r
57 private List<Title> titles;
\r
60 /// A list of native memory locations allocated by this instance.
\r
62 private List<IntPtr> encodeAllocatedMemory;
\r
65 /// The callback for log messages from HandBrake.
\r
67 private static LoggingCallback loggingCallback;
\r
70 /// The callback for error messages from HandBrake.
\r
72 private static LoggingCallback errorCallback;
\r
75 /// Fires for progress updates when scanning.
\r
77 public event EventHandler<ScanProgressEventArgs> ScanProgress;
\r
80 /// Fires when a scan has completed.
\r
82 public event EventHandler<EventArgs> ScanCompleted;
\r
85 /// Fires for progress updates when encoding.
\r
87 public event EventHandler<EncodeProgressEventArgs> EncodeProgress;
\r
90 /// Fires when an encode has completed.
\r
92 public event EventHandler<EventArgs> EncodeCompleted;
\r
95 /// Fires when HandBrake has logged a message.
\r
97 public static event EventHandler<MessageLoggedEventArgs> MessageLogged;
\r
100 /// Fires when HandBrake has logged an error.
\r
102 public static event EventHandler<MessageLoggedEventArgs> ErrorLogged;
\r
107 ~HandBrakeInstance()
\r
109 this.Dispose(false);
\r
113 /// The list of titles on this instance.
\r
115 public List<Title> Titles
\r
119 return this.titles;
\r
124 /// Initializes this instance.
\r
126 /// <param name="verbosity"></param>
\r
127 public void Initialize(int verbosity)
\r
129 // Register the logger if we have not already
\r
130 if (loggingCallback == null)
\r
132 // Keep the callback as a member to prevent it from being garbage collected.
\r
133 loggingCallback = new LoggingCallback(HandBrakeInstance.LoggingHandler);
\r
134 errorCallback = new LoggingCallback(HandBrakeInstance.ErrorHandler);
\r
135 HbLib.hb_register_logger(loggingCallback);
\r
136 HbLib.hb_register_error_handler(errorCallback);
\r
139 this.hbHandle = HbLib.hb_init(verbosity, 0);
\r
143 /// Handles log messages from HandBrake.
\r
145 /// <param name="message">The log message (including newline).</param>
\r
146 public static void LoggingHandler(string message)
\r
148 if (!string.IsNullOrEmpty(message))
\r
150 string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
\r
152 if (messageParts.Length > 0)
\r
154 if (MessageLogged != null)
\r
156 MessageLogged(null, new MessageLoggedEventArgs { Message = messageParts[0] });
\r
159 System.Diagnostics.Debug.WriteLine(messageParts[0]);
\r
165 /// Handles errors from HandBrake.
\r
167 /// <param name="message">The error message.</param>
\r
168 public static void ErrorHandler(string message)
\r
170 if (!string.IsNullOrEmpty(message))
\r
172 if (ErrorLogged != null)
\r
174 ErrorLogged(null, new MessageLoggedEventArgs { Message = message });
\r
177 System.Diagnostics.Debug.WriteLine("ERROR: " + message);
\r
182 /// Starts scanning the given path.
\r
184 /// <param name="path">The path to the video to scan.</param>
\r
185 /// <param name="previewCount">The number of preview images to make.</param>
\r
186 public void StartScan(string path, int previewCount)
\r
188 this.StartScan(path, previewCount, 0);
\r
192 /// Starts a scan of the given path.
\r
194 /// <param name="path">The path of the video to scan.</param>
\r
195 /// <param name="previewCount">The number of previews to make on each title.</param>
\r
196 /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param>
\r
197 public void StartScan(string path, int previewCount, int titleIndex)
\r
199 HbLib.hb_scan(hbHandle, path, titleIndex, previewCount, 1);
\r
200 this.scanPollTimer = new System.Timers.Timer();
\r
201 this.scanPollTimer.Interval = ScanPollIntervalMs;
\r
203 // Lambda notation used to make sure we can view any JIT exceptions the method throws
\r
204 this.scanPollTimer.Elapsed += (o, e) =>
\r
206 this.PollScanProgress();
\r
208 this.scanPollTimer.Start();
\r
212 /// Gets an image for the given job and preview
\r
215 /// Only incorporates sizing and aspect ratio into preview image.
\r
217 /// <param name="job">The encode job to preview.</param>
\r
218 /// <param name="previewNumber">The index of the preview to get (0-based).</param>
\r
219 /// <returns>An image with the requested preview.</returns>
\r
220 public BitmapImage GetPreview(EncodeJob job, int previewNumber)
\r
222 hb_title_s title = this.GetOriginalTitle(job.Title);
\r
224 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
\r
225 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
227 // Create a new job pointer from our modified job object
\r
228 IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));
\r
229 Marshal.StructureToPtr(nativeJob, newJob, false);
\r
230 allocatedMemory.Add(newJob);
\r
232 int outputWidth = nativeJob.width;
\r
233 int outputHeight = nativeJob.height;
\r
234 int imageBufferSize = outputWidth * outputHeight * 4;
\r
235 IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);
\r
236 allocatedMemory.Add(nativeBuffer);
\r
237 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
238 HbLib.hb_get_preview_by_index(this.hbHandle, job.Title, previewNumber, nativeBuffer);
\r
240 // Copy the filled image buffer to a managed array.
\r
241 byte[] managedBuffer = new byte[imageBufferSize];
\r
242 Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);
\r
244 InteropUtilities.FreeMemory(allocatedMemory);
\r
246 System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight);
\r
247 System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, outputWidth, outputHeight), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
\r
249 IntPtr ptr = bitmapData.Scan0;
\r
251 for (int i = 0; i < nativeJob.height; i++)
\r
253 Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4);
\r
254 ptr = AddOffset(ptr, bitmapData.Stride);
\r
257 bitmap.UnlockBits(bitmapData);
\r
258 //bitmap.Save(@"d:\docs\test_" + previewNumber + ".png", System.Drawing.Imaging.ImageFormat.Png);
\r
260 using (MemoryStream memoryStream = new MemoryStream())
\r
262 bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
\r
265 BitmapImage wpfBitmap = new BitmapImage();
\r
266 wpfBitmap.BeginInit();
\r
267 wpfBitmap.CacheOption = BitmapCacheOption.OnLoad;
\r
268 wpfBitmap.StreamSource = memoryStream;
\r
269 wpfBitmap.EndInit();
\r
275 public static IntPtr AddOffset(IntPtr src, int offset)
\r
277 return new IntPtr(src.ToInt64() + offset);
\r
281 /// Starts an encode with the given job.
\r
283 /// <param name="job">The job to start.</param>
\r
284 public void StartEncode(EncodeJob job)
\r
286 this.StartEncode(job, false, 0, 0);
\r
290 /// Starts an encode with the given job.
\r
292 /// <param name="job">The job to start.</param>
\r
293 /// <param name="preview">True if this is a preview encode.</param>
\r
294 /// <param name="previewNumber">The preview number to start the encode at (0-based).</param>
\r
295 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
296 public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
298 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
299 this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);
\r
301 if (!preview && job.EncodingProfile.IncludeChapterMarkers)
\r
303 Title title = this.GetTitle(job.Title);
\r
304 int numChapters = title.Chapters.Count;
\r
306 if (job.UseDefaultChapterNames)
\r
308 for (int i = 0; i < numChapters; i++)
\r
310 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));
\r
315 for (int i = 0; i < numChapters; i++)
\r
317 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);
\r
322 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
324 if (job.EncodingProfile.TwoPass)
\r
326 nativeJob.pass = 2;
\r
328 string x264Opts = job.EncodingProfile.X264Options ?? string.Empty;
\r
329 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Opts);
\r
330 this.encodeAllocatedMemory.Add(nativeJob.x264opts);
\r
332 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
335 HbLib.hb_start(this.hbHandle);
\r
337 this.encodePollTimer = new System.Timers.Timer();
\r
338 this.encodePollTimer.Interval = EncodePollIntervalMs;
\r
340 this.encodePollTimer.Elapsed += (o, e) =>
\r
342 this.PollEncodeProgress();
\r
344 this.encodePollTimer.Start();
\r
348 /// Pauses the current encode.
\r
350 public void PauseEncode()
\r
352 HbLib.hb_pause(this.hbHandle);
\r
356 /// Resumes a paused encode.
\r
358 public void ResumeEncode()
\r
360 HbLib.hb_resume(this.hbHandle);
\r
364 /// Stops the current encode.
\r
366 public void StopEncode()
\r
368 HbLib.hb_stop(this.hbHandle);
\r
370 // Also remove all jobs from the queue (in case we stopped a 2-pass encode)
\r
371 var currentJobs = new List<IntPtr>();
\r
373 int jobs = HbLib.hb_count(this.hbHandle);
\r
374 for (int i = 0; i < jobs; i++)
\r
376 currentJobs.Add(HbLib.hb_job(this.hbHandle, 0));
\r
379 foreach (IntPtr job in currentJobs)
\r
381 HbLib.hb_rem(this.hbHandle, job);
\r
386 /// Gets the final size when using Anamorphic for a given encode job.
\r
388 /// <param name="job">The encode job to use.</param>
\r
389 /// <param name="width">The storage width.</param>
\r
390 /// <param name="height">The storage height.</param>
\r
391 /// <param name="parWidth">The pixel aspect X number.</param>
\r
392 /// <param name="parHeight">The pixel aspect Y number.</param>
\r
393 public void GetAnamorphicSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight)
\r
395 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
396 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
400 int refParWidth = 0;
\r
401 int refParHeight = 0;
\r
402 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
403 HbLib.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
404 //HbLib.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
405 InteropUtilities.FreeMemory(allocatedMemory);
\r
408 height = refHeight;
\r
409 parWidth = refParWidth;
\r
410 parHeight = refParHeight;
\r
414 /// Frees any resources associated with this object.
\r
416 public void Dispose()
\r
418 this.Dispose(true);
\r
419 GC.SuppressFinalize(this);
\r
423 /// Call before app shutdown. Performs global cleanup.
\r
425 public static void DisposeGlobal()
\r
427 HbLib.hb_global_close();
\r
431 /// Frees any resources associated with this object.
\r
433 /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param>
\r
434 protected virtual void Dispose(bool disposing)
\r
438 // Free other state (managed objects).
\r
441 // Free unmanaged objects.
\r
442 IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
\r
443 Marshal.WriteIntPtr(handlePtr, this.hbHandle);
\r
444 HbLib.hb_close(handlePtr);
\r
445 Marshal.FreeHGlobal(handlePtr);
\r
449 /// Checks the status of the ongoing scan.
\r
451 private void PollScanProgress()
\r
453 hb_state_s state = new hb_state_s();
\r
454 HbLib.hb_get_state(this.hbHandle, ref state);
\r
456 if (state.state == NativeConstants.HB_STATE_SCANNING)
\r
458 if (this.ScanProgress != null)
\r
460 int currentTitle = state.param.scanning.title_cur;
\r
461 int totalTitles = state.param.scanning.title_count;
\r
462 this.ScanProgress(this, new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = totalTitles });
\r
465 else if (state.state == NativeConstants.HB_STATE_SCANDONE)
\r
467 this.titles = new List<Title>();
\r
469 IntPtr listPtr = HbLib.hb_get_titles(this.hbHandle);
\r
470 this.originalTitles = InteropUtilities.ConvertList<hb_title_s>(listPtr);
\r
472 foreach (hb_title_s title in this.originalTitles)
\r
474 var newTitle = this.ConvertTitle(title);
\r
475 this.titles.Add(newTitle);
\r
478 this.scanPollTimer.Stop();
\r
480 if (this.ScanCompleted != null)
\r
482 this.ScanCompleted(this, new EventArgs());
\r
488 /// Checks the status of the ongoing encode.
\r
490 private void PollEncodeProgress()
\r
492 hb_state_s state = new hb_state_s();
\r
493 HbLib.hb_get_state(this.hbHandle, ref state);
\r
495 if (state.state == NativeConstants.HB_STATE_WORKING)
\r
497 if (this.EncodeProgress != null)
\r
499 var progressEventArgs = new EncodeProgressEventArgs
\r
501 FractionComplete = state.param.working.progress,
\r
502 CurrentFrameRate = state.param.working.rate_cur,
\r
503 AverageFrameRate = state.param.working.rate_avg,
\r
504 EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds),
\r
505 Pass = state.param.working.job_cur
\r
508 this.EncodeProgress(this, progressEventArgs);
\r
511 else if (state.state == NativeConstants.HB_STATE_MUXING)
\r
513 //System.Diagnostics.Debug.WriteLine("Muxing...");
\r
515 else if (state.state == NativeConstants.HB_STATE_WORKDONE)
\r
517 InteropUtilities.FreeMemory(this.encodeAllocatedMemory);
\r
518 this.encodePollTimer.Stop();
\r
520 if (this.EncodeCompleted != null)
\r
522 this.EncodeCompleted(this, new EventArgs());
\r
528 /// Applies the encoding job to the native memory structure and returns a list of memory
\r
529 /// locations allocated during this.
\r
531 /// <param name="nativeJob">The native structure to apply to job info to.</param>
\r
532 /// <param name="job">The job info to apply.</param>
\r
533 /// <param name="preview">True if this is a preview encode.</param>
\r
534 /// <param name="previewNumber">The preview number (0-based) to encode.</param>
\r
535 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
536 /// <returns>The list of memory locations allocated for the job.</returns>
\r
537 private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
539 var allocatedMemory = new List<IntPtr>();
\r
540 Title title = this.GetTitle(job.Title);
\r
541 hb_title_s originalTitle = this.GetOriginalTitle(job.Title);
\r
543 EncodingProfile profile = job.EncodingProfile;
\r
547 nativeJob.start_at_preview = previewNumber + 1;
\r
548 nativeJob.seek_points = 10;
\r
550 // There are 90,000 PTS per second.
\r
551 nativeJob.pts_to_stop = previewSeconds * 90000;
\r
553 else if (job.ChapterStart > 0 && job.ChapterEnd > 0)
\r
555 nativeJob.chapter_start = job.ChapterStart;
\r
556 nativeJob.chapter_end = job.ChapterEnd;
\r
560 nativeJob.chapter_start = 1;
\r
561 nativeJob.chapter_end = title.Chapters.Count;
\r
564 nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;
\r
568 if (profile.CustomCropping)
\r
570 crop = profile.Cropping;
\r
574 crop = title.AutoCropDimensions;
\r
577 nativeJob.crop[0] = crop.Top;
\r
578 nativeJob.crop[1] = crop.Bottom;
\r
579 nativeJob.crop[2] = crop.Left;
\r
580 nativeJob.crop[3] = crop.Right;
\r
582 List<IntPtr> filterList = new List<IntPtr>();
\r
583 if (profile.Deinterlace != Deinterlace.Off)
\r
585 nativeJob.deinterlace = 1;
\r
586 string settings = null;
\r
588 switch (profile.Deinterlace)
\r
590 case Deinterlace.Fast:
\r
593 case Deinterlace.Slow:
\r
596 case Deinterlace.Slower:
\r
599 case Deinterlace.Custom:
\r
600 settings = profile.CustomDeinterlace;
\r
606 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);
\r
607 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEINTERLACE, settings));
\r
611 nativeJob.deinterlace = 0;
\r
614 if (profile.Detelecine != Detelecine.Off)
\r
616 string settings = null;
\r
617 if (profile.Detelecine == Detelecine.Custom)
\r
619 settings = profile.CustomDetelecine;
\r
622 this.AddFilter(filterList, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);
\r
623 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DETELECINE, settings));
\r
626 if (profile.Decomb != Decomb.Off)
\r
628 string settings = null;
\r
629 if (profile.Decomb == Decomb.Custom)
\r
631 settings = profile.CustomDecomb;
\r
634 this.AddFilter(filterList, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);
\r
635 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DECOMB, settings));
\r
638 if (profile.Deblock > 0)
\r
640 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);
\r
641 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString()));
\r
644 if (profile.Denoise != Denoise.Off)
\r
646 string settings = null;
\r
647 switch (profile.Denoise)
\r
650 settings = "2:1:2:3";
\r
652 case Denoise.Medium:
\r
653 settings = "3:2:2:3";
\r
655 case Denoise.Strong:
\r
656 settings = "7:7:5:5";
\r
658 case Denoise.Custom:
\r
659 settings = profile.CustomDenoise;
\r
665 this.AddFilter(filterList, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);
\r
666 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DENOISE, settings));
\r
669 NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterList);
\r
670 nativeJob.filters = filterListNative.ListPtr;
\r
671 allocatedMemory.AddRange(filterListNative.AllocatedMemory);
\r
673 int width = profile.Width;
\r
674 int height = profile.Height;
\r
678 width = title.Resolution.Width;
\r
681 if (profile.MaxWidth > 0 && width > profile.MaxWidth)
\r
683 width = profile.MaxWidth;
\r
688 height = title.Resolution.Height;
\r
691 if (profile.MaxHeight > 0 && height > profile.MaxHeight)
\r
693 height = profile.MaxHeight;
\r
696 nativeJob.grayscale = profile.Grayscale ? 1 : 0;
\r
698 switch (profile.Anamorphic)
\r
700 case Anamorphic.None:
\r
701 nativeJob.anamorphic.mode = 0;
\r
703 if (profile.KeepDisplayAspect)
\r
705 if (profile.Width == 0 && profile.Height == 0 || profile.Width == 0)
\r
707 width = (int)((double)height * this.GetTitle(job.Title).AspectRatio);
\r
709 else if (profile.Height == 0)
\r
711 height = (int)((double)width / this.GetTitle(job.Title).AspectRatio);
\r
715 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
717 case Anamorphic.Strict:
\r
718 nativeJob.anamorphic.mode = 1;
\r
720 case Anamorphic.Loose:
\r
721 nativeJob.anamorphic.mode = 2;
\r
723 case Anamorphic.Custom:
\r
724 nativeJob.anamorphic.mode = 3;
\r
726 nativeJob.modulus = profile.Modulus;
\r
728 if (profile.UseDisplayWidth)
\r
730 if (profile.KeepDisplayAspect)
\r
732 height = (int)((double)profile.DisplayWidth / this.GetTitle(job.Title).AspectRatio);
\r
735 nativeJob.anamorphic.dar_width = profile.DisplayWidth;
\r
736 nativeJob.anamorphic.dar_height = height;
\r
737 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
741 nativeJob.anamorphic.par_width = profile.PixelAspectX;
\r
742 nativeJob.anamorphic.par_height = profile.PixelAspectY;
\r
743 nativeJob.anamorphic.keep_display_aspect = 0;
\r
750 nativeJob.width = width;
\r
751 nativeJob.height = height;
\r
753 nativeJob.maxWidth = profile.MaxWidth;
\r
754 nativeJob.maxHeight = profile.MaxHeight;
\r
756 switch (profile.VideoEncoder)
\r
758 case VideoEncoder.X264:
\r
759 nativeJob.vcodec = NativeConstants.HB_VCODEC_X264;
\r
761 case VideoEncoder.Theora:
\r
762 nativeJob.vcodec = NativeConstants.HB_VCODEC_THEORA;
\r
764 case VideoEncoder.FFMpeg:
\r
765 nativeJob.vcodec = NativeConstants.HB_VCODEC_FFMPEG;
\r
771 if (profile.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality)
\r
773 nativeJob.vquality = (float)profile.Quality;
\r
774 nativeJob.vbitrate = 0;
\r
776 else if (profile.VideoEncodeRateType == VideoEncodeRateType.AverageBitrate)
\r
778 nativeJob.vquality = -1;
\r
779 nativeJob.vbitrate = profile.VideoBitrate;
\r
788 List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);
\r
790 List<hb_audio_s> audioList = new List<hb_audio_s>();
\r
792 foreach (AudioEncoding encoding in profile.AudioEncodings)
\r
794 if (encoding.InputNumber == 0)
\r
796 // Add this encoding for all chosen tracks
\r
797 foreach (int chosenTrack in job.ChosenAudioTracks)
\r
799 if (titleAudio.Count >= chosenTrack)
\r
801 audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++));
\r
805 else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
\r
807 // Add this encoding for the specified track, if it exists
\r
808 int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
\r
809 audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++));
\r
813 NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);
\r
814 nativeJob.list_audio = nativeAudioList.ListPtr;
\r
815 allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);
\r
817 List<hb_subtitle_s> subtitleList = new List<hb_subtitle_s>();
\r
819 if (job.Subtitles != null)
\r
821 if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)
\r
823 List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);
\r
825 foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles)
\r
827 if (sourceSubtitle.TrackNumber == 0)
\r
829 // Use subtitle search.
\r
830 nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0;
\r
831 nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
833 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv)
\r
835 nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
838 nativeJob.indepth_scan = 1;
\r
842 // Use specified subtitle.
\r
843 hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1];
\r
844 nativeSubtitle.config.force = sourceSubtitle.Forced ? 1 : 0;
\r
845 nativeSubtitle.config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
847 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv && nativeSubtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
849 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
852 subtitleList.Add(nativeSubtitle);
\r
857 if (job.Subtitles.SrtSubtitles != null)
\r
859 foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles)
\r
861 hb_subtitle_s nativeSubtitle = new hb_subtitle_s();
\r
862 nativeSubtitle.id = subtitleList.Count << 8 | 0xFF;
\r
863 nativeSubtitle.iso639_2 = srtSubtitle.LanguageCode;
\r
864 nativeSubtitle.lang = LanguageCodes.Decode(srtSubtitle.LanguageCode);
\r
865 nativeSubtitle.source = hb_subtitle_s_subsource.SRTSUB;
\r
866 nativeSubtitle.format = hb_subtitle_s_subtype.TEXTSUB;
\r
868 nativeSubtitle.config.src_codeset = srtSubtitle.CharacterCode;
\r
869 nativeSubtitle.config.src_filename = srtSubtitle.FileName;
\r
870 nativeSubtitle.config.offset = srtSubtitle.Offset;
\r
871 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
872 nativeSubtitle.config.default_track = srtSubtitle.Default ? 1 : 0;
\r
874 subtitleList.Add(nativeSubtitle);
\r
879 NativeList nativeSubtitleList = InteropUtilities.ConvertListBack<hb_subtitle_s>(subtitleList);
\r
880 nativeJob.list_subtitle = nativeSubtitleList.ListPtr;
\r
881 allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);
\r
883 if (profile.OutputFormat == OutputFormat.Mp4)
\r
885 nativeJob.mux = NativeConstants.HB_MUX_MP4;
\r
889 nativeJob.mux = NativeConstants.HB_MUX_MKV;
\r
892 nativeJob.file = job.OutputPath;
\r
894 nativeJob.largeFileSize = profile.LargeFile ? 1 : 0;
\r
895 nativeJob.mp4_optimize = profile.Optimize ? 1 : 0;
\r
896 nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0;
\r
898 string x264Options = profile.X264Options ?? string.Empty;
\r
899 if (profile.TwoPass)
\r
901 nativeJob.pass = 1;
\r
903 if (profile.TurboFirstPass)
\r
905 if (x264Options == string.Empty)
\r
907 x264Options = TurboX264Opts;
\r
911 x264Options += ":" + TurboX264Opts;
\r
916 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Options);
\r
917 allocatedMemory.Add(nativeJob.x264opts);
\r
921 if (title.AngleCount > 1)
\r
923 nativeJob.angle = job.Angle;
\r
928 return allocatedMemory;
\r
932 /// Adds a filter to the given filter list.
\r
934 /// <param name="filterList">The filter list to add to.</param>
\r
935 /// <param name="filterType">The type of filter.</param>
\r
936 /// <param name="settings">Settings for the filter.</param>
\r
937 /// <param name="allocatedMemory">The list of allocated memory.</param>
\r
938 private void AddFilter(List<IntPtr> filterList, int filterType, string settings, List<IntPtr> allocatedMemory)
\r
940 IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings);
\r
941 filterList.Add(HbLib.hb_get_filter_object(filterType, settingsNativeString));
\r
943 allocatedMemory.Add(settingsNativeString);
\r
947 /// Gets the title, given the 1-based index.
\r
949 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
950 /// <returns>The requested Title.</returns>
\r
951 private Title GetTitle(int titleIndex)
\r
953 return this.Titles.SingleOrDefault(title => title.TitleNumber == titleIndex);
\r
957 /// Gets the native title object from the title index.
\r
959 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
960 /// <returns>Gets the native title object for the given index.</returns>
\r
961 private hb_title_s GetOriginalTitle(int titleIndex)
\r
963 List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList();
\r
964 if (matchingTitles.Count == 0)
\r
966 throw new ArgumentException("Could not find specified title.");
\r
969 if (matchingTitles.Count > 1)
\r
971 throw new ArgumentException("Multiple titles matched.");
\r
974 return matchingTitles[0];
\r
978 /// Applies an audio encoding to a native audio encoding base structure.
\r
980 /// <param name="encoding">The encoding to apply.</param>
\r
981 /// <param name="baseStruct">The base native structure.</param>
\r
982 /// <param name="track"></param>
\r
983 /// <param name="outputTrack"></param>
\r
984 /// <returns>The resulting native audio structure.</returns>
\r
985 private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int track, int outputTrack)
\r
987 hb_audio_s nativeAudio = baseStruct;
\r
989 //nativeAudio.config.input.track = track;
\r
990 nativeAudio.config.output.track = outputTrack;
\r
992 switch (encoding.Encoder)
\r
994 case AudioEncoder.Ac3Passthrough:
\r
995 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_AC3;
\r
997 case AudioEncoder.DtsPassthrough:
\r
998 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_DCA;
\r
1000 case AudioEncoder.Faac:
\r
1001 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_FAAC;
\r
1003 case AudioEncoder.Lame:
\r
1004 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_LAME;
\r
1006 case AudioEncoder.Vorbis:
\r
1007 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_VORBIS;
\r
1013 nativeAudio.config.output.bitrate = encoding.Bitrate;
\r
1014 nativeAudio.config.output.dynamic_range_compression = 0.0;
\r
1016 switch (encoding.Mixdown)
\r
1018 case Mixdown.DolbyProLogicII:
\r
1019 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBYPLII;
\r
1021 case Mixdown.DolbySurround:
\r
1022 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBY;
\r
1024 case Mixdown.Mono:
\r
1025 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_MONO;
\r
1027 case Mixdown.SixChannelDiscrete:
\r
1028 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_6CH;
\r
1030 case Mixdown.Stereo:
\r
1031 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_STEREO;
\r
1037 if (encoding.SampleRate != null)
\r
1039 nativeAudio.config.output.samplerate = (int)(double.Parse(encoding.SampleRate) * 1000);
\r
1042 nativeAudio.padding = new byte[24600];
\r
1044 return nativeAudio;
\r
1048 /// Converts a native title to a Title object.
\r
1050 /// <param name="title">The native title structure.</param>
\r
1051 /// <returns>The managed Title object.</returns>
\r
1052 private Title ConvertTitle(hb_title_s title)
\r
1054 var newTitle = new Title
\r
1056 TitleNumber = title.index,
\r
1057 Resolution = new Size(title.width, title.height),
\r
1058 ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height),
\r
1059 Duration = TimeSpan.FromSeconds(((double)title.duration) / 90000),
\r
1060 AutoCropDimensions = new Cropping
\r
1062 Top = title.crop[0],
\r
1063 Bottom = title.crop[1],
\r
1064 Left = title.crop[2],
\r
1065 Right = title.crop[3]
\r
1067 AspectRatio = title.aspect,
\r
1068 AngleCount = title.angle_count
\r
1071 int currentSubtitleTrack = 1;
\r
1072 List<hb_subtitle_s> subtitleList = InteropUtilities.ConvertList<hb_subtitle_s>(title.list_subtitle);
\r
1073 foreach (hb_subtitle_s subtitle in subtitleList)
\r
1075 var newSubtitle = new Subtitle
\r
1077 TrackNumber = currentSubtitleTrack,
\r
1078 Language = subtitle.lang,
\r
1079 LanguageCode = subtitle.iso639_2
\r
1082 if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
1084 newSubtitle.SubtitleType = SubtitleType.Picture;
\r
1086 else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB)
\r
1088 newSubtitle.SubtitleType = SubtitleType.Text;
\r
1091 newTitle.Subtitles.Add(newSubtitle);
\r
1093 currentSubtitleTrack++;
\r
1096 int currentAudioTrack = 1;
\r
1097 List<hb_audio_s> audioList = InteropUtilities.ConvertList<hb_audio_s>(title.list_audio);
\r
1098 foreach (hb_audio_s audio in audioList)
\r
1100 var newAudio = new AudioTrack
\r
1102 TrackNumber = currentAudioTrack,
\r
1103 Language = audio.config.lang.simple,
\r
1104 LanguageCode = audio.config.lang.iso639_2,
\r
1105 Description = audio.config.lang.description
\r
1108 newTitle.AudioTracks.Add(newAudio);
\r
1110 currentAudioTrack++;
\r
1113 List<hb_chapter_s> chapterList = InteropUtilities.ConvertList<hb_chapter_s>(title.list_chapter);
\r
1114 foreach (hb_chapter_s chapter in chapterList)
\r
1116 var newChapter = new Chapter
\r
1118 ChapterNumber = chapter.index,
\r
1119 Duration = TimeSpan.FromSeconds(((double)chapter.duration) / 90000)
\r
1122 newTitle.Chapters.Add(newChapter);
\r