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 using System.Drawing;
\r
12 namespace HandBrake.Interop
\r
15 using System.Collections.Generic;
\r
18 using System.Runtime.InteropServices;
\r
19 using System.Windows.Media.Imaging;
\r
21 using Model.Encoding;
\r
25 /// A wrapper for a HandBrake instance.
\r
27 public class HandBrakeInstance : IDisposable
\r
30 /// The number of MS between status polls when scanning.
\r
32 private const double ScanPollIntervalMs = 200;
\r
35 /// The number of MS between status polls when encoding.
\r
37 private const double EncodePollIntervalMs = 200;
\r
40 /// X264 options to add for a turbo first pass.
\r
42 private const string TurboX264Opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0";
\r
45 /// The native handle to the HandBrake instance.
\r
47 private IntPtr hbHandle;
\r
50 /// The timer to poll for scan status.
\r
52 private System.Timers.Timer scanPollTimer;
\r
55 /// The timer to poll for encode status.
\r
57 private System.Timers.Timer encodePollTimer;
\r
60 /// The list of original titles in native structure form.
\r
62 private List<hb_title_s> originalTitles;
\r
65 /// The list of titles on this instance.
\r
67 private List<Title> titles;
\r
70 /// A list of native memory locations allocated by this instance.
\r
72 private List<IntPtr> encodeAllocatedMemory;
\r
75 /// The callback for log messages from HandBrake.
\r
77 private static LoggingCallback loggingCallback;
\r
80 /// The callback for error messages from HandBrake.
\r
82 private static LoggingCallback errorCallback;
\r
85 /// Fires for progress updates when scanning.
\r
87 public event EventHandler<ScanProgressEventArgs> ScanProgress;
\r
90 /// Fires when a scan has completed.
\r
92 public event EventHandler<EventArgs> ScanCompleted;
\r
95 /// Fires for progress updates when encoding.
\r
97 public event EventHandler<EncodeProgressEventArgs> EncodeProgress;
\r
100 /// Fires when an encode has completed.
\r
102 public event EventHandler<EventArgs> EncodeCompleted;
\r
105 /// Fires when HandBrake has logged a message.
\r
107 public static event EventHandler<MessageLoggedEventArgs> MessageLogged;
\r
110 /// Fires when HandBrake has logged an error.
\r
112 public static event EventHandler<MessageLoggedEventArgs> ErrorLogged;
\r
117 ~HandBrakeInstance()
\r
119 this.Dispose(false);
\r
123 /// The list of titles on this instance.
\r
125 public List<Title> Titles
\r
129 return this.titles;
\r
134 /// Initializes this instance.
\r
136 /// <param name="verbosity"></param>
\r
137 public void Initialize(int verbosity)
\r
139 // Register the logger if we have not already
\r
140 if (loggingCallback == null)
\r
142 // Keep the callback as a member to prevent it from being garbage collected.
\r
143 loggingCallback = new LoggingCallback(HandBrakeInstance.LoggingHandler);
\r
144 errorCallback = new LoggingCallback(HandBrakeInstance.ErrorHandler);
\r
145 HbLib.hb_register_logger(loggingCallback);
\r
146 HbLib.hb_register_error_handler(errorCallback);
\r
149 this.hbHandle = HbLib.hb_init(verbosity, 0);
\r
153 /// Handles log messages from HandBrake.
\r
155 /// <param name="message">The log message (including newline).</param>
\r
156 public static void LoggingHandler(string message)
\r
158 if (!string.IsNullOrEmpty(message))
\r
160 string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
\r
162 if (messageParts.Length > 0)
\r
164 if (MessageLogged != null)
\r
166 MessageLogged(null, new MessageLoggedEventArgs { Message = messageParts[0] });
\r
169 System.Diagnostics.Debug.WriteLine(messageParts[0]);
\r
175 /// Handles errors from HandBrake.
\r
177 /// <param name="message">The error message.</param>
\r
178 public static void ErrorHandler(string message)
\r
180 if (!string.IsNullOrEmpty(message))
\r
182 if (ErrorLogged != null)
\r
184 ErrorLogged(null, new MessageLoggedEventArgs { Message = message });
\r
187 System.Diagnostics.Debug.WriteLine("ERROR: " + message);
\r
192 /// Starts scanning the given path.
\r
194 /// <param name="path">The path to the video to scan.</param>
\r
195 /// <param name="previewCount">The number of preview images to make.</param>
\r
196 public void StartScan(string path, int previewCount)
\r
198 this.StartScan(path, previewCount, 0);
\r
202 /// Starts a scan of the given path.
\r
204 /// <param name="path">The path of the video to scan.</param>
\r
205 /// <param name="previewCount">The number of previews to make on each title.</param>
\r
206 /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param>
\r
207 public void StartScan(string path, int previewCount, int titleIndex)
\r
209 HbLib.hb_scan(hbHandle, path, titleIndex, previewCount, 1);
\r
210 this.scanPollTimer = new System.Timers.Timer();
\r
211 this.scanPollTimer.Interval = ScanPollIntervalMs;
\r
213 // Lambda notation used to make sure we can view any JIT exceptions the method throws
\r
214 this.scanPollTimer.Elapsed += (o, e) =>
\r
216 this.PollScanProgress();
\r
218 this.scanPollTimer.Start();
\r
222 /// Gets an image for the given job and preview
\r
225 /// Only incorporates sizing and aspect ratio into preview image.
\r
227 /// <param name="job">The encode job to preview.</param>
\r
228 /// <param name="previewNumber">The index of the preview to get (0-based).</param>
\r
229 /// <returns>An image with the requested preview.</returns>
\r
230 public BitmapImage GetPreview(EncodeJob job, int previewNumber)
\r
232 hb_title_s title = this.GetOriginalTitle(job.Title);
\r
234 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
\r
235 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
237 // Create a new job pointer from our modified job object
\r
238 IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));
\r
239 Marshal.StructureToPtr(nativeJob, newJob, false);
\r
240 allocatedMemory.Add(newJob);
\r
242 int outputWidth = nativeJob.width;
\r
243 int outputHeight = nativeJob.height;
\r
244 int imageBufferSize = outputWidth * outputHeight * 4;
\r
245 IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);
\r
246 allocatedMemory.Add(nativeBuffer);
\r
247 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
248 HbLib.hb_get_preview_by_index(this.hbHandle, job.Title, previewNumber, nativeBuffer);
\r
250 // Copy the filled image buffer to a managed array.
\r
251 byte[] managedBuffer = new byte[imageBufferSize];
\r
252 Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);
\r
254 InteropUtilities.FreeMemory(allocatedMemory);
\r
256 System.Drawing.Bitmap bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight);
\r
257 System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, outputWidth, outputHeight), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
\r
259 IntPtr ptr = bitmapData.Scan0;
\r
261 for (int i = 0; i < nativeJob.height; i++)
\r
263 Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4);
\r
264 ptr = AddOffset(ptr, bitmapData.Stride);
\r
267 bitmap.UnlockBits(bitmapData);
\r
268 //bitmap.Save(@"d:\docs\test_" + previewNumber + ".png", System.Drawing.Imaging.ImageFormat.Png);
\r
270 using (MemoryStream memoryStream = new MemoryStream())
\r
272 bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
\r
275 BitmapImage wpfBitmap = new BitmapImage();
\r
276 wpfBitmap.BeginInit();
\r
277 wpfBitmap.CacheOption = BitmapCacheOption.OnLoad;
\r
278 wpfBitmap.StreamSource = memoryStream;
\r
279 wpfBitmap.EndInit();
\r
285 public static IntPtr AddOffset(IntPtr src, int offset)
\r
287 return new IntPtr(src.ToInt64() + offset);
\r
291 /// Starts an encode with the given job.
\r
293 /// <param name="job">The job to start.</param>
\r
294 public void StartEncode(EncodeJob job)
\r
296 this.StartEncode(job, false, 0, 0);
\r
300 /// Starts an encode with the given job.
\r
302 /// <param name="job">The job to start.</param>
\r
303 /// <param name="preview">True if this is a preview encode.</param>
\r
304 /// <param name="previewNumber">The preview number to start the encode at (0-based).</param>
\r
305 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
306 public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
308 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
309 this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds);
\r
311 if (!preview && job.EncodingProfile.IncludeChapterMarkers)
\r
313 Title title = this.GetTitle(job.Title);
\r
314 int numChapters = title.Chapters.Count;
\r
316 if (job.UseDefaultChapterNames)
\r
318 for (int i = 0; i < numChapters; i++)
\r
320 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));
\r
325 for (int i = 0; i < numChapters; i++)
\r
327 HbLib.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);
\r
332 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
334 if (job.EncodingProfile.TwoPass)
\r
336 nativeJob.pass = 2;
\r
338 string x264Opts = job.EncodingProfile.X264Options ?? string.Empty;
\r
339 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Opts);
\r
340 this.encodeAllocatedMemory.Add(nativeJob.x264opts);
\r
342 HbLib.hb_add(this.hbHandle, ref nativeJob);
\r
345 HbLib.hb_start(this.hbHandle);
\r
347 this.encodePollTimer = new System.Timers.Timer();
\r
348 this.encodePollTimer.Interval = EncodePollIntervalMs;
\r
350 this.encodePollTimer.Elapsed += (o, e) =>
\r
352 this.PollEncodeProgress();
\r
354 this.encodePollTimer.Start();
\r
358 /// Pauses the current encode.
\r
360 public void PauseEncode()
\r
362 HbLib.hb_pause(this.hbHandle);
\r
366 /// Resumes a paused encode.
\r
368 public void ResumeEncode()
\r
370 HbLib.hb_resume(this.hbHandle);
\r
374 /// Stops the current encode.
\r
376 public void StopEncode()
\r
378 HbLib.hb_stop(this.hbHandle);
\r
380 // Also remove all jobs from the queue (in case we stopped a 2-pass encode)
\r
381 var currentJobs = new List<IntPtr>();
\r
383 int jobs = HbLib.hb_count(this.hbHandle);
\r
384 for (int i = 0; i < jobs; i++)
\r
386 currentJobs.Add(HbLib.hb_job(this.hbHandle, 0));
\r
389 foreach (IntPtr job in currentJobs)
\r
391 HbLib.hb_rem(this.hbHandle, job);
\r
396 /// Gets the final size when using Anamorphic for a given encode job.
\r
398 /// <param name="job">The encode job to use.</param>
\r
399 /// <param name="width">The storage width.</param>
\r
400 /// <param name="height">The storage height.</param>
\r
401 /// <param name="parWidth">The pixel aspect X number.</param>
\r
402 /// <param name="parHeight">The pixel aspect Y number.</param>
\r
403 public void GetAnamorphicSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight)
\r
405 hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
\r
406 List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job, false, 0, 0);
\r
410 int refParWidth = 0;
\r
411 int refParHeight = 0;
\r
412 HbLib.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
\r
413 HbLib.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
414 //HbLib.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
\r
415 InteropUtilities.FreeMemory(allocatedMemory);
\r
418 height = refHeight;
\r
419 parWidth = refParWidth;
\r
420 parHeight = refParHeight;
\r
424 /// Frees any resources associated with this object.
\r
426 public void Dispose()
\r
428 this.Dispose(true);
\r
429 GC.SuppressFinalize(this);
\r
433 /// Call before app shutdown. Performs global cleanup.
\r
435 public static void DisposeGlobal()
\r
437 HbLib.hb_global_close();
\r
441 /// Frees any resources associated with this object.
\r
443 /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param>
\r
444 protected virtual void Dispose(bool disposing)
\r
448 // Free other state (managed objects).
\r
451 // Free unmanaged objects.
\r
452 IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
\r
453 Marshal.WriteIntPtr(handlePtr, this.hbHandle);
\r
454 HbLib.hb_close(handlePtr);
\r
455 Marshal.FreeHGlobal(handlePtr);
\r
459 /// Checks the status of the ongoing scan.
\r
461 private void PollScanProgress()
\r
463 hb_state_s state = new hb_state_s();
\r
464 HbLib.hb_get_state(this.hbHandle, ref state);
\r
466 if (state.state == NativeConstants.HB_STATE_SCANNING)
\r
468 if (this.ScanProgress != null)
\r
470 int currentTitle = state.param.scanning.title_cur;
\r
471 int totalTitles = state.param.scanning.title_count;
\r
472 this.ScanProgress(this, new ScanProgressEventArgs { CurrentTitle = currentTitle, Titles = totalTitles });
\r
475 else if (state.state == NativeConstants.HB_STATE_SCANDONE)
\r
477 this.titles = new List<Title>();
\r
479 IntPtr listPtr = HbLib.hb_get_titles(this.hbHandle);
\r
480 this.originalTitles = InteropUtilities.ConvertList<hb_title_s>(listPtr);
\r
482 foreach (hb_title_s title in this.originalTitles)
\r
484 var newTitle = this.ConvertTitle(title);
\r
485 this.titles.Add(newTitle);
\r
488 this.scanPollTimer.Stop();
\r
490 if (this.ScanCompleted != null)
\r
492 this.ScanCompleted(this, new EventArgs());
\r
498 /// Checks the status of the ongoing encode.
\r
500 private void PollEncodeProgress()
\r
502 hb_state_s state = new hb_state_s();
\r
503 HbLib.hb_get_state(this.hbHandle, ref state);
\r
505 if (state.state == NativeConstants.HB_STATE_WORKING)
\r
507 if (this.EncodeProgress != null)
\r
509 var progressEventArgs = new EncodeProgressEventArgs
\r
511 FractionComplete = state.param.working.progress,
\r
512 CurrentFrameRate = state.param.working.rate_cur,
\r
513 AverageFrameRate = state.param.working.rate_avg,
\r
514 EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds),
\r
515 Pass = state.param.working.job_cur
\r
518 this.EncodeProgress(this, progressEventArgs);
\r
521 else if (state.state == NativeConstants.HB_STATE_MUXING)
\r
523 //System.Diagnostics.Debug.WriteLine("Muxing...");
\r
525 else if (state.state == NativeConstants.HB_STATE_WORKDONE)
\r
527 InteropUtilities.FreeMemory(this.encodeAllocatedMemory);
\r
528 this.encodePollTimer.Stop();
\r
530 if (this.EncodeCompleted != null)
\r
532 this.EncodeCompleted(this, new EventArgs());
\r
538 /// Applies the encoding job to the native memory structure and returns a list of memory
\r
539 /// locations allocated during this.
\r
541 /// <param name="nativeJob">The native structure to apply to job info to.</param>
\r
542 /// <param name="job">The job info to apply.</param>
\r
543 /// <param name="preview">True if this is a preview encode.</param>
\r
544 /// <param name="previewNumber">The preview number (0-based) to encode.</param>
\r
545 /// <param name="previewSeconds">The number of seconds in the preview.</param>
\r
546 /// <returns>The list of memory locations allocated for the job.</returns>
\r
547 private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds)
\r
549 var allocatedMemory = new List<IntPtr>();
\r
550 Title title = this.GetTitle(job.Title);
\r
551 hb_title_s originalTitle = this.GetOriginalTitle(job.Title);
\r
553 EncodingProfile profile = job.EncodingProfile;
\r
557 nativeJob.start_at_preview = previewNumber + 1;
\r
558 nativeJob.seek_points = 10;
\r
560 // There are 90,000 PTS per second.
\r
561 nativeJob.pts_to_stop = previewSeconds * 90000;
\r
563 else if (job.ChapterStart > 0 && job.ChapterEnd > 0)
\r
565 nativeJob.chapter_start = job.ChapterStart;
\r
566 nativeJob.chapter_end = job.ChapterEnd;
\r
570 nativeJob.chapter_start = 1;
\r
571 nativeJob.chapter_end = title.Chapters.Count;
\r
574 nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;
\r
578 if (profile.CustomCropping)
\r
580 crop = profile.Cropping;
\r
584 crop = title.AutoCropDimensions;
\r
587 nativeJob.crop[0] = crop.Top;
\r
588 nativeJob.crop[1] = crop.Bottom;
\r
589 nativeJob.crop[2] = crop.Left;
\r
590 nativeJob.crop[3] = crop.Right;
\r
592 List<IntPtr> filterList = new List<IntPtr>();
\r
593 if (profile.Deinterlace != Deinterlace.Off)
\r
595 nativeJob.deinterlace = 1;
\r
596 string settings = null;
\r
598 switch (profile.Deinterlace)
\r
600 case Deinterlace.Fast:
\r
603 case Deinterlace.Slow:
\r
606 case Deinterlace.Slower:
\r
609 case Deinterlace.Custom:
\r
610 settings = profile.CustomDeinterlace;
\r
616 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);
\r
617 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEINTERLACE, settings));
\r
621 nativeJob.deinterlace = 0;
\r
624 if (profile.Detelecine != Detelecine.Off)
\r
626 string settings = null;
\r
627 if (profile.Detelecine == Detelecine.Custom)
\r
629 settings = profile.CustomDetelecine;
\r
632 this.AddFilter(filterList, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);
\r
633 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DETELECINE, settings));
\r
636 if (profile.Decomb != Decomb.Off)
\r
638 string settings = null;
\r
639 if (profile.Decomb == Decomb.Custom)
\r
641 settings = profile.CustomDecomb;
\r
644 this.AddFilter(filterList, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);
\r
645 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DECOMB, settings));
\r
648 if (profile.Deblock > 0)
\r
650 this.AddFilter(filterList, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);
\r
651 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString()));
\r
654 if (profile.Denoise != Denoise.Off)
\r
656 string settings = null;
\r
657 switch (profile.Denoise)
\r
660 settings = "2:1:2:3";
\r
662 case Denoise.Medium:
\r
663 settings = "3:2:2:3";
\r
665 case Denoise.Strong:
\r
666 settings = "7:7:5:5";
\r
668 case Denoise.Custom:
\r
669 settings = profile.CustomDenoise;
\r
675 this.AddFilter(filterList, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);
\r
676 //filterList.Add(HbLib.hb_get_filter_object(NativeConstants.HB_FILTER_DENOISE, settings));
\r
679 NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterList);
\r
680 nativeJob.filters = filterListNative.ListPtr;
\r
681 allocatedMemory.AddRange(filterListNative.AllocatedMemory);
\r
683 int width = profile.Width;
\r
684 int height = profile.Height;
\r
688 width = title.Resolution.Width;
\r
691 if (profile.MaxWidth > 0 && width > profile.MaxWidth)
\r
693 width = profile.MaxWidth;
\r
698 height = title.Resolution.Height;
\r
701 if (profile.MaxHeight > 0 && height > profile.MaxHeight)
\r
703 height = profile.MaxHeight;
\r
706 nativeJob.grayscale = profile.Grayscale ? 1 : 0;
\r
708 switch (profile.Anamorphic)
\r
710 case Anamorphic.None:
\r
711 nativeJob.anamorphic.mode = 0;
\r
713 if (profile.KeepDisplayAspect)
\r
715 if (profile.Width == 0 && profile.Height == 0 || profile.Width == 0)
\r
717 width = (int)((double)height * this.GetTitle(job.Title).AspectRatio);
\r
719 else if (profile.Height == 0)
\r
721 height = (int)((double)width / this.GetTitle(job.Title).AspectRatio);
\r
725 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
727 case Anamorphic.Strict:
\r
728 nativeJob.anamorphic.mode = 1;
\r
730 case Anamorphic.Loose:
\r
731 nativeJob.anamorphic.mode = 2;
\r
733 case Anamorphic.Custom:
\r
734 nativeJob.anamorphic.mode = 3;
\r
736 nativeJob.modulus = profile.Modulus;
\r
738 if (profile.UseDisplayWidth)
\r
740 if (profile.KeepDisplayAspect)
\r
742 height = (int)((double)profile.DisplayWidth / this.GetTitle(job.Title).AspectRatio);
\r
745 nativeJob.anamorphic.dar_width = profile.DisplayWidth;
\r
746 nativeJob.anamorphic.dar_height = height;
\r
747 nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
\r
751 nativeJob.anamorphic.par_width = profile.PixelAspectX;
\r
752 nativeJob.anamorphic.par_height = profile.PixelAspectY;
\r
753 nativeJob.anamorphic.keep_display_aspect = 0;
\r
760 nativeJob.width = width;
\r
761 nativeJob.height = height;
\r
763 nativeJob.maxWidth = profile.MaxWidth;
\r
764 nativeJob.maxHeight = profile.MaxHeight;
\r
766 switch (profile.VideoEncoder)
\r
768 case VideoEncoder.X264:
\r
769 nativeJob.vcodec = NativeConstants.HB_VCODEC_X264;
\r
771 case VideoEncoder.Theora:
\r
772 nativeJob.vcodec = NativeConstants.HB_VCODEC_THEORA;
\r
774 case VideoEncoder.FFMpeg:
\r
775 nativeJob.vcodec = NativeConstants.HB_VCODEC_FFMPEG;
\r
781 if (profile.VideoEncodeRateType == VideoEncodeRateType.ConstantQuality)
\r
783 nativeJob.vquality = (float)profile.Quality;
\r
784 nativeJob.vbitrate = 0;
\r
786 else if (profile.VideoEncodeRateType == VideoEncodeRateType.AverageBitrate)
\r
788 nativeJob.vquality = -1;
\r
789 nativeJob.vbitrate = profile.VideoBitrate;
\r
798 List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);
\r
800 List<hb_audio_s> audioList = new List<hb_audio_s>();
\r
802 foreach (AudioEncoding encoding in profile.AudioEncodings)
\r
804 if (encoding.InputNumber == 0)
\r
806 // Add this encoding for all chosen tracks
\r
807 foreach (int chosenTrack in job.ChosenAudioTracks)
\r
809 if (titleAudio.Count >= chosenTrack)
\r
811 audioList.Add(ConvertAudioBack(encoding, titleAudio[chosenTrack - 1], chosenTrack, numTracks++));
\r
815 else if (encoding.InputNumber <= job.ChosenAudioTracks.Count)
\r
817 // Add this encoding for the specified track, if it exists
\r
818 int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1];
\r
819 audioList.Add(ConvertAudioBack(encoding, titleAudio[trackNumber - 1], trackNumber, numTracks++));
\r
823 NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList);
\r
824 nativeJob.list_audio = nativeAudioList.ListPtr;
\r
825 allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);
\r
827 List<hb_subtitle_s> subtitleList = new List<hb_subtitle_s>();
\r
829 if (job.Subtitles != null)
\r
831 if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)
\r
833 List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);
\r
835 foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles)
\r
837 if (sourceSubtitle.TrackNumber == 0)
\r
839 // Use subtitle search.
\r
840 nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0;
\r
841 nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
843 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv)
\r
845 nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
848 nativeJob.indepth_scan = 1;
\r
852 // Use specified subtitle.
\r
853 hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1];
\r
854 nativeSubtitle.config.force = sourceSubtitle.Forced ? 1 : 0;
\r
855 nativeSubtitle.config.default_track = sourceSubtitle.Default ? 1 : 0;
\r
857 if (!sourceSubtitle.BurnedIn && profile.OutputFormat == OutputFormat.Mkv && nativeSubtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
859 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
862 subtitleList.Add(nativeSubtitle);
\r
867 if (job.Subtitles.SrtSubtitles != null)
\r
869 foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles)
\r
871 hb_subtitle_s nativeSubtitle = new hb_subtitle_s();
\r
872 nativeSubtitle.id = subtitleList.Count << 8 | 0xFF;
\r
873 nativeSubtitle.iso639_2 = srtSubtitle.LanguageCode;
\r
874 nativeSubtitle.lang = LanguageCodes.Decode(srtSubtitle.LanguageCode);
\r
875 nativeSubtitle.source = hb_subtitle_s_subsource.SRTSUB;
\r
876 nativeSubtitle.format = hb_subtitle_s_subtype.TEXTSUB;
\r
878 nativeSubtitle.config.src_codeset = srtSubtitle.CharacterCode;
\r
879 nativeSubtitle.config.src_filename = srtSubtitle.FileName;
\r
880 nativeSubtitle.config.offset = srtSubtitle.Offset;
\r
881 nativeSubtitle.config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB;
\r
882 nativeSubtitle.config.default_track = srtSubtitle.Default ? 1 : 0;
\r
884 subtitleList.Add(nativeSubtitle);
\r
889 NativeList nativeSubtitleList = InteropUtilities.ConvertListBack<hb_subtitle_s>(subtitleList);
\r
890 nativeJob.list_subtitle = nativeSubtitleList.ListPtr;
\r
891 allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);
\r
893 if (profile.OutputFormat == OutputFormat.Mp4)
\r
895 nativeJob.mux = NativeConstants.HB_MUX_MP4;
\r
899 nativeJob.mux = NativeConstants.HB_MUX_MKV;
\r
902 nativeJob.file = job.OutputPath;
\r
904 nativeJob.largeFileSize = profile.LargeFile ? 1 : 0;
\r
905 nativeJob.mp4_optimize = profile.Optimize ? 1 : 0;
\r
906 nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0;
\r
908 string x264Options = profile.X264Options ?? string.Empty;
\r
909 if (profile.TwoPass)
\r
911 nativeJob.pass = 1;
\r
913 if (profile.TurboFirstPass)
\r
915 if (x264Options == string.Empty)
\r
917 x264Options = TurboX264Opts;
\r
921 x264Options += ":" + TurboX264Opts;
\r
926 nativeJob.x264opts = Marshal.StringToHGlobalAnsi(x264Options);
\r
927 allocatedMemory.Add(nativeJob.x264opts);
\r
931 if (title.AngleCount > 1)
\r
933 nativeJob.angle = job.Angle;
\r
938 return allocatedMemory;
\r
942 /// Adds a filter to the given filter list.
\r
944 /// <param name="filterList">The filter list to add to.</param>
\r
945 /// <param name="filterType">The type of filter.</param>
\r
946 /// <param name="settings">Settings for the filter.</param>
\r
947 /// <param name="allocatedMemory">The list of allocated memory.</param>
\r
948 private void AddFilter(List<IntPtr> filterList, int filterType, string settings, List<IntPtr> allocatedMemory)
\r
950 IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings);
\r
951 filterList.Add(HbLib.hb_get_filter_object(filterType, settingsNativeString));
\r
953 allocatedMemory.Add(settingsNativeString);
\r
957 /// Gets the title, given the 1-based index.
\r
959 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
960 /// <returns>The requested Title.</returns>
\r
961 private Title GetTitle(int titleIndex)
\r
963 return this.Titles.SingleOrDefault(title => title.TitleNumber == titleIndex);
\r
967 /// Gets the native title object from the title index.
\r
969 /// <param name="titleIndex">The index of the title (1-based).</param>
\r
970 /// <returns>Gets the native title object for the given index.</returns>
\r
971 private hb_title_s GetOriginalTitle(int titleIndex)
\r
973 List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList();
\r
974 if (matchingTitles.Count == 0)
\r
976 throw new ArgumentException("Could not find specified title.");
\r
979 if (matchingTitles.Count > 1)
\r
981 throw new ArgumentException("Multiple titles matched.");
\r
984 return matchingTitles[0];
\r
988 /// Applies an audio encoding to a native audio encoding base structure.
\r
990 /// <param name="encoding">The encoding to apply.</param>
\r
991 /// <param name="baseStruct">The base native structure.</param>
\r
992 /// <param name="track"></param>
\r
993 /// <param name="outputTrack"></param>
\r
994 /// <returns>The resulting native audio structure.</returns>
\r
995 private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int track, int outputTrack)
\r
997 hb_audio_s nativeAudio = baseStruct;
\r
999 //nativeAudio.config.input.track = track;
\r
1000 nativeAudio.config.output.track = outputTrack;
\r
1002 switch (encoding.Encoder)
\r
1004 case AudioEncoder.Ac3Passthrough:
\r
1005 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_AC3;
\r
1007 case AudioEncoder.DtsPassthrough:
\r
1008 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_DCA;
\r
1010 case AudioEncoder.Faac:
\r
1011 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_FAAC;
\r
1013 case AudioEncoder.Lame:
\r
1014 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_LAME;
\r
1016 case AudioEncoder.Vorbis:
\r
1017 nativeAudio.config.output.codec = NativeConstants.HB_ACODEC_VORBIS;
\r
1023 nativeAudio.config.output.bitrate = encoding.Bitrate;
\r
1024 nativeAudio.config.output.dynamic_range_compression = 0.0;
\r
1026 switch (encoding.Mixdown)
\r
1028 case Mixdown.DolbyProLogicII:
\r
1029 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBYPLII;
\r
1031 case Mixdown.DolbySurround:
\r
1032 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_DOLBY;
\r
1034 case Mixdown.Mono:
\r
1035 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_MONO;
\r
1037 case Mixdown.SixChannelDiscrete:
\r
1038 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_6CH;
\r
1040 case Mixdown.Stereo:
\r
1041 nativeAudio.config.output.mixdown = NativeConstants.HB_AMIXDOWN_STEREO;
\r
1047 if (encoding.SampleRate != null)
\r
1049 nativeAudio.config.output.samplerate = (int)(double.Parse(encoding.SampleRate) * 1000);
\r
1052 nativeAudio.padding = new byte[24600];
\r
1054 return nativeAudio;
\r
1058 /// Converts a native title to a Title object.
\r
1060 /// <param name="title">The native title structure.</param>
\r
1061 /// <returns>The managed Title object.</returns>
\r
1062 private Title ConvertTitle(hb_title_s title)
\r
1064 var newTitle = new Title
\r
1066 TitleNumber = title.index,
\r
1067 Resolution = new Size(title.width, title.height),
\r
1068 ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height),
\r
1069 Duration = TimeSpan.FromSeconds(((double)title.duration) / 90000),
\r
1070 AutoCropDimensions = new Cropping
\r
1072 Top = title.crop[0],
\r
1073 Bottom = title.crop[1],
\r
1074 Left = title.crop[2],
\r
1075 Right = title.crop[3]
\r
1077 AspectRatio = title.aspect,
\r
1078 AngleCount = title.angle_count
\r
1081 int currentSubtitleTrack = 1;
\r
1082 List<hb_subtitle_s> subtitleList = InteropUtilities.ConvertList<hb_subtitle_s>(title.list_subtitle);
\r
1083 foreach (hb_subtitle_s subtitle in subtitleList)
\r
1085 var newSubtitle = new Subtitle
\r
1087 TrackNumber = currentSubtitleTrack,
\r
1088 Language = subtitle.lang,
\r
1089 LanguageCode = subtitle.iso639_2
\r
1092 if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB)
\r
1094 newSubtitle.SubtitleType = SubtitleType.Picture;
\r
1096 else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB)
\r
1098 newSubtitle.SubtitleType = SubtitleType.Text;
\r
1101 newTitle.Subtitles.Add(newSubtitle);
\r
1103 currentSubtitleTrack++;
\r
1106 int currentAudioTrack = 1;
\r
1107 List<hb_audio_s> audioList = InteropUtilities.ConvertList<hb_audio_s>(title.list_audio);
\r
1108 foreach (hb_audio_s audio in audioList)
\r
1110 var newAudio = new AudioTrack
\r
1112 TrackNumber = currentAudioTrack,
\r
1113 Language = audio.config.lang.simple,
\r
1114 LanguageCode = audio.config.lang.iso639_2,
\r
1115 Description = audio.config.lang.description
\r
1118 newTitle.AudioTracks.Add(newAudio);
\r
1120 currentAudioTrack++;
\r
1123 List<hb_chapter_s> chapterList = InteropUtilities.ConvertList<hb_chapter_s>(title.list_chapter);
\r
1124 foreach (hb_chapter_s chapter in chapterList)
\r
1126 var newChapter = new Chapter
\r
1128 ChapterNumber = chapter.index,
\r
1129 Duration = TimeSpan.FromSeconds(((double)chapter.duration) / 90000)
\r
1132 newTitle.Chapters.Add(newChapter);
\r