1 namespace HandBrake.Interop
\r
4 using System.Collections.Generic;
\r
7 using System.Runtime.InteropServices;
\r
10 using System.Threading;
\r
11 using System.Windows.Media.Imaging;
\r
12 using HandBrake.SourceData;
\r
13 using HandBrake.Interop;
\r
16 /// A wrapper for a HandBrake instance.
\r
18 public class HandBrakeInstance : IDisposable
\r
21 /// The number of MS between status polls when scanning.
\r
23 private const double ScanPollIntervalMs = 200;
\r
26 /// The number of MS between status polls when encoding.
\r
28 private const double EncodePollIntervalMs = 200;
\r
31 /// X264 options to add for a turbo first pass.
\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
36 /// The native handle to the HandBrake instance.
\r
38 private IntPtr hbHandle;
\r
41 /// The timer to poll for scan status.
\r
43 private System.Timers.Timer scanPollTimer;
\r
46 /// The timer to poll for encode status.
\r
48 private System.Timers.Timer encodePollTimer;
\r
51 /// The list of original titles in native structure form.
\r
53 private List<hb_title_s> originalTitles;
\r
56 /// The list of titles on this instance.
\r
58 private List<Title> titles;
\r
61 /// A list of native memory locations allocated by this instance.
\r
63 private List<IntPtr> encodeAllocatedMemory;
\r
66 /// The callback for log messages from HandBrake.
\r
68 private static LoggingCallback loggingCallback;
\r
71 /// The callback for error messages from HandBrake.
\r
73 private static LoggingCallback errorCallback;
\r
76 /// Fires for progress updates when scanning.
\r
78 public event EventHandler<ScanProgressEventArgs> ScanProgress;
\r
81 /// Fires when a scan has completed.
\r
83 public event EventHandler<EventArgs> ScanCompleted;
\r
86 /// Fires for progress updates when encoding.
\r
88 public event EventHandler<EncodeProgressEventArgs> EncodeProgress;
\r
91 /// Fires when an encode has completed.
\r
93 public event EventHandler<EventArgs> EncodeCompleted;
\r
96 /// Fires when HandBrake has logged a message.
\r
98 public static event EventHandler<MessageLoggedEventArgs> MessageLogged;
\r
101 /// Fires when HandBrake has logged an error.
\r
103 public static event EventHandler<MessageLoggedEventArgs> ErrorLogged;
\r
108 ~HandBrakeInstance()
\r
110 this.Dispose(false);
\r
114 /// The list of titles on this instance.
\r
116 public List<Title> Titles
\r
120 return this.titles;
\r
125 /// Initializes this instance.
\r
127 /// <param name="verbosity"></param>
\r
128 public void Initialize(int verbosity)
\r
130 // Register the logger if we have not already
\r
131 if (loggingCallback == null)
\r
133 // Keep the callback as a member to prevent it from being garbage collected.
\r
134 loggingCallback = new LoggingCallback(HandBrakeInstance.LoggingHandler);
\r
135 errorCallback = new LoggingCallback(HandBrakeInstance.ErrorHandler);
\r
136 HbLib.hb_register_logger(loggingCallback);
\r
137 HbLib.hb_register_error_handler(errorCallback);
\r
140 this.hbHandle = HbLib.hb_init(verbosity, update_check: 0);
\r
144 /// Handles log messages from HandBrake.
\r
146 /// <param name="message">The log message (including newline).</param>
\r
147 public static void LoggingHandler(string message)
\r
149 if (!string.IsNullOrEmpty(message))
\r
151 string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
\r
153 if (messageParts.Length > 0)
\r
155 if (MessageLogged != null)
\r
157 MessageLogged(null, new MessageLoggedEventArgs { Message = messageParts[0] });
\r
160 System.Diagnostics.Debug.WriteLine(messageParts[0]);
\r
166 /// Handles errors from HandBrake.
\r
168 /// <param name="message">The error message.</param>
\r
169 public static void ErrorHandler(string message)
\r
171 if (!string.IsNullOrEmpty(message))
\r
173 if (ErrorLogged != null)
\r
175 ErrorLogged(null, new MessageLoggedEventArgs { Message = message });
\r
178 System.Diagnostics.Debug.WriteLine("ERROR: " + message);
\r
183 /// Starts scanning the given path.
\r
185 /// <param name="path">The path to the video to scan.</param>
\r
186 /// <param name="previewCount">The number of preview images to make.</param>
\r
187 public void StartScan(string path, int previewCount)
\r
189 this.StartScan(path, previewCount, 0);
\r
193 /// Starts a scan of the given path.
\r
195 /// <param name="path">The path of the video to scan.</param>
\r
196 /// <param name="previewCount">The number of previews to make on each title.</param>
\r
197 /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param>
\r
198 public void StartScan(string path, int previewCount, int titleIndex)
\r
200 HbLib.hb_scan(hbHandle, path, titleIndex, previewCount, 1);
\r
201 this.scanPollTimer = new System.Timers.Timer();
\r
202 this.scanPollTimer.Interval = ScanPollIntervalMs;
\r
204 // Lambda notation used to make sure we can view any JIT exceptions the method throws
\r
205 this.scanPollTimer.Elapsed += (o, e) =>
\r
207 this.PollScanProgress();
\r
209 this.scanPollTimer.Start();
\r
213 /// Gets an image for the given job and preview
\r
216 /// Only incorporates sizing and aspect ratio into preview image.
\r
218 /// <param name="job">The encode job to preview.</param>
\r
219 /// <param name="previewNumber">The index of the preview to get (0-based).</param>
\r
220 /// <returns>An image with the requested preview.</returns>
\r
221 public BitmapImage GetPreview(EncodeJob job, int previewNumber)
\r
223 hb_title_s title = this.GetOriginalTitle(job.Title);
\r
225 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
\r
226 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
228 // Create a new job pointer from our modified job object
\r
229 IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));
\r
230 Marshal.StructureToPtr(nativeJob, newJob, false);
\r
231 allocatedMemory.Add(newJob);
\r
233 int outputWidth = nativeJob.width;
\r
234 int outputHeight = nativeJob.height;
\r
235 int imageBufferSize = outputWidth * outputHeight * 4;
\r
236 IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);
\r
237 allocatedMemory.Add(nativeBuffer);
\r
238 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
239 HbLib.hb_get_preview_by_index(this.hbHandle, job.Title, previewNumber, nativeBuffer);
\r
241 // Copy the filled image buffer to a managed array.
\r
242 byte[] managedBuffer = new byte[imageBufferSize];
\r
243 Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);
\r
245 InteropUtilities.FreeMemory(allocatedMemory);
\r
247 System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight);
\r
248 System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, outputWidth, outputHeight), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
\r
250 IntPtr ptr = bitmapData.Scan0;
\r
252 for (int i = 0; i < nativeJob.height; i++)
\r
254 Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4);
\r
255 ptr = IntPtr.Add(ptr, bitmapData.Stride);
\r
258 bitmap.UnlockBits(bitmapData);
\r
259 //bitmap.Save(@"d:\docs\test_" + previewNumber + ".png", System.Drawing.Imaging.ImageFormat.Png);
\r
261 using (MemoryStream memoryStream = new MemoryStream())
\r
263 bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
\r
266 BitmapImage wpfBitmap = new BitmapImage();
\r
267 wpfBitmap.BeginInit();
\r
268 wpfBitmap.CacheOption = BitmapCacheOption.OnLoad;
\r
269 wpfBitmap.StreamSource = memoryStream;
\r
270 wpfBitmap.EndInit();
\r
277 /// Starts an encode with the given job.
\r
279 /// <param name="job">The job to start.</param>
\r
280 public void StartEncode(EncodeJob job)
\r
282 this.StartEncode(job, false, 0, 0);
\r
286 /// Starts an encode with the given job.
\r
288 /// <param name="job">The job to start.</param>
\r
289 /// <param name="preview">True if this is a preview encode.</param>
\r
290 /// <param name="previewNumber">The preview number to start the encode at (0-based).</param>
\r
291 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
292 public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
294 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
295 this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);
\r
297 if (!preview && job.EncodingProfile.IncludeChapterMarkers)
\r
299 Title title = this.GetTitle(job.Title);
\r
300 int numChapters = title.Chapters.Count;
\r
302 if (job.UseDefaultChapterNames)
\r
304 for (int i = 0; i < numChapters; i++)
\r
306 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));
\r
311 for (int i = 0; i < numChapters; i++)
\r
313 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);
\r
318 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
320 if (job.EncodingProfile.TwoPass)
\r
322 nativeJob.pass = 2;
\r
324 string x264Opts = job.EncodingProfile.X264Options ?? string.Empty;
\r
325 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Opts);
\r
326 this.encodeAllocatedMemory.Add(nativeJob.x264opts);
\r
328 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
331 HbLib.hb_start(this.hbHandle);
\r
333 this.encodePollTimer = new System.Timers.Timer();
\r
334 this.encodePollTimer.Interval = EncodePollIntervalMs;
\r
336 this.encodePollTimer.Elapsed += (o, e) =>
\r
338 this.PollEncodeProgress();
\r
340 this.encodePollTimer.Start();
\r
344 /// Pauses the current encode.
\r
346 public void PauseEncode()
\r
348 HbLib.hb_pause(this.hbHandle);
\r
352 /// Resumes a paused encode.
\r
354 public void ResumeEncode()
\r
356 HbLib.hb_resume(this.hbHandle);
\r
360 /// Stops the current encode.
\r
362 public void StopEncode()
\r
364 HbLib.hb_stop(this.hbHandle);
\r
366 // Also remove all jobs from the queue (in case we stopped a 2-pass encode)
\r
367 var currentJobs = new List<IntPtr>();
\r
369 int jobs = HbLib.hb_count(this.hbHandle);
\r
370 for (int i = 0; i < jobs; i++)
\r
372 currentJobs.Add(HbLib.hb_job(this.hbHandle, 0));
\r
375 foreach (IntPtr job in currentJobs)
\r
377 HbLib.hb_rem(this.hbHandle, job);
\r
382 /// Gets the final size when using Anamorphic for a given encode job.
\r
384 /// <param name="job">The encode job to use.</param>
\r
385 /// <param name="width">The storage width.</param>
\r
386 /// <param name="height">The storage height.</param>
\r
387 /// <param name="parWidth">The pixel aspect X number.</param>
\r
388 /// <param name="parHeight">The pixel aspect Y number.</param>
\r
389 public void GetAnamorphicSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight)
\r
391 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
392 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
396 int refParWidth = 0;
\r
397 int refParHeight = 0;
\r
398 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
399 HbLib.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
400 //HbLib.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
401 InteropUtilities.FreeMemory(allocatedMemory);
\r
404 height = refHeight;
\r
405 parWidth = refParWidth;
\r
406 parHeight = refParHeight;
\r
410 /// Frees any resources associated with this object.
\r
412 public void Dispose()
\r
414 this.Dispose(true);
\r
415 GC.SuppressFinalize(this);
\r
419 /// Call before app shutdown. Performs global cleanup.
\r
421 public static void DisposeGlobal()
\r
423 HbLib.hb_global_close();
\r
427 /// Frees any resources associated with this object.
\r
429 /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param>
\r
430 protected virtual void Dispose(bool disposing)
\r
434 // Free other state (managed objects).
\r
437 // Free unmanaged objects.
\r
438 IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
\r
439 Marshal.WriteIntPtr(handlePtr, this.hbHandle);
\r
440 HbLib.hb_close(handlePtr);
\r
441 Marshal.FreeHGlobal(handlePtr);
\r
445 /// Checks the status of the ongoing scan.
\r
447 private void PollScanProgress()
\r
449 hb_state_s state = new hb_state_s();
\r
450 HbLib.hb_get_state(this.hbHandle, ref state);
\r
452 if (state.state == NativeConstants.HB_STATE_SCANNING)
\r
454 if (this.ScanProgress != null)
\r
456 int currentTitle = state.param.scanning.title_cur;
\r
457 int totalTitles = state.param.scanning.title_count;
\r
458 this.ScanProgress(this, new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = totalTitles });
\r
461 else if (state.state == NativeConstants.HB_STATE_SCANDONE)
\r
463 this.titles = new List<Title>();
\r
465 IntPtr listPtr = HbLib.hb_get_titles(this.hbHandle);
\r
466 this.originalTitles = InteropUtilities.ConvertList<hb_title_s>(listPtr);
\r
468 foreach (hb_title_s title in this.originalTitles)
\r
470 var newTitle = this.ConvertTitle(title);
\r
471 this.titles.Add(newTitle);
\r
474 this.scanPollTimer.Stop();
\r
476 if (this.ScanCompleted != null)
\r
478 this.ScanCompleted(this, new EventArgs());
\r
484 /// Checks the status of the ongoing encode.
\r
486 private void PollEncodeProgress()
\r
488 hb_state_s state = new hb_state_s();
\r
489 HbLib.hb_get_state(this.hbHandle, ref state);
\r
491 if (state.state == NativeConstants.HB_STATE_WORKING)
\r
493 if (this.EncodeProgress != null)
\r
495 var progressEventArgs = new EncodeProgressEventArgs
\r
497 FractionComplete = state.param.working.progress,
\r
498 CurrentFrameRate = state.param.working.rate_cur,
\r
499 AverageFrameRate = state.param.working.rate_avg,
\r
500 EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds),
\r
501 Pass = state.param.working.job_cur
\r
504 this.EncodeProgress(this, progressEventArgs);
\r
507 else if (state.state == NativeConstants.HB_STATE_MUXING)
\r
509 //System.Diagnostics.Debug.WriteLine("Muxing...");
\r
511 else if (state.state == NativeConstants.HB_STATE_WORKDONE)
\r
513 InteropUtilities.FreeMemory(this.encodeAllocatedMemory);
\r
514 this.encodePollTimer.Stop();
\r
516 if (this.EncodeCompleted != null)
\r
518 this.EncodeCompleted(this, new EventArgs());
\r
524 /// Applies the encoding job to the native memory structure and returns a list of memory
\r
525 /// locations allocated during this.
\r
527 /// <param name="nativeJob">The native structure to apply to job info to.</param>
\r
528 /// <param name="job">The job info to apply.</param>
\r
529 /// <param name="preview">True if this is a preview encode.</param>
\r
530 /// <param name="previewNumber">The preview number (0-based) to encode.</param>
\r
531 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
532 /// <returns>The list of memory locations allocated for the job.</returns>
\r
533 private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
535 var allocatedMemory = new List<IntPtr>();
\r
536 Title title = this.GetTitle(job.Title);
\r
537 hb_title_s originalTitle = this.GetOriginalTitle(job.Title);
\r
539 EncodingProfile profile = job.EncodingProfile;
\r
543 nativeJob.start_at_preview = previewNumber + 1;
\r
544 nativeJob.seek_points = 10;
\r
546 // There are 90,000 PTS per second.
\r
547 nativeJob.pts_to_stop = previewSeconds * 90000;
\r
549 else if (job.ChapterStart > 0 && job.ChapterEnd > 0)
\r
551 nativeJob.chapter_start = job.ChapterStart;
\r
552 nativeJob.chapter_end = job.ChapterEnd;
\r
556 nativeJob.chapter_start = 1;
\r
557 nativeJob.chapter_end = title.Chapters.Count;
\r
560 nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;
\r
564 if (profile.CustomCropping)
\r
566 crop = profile.Cropping;
\r
570 crop = title.AutoCropDimensions;
\r
573 nativeJob.crop[0] = crop.Top;
\r
574 nativeJob.crop[1] = crop.Bottom;
\r
575 nativeJob.crop[2] = crop.Left;
\r
576 nativeJob.crop[3] = crop.Right;
\r
578 List<IntPtr> filterList = new List<IntPtr>();
\r
579 if (profile.Deinterlace != Deinterlace.Off)
\r
581 nativeJob.deinterlace = 1;
\r
582 string settings = null;
\r
584 switch (profile.Deinterlace)
\r
586 case Deinterlace.Fast:
\r
589 case Deinterlace.Slow:
\r
592 case Deinterlace.Slower:
\r
595 case Deinterlace.Custom:
\r
596 settings = profile.CustomDeinterlace;
\r
602 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);
\r
603 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEINTERLACE, settings));
\r
607 nativeJob.deinterlace = 0;
\r
610 if (profile.Detelecine != Detelecine.Off)
\r
612 string settings = null;
\r
613 if (profile.Detelecine == Detelecine.Custom)
\r
615 settings = profile.CustomDetelecine;
\r
618 this.AddFilter(filterList, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);
\r
619 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DETELECINE, settings));
\r
622 if (profile.Decomb != Decomb.Off)
\r
624 string settings = null;
\r
625 if (profile.Decomb == Decomb.Custom)
\r
627 settings = profile.CustomDecomb;
\r
630 this.AddFilter(filterList, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);
\r
631 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DECOMB, settings));
\r
634 if (profile.Deblock > 0)
\r
636 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);
\r
637 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString()));
\r
640 if (profile.Denoise != Denoise.Off)
\r
642 string settings = null;
\r
643 switch (profile.Denoise)
\r
646 settings = "2:1:2:3";
\r
648 case Denoise.Medium:
\r
649 settings = "3:2:2:3";
\r
651 case Denoise.Strong:
\r
652 settings = "7:7:5:5";
\r
654 case Denoise.Custom:
\r
655 settings = profile.CustomDenoise;
\r
661 this.AddFilter(filterList, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);
\r
662 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DENOISE, settings));
\r
665 NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterList);
\r
666 nativeJob.filters = filterListNative.ListPtr;
\r
667 allocatedMemory.AddRange(filterListNative.AllocatedMemory);
\r
669 int width = profile.Width;
\r
670 int height = profile.Height;
\r
674 width = title.Resolution.Width;
\r
677 if (profile.MaxWidth > 0 && width > profile.MaxWidth)
\r
679 width = profile.MaxWidth;
\r
684 height = title.Resolution.Height;
\r
687 if (profile.MaxHeight > 0 && height > profile.MaxHeight)
\r
689 height = profile.MaxHeight;
\r
692 nativeJob.grayscale = profile.Grayscale ? 1 : 0;
\r
694 switch (profile.Anamorphic)
\r
696 case Anamorphic.None:
\r
697 nativeJob.anamorphic.mode = 0;
\r
699 if (profile.KeepDisplayAspect)
\r
701 if (profile.Width == 0 && profile.Height == 0 || profile.Width == 0)
\r
703 width = (int)((double)height * this.GetTitle(job.Title).AspectRatio);
\r
705 else if (profile.Height == 0)
\r
707 height = (int)((double)width / this.GetTitle(job.Title).AspectRatio);
\r
711 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
713 case Anamorphic.Strict:
\r
714 nativeJob.anamorphic.mode = 1;
\r
716 case Anamorphic.Loose:
\r
717 nativeJob.anamorphic.mode = 2;
\r
719 case Anamorphic.Custom:
\r
720 nativeJob.anamorphic.mode = 3;
\r
722 nativeJob.modulus = profile.Modulus;
\r
724 if (profile.UseDisplayWidth)
\r
726 if (profile.KeepDisplayAspect)
\r
728 height = (int)((double)profile.DisplayWidth / this.GetTitle(job.Title).AspectRatio);
\r
731 nativeJob.anamorphic.dar_width = profile.DisplayWidth;
\r
732 nativeJob.anamorphic.dar_height = height;
\r
733 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
737 nativeJob.anamorphic.par_width = profile.PixelAspectX;
\r
738 nativeJob.anamorphic.par_height = profile.PixelAspectY;
\r
739 nativeJob.anamorphic.keep_display_aspect = 0;
\r
746 nativeJob.width = width;
\r
747 nativeJob.height = height;
\r
749 nativeJob.maxWidth = profile.MaxWidth;
\r
750 nativeJob.maxHeight = profile.MaxHeight;
\r
752 switch (profile.VideoEncoder)
\r
754 case VideoEncoder.X264:
\r
755 nativeJob.vcodec = NativeConstants.HB_VCODEC_X264;
\r
757 case VideoEncoder.Theora:
\r
758 nativeJob.vcodec = NativeConstants.HB_VCODEC_THEORA;
\r
760 case VideoEncoder.FFMpeg:
\r
761 nativeJob.vcodec = NativeConstants.HB_VCODEC_FFMPEG;
\r
767 if (profile.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality)
\r
769 nativeJob.vquality = (float)profile.Quality;
\r
770 nativeJob.vbitrate = 0;
\r
772 else if (profile.VideoEncodeRateType == VideoEncodeRateType.AverageBitrate)
\r
774 nativeJob.vquality = -1;
\r
775 nativeJob.vbitrate = profile.VideoBitrate;
\r
784 List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);
\r
786 List<hb_audio_s> audioList = new List<hb_audio_s>();
\r
788 foreach (AudioEncoding encoding in profile.AudioEncodings)
\r
790 if (encoding.InputNumber == 0)
\r
792 // Add this encoding for all chosen tracks
\r
793 foreach (int chosenTrack in job.ChosenAudioTracks)
\r
795 if (titleAudio.Count >= chosenTrack)
\r
797 audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++));
\r
801 else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
\r
803 // Add this encoding for the specified track, if it exists
\r
804 int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
\r
805 audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++));
\r
809 NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);
\r
810 nativeJob.list_audio = nativeAudioList.ListPtr;
\r
811 allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);
\r
813 List<hb_subtitle_s> subtitleList = new List<hb_subtitle_s>();
\r
815 if (job.Subtitles != null)
\r
817 if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)
\r
819 List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);
\r
821 foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles)
\r
823 if (sourceSubtitle.TrackNumber == 0)
\r
825 // Use subtitle search.
\r
826 nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0;
\r
827 nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
829 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv)
\r
831 nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
834 nativeJob.indepth_scan = 1;
\r
838 // Use specified subtitle.
\r
839 hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1];
\r
840 nativeSubtitle.config.force = sourceSubtitle.Forced ? 1 : 0;
\r
841 nativeSubtitle.config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
843 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv && nativeSubtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
845 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
848 subtitleList.Add(nativeSubtitle);
\r
853 if (job.Subtitles.SrtSubtitles != null)
\r
855 foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles)
\r
857 hb_subtitle_s nativeSubtitle = new hb_subtitle_s();
\r
858 nativeSubtitle.id = subtitleList.Count << 8 | 0xFF;
\r
859 nativeSubtitle.iso639_2 = srtSubtitle.LanguageCode;
\r
860 nativeSubtitle.lang = LanguageCodes.Decode(srtSubtitle.LanguageCode);
\r
861 nativeSubtitle.source = hb_subtitle_s_subsource.SRTSUB;
\r
862 nativeSubtitle.format = hb_subtitle_s_subtype.TEXTSUB;
\r
864 nativeSubtitle.config.src_codeset = srtSubtitle.CharacterCode;
\r
865 nativeSubtitle.config.src_filename = srtSubtitle.FileName;
\r
866 nativeSubtitle.config.offset = srtSubtitle.Offset;
\r
867 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
868 nativeSubtitle.config.default_track = srtSubtitle.Default ? 1 : 0;
\r
870 subtitleList.Add(nativeSubtitle);
\r
875 NativeList nativeSubtitleList = InteropUtilities.ConvertListBack<hb_subtitle_s>(subtitleList);
\r
876 nativeJob.list_subtitle = nativeSubtitleList.ListPtr;
\r
877 allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);
\r
879 if (profile.OutputFormat == OutputFormat.Mp4)
\r
881 nativeJob.mux = NativeConstants.HB_MUX_MP4;
\r
885 nativeJob.mux = NativeConstants.HB_MUX_MKV;
\r
888 nativeJob.file = job.OutputPath;
\r
890 nativeJob.largeFileSize = profile.LargeFile ? 1 : 0;
\r
891 nativeJob.mp4_optimize = profile.Optimize ? 1 : 0;
\r
892 nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0;
\r
894 string x264Options = profile.X264Options ?? string.Empty;
\r
895 if (profile.TwoPass)
\r
897 nativeJob.pass = 1;
\r
899 if (profile.TurboFirstPass)
\r
901 if (x264Options == string.Empty)
\r
903 x264Options = TurboX264Opts;
\r
907 x264Options += ":" + TurboX264Opts;
\r
912 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Options);
\r
913 allocatedMemory.Add(nativeJob.x264opts);
\r
917 if (title.AngleCount > 1)
\r
919 nativeJob.angle = job.Angle;
\r
924 return allocatedMemory;
\r
928 /// Adds a filter to the given filter list.
\r
930 /// <param name="filterList">The filter list to add to.</param>
\r
931 /// <param name="filterType">The type of filter.</param>
\r
932 /// <param name="settings">Settings for the filter.</param>
\r
933 /// <param name="allocatedMemory">The list of allocated memory.</param>
\r
934 private void AddFilter(List<IntPtr> filterList, int filterType, string settings, List<IntPtr> allocatedMemory)
\r
936 IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings);
\r
937 filterList.Add(HbLib.hb_get_filter_object(filterType, settingsNativeString));
\r
939 allocatedMemory.Add(settingsNativeString);
\r
943 /// Gets the title, given the 1-based index.
\r
945 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
946 /// <returns>The requested Title.</returns>
\r
947 private Title GetTitle(int titleIndex)
\r
949 return this.Titles.SingleOrDefault(title => title.TitleNumber == titleIndex);
\r
953 /// Gets the native title object from the title index.
\r
955 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
956 /// <returns>Gets the native title object for the given index.</returns>
\r
957 private hb_title_s GetOriginalTitle(int titleIndex)
\r
959 List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList();
\r
960 if (matchingTitles.Count == 0)
\r
962 throw new ArgumentException("Could not find specified title.");
\r
965 if (matchingTitles.Count > 1)
\r
967 throw new ArgumentException("Multiple titles matched.");
\r
970 return matchingTitles[0];
\r
974 /// Applies an audio encoding to a native audio encoding base structure.
\r
976 /// <param name="encoding">The encoding to apply.</param>
\r
977 /// <param name="baseStruct">The base native structure.</param>
\r
978 /// <param name="track"></param>
\r
979 /// <param name="outputTrack"></param>
\r
980 /// <returns>The resulting native audio structure.</returns>
\r
981 private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int track, int outputTrack)
\r
983 hb_audio_s nativeAudio = baseStruct;
\r
985 //nativeAudio.config.input.track = track;
\r
986 nativeAudio.config.output.track = outputTrack;
\r
988 switch (encoding.Encoder)
\r
990 case AudioEncoder.Ac3Passthrough:
\r
991 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_AC3;
\r
993 case AudioEncoder.DtsPassthrough:
\r
994 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_DCA;
\r
996 case AudioEncoder.Faac:
\r
997 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_FAAC;
\r
999 case AudioEncoder.Lame:
\r
1000 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_LAME;
\r
1002 case AudioEncoder.Vorbis:
\r
1003 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_VORBIS;
\r
1009 nativeAudio.config.output.bitrate = encoding.Bitrate;
\r
1010 nativeAudio.config.output.dynamic_range_compression = 0.0;
\r
1012 switch (encoding.Mixdown)
\r
1014 case Mixdown.DolbyProLogicII:
\r
1015 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBYPLII;
\r
1017 case Mixdown.DolbySurround:
\r
1018 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBY;
\r
1020 case Mixdown.Mono:
\r
1021 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_MONO;
\r
1023 case Mixdown.SixChannelDiscrete:
\r
1024 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_6CH;
\r
1026 case Mixdown.Stereo:
\r
1027 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_STEREO;
\r
1033 if (encoding.SampleRate != null)
\r
1035 nativeAudio.config.output.samplerate = (int)(double.Parse(encoding.SampleRate) * 1000);
\r
1038 nativeAudio.padding = new byte[24600];
\r
1040 return nativeAudio;
\r
1044 /// Converts a native title to a Title object.
\r
1046 /// <param name="title">The native title structure.</param>
\r
1047 /// <returns>The managed Title object.</returns>
\r
1048 private Title ConvertTitle(hb_title_s title)
\r
1050 var newTitle = new Title
\r
1052 TitleNumber = title.index,
\r
1053 Resolution = new Size(title.width, title.height),
\r
1054 ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height),
\r
1055 Duration = TimeSpan.FromSeconds(((double)title.duration) / 90000),
\r
1056 AutoCropDimensions = new Cropping
\r
1058 Top = title.crop[0],
\r
1059 Bottom = title.crop[1],
\r
1060 Left = title.crop[2],
\r
1061 Right = title.crop[3]
\r
1063 AspectRatio = title.aspect,
\r
1064 AngleCount = title.angle_count
\r
1067 int currentSubtitleTrack = 1;
\r
1068 List<hb_subtitle_s> subtitleList = InteropUtilities.ConvertList<hb_subtitle_s>(title.list_subtitle);
\r
1069 foreach (hb_subtitle_s subtitle in subtitleList)
\r
1071 var newSubtitle = new Subtitle
\r
1073 TrackNumber = currentSubtitleTrack,
\r
1074 Language = subtitle.lang,
\r
1075 LanguageCode = subtitle.iso639_2
\r
1078 if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
1080 newSubtitle.SubtitleType = SubtitleType.Picture;
\r
1082 else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB)
\r
1084 newSubtitle.SubtitleType = SubtitleType.Text;
\r
1087 newTitle.Subtitles.Add(newSubtitle);
\r
1089 currentSubtitleTrack++;
\r
1092 int currentAudioTrack = 1;
\r
1093 List<hb_audio_s> audioList = InteropUtilities.ConvertList<hb_audio_s>(title.list_audio);
\r
1094 foreach (hb_audio_s audio in audioList)
\r
1096 var newAudio = new AudioTrack
\r
1098 TrackNumber = currentAudioTrack,
\r
1099 Language = audio.config.lang.simple,
\r
1100 LanguageCode = audio.config.lang.iso639_2,
\r
1101 Description = audio.config.lang.description
\r
1104 newTitle.AudioTracks.Add(newAudio);
\r
1106 currentAudioTrack++;
\r
1109 List<hb_chapter_s> chapterList = InteropUtilities.ConvertList<hb_chapter_s>(title.list_chapter);
\r
1110 foreach (hb_chapter_s chapter in chapterList)
\r
1112 var newChapter = new Chapter
\r
1114 ChapterNumber = chapter.index,
\r
1115 Duration = TimeSpan.FromSeconds(((double)chapter.duration) / 90000)
\r
1118 newTitle.Chapters.Add(newChapter);
\r