1 // --------------------------------------------------------------------------------------------------------------------
\r
2 // <copyright file="HandBrakeInstance.cs" company="HandBrake Project (http://handbrake.fr)">
\r
3 // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License.
\r
6 // A wrapper for a HandBrake instance.
\r
8 // --------------------------------------------------------------------------------------------------------------------
\r
10 namespace HandBrake.Interop
\r
13 using System.Collections.Generic;
\r
16 using System.Runtime.InteropServices;
\r
17 using System.Windows.Media.Imaging;
\r
19 using Model.Encoding;
\r
23 /// A wrapper for a HandBrake instance.
\r
25 public class HandBrakeInstance : IDisposable
\r
28 /// The number of MS between status polls when scanning.
\r
30 private const double ScanPollIntervalMs = 200;
\r
33 /// The number of MS between status polls when encoding.
\r
35 private const double EncodePollIntervalMs = 200;
\r
38 /// X264 options to add for a turbo first pass.
\r
40 private const string TurboX264Opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0";
\r
43 /// The native handle to the HandBrake instance.
\r
45 private IntPtr hbHandle;
\r
48 /// The timer to poll for scan status.
\r
50 private System.Timers.Timer scanPollTimer;
\r
53 /// The timer to poll for encode status.
\r
55 private System.Timers.Timer encodePollTimer;
\r
58 /// The list of original titles in native structure form.
\r
60 private List<hb_title_s> originalTitles;
\r
63 /// The list of titles on this instance.
\r
65 private List<Title> titles;
\r
68 /// A list of native memory locations allocated by this instance.
\r
70 private List<IntPtr> encodeAllocatedMemory;
\r
73 /// The callback for log messages from HandBrake.
\r
75 private static LoggingCallback loggingCallback;
\r
78 /// The callback for error messages from HandBrake.
\r
80 private static LoggingCallback errorCallback;
\r
83 /// Fires for progress updates when scanning.
\r
85 public event EventHandler<ScanProgressEventArgs> ScanProgress;
\r
88 /// Fires when a scan has completed.
\r
90 public event EventHandler<EventArgs> ScanCompleted;
\r
93 /// Fires for progress updates when encoding.
\r
95 public event EventHandler<EncodeProgressEventArgs> EncodeProgress;
\r
98 /// Fires when an encode has completed.
\r
100 public event EventHandler<EventArgs> EncodeCompleted;
\r
103 /// Fires when HandBrake has logged a message.
\r
105 public static event EventHandler<MessageLoggedEventArgs> MessageLogged;
\r
108 /// Fires when HandBrake has logged an error.
\r
110 public static event EventHandler<MessageLoggedEventArgs> ErrorLogged;
\r
115 ~HandBrakeInstance()
\r
117 this.Dispose(false);
\r
121 /// The list of titles on this instance.
\r
123 public List<Title> Titles
\r
127 return this.titles;
\r
132 /// Initializes this instance.
\r
134 /// <param name="verbosity"></param>
\r
135 public void Initialize(int verbosity)
\r
137 // Register the logger if we have not already
\r
138 if (loggingCallback == null)
\r
140 // Keep the callback as a member to prevent it from being garbage collected.
\r
141 loggingCallback = new LoggingCallback(HandBrakeInstance.LoggingHandler);
\r
142 errorCallback = new LoggingCallback(HandBrakeInstance.ErrorHandler);
\r
143 HbLib.hb_register_logger(loggingCallback);
\r
144 HbLib.hb_register_error_handler(errorCallback);
\r
147 this.hbHandle = HbLib.hb_init(verbosity, 0);
\r
151 /// Handles log messages from HandBrake.
\r
153 /// <param name="message">The log message (including newline).</param>
\r
154 public static void LoggingHandler(string message)
\r
156 if (!string.IsNullOrEmpty(message))
\r
158 string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
\r
160 if (messageParts.Length > 0)
\r
162 if (MessageLogged != null)
\r
164 MessageLogged(null, new MessageLoggedEventArgs { Message = messageParts[0] });
\r
167 System.Diagnostics.Debug.WriteLine(messageParts[0]);
\r
173 /// Handles errors from HandBrake.
\r
175 /// <param name="message">The error message.</param>
\r
176 public static void ErrorHandler(string message)
\r
178 if (!string.IsNullOrEmpty(message))
\r
180 if (ErrorLogged != null)
\r
182 ErrorLogged(null, new MessageLoggedEventArgs { Message = message });
\r
185 System.Diagnostics.Debug.WriteLine("ERROR: " + message);
\r
190 /// Starts scanning the given path.
\r
192 /// <param name="path">The path to the video to scan.</param>
\r
193 /// <param name="previewCount">The number of preview images to make.</param>
\r
194 public void StartScan(string path, int previewCount)
\r
196 this.StartScan(path, previewCount, 0);
\r
200 /// Starts a scan of the given path.
\r
202 /// <param name="path">The path of the video to scan.</param>
\r
203 /// <param name="previewCount">The number of previews to make on each title.</param>
\r
204 /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param>
\r
205 public void StartScan(string path, int previewCount, int titleIndex)
\r
207 HbLib.hb_scan(hbHandle, path, titleIndex, previewCount, 1);
\r
208 this.scanPollTimer = new System.Timers.Timer();
\r
209 this.scanPollTimer.Interval = ScanPollIntervalMs;
\r
211 // Lambda notation used to make sure we can view any JIT exceptions the method throws
\r
212 this.scanPollTimer.Elapsed += (o, e) =>
\r
214 this.PollScanProgress();
\r
216 this.scanPollTimer.Start();
\r
220 /// Gets an image for the given job and preview
\r
223 /// Only incorporates sizing and aspect ratio into preview image.
\r
225 /// <param name="job">The encode job to preview.</param>
\r
226 /// <param name="previewNumber">The index of the preview to get (0-based).</param>
\r
227 /// <returns>An image with the requested preview.</returns>
\r
228 public BitmapImage GetPreview(EncodeJob job, int previewNumber)
\r
230 hb_title_s title = this.GetOriginalTitle(job.Title);
\r
232 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
\r
233 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
235 // Create a new job pointer from our modified job object
\r
236 IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));
\r
237 Marshal.StructureToPtr(nativeJob, newJob, false);
\r
238 allocatedMemory.Add(newJob);
\r
240 int outputWidth = nativeJob.width;
\r
241 int outputHeight = nativeJob.height;
\r
242 int imageBufferSize = outputWidth * outputHeight * 4;
\r
243 IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);
\r
244 allocatedMemory.Add(nativeBuffer);
\r
245 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
246 HbLib.hb_get_preview_by_index(this.hbHandle, job.Title, previewNumber, nativeBuffer);
\r
248 // Copy the filled image buffer to a managed array.
\r
249 byte[] managedBuffer = new byte[imageBufferSize];
\r
250 Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);
\r
252 InteropUtilities.FreeMemory(allocatedMemory);
\r
254 System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight);
\r
255 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
257 IntPtr ptr = bitmapData.Scan0;
\r
259 for (int i = 0; i < nativeJob.height; i++)
\r
261 Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4);
\r
262 ptr = AddOffset(ptr, bitmapData.Stride);
\r
265 bitmap.UnlockBits(bitmapData);
\r
266 //bitmap.Save(@"d:\docs\test_" + previewNumber + ".png", System.Drawing.Imaging.ImageFormat.Png);
\r
268 using (MemoryStream memoryStream = new MemoryStream())
\r
270 bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
\r
273 BitmapImage wpfBitmap = new BitmapImage();
\r
274 wpfBitmap.BeginInit();
\r
275 wpfBitmap.CacheOption = BitmapCacheOption.OnLoad;
\r
276 wpfBitmap.StreamSource = memoryStream;
\r
277 wpfBitmap.EndInit();
\r
283 public static IntPtr AddOffset(IntPtr src, int offset)
\r
285 return new IntPtr(src.ToInt64() + offset);
\r
289 /// Starts an encode with the given job.
\r
291 /// <param name="job">The job to start.</param>
\r
292 public void StartEncode(EncodeJob job)
\r
294 this.StartEncode(job, false, 0, 0);
\r
298 /// Starts an encode with the given job.
\r
300 /// <param name="job">The job to start.</param>
\r
301 /// <param name="preview">True if this is a preview encode.</param>
\r
302 /// <param name="previewNumber">The preview number to start the encode at (0-based).</param>
\r
303 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
304 public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
306 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
307 this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);
\r
309 if (!preview && job.EncodingProfile.IncludeChapterMarkers)
\r
311 Title title = this.GetTitle(job.Title);
\r
312 int numChapters = title.Chapters.Count;
\r
314 if (job.UseDefaultChapterNames)
\r
316 for (int i = 0; i < numChapters; i++)
\r
318 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));
\r
323 for (int i = 0; i < numChapters; i++)
\r
325 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);
\r
330 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
332 if (job.EncodingProfile.TwoPass)
\r
334 nativeJob.pass = 2;
\r
336 string x264Opts = job.EncodingProfile.X264Options ?? string.Empty;
\r
337 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Opts);
\r
338 this.encodeAllocatedMemory.Add(nativeJob.x264opts);
\r
340 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
343 HbLib.hb_start(this.hbHandle);
\r
345 this.encodePollTimer = new System.Timers.Timer();
\r
346 this.encodePollTimer.Interval = EncodePollIntervalMs;
\r
348 this.encodePollTimer.Elapsed += (o, e) =>
\r
350 this.PollEncodeProgress();
\r
352 this.encodePollTimer.Start();
\r
356 /// Pauses the current encode.
\r
358 public void PauseEncode()
\r
360 HbLib.hb_pause(this.hbHandle);
\r
364 /// Resumes a paused encode.
\r
366 public void ResumeEncode()
\r
368 HbLib.hb_resume(this.hbHandle);
\r
372 /// Stops the current encode.
\r
374 public void StopEncode()
\r
376 HbLib.hb_stop(this.hbHandle);
\r
378 // Also remove all jobs from the queue (in case we stopped a 2-pass encode)
\r
379 var currentJobs = new List<IntPtr>();
\r
381 int jobs = HbLib.hb_count(this.hbHandle);
\r
382 for (int i = 0; i < jobs; i++)
\r
384 currentJobs.Add(HbLib.hb_job(this.hbHandle, 0));
\r
387 foreach (IntPtr job in currentJobs)
\r
389 HbLib.hb_rem(this.hbHandle, job);
\r
394 /// Gets the final size when using Anamorphic for a given encode job.
\r
396 /// <param name="job">The encode job to use.</param>
\r
397 /// <param name="width">The storage width.</param>
\r
398 /// <param name="height">The storage height.</param>
\r
399 /// <param name="parWidth">The pixel aspect X number.</param>
\r
400 /// <param name="parHeight">The pixel aspect Y number.</param>
\r
401 public void GetAnamorphicSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight)
\r
403 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
404 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
408 int refParWidth = 0;
\r
409 int refParHeight = 0;
\r
410 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
411 HbLib.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
412 //HbLib.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
413 InteropUtilities.FreeMemory(allocatedMemory);
\r
416 height = refHeight;
\r
417 parWidth = refParWidth;
\r
418 parHeight = refParHeight;
\r
422 /// Frees any resources associated with this object.
\r
424 public void Dispose()
\r
426 this.Dispose(true);
\r
427 GC.SuppressFinalize(this);
\r
431 /// Call before app shutdown. Performs global cleanup.
\r
433 public static void DisposeGlobal()
\r
435 HbLib.hb_global_close();
\r
439 /// Frees any resources associated with this object.
\r
441 /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param>
\r
442 protected virtual void Dispose(bool disposing)
\r
446 // Free other state (managed objects).
\r
449 // Free unmanaged objects.
\r
450 IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
\r
451 Marshal.WriteIntPtr(handlePtr, this.hbHandle);
\r
452 HbLib.hb_close(handlePtr);
\r
453 Marshal.FreeHGlobal(handlePtr);
\r
457 /// Checks the status of the ongoing scan.
\r
459 private void PollScanProgress()
\r
461 hb_state_s state = new hb_state_s();
\r
462 HbLib.hb_get_state(this.hbHandle, ref state);
\r
464 if (state.state == NativeConstants.HB_STATE_SCANNING)
\r
466 if (this.ScanProgress != null)
\r
468 int currentTitle = state.param.scanning.title_cur;
\r
469 int totalTitles = state.param.scanning.title_count;
\r
470 this.ScanProgress(this, new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = totalTitles });
\r
473 else if (state.state == NativeConstants.HB_STATE_SCANDONE)
\r
475 this.titles = new List<Title>();
\r
477 IntPtr listPtr = HbLib.hb_get_titles(this.hbHandle);
\r
478 this.originalTitles = InteropUtilities.ConvertList<hb_title_s>(listPtr);
\r
480 foreach (hb_title_s title in this.originalTitles)
\r
482 var newTitle = this.ConvertTitle(title);
\r
483 this.titles.Add(newTitle);
\r
486 this.scanPollTimer.Stop();
\r
488 if (this.ScanCompleted != null)
\r
490 this.ScanCompleted(this, new EventArgs());
\r
496 /// Checks the status of the ongoing encode.
\r
498 private void PollEncodeProgress()
\r
500 hb_state_s state = new hb_state_s();
\r
501 HbLib.hb_get_state(this.hbHandle, ref state);
\r
503 if (state.state == NativeConstants.HB_STATE_WORKING)
\r
505 if (this.EncodeProgress != null)
\r
507 var progressEventArgs = new EncodeProgressEventArgs
\r
509 FractionComplete = state.param.working.progress,
\r
510 CurrentFrameRate = state.param.working.rate_cur,
\r
511 AverageFrameRate = state.param.working.rate_avg,
\r
512 EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds),
\r
513 Pass = state.param.working.job_cur
\r
516 this.EncodeProgress(this, progressEventArgs);
\r
519 else if (state.state == NativeConstants.HB_STATE_MUXING)
\r
521 //System.Diagnostics.Debug.WriteLine("Muxing...");
\r
523 else if (state.state == NativeConstants.HB_STATE_WORKDONE)
\r
525 InteropUtilities.FreeMemory(this.encodeAllocatedMemory);
\r
526 this.encodePollTimer.Stop();
\r
528 if (this.EncodeCompleted != null)
\r
530 this.EncodeCompleted(this, new EventArgs());
\r
536 /// Applies the encoding job to the native memory structure and returns a list of memory
\r
537 /// locations allocated during this.
\r
539 /// <param name="nativeJob">The native structure to apply to job info to.</param>
\r
540 /// <param name="job">The job info to apply.</param>
\r
541 /// <param name="preview">True if this is a preview encode.</param>
\r
542 /// <param name="previewNumber">The preview number (0-based) to encode.</param>
\r
543 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
544 /// <returns>The list of memory locations allocated for the job.</returns>
\r
545 private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
547 var allocatedMemory = new List<IntPtr>();
\r
548 Title title = this.GetTitle(job.Title);
\r
549 hb_title_s originalTitle = this.GetOriginalTitle(job.Title);
\r
551 EncodingProfile profile = job.EncodingProfile;
\r
555 nativeJob.start_at_preview = previewNumber + 1;
\r
556 nativeJob.seek_points = 10;
\r
558 // There are 90,000 PTS per second.
\r
559 nativeJob.pts_to_stop = previewSeconds * 90000;
\r
561 else if (job.ChapterStart > 0 && job.ChapterEnd > 0)
\r
563 nativeJob.chapter_start = job.ChapterStart;
\r
564 nativeJob.chapter_end = job.ChapterEnd;
\r
568 nativeJob.chapter_start = 1;
\r
569 nativeJob.chapter_end = title.Chapters.Count;
\r
572 nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;
\r
576 if (profile.CustomCropping)
\r
578 crop = profile.Cropping;
\r
582 crop = title.AutoCropDimensions;
\r
585 nativeJob.crop[0] = crop.Top;
\r
586 nativeJob.crop[1] = crop.Bottom;
\r
587 nativeJob.crop[2] = crop.Left;
\r
588 nativeJob.crop[3] = crop.Right;
\r
590 List<IntPtr> filterList = new List<IntPtr>();
\r
591 if (profile.Deinterlace != Deinterlace.Off)
\r
593 nativeJob.deinterlace = 1;
\r
594 string settings = null;
\r
596 switch (profile.Deinterlace)
\r
598 case Deinterlace.Fast:
\r
601 case Deinterlace.Slow:
\r
604 case Deinterlace.Slower:
\r
607 case Deinterlace.Custom:
\r
608 settings = profile.CustomDeinterlace;
\r
614 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);
\r
615 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEINTERLACE, settings));
\r
619 nativeJob.deinterlace = 0;
\r
622 if (profile.Detelecine != Detelecine.Off)
\r
624 string settings = null;
\r
625 if (profile.Detelecine == Detelecine.Custom)
\r
627 settings = profile.CustomDetelecine;
\r
630 this.AddFilter(filterList, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);
\r
631 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DETELECINE, settings));
\r
634 if (profile.Decomb != Decomb.Off)
\r
636 string settings = null;
\r
637 if (profile.Decomb == Decomb.Custom)
\r
639 settings = profile.CustomDecomb;
\r
642 this.AddFilter(filterList, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);
\r
643 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DECOMB, settings));
\r
646 if (profile.Deblock > 0)
\r
648 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);
\r
649 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString()));
\r
652 if (profile.Denoise != Denoise.Off)
\r
654 string settings = null;
\r
655 switch (profile.Denoise)
\r
658 settings = "2:1:2:3";
\r
660 case Denoise.Medium:
\r
661 settings = "3:2:2:3";
\r
663 case Denoise.Strong:
\r
664 settings = "7:7:5:5";
\r
666 case Denoise.Custom:
\r
667 settings = profile.CustomDenoise;
\r
673 this.AddFilter(filterList, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);
\r
674 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DENOISE, settings));
\r
677 NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterList);
\r
678 nativeJob.filters = filterListNative.ListPtr;
\r
679 allocatedMemory.AddRange(filterListNative.AllocatedMemory);
\r
681 int width = profile.Width;
\r
682 int height = profile.Height;
\r
686 width = title.Resolution.Width;
\r
689 if (profile.MaxWidth > 0 && width > profile.MaxWidth)
\r
691 width = profile.MaxWidth;
\r
696 height = title.Resolution.Height;
\r
699 if (profile.MaxHeight > 0 && height > profile.MaxHeight)
\r
701 height = profile.MaxHeight;
\r
704 nativeJob.grayscale = profile.Grayscale ? 1 : 0;
\r
706 switch (profile.Anamorphic)
\r
708 case Anamorphic.None:
\r
709 nativeJob.anamorphic.mode = 0;
\r
711 if (profile.KeepDisplayAspect)
\r
713 if (profile.Width == 0 && profile.Height == 0 || profile.Width == 0)
\r
715 width = (int)((double)height * this.GetTitle(job.Title).AspectRatio);
\r
717 else if (profile.Height == 0)
\r
719 height = (int)((double)width / this.GetTitle(job.Title).AspectRatio);
\r
723 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
725 case Anamorphic.Strict:
\r
726 nativeJob.anamorphic.mode = 1;
\r
728 case Anamorphic.Loose:
\r
729 nativeJob.anamorphic.mode = 2;
\r
731 case Anamorphic.Custom:
\r
732 nativeJob.anamorphic.mode = 3;
\r
734 nativeJob.modulus = profile.Modulus;
\r
736 if (profile.UseDisplayWidth)
\r
738 if (profile.KeepDisplayAspect)
\r
740 height = (int)((double)profile.DisplayWidth / this.GetTitle(job.Title).AspectRatio);
\r
743 nativeJob.anamorphic.dar_width = profile.DisplayWidth;
\r
744 nativeJob.anamorphic.dar_height = height;
\r
745 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
749 nativeJob.anamorphic.par_width = profile.PixelAspectX;
\r
750 nativeJob.anamorphic.par_height = profile.PixelAspectY;
\r
751 nativeJob.anamorphic.keep_display_aspect = 0;
\r
758 nativeJob.width = width;
\r
759 nativeJob.height = height;
\r
761 nativeJob.maxWidth = profile.MaxWidth;
\r
762 nativeJob.maxHeight = profile.MaxHeight;
\r
764 switch (profile.VideoEncoder)
\r
766 case VideoEncoder.X264:
\r
767 nativeJob.vcodec = NativeConstants.HB_VCODEC_X264;
\r
769 case VideoEncoder.Theora:
\r
770 nativeJob.vcodec = NativeConstants.HB_VCODEC_THEORA;
\r
772 case VideoEncoder.FFMpeg:
\r
773 nativeJob.vcodec = NativeConstants.HB_VCODEC_FFMPEG;
\r
779 if (profile.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality)
\r
781 nativeJob.vquality = (float)profile.Quality;
\r
782 nativeJob.vbitrate = 0;
\r
784 else if (profile.VideoEncodeRateType == VideoEncodeRateType.AverageBitrate)
\r
786 nativeJob.vquality = -1;
\r
787 nativeJob.vbitrate = profile.VideoBitrate;
\r
796 List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);
\r
798 List<hb_audio_s> audioList = new List<hb_audio_s>();
\r
800 foreach (AudioEncoding encoding in profile.AudioEncodings)
\r
802 if (encoding.InputNumber == 0)
\r
804 // Add this encoding for all chosen tracks
\r
805 foreach (int chosenTrack in job.ChosenAudioTracks)
\r
807 if (titleAudio.Count >= chosenTrack)
\r
809 audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++));
\r
813 else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
\r
815 // Add this encoding for the specified track, if it exists
\r
816 int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
\r
817 audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++));
\r
821 NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);
\r
822 nativeJob.list_audio = nativeAudioList.ListPtr;
\r
823 allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);
\r
825 List<hb_subtitle_s> subtitleList = new List<hb_subtitle_s>();
\r
827 if (job.Subtitles != null)
\r
829 if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)
\r
831 List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);
\r
833 foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles)
\r
835 if (sourceSubtitle.TrackNumber == 0)
\r
837 // Use subtitle search.
\r
838 nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0;
\r
839 nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
841 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv)
\r
843 nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
846 nativeJob.indepth_scan = 1;
\r
850 // Use specified subtitle.
\r
851 hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1];
\r
852 nativeSubtitle.config.force = sourceSubtitle.Forced ? 1 : 0;
\r
853 nativeSubtitle.config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
855 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv && nativeSubtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
857 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
860 subtitleList.Add(nativeSubtitle);
\r
865 if (job.Subtitles.SrtSubtitles != null)
\r
867 foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles)
\r
869 hb_subtitle_s nativeSubtitle = new hb_subtitle_s();
\r
870 nativeSubtitle.id = subtitleList.Count << 8 | 0xFF;
\r
871 nativeSubtitle.iso639_2 = srtSubtitle.LanguageCode;
\r
872 nativeSubtitle.lang = LanguageCodes.Decode(srtSubtitle.LanguageCode);
\r
873 nativeSubtitle.source = hb_subtitle_s_subsource.SRTSUB;
\r
874 nativeSubtitle.format = hb_subtitle_s_subtype.TEXTSUB;
\r
876 nativeSubtitle.config.src_codeset = srtSubtitle.CharacterCode;
\r
877 nativeSubtitle.config.src_filename = srtSubtitle.FileName;
\r
878 nativeSubtitle.config.offset = srtSubtitle.Offset;
\r
879 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
880 nativeSubtitle.config.default_track = srtSubtitle.Default ? 1 : 0;
\r
882 subtitleList.Add(nativeSubtitle);
\r
887 NativeList nativeSubtitleList = InteropUtilities.ConvertListBack<hb_subtitle_s>(subtitleList);
\r
888 nativeJob.list_subtitle = nativeSubtitleList.ListPtr;
\r
889 allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);
\r
891 if (profile.OutputFormat == OutputFormat.Mp4)
\r
893 nativeJob.mux = NativeConstants.HB_MUX_MP4;
\r
897 nativeJob.mux = NativeConstants.HB_MUX_MKV;
\r
900 nativeJob.file = job.OutputPath;
\r
902 nativeJob.largeFileSize = profile.LargeFile ? 1 : 0;
\r
903 nativeJob.mp4_optimize = profile.Optimize ? 1 : 0;
\r
904 nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0;
\r
906 string x264Options = profile.X264Options ?? string.Empty;
\r
907 if (profile.TwoPass)
\r
909 nativeJob.pass = 1;
\r
911 if (profile.TurboFirstPass)
\r
913 if (x264Options == string.Empty)
\r
915 x264Options = TurboX264Opts;
\r
919 x264Options += ":" + TurboX264Opts;
\r
924 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Options);
\r
925 allocatedMemory.Add(nativeJob.x264opts);
\r
929 if (title.AngleCount > 1)
\r
931 nativeJob.angle = job.Angle;
\r
936 return allocatedMemory;
\r
940 /// Adds a filter to the given filter list.
\r
942 /// <param name="filterList">The filter list to add to.</param>
\r
943 /// <param name="filterType">The type of filter.</param>
\r
944 /// <param name="settings">Settings for the filter.</param>
\r
945 /// <param name="allocatedMemory">The list of allocated memory.</param>
\r
946 private void AddFilter(List<IntPtr> filterList, int filterType, string settings, List<IntPtr> allocatedMemory)
\r
948 IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings);
\r
949 filterList.Add(HbLib.hb_get_filter_object(filterType, settingsNativeString));
\r
951 allocatedMemory.Add(settingsNativeString);
\r
955 /// Gets the title, given the 1-based index.
\r
957 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
958 /// <returns>The requested Title.</returns>
\r
959 private Title GetTitle(int titleIndex)
\r
961 return this.Titles.SingleOrDefault(title => title.TitleNumber == titleIndex);
\r
965 /// Gets the native title object from the title index.
\r
967 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
968 /// <returns>Gets the native title object for the given index.</returns>
\r
969 private hb_title_s GetOriginalTitle(int titleIndex)
\r
971 List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList();
\r
972 if (matchingTitles.Count == 0)
\r
974 throw new ArgumentException("Could not find specified title.");
\r
977 if (matchingTitles.Count > 1)
\r
979 throw new ArgumentException("Multiple titles matched.");
\r
982 return matchingTitles[0];
\r
986 /// Applies an audio encoding to a native audio encoding base structure.
\r
988 /// <param name="encoding">The encoding to apply.</param>
\r
989 /// <param name="baseStruct">The base native structure.</param>
\r
990 /// <param name="track"></param>
\r
991 /// <param name="outputTrack"></param>
\r
992 /// <returns>The resulting native audio structure.</returns>
\r
993 private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int track, int outputTrack)
\r
995 hb_audio_s nativeAudio = baseStruct;
\r
997 //nativeAudio.config.input.track = track;
\r
998 nativeAudio.config.output.track = outputTrack;
\r
1000 switch (encoding.Encoder)
\r
1002 case AudioEncoder.Ac3Passthrough:
\r
1003 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_AC3;
\r
1005 case AudioEncoder.DtsPassthrough:
\r
1006 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_DCA;
\r
1008 case AudioEncoder.Faac:
\r
1009 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_FAAC;
\r
1011 case AudioEncoder.Lame:
\r
1012 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_LAME;
\r
1014 case AudioEncoder.Vorbis:
\r
1015 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_VORBIS;
\r
1021 nativeAudio.config.output.bitrate = encoding.Bitrate;
\r
1022 nativeAudio.config.output.dynamic_range_compression = 0.0;
\r
1024 switch (encoding.Mixdown)
\r
1026 case Mixdown.DolbyProLogicII:
\r
1027 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBYPLII;
\r
1029 case Mixdown.DolbySurround:
\r
1030 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBY;
\r
1032 case Mixdown.Mono:
\r
1033 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_MONO;
\r
1035 case Mixdown.SixChannelDiscrete:
\r
1036 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_6CH;
\r
1038 case Mixdown.Stereo:
\r
1039 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_STEREO;
\r
1045 if (encoding.SampleRate != null)
\r
1047 nativeAudio.config.output.samplerate = (int)(double.Parse(encoding.SampleRate) * 1000);
\r
1050 nativeAudio.padding = new byte[24600];
\r
1052 return nativeAudio;
\r
1056 /// Converts a native title to a Title object.
\r
1058 /// <param name="title">The native title structure.</param>
\r
1059 /// <returns>The managed Title object.</returns>
\r
1060 private Title ConvertTitle(hb_title_s title)
\r
1062 var newTitle = new Title
\r
1064 TitleNumber = title.index,
\r
1065 Resolution = new Size(title.width, title.height),
\r
1066 ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height),
\r
1067 Duration = TimeSpan.FromSeconds(((double)title.duration) / 90000),
\r
1068 AutoCropDimensions = new Cropping
\r
1070 Top = title.crop[0],
\r
1071 Bottom = title.crop[1],
\r
1072 Left = title.crop[2],
\r
1073 Right = title.crop[3]
\r
1075 AspectRatio = title.aspect,
\r
1076 AngleCount = title.angle_count
\r
1079 int currentSubtitleTrack = 1;
\r
1080 List<hb_subtitle_s> subtitleList = InteropUtilities.ConvertList<hb_subtitle_s>(title.list_subtitle);
\r
1081 foreach (hb_subtitle_s subtitle in subtitleList)
\r
1083 var newSubtitle = new Subtitle
\r
1085 TrackNumber = currentSubtitleTrack,
\r
1086 Language = subtitle.lang,
\r
1087 LanguageCode = subtitle.iso639_2
\r
1090 if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
1092 newSubtitle.SubtitleType = SubtitleType.Picture;
\r
1094 else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB)
\r
1096 newSubtitle.SubtitleType = SubtitleType.Text;
\r
1099 newTitle.Subtitles.Add(newSubtitle);
\r
1101 currentSubtitleTrack++;
\r
1104 int currentAudioTrack = 1;
\r
1105 List<hb_audio_s> audioList = InteropUtilities.ConvertList<hb_audio_s>(title.list_audio);
\r
1106 foreach (hb_audio_s audio in audioList)
\r
1108 var newAudio = new AudioTrack
\r
1110 TrackNumber = currentAudioTrack,
\r
1111 Language = audio.config.lang.simple,
\r
1112 LanguageCode = audio.config.lang.iso639_2,
\r
1113 Description = audio.config.lang.description
\r
1116 newTitle.AudioTracks.Add(newAudio);
\r
1118 currentAudioTrack++;
\r
1121 List<hb_chapter_s> chapterList = InteropUtilities.ConvertList<hb_chapter_s>(title.list_chapter);
\r
1122 foreach (hb_chapter_s chapter in chapterList)
\r
1124 var newChapter = new Chapter
\r
1126 ChapterNumber = chapter.index,
\r
1127 Duration = TimeSpan.FromSeconds(((double)chapter.duration) / 90000)
\r
1130 newTitle.Chapters.Add(newChapter);
\r