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 /// The index of the default title.
\r
63 private int featureTitle;
\r
66 /// A list of native memory locations allocated by this instance.
\r
68 private List<IntPtr> encodeAllocatedMemory;
\r
71 /// The callback for log messages from HandBrake.
\r
73 private static LoggingCallback loggingCallback;
\r
76 /// The callback for error messages from HandBrake.
\r
78 private static LoggingCallback errorCallback;
\r
81 /// Fires for progress updates when scanning.
\r
83 public event EventHandler<ScanProgressEventArgs> ScanProgress;
\r
86 /// Fires when a scan has completed.
\r
88 public event EventHandler<EventArgs> ScanCompleted;
\r
91 /// Fires for progress updates when encoding.
\r
93 public event EventHandler<EncodeProgressEventArgs> EncodeProgress;
\r
96 /// Fires when an encode has completed.
\r
98 public event EventHandler<EncodeCompletedEventArgs> EncodeCompleted;
\r
101 /// Fires when HandBrake has logged a message.
\r
103 public static event EventHandler<MessageLoggedEventArgs> MessageLogged;
\r
106 /// Fires when HandBrake has logged an error.
\r
108 public static event EventHandler<MessageLoggedEventArgs> ErrorLogged;
\r
113 ~HandBrakeInstance()
\r
115 this.Dispose(false);
\r
119 /// The list of titles on this instance.
\r
121 public List<Title> Titles
\r
125 return this.titles;
\r
130 /// Gets the index of the default title.
\r
132 public int FeatureTitle
\r
136 return this.featureTitle;
\r
141 /// Initializes this instance.
\r
143 /// <param name="verbosity"></param>
\r
144 public void Initialize(int verbosity)
\r
146 // Register the logger if we have not already
\r
147 if (loggingCallback == null)
\r
149 // Keep the callback as a member to prevent it from being garbage collected.
\r
150 loggingCallback = new LoggingCallback(HandBrakeInstance.LoggingHandler);
\r
151 errorCallback = new LoggingCallback(HandBrakeInstance.ErrorHandler);
\r
152 HbLib.hb_register_logger(loggingCallback);
\r
153 HbLib.hb_register_error_handler(errorCallback);
\r
156 this.hbHandle = HbLib.hb_init(verbosity, update_check: 0);
\r
160 /// Handles log messages from HandBrake.
\r
162 /// <param name="message">The log message (including newline).</param>
\r
163 public static void LoggingHandler(string message)
\r
165 if (!string.IsNullOrEmpty(message))
\r
167 string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
\r
169 if (messageParts.Length > 0)
\r
171 if (MessageLogged != null)
\r
173 MessageLogged(null, new MessageLoggedEventArgs { Message = messageParts[0] });
\r
176 System.Diagnostics.Debug.WriteLine(messageParts[0]);
\r
182 /// Handles errors from HandBrake.
\r
184 /// <param name="message">The error message.</param>
\r
185 public static void ErrorHandler(string message)
\r
187 if (!string.IsNullOrEmpty(message))
\r
189 if (ErrorLogged != null)
\r
191 ErrorLogged(null, new MessageLoggedEventArgs { Message = message });
\r
194 System.Diagnostics.Debug.WriteLine("ERROR: " + message);
\r
199 /// Starts scanning the given path.
\r
201 /// <param name="path">The path to the video to scan.</param>
\r
202 /// <param name="previewCount">The number of preview images to make.</param>
\r
203 public void StartScan(string path, int previewCount)
\r
205 this.StartScan(path, previewCount, 0);
\r
209 /// Starts a scan of the given path.
\r
211 /// <param name="path">The path of the video to scan.</param>
\r
212 /// <param name="previewCount">The number of previews to make on each title.</param>
\r
213 /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param>
\r
214 public void StartScan(string path, int previewCount, int titleIndex)
\r
216 HbLib.hb_scan(hbHandle, path, titleIndex, previewCount, 1);
\r
217 this.scanPollTimer = new System.Timers.Timer();
\r
218 this.scanPollTimer.Interval = ScanPollIntervalMs;
\r
220 // Lambda notation used to make sure we can view any JIT exceptions the method throws
\r
221 this.scanPollTimer.Elapsed += (o, e) =>
\r
223 this.PollScanProgress();
\r
225 this.scanPollTimer.Start();
\r
229 /// Gets an image for the given job and preview
\r
232 /// Only incorporates sizing and aspect ratio into preview image.
\r
234 /// <param name="job">The encode job to preview.</param>
\r
235 /// <param name="previewNumber">The index of the preview to get (0-based).</param>
\r
236 /// <returns>An image with the requested preview.</returns>
\r
237 public BitmapImage GetPreview(EncodeJob job, int previewNumber)
\r
239 hb_title_s title = this.GetOriginalTitle(job.Title);
\r
241 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
\r
242 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
244 // Create a new job pointer from our modified job object
\r
245 IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));
\r
246 Marshal.StructureToPtr(nativeJob, newJob, false);
\r
247 allocatedMemory.Add(newJob);
\r
249 int outputWidth = nativeJob.width;
\r
250 int outputHeight = nativeJob.height;
\r
251 int imageBufferSize = outputWidth * outputHeight * 4;
\r
252 IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);
\r
253 allocatedMemory.Add(nativeBuffer);
\r
254 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
255 HbLib.hb_get_preview_by_index(this.hbHandle, job.Title, previewNumber, nativeBuffer);
\r
257 // Copy the filled image buffer to a managed array.
\r
258 byte[] managedBuffer = new byte[imageBufferSize];
\r
259 Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);
\r
261 InteropUtilities.FreeMemory(allocatedMemory);
\r
263 System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight);
\r
264 System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, outputWidth, outputHeight), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
\r
266 IntPtr ptr = bitmapData.Scan0;
\r
268 for (int i = 0; i < nativeJob.height; i++)
\r
270 Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4);
\r
271 ptr = IntPtr.Add(ptr, bitmapData.Stride);
\r
274 bitmap.UnlockBits(bitmapData);
\r
275 //bitmap.Save(@"d:\docs\test_" + previewNumber + ".png", System.Drawing.Imaging.ImageFormat.Png);
\r
277 using (MemoryStream memoryStream = new MemoryStream())
\r
279 bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
\r
282 BitmapImage wpfBitmap = new BitmapImage();
\r
283 wpfBitmap.BeginInit();
\r
284 wpfBitmap.CacheOption = BitmapCacheOption.OnLoad;
\r
285 wpfBitmap.StreamSource = memoryStream;
\r
286 wpfBitmap.EndInit();
\r
293 /// Starts an encode with the given job.
\r
295 /// <param name="job">The job to start.</param>
\r
296 public void StartEncode(EncodeJob job)
\r
298 this.StartEncode(job, false, 0, 0);
\r
302 /// Starts an encode with the given job.
\r
304 /// <param name="job">The job to start.</param>
\r
305 /// <param name="preview">True if this is a preview encode.</param>
\r
306 /// <param name="previewNumber">The preview number to start the encode at (0-based).</param>
\r
307 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
308 public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
310 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
311 this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);
\r
313 if (!preview && job.EncodingProfile.IncludeChapterMarkers)
\r
315 Title title = this.GetTitle(job.Title);
\r
316 int numChapters = title.Chapters.Count;
\r
318 if (job.UseDefaultChapterNames)
\r
320 for (int i = 0; i < numChapters; i++)
\r
322 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));
\r
327 for (int i = 0; i < numChapters; i++)
\r
329 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);
\r
334 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
336 if (job.EncodingProfile.TwoPass)
\r
338 nativeJob.pass = 2;
\r
340 string x264Opts = job.EncodingProfile.X264Options ?? string.Empty;
\r
341 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Opts);
\r
342 this.encodeAllocatedMemory.Add(nativeJob.x264opts);
\r
344 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
347 HbLib.hb_start(this.hbHandle);
\r
349 this.encodePollTimer = new System.Timers.Timer();
\r
350 this.encodePollTimer.Interval = EncodePollIntervalMs;
\r
352 this.encodePollTimer.Elapsed += (o, e) =>
\r
354 this.PollEncodeProgress();
\r
356 this.encodePollTimer.Start();
\r
360 /// Pauses the current encode.
\r
362 public void PauseEncode()
\r
364 HbLib.hb_pause(this.hbHandle);
\r
368 /// Resumes a paused encode.
\r
370 public void ResumeEncode()
\r
372 HbLib.hb_resume(this.hbHandle);
\r
376 /// Stops the current encode.
\r
378 public void StopEncode()
\r
380 HbLib.hb_stop(this.hbHandle);
\r
382 // Also remove all jobs from the queue (in case we stopped a 2-pass encode)
\r
383 var currentJobs = new List<IntPtr>();
\r
385 int jobs = HbLib.hb_count(this.hbHandle);
\r
386 for (int i = 0; i < jobs; i++)
\r
388 currentJobs.Add(HbLib.hb_job(this.hbHandle, 0));
\r
391 foreach (IntPtr job in currentJobs)
\r
393 HbLib.hb_rem(this.hbHandle, job);
\r
398 /// Gets the final size when using Anamorphic for a given encode job.
\r
400 /// <param name="job">The encode job to use.</param>
\r
401 /// <param name="width">The storage width.</param>
\r
402 /// <param name="height">The storage height.</param>
\r
403 /// <param name="parWidth">The pixel aspect X number.</param>
\r
404 /// <param name="parHeight">The pixel aspect Y number.</param>
\r
405 public void GetAnamorphicSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight)
\r
407 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
408 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
412 int refParWidth = 0;
\r
413 int refParHeight = 0;
\r
414 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
415 HbLib.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
416 //HbLib.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
417 InteropUtilities.FreeMemory(allocatedMemory);
\r
420 height = refHeight;
\r
421 parWidth = refParWidth;
\r
422 parHeight = refParHeight;
\r
426 /// Frees any resources associated with this object.
\r
428 public void Dispose()
\r
430 this.Dispose(true);
\r
431 GC.SuppressFinalize(this);
\r
435 /// Call before app shutdown. Performs global cleanup.
\r
437 public static void DisposeGlobal()
\r
439 HbLib.hb_global_close();
\r
443 /// Frees any resources associated with this object.
\r
445 /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param>
\r
446 protected virtual void Dispose(bool disposing)
\r
450 // Free other state (managed objects).
\r
453 // Free unmanaged objects.
\r
454 IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
\r
455 Marshal.WriteIntPtr(handlePtr, this.hbHandle);
\r
456 HbLib.hb_close(handlePtr);
\r
457 Marshal.FreeHGlobal(handlePtr);
\r
461 /// Checks the status of the ongoing scan.
\r
463 private void PollScanProgress()
\r
465 hb_state_s state = new hb_state_s();
\r
466 HbLib.hb_get_state(this.hbHandle, ref state);
\r
468 if (state.state == NativeConstants.HB_STATE_SCANNING)
\r
470 if (this.ScanProgress != null)
\r
472 int currentTitle = state.param.scanning.title_cur;
\r
473 int totalTitles = state.param.scanning.title_count;
\r
474 this.ScanProgress(this, new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = totalTitles });
\r
477 else if (state.state == NativeConstants.HB_STATE_SCANDONE)
\r
479 this.titles = new List<Title>();
\r
481 IntPtr listPtr = HbLib.hb_get_titles(this.hbHandle);
\r
482 this.originalTitles = InteropUtilities.ConvertList<hb_title_s>(listPtr);
\r
484 foreach (hb_title_s title in this.originalTitles)
\r
486 var newTitle = this.ConvertTitle(title);
\r
487 this.titles.Add(newTitle);
\r
490 if (this.originalTitles.Count > 0)
\r
492 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.originalTitles[0].job);
\r
493 this.featureTitle = nativeJob.feature;
\r
497 this.featureTitle = 0;
\r
500 this.scanPollTimer.Stop();
\r
502 if (this.ScanCompleted != null)
\r
504 this.ScanCompleted(this, new EventArgs());
\r
510 /// Checks the status of the ongoing encode.
\r
512 private void PollEncodeProgress()
\r
514 hb_state_s state = new hb_state_s();
\r
515 HbLib.hb_get_state(this.hbHandle, ref state);
\r
517 if (state.state == NativeConstants.HB_STATE_WORKING)
\r
519 if (this.EncodeProgress != null)
\r
521 var progressEventArgs = new EncodeProgressEventArgs
\r
523 FractionComplete = state.param.working.progress,
\r
524 CurrentFrameRate = state.param.working.rate_cur,
\r
525 AverageFrameRate = state.param.working.rate_avg,
\r
526 EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds),
\r
527 Pass = state.param.working.job_cur
\r
530 this.EncodeProgress(this, progressEventArgs);
\r
533 else if (state.state == NativeConstants.HB_STATE_MUXING)
\r
535 //System.Diagnostics.Debug.WriteLine("Muxing...");
\r
537 else if (state.state == NativeConstants.HB_STATE_WORKDONE)
\r
539 InteropUtilities.FreeMemory(this.encodeAllocatedMemory);
\r
540 this.encodePollTimer.Stop();
\r
542 if (this.EncodeCompleted != null)
\r
544 this.EncodeCompleted(this, new EncodeCompletedEventArgs { Error = state.param.workdone.error > 0 });
\r
550 /// Applies the encoding job to the native memory structure and returns a list of memory
\r
551 /// locations allocated during this.
\r
553 /// <param name="nativeJob">The native structure to apply to job info to.</param>
\r
554 /// <param name="job">The job info to apply.</param>
\r
555 /// <param name="preview">True if this is a preview encode.</param>
\r
556 /// <param name="previewNumber">The preview number (0-based) to encode.</param>
\r
557 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
558 /// <returns>The list of memory locations allocated for the job.</returns>
\r
559 private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
561 var allocatedMemory = new List<IntPtr>();
\r
562 Title title = this.GetTitle(job.Title);
\r
563 hb_title_s originalTitle = this.GetOriginalTitle(job.Title);
\r
565 EncodingProfile profile = job.EncodingProfile;
\r
569 nativeJob.start_at_preview = previewNumber + 1;
\r
570 nativeJob.seek_points = 10;
\r
572 // There are 90,000 PTS per second.
\r
573 nativeJob.pts_to_stop = previewSeconds * 90000;
\r
575 else if (job.ChapterStart > 0 && job.ChapterEnd > 0)
\r
577 nativeJob.chapter_start = job.ChapterStart;
\r
578 nativeJob.chapter_end = job.ChapterEnd;
\r
582 nativeJob.chapter_start = 1;
\r
583 nativeJob.chapter_end = title.Chapters.Count;
\r
586 nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;
\r
590 if (profile.CustomCropping)
\r
592 crop = profile.Cropping;
\r
596 crop = title.AutoCropDimensions;
\r
599 nativeJob.crop[0] = crop.Top;
\r
600 nativeJob.crop[1] = crop.Bottom;
\r
601 nativeJob.crop[2] = crop.Left;
\r
602 nativeJob.crop[3] = crop.Right;
\r
604 List<IntPtr> filterList = new List<IntPtr>();
\r
605 if (profile.Deinterlace != Deinterlace.Off)
\r
607 nativeJob.deinterlace = 1;
\r
608 string settings = null;
\r
610 switch (profile.Deinterlace)
\r
612 case Deinterlace.Fast:
\r
615 case Deinterlace.Slow:
\r
618 case Deinterlace.Slower:
\r
621 case Deinterlace.Custom:
\r
622 settings = profile.CustomDeinterlace;
\r
628 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);
\r
632 nativeJob.deinterlace = 0;
\r
635 if (profile.Detelecine != Detelecine.Off)
\r
637 string settings = null;
\r
638 if (profile.Detelecine == Detelecine.Custom)
\r
640 settings = profile.CustomDetelecine;
\r
643 this.AddFilter(filterList, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);
\r
646 if (profile.Decomb != Decomb.Off)
\r
648 string settings = null;
\r
649 if (profile.Decomb == Decomb.Custom)
\r
651 settings = profile.CustomDecomb;
\r
654 this.AddFilter(filterList, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);
\r
657 if (profile.Deblock > 0)
\r
659 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);
\r
662 if (profile.Denoise != Denoise.Off)
\r
664 string settings = null;
\r
665 switch (profile.Denoise)
\r
668 settings = "2:1:2:3";
\r
670 case Denoise.Medium:
\r
671 settings = "3:2:2:3";
\r
673 case Denoise.Strong:
\r
674 settings = "7:7:5:5";
\r
676 case Denoise.Custom:
\r
677 settings = profile.CustomDenoise;
\r
683 this.AddFilter(filterList, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);
\r
686 NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterList);
\r
687 nativeJob.filters = filterListNative.ListPtr;
\r
688 allocatedMemory.AddRange(filterListNative.AllocatedMemory);
\r
690 int width = profile.Width;
\r
691 int height = profile.Height;
\r
695 width = title.Resolution.Width;
\r
698 if (profile.MaxWidth > 0 && width > profile.MaxWidth)
\r
700 width = profile.MaxWidth;
\r
705 height = title.Resolution.Height;
\r
708 if (profile.MaxHeight > 0 && height > profile.MaxHeight)
\r
710 height = profile.MaxHeight;
\r
713 nativeJob.grayscale = profile.Grayscale ? 1 : 0;
\r
715 switch (profile.Anamorphic)
\r
717 case Anamorphic.None:
\r
718 nativeJob.anamorphic.mode = 0;
\r
720 if (profile.KeepDisplayAspect)
\r
722 if (profile.Width == 0 && profile.Height == 0 || profile.Width == 0)
\r
724 width = (int)((double)height * this.GetTitle(job.Title).AspectRatio);
\r
726 else if (profile.Height == 0)
\r
728 height = (int)((double)width / this.GetTitle(job.Title).AspectRatio);
\r
732 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
734 case Anamorphic.Strict:
\r
735 nativeJob.anamorphic.mode = 1;
\r
737 case Anamorphic.Loose:
\r
738 nativeJob.anamorphic.mode = 2;
\r
740 case Anamorphic.Custom:
\r
741 nativeJob.anamorphic.mode = 3;
\r
743 nativeJob.modulus = profile.Modulus;
\r
745 if (profile.UseDisplayWidth)
\r
747 if (profile.KeepDisplayAspect)
\r
749 height = (int)((double)profile.DisplayWidth / this.GetTitle(job.Title).AspectRatio);
\r
752 nativeJob.anamorphic.dar_width = profile.DisplayWidth;
\r
753 nativeJob.anamorphic.dar_height = height;
\r
754 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
758 nativeJob.anamorphic.par_width = profile.PixelAspectX;
\r
759 nativeJob.anamorphic.par_height = profile.PixelAspectY;
\r
760 nativeJob.anamorphic.keep_display_aspect = 0;
\r
767 nativeJob.width = width;
\r
768 nativeJob.height = height;
\r
770 nativeJob.maxWidth = profile.MaxWidth;
\r
771 nativeJob.maxHeight = profile.MaxHeight;
\r
773 switch (profile.VideoEncoder)
\r
775 case VideoEncoder.X264:
\r
776 nativeJob.vcodec = NativeConstants.HB_VCODEC_X264;
\r
778 case VideoEncoder.Theora:
\r
779 nativeJob.vcodec = NativeConstants.HB_VCODEC_THEORA;
\r
781 case VideoEncoder.FFMpeg:
\r
782 nativeJob.vcodec = NativeConstants.HB_VCODEC_FFMPEG;
\r
788 if (profile.Framerate == 0)
\r
794 if (profile.PeakFramerate)
\r
803 nativeJob.vrate = 27000000;
\r
804 nativeJob.vrate_base = Converters.FramerateToVrate(profile.Framerate);
\r
810 List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);
\r
812 List<hb_audio_s> audioList = new List<hb_audio_s>();
\r
814 foreach (AudioEncoding encoding in profile.AudioEncodings)
\r
816 if (encoding.InputNumber == 0)
\r
818 // Add this encoding for all chosen tracks
\r
819 foreach (int chosenTrack in job.ChosenAudioTracks)
\r
821 if (titleAudio.Count >= chosenTrack)
\r
823 audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++));
\r
827 else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
\r
829 // Add this encoding for the specified track, if it exists
\r
830 int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
\r
831 audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++));
\r
835 NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);
\r
836 nativeJob.list_audio = nativeAudioList.ListPtr;
\r
837 allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);
\r
839 List<hb_subtitle_s> subtitleList = new List<hb_subtitle_s>();
\r
841 if (job.Subtitles != null)
\r
843 if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)
\r
845 List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);
\r
847 foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles)
\r
849 if (sourceSubtitle.TrackNumber == 0)
\r
851 // Use subtitle search.
\r
852 nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0;
\r
853 nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
855 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv)
\r
857 nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
860 nativeJob.indepth_scan = 1;
\r
864 // Use specified subtitle.
\r
865 hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1];
\r
866 nativeSubtitle.config.force = sourceSubtitle.Forced ? 1 : 0;
\r
867 nativeSubtitle.config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
869 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv && nativeSubtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
871 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
874 subtitleList.Add(nativeSubtitle);
\r
879 if (job.Subtitles.SrtSubtitles != null)
\r
881 foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles)
\r
883 hb_subtitle_s nativeSubtitle = new hb_subtitle_s();
\r
884 nativeSubtitle.id = subtitleList.Count << 8 | 0xFF;
\r
885 nativeSubtitle.iso639_2 = srtSubtitle.LanguageCode;
\r
886 nativeSubtitle.lang = LanguageCodes.Decode(srtSubtitle.LanguageCode);
\r
887 nativeSubtitle.source = hb_subtitle_s_subsource.SRTSUB;
\r
888 nativeSubtitle.format = hb_subtitle_s_subtype.TEXTSUB;
\r
890 nativeSubtitle.config.src_codeset = srtSubtitle.CharacterCode;
\r
891 nativeSubtitle.config.src_filename = srtSubtitle.FileName;
\r
892 nativeSubtitle.config.offset = srtSubtitle.Offset;
\r
893 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
894 nativeSubtitle.config.default_track = srtSubtitle.Default ? 1 : 0;
\r
896 subtitleList.Add(nativeSubtitle);
\r
901 NativeList nativeSubtitleList = InteropUtilities.ConvertListBack<hb_subtitle_s>(subtitleList);
\r
902 nativeJob.list_subtitle = nativeSubtitleList.ListPtr;
\r
903 allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);
\r
905 if (profile.OutputFormat == OutputFormat.Mp4)
\r
907 nativeJob.mux = NativeConstants.HB_MUX_MP4;
\r
911 nativeJob.mux = NativeConstants.HB_MUX_MKV;
\r
914 nativeJob.file = job.OutputPath;
\r
916 nativeJob.largeFileSize = profile.LargeFile ? 1 : 0;
\r
917 nativeJob.mp4_optimize = profile.Optimize ? 1 : 0;
\r
918 nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0;
\r
920 string x264Options = profile.X264Options ?? string.Empty;
\r
921 if (profile.TwoPass)
\r
923 nativeJob.pass = 1;
\r
925 if (profile.TurboFirstPass)
\r
927 if (x264Options == string.Empty)
\r
929 x264Options = TurboX264Opts;
\r
933 x264Options += ":" + TurboX264Opts;
\r
938 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Options);
\r
939 allocatedMemory.Add(nativeJob.x264opts);
\r
943 if (title.AngleCount > 1)
\r
945 nativeJob.angle = job.Angle;
\r
948 switch (profile.VideoEncodeRateType)
\r
950 case VideoEncodeRateType.ConstantQuality:
\r
951 nativeJob.vquality = (float)profile.Quality;
\r
952 nativeJob.vbitrate = 0;
\r
954 case VideoEncodeRateType.AverageBitrate:
\r
955 nativeJob.vquality = -1;
\r
956 nativeJob.vbitrate = profile.VideoBitrate;
\r
958 case VideoEncodeRateType.TargetSize:
\r
959 nativeJob.vquality = -1;
\r
960 nativeJob.vbitrate = HbLib.hb_calc_bitrate(ref nativeJob, profile.TargetSize);
\r
968 return allocatedMemory;
\r
972 /// Adds a filter to the given filter list.
\r
974 /// <param name="filterList">The filter list to add to.</param>
\r
975 /// <param name="filterType">The type of filter.</param>
\r
976 /// <param name="settings">Settings for the filter.</param>
\r
977 /// <param name="allocatedMemory">The list of allocated memory.</param>
\r
978 private void AddFilter(List<IntPtr> filterList, int filterType, string settings, List<IntPtr> allocatedMemory)
\r
980 IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings);
\r
981 filterList.Add(HbLib.hb_get_filter_object(filterType, settingsNativeString));
\r
983 allocatedMemory.Add(settingsNativeString);
\r
987 /// Gets the title, given the 1-based index.
\r
989 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
990 /// <returns>The requested Title.</returns>
\r
991 private Title GetTitle(int titleIndex)
\r
993 return this.Titles.SingleOrDefault(title => title.TitleNumber == titleIndex);
\r
997 /// Gets the native title object from the title index.
\r
999 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
1000 /// <returns>Gets the native title object for the given index.</returns>
\r
1001 private hb_title_s GetOriginalTitle(int titleIndex)
\r
1003 List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList();
\r
1004 if (matchingTitles.Count == 0)
\r
1006 throw new ArgumentException("Could not find specified title.");
\r
1009 if (matchingTitles.Count > 1)
\r
1011 throw new ArgumentException("Multiple titles matched.");
\r
1014 return matchingTitles[0];
\r
1018 /// Applies an audio encoding to a native audio encoding base structure.
\r
1020 /// <param name="encoding">The encoding to apply.</param>
\r
1021 /// <param name="baseStruct">The base native structure.</param>
\r
1022 /// <param name="track"></param>
\r
1023 /// <param name="outputTrack"></param>
\r
1024 /// <returns>The resulting native audio structure.</returns>
\r
1025 private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int track, int outputTrack)
\r
1027 hb_audio_s nativeAudio = baseStruct;
\r
1029 //nativeAudio.config.input.track = track;
\r
1030 nativeAudio.config.output.track = outputTrack;
\r
1032 switch (encoding.Encoder)
\r
1034 case AudioEncoder.Ac3Passthrough:
\r
1035 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_AC3;
\r
1037 case AudioEncoder.DtsPassthrough:
\r
1038 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_DCA;
\r
1040 case AudioEncoder.Faac:
\r
1041 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_FAAC;
\r
1043 case AudioEncoder.Lame:
\r
1044 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_LAME;
\r
1046 case AudioEncoder.Vorbis:
\r
1047 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_VORBIS;
\r
1053 nativeAudio.config.output.bitrate = encoding.Bitrate;
\r
1054 nativeAudio.config.output.dynamic_range_compression = 0.0;
\r
1056 switch (encoding.Mixdown)
\r
1058 case Mixdown.DolbyProLogicII:
\r
1059 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBYPLII;
\r
1061 case Mixdown.DolbySurround:
\r
1062 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBY;
\r
1064 case Mixdown.Mono:
\r
1065 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_MONO;
\r
1067 case Mixdown.SixChannelDiscrete:
\r
1068 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_6CH;
\r
1070 case Mixdown.Stereo:
\r
1071 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_STEREO;
\r
1077 if (encoding.SampleRate != null)
\r
1079 nativeAudio.config.output.samplerate = (int)(double.Parse(encoding.SampleRate) * 1000);
\r
1082 nativeAudio.padding = new byte[24600];
\r
1084 return nativeAudio;
\r
1088 /// Converts a native title to a Title object.
\r
1090 /// <param name="title">The native title structure.</param>
\r
1091 /// <returns>The managed Title object.</returns>
\r
1092 private Title ConvertTitle(hb_title_s title)
\r
1094 var newTitle = new Title
\r
1096 TitleNumber = title.index,
\r
1097 Resolution = new Size(title.width, title.height),
\r
1098 ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height),
\r
1099 Duration = TimeSpan.FromSeconds(((double)title.duration) / 90000),
\r
1100 AutoCropDimensions = new Cropping
\r
1102 Top = title.crop[0],
\r
1103 Bottom = title.crop[1],
\r
1104 Left = title.crop[2],
\r
1105 Right = title.crop[3]
\r
1107 AspectRatio = title.aspect,
\r
1108 AngleCount = title.angle_count
\r
1111 int currentSubtitleTrack = 1;
\r
1112 List<hb_subtitle_s> subtitleList = InteropUtilities.ConvertList<hb_subtitle_s>(title.list_subtitle);
\r
1113 foreach (hb_subtitle_s subtitle in subtitleList)
\r
1115 var newSubtitle = new Subtitle
\r
1117 TrackNumber = currentSubtitleTrack,
\r
1118 Language = subtitle.lang,
\r
1119 LanguageCode = subtitle.iso639_2
\r
1122 if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
1124 newSubtitle.SubtitleType = SubtitleType.Picture;
\r
1126 else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB)
\r
1128 newSubtitle.SubtitleType = SubtitleType.Text;
\r
1131 newTitle.Subtitles.Add(newSubtitle);
\r
1133 currentSubtitleTrack++;
\r
1136 int currentAudioTrack = 1;
\r
1137 List<hb_audio_s> audioList = InteropUtilities.ConvertList<hb_audio_s>(title.list_audio);
\r
1138 foreach (hb_audio_s audio in audioList)
\r
1140 var newAudio = new AudioTrack
\r
1142 TrackNumber = currentAudioTrack,
\r
1143 Language = audio.config.lang.simple,
\r
1144 LanguageCode = audio.config.lang.iso639_2,
\r
1145 Description = audio.config.lang.description
\r
1148 newTitle.AudioTracks.Add(newAudio);
\r
1150 currentAudioTrack++;
\r
1153 List<hb_chapter_s> chapterList = InteropUtilities.ConvertList<hb_chapter_s>(title.list_chapter);
\r
1154 foreach (hb_chapter_s chapter in chapterList)
\r
1156 var newChapter = new Chapter
\r
1158 ChapterNumber = chapter.index,
\r
1159 Duration = TimeSpan.FromSeconds(((double)chapter.duration) / 90000)
\r
1162 newTitle.Chapters.Add(newChapter);
\r