From 9c2f166bc71277e98ced2d39eb56aa88ff5452af Mon Sep 17 00:00:00 2001 From: sr55 Date: Sun, 22 Dec 2013 17:05:56 +0000 Subject: WinGui: Tags/Spaces tidyup git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@5940 b64f7644-9d1e-0410-96f1-a4d463321fa5 --- .../HandBrakeInterop/HandBrakeInstance.cs | 3826 ++++++++++---------- 1 file changed, 1913 insertions(+), 1913 deletions(-) diff --git a/win/CS/HandBrake.Interop/HandBrakeInterop/HandBrakeInstance.cs b/win/CS/HandBrake.Interop/HandBrakeInterop/HandBrakeInstance.cs index 494a8d717..83f104368 100644 --- a/win/CS/HandBrake.Interop/HandBrakeInterop/HandBrakeInstance.cs +++ b/win/CS/HandBrake.Interop/HandBrakeInterop/HandBrakeInstance.cs @@ -9,1926 +9,1926 @@ namespace HandBrake.Interop { - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Text; - using System.Windows.Media.Imaging; - - using HandBrake.Interop.EventArgs; - using HandBrake.Interop.HbLib; - using HandBrake.Interop.Interfaces; - using HandBrake.Interop.Model; - using HandBrake.Interop.Model.Encoding; - using HandBrake.Interop.SourceData; - - /// - /// A wrapper for a HandBrake instance. - /// - public class HandBrakeInstance : IHandBrakeInstance, IDisposable - { - /// - /// The modulus for picture size when auto-sizing dimensions. - /// - private const int PictureAutoSizeModulus = 2; - - /// - /// The number of MS between status polls when scanning. - /// - private const double ScanPollIntervalMs = 200; - - /// - /// The number of MS between status polls when encoding. - /// - private const double EncodePollIntervalMs = 200; - - /// - /// X264 options to add for a turbo first pass. - /// - private const string TurboX264Opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0"; - - /// - /// The native handle to the HandBrake instance. - /// - private IntPtr hbHandle; - - /// - /// The number of previews created during scan. - /// - private int previewCount; - - /// - /// The timer to poll for scan status. - /// - private System.Timers.Timer scanPollTimer; - - /// - /// The timer to poll for encode status. - /// - private System.Timers.Timer encodePollTimer; - - /// - /// The list of original titles in native structure form. - /// - private List originalTitles; - - /// - /// The list of titles on this instance. - /// - private List titles; - - /// <summary> - /// The current encode job for this instance. - /// </summary> - private EncodeJob currentJob; - - /// <summary> - /// True if the current job is scanning for subtitles. - /// </summary> - private bool subtitleScan; - - /// <summary> - /// The index of the default title. - /// </summary> - private int featureTitle; - - /// <summary> - /// A list of native memory locations allocated by this instance. - /// </summary> - private List<IntPtr> encodeAllocatedMemory; - - /// <summary> - /// A value indicating whether this object has been disposed or not. - /// </summary> - private bool disposed; - - /// <summary> - /// Finalizes an instance of the HandBrakeInstance class. - /// </summary> - ~HandBrakeInstance() - { - this.Dispose(false); - } - - /// <summary> - /// Fires for progress updates when scanning. - /// </summary> - public event EventHandler<ScanProgressEventArgs> ScanProgress; - - /// <summary> - /// Fires when a scan has completed. - /// </summary> - public event EventHandler<System.EventArgs> ScanCompleted; - - /// <summary> - /// Fires for progress updates when encoding. - /// </summary> - public event EventHandler<EncodeProgressEventArgs> EncodeProgress; - - /// <summary> - /// Fires when an encode has completed. - /// </summary> - public event EventHandler<EncodeCompletedEventArgs> EncodeCompleted; - - /// <summary> - /// Gets the list of titles on this instance. - /// </summary> - public List<Title> Titles - { - get - { - return this.titles; - } - } - - /// <summary> - /// Gets the number of previews created during scan. - /// </summary> - public int PreviewCount - { - get - { - return this.previewCount; - } - } - - /// <summary> - /// Gets the index of the default title. - /// </summary> - public int FeatureTitle - { - get - { - return this.featureTitle; - } - } - - /// <summary> - /// Gets the HandBrake version string. - /// </summary> - public string Version - { - get - { - var versionPtr = HBFunctions.hb_get_version(this.hbHandle); - return Marshal.PtrToStringAnsi(versionPtr); - } - } - - /// <summary> - /// Gets the HandBrake build number. - /// </summary> - public int Build - { - get - { - return HBFunctions.hb_get_build(this.hbHandle); - } - } - - /// <summary> - /// Initializes this instance. - /// </summary> - /// <param name="verbosity">The code for the logging verbosity to use.</param> - public void Initialize(int verbosity) - { - HandBrakeUtils.EnsureGlobalInit(); - - HandBrakeUtils.RegisterLogger(); - this.hbHandle = HBFunctions.hb_init(verbosity, update_check: 0); - } - - /// <summary> - /// Starts scanning the given path. - /// </summary> - /// <param name="path">The path to the video to scan.</param> - /// <param name="previewCount">The number of preview images to make.</param> - /// <param name="minDuration">The minimum duration of a title to show up on the scan.</param> - public void StartScan(string path, int previewCount, TimeSpan minDuration) - { - this.StartScan(path, previewCount, minDuration, 0); - } - - /// <summary> - /// Starts a scan for the given input path. - /// </summary> - /// <param name="path">The path of the video to scan.</param> - /// <param name="previewCount">The number of preview images to generate for each title while scanning.</param> - public void StartScan(string path, int previewCount) - { - this.StartScan(path, previewCount, TimeSpan.FromSeconds(10), 0); - } - - /// <summary> - /// Starts a scan of the given path. - /// </summary> - /// <param name="path">The path of the video to scan.</param> - /// <param name="previewCount">The number of preview images to generate for each title while scanning.</param> - /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param> - public void StartScan(string path, int previewCount, int titleIndex) - { - this.StartScan(path, previewCount, TimeSpan.Zero, titleIndex); - } - - /// <summary> - /// Starts a scan of the given path. - /// </summary> - /// <param name="path">The path of the video to scan.</param> - /// <param name="previewCount">The number of previews to make on each title.</param> - /// <param name="minDuration">The minimum duration of a title to show up on the scan.</param> - /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param> - public void StartScan(string path, int previewCount, TimeSpan minDuration, int titleIndex) - { - this.previewCount = previewCount; - - IntPtr pathPtr = InteropUtilities.CreateUtf8Ptr(path); - HBFunctions.hb_scan(this.hbHandle, pathPtr, titleIndex, previewCount, 1, (ulong)(minDuration.TotalSeconds * 90000)); - Marshal.FreeHGlobal(pathPtr); - - this.scanPollTimer = new System.Timers.Timer(); - this.scanPollTimer.Interval = ScanPollIntervalMs; - - // Lambda notation used to make sure we can view any JIT exceptions the method throws - this.scanPollTimer.Elapsed += (o, e) => - { - this.PollScanProgress(); - }; - this.scanPollTimer.Start(); - } - - /// <summary> - /// Stops an ongoing scan. - /// </summary> - public void StopScan() - { - HBFunctions.hb_scan_stop(this.hbHandle); - } - - - - /// <summary> - /// Gets an image for the given job and preview - /// </summary> - /// <remarks> - /// Only incorporates sizing and aspect ratio into preview image. - /// </remarks> - /// <param name="job">The encode job to preview.</param> - /// <param name="previewNumber">The index of the preview to get (0-based).</param> - /// <returns>An image with the requested preview.</returns> - public BitmapImage GetPreview(EncodeJob job, int previewNumber) - { - IntPtr nativeJobPtr = HBFunctions.hb_job_init_by_index(this.hbHandle, this.GetTitleIndex(job.Title)); - var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(nativeJobPtr); - - List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job); - - // There are some problems with getting previews with deinterlacing. Disabling for now. - nativeJob.deinterlace = 0; - - int outputWidth = nativeJob.width; - int outputHeight = nativeJob.height; - int imageBufferSize = outputWidth * outputHeight * 4; - IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize); - allocatedMemory.Add(nativeBuffer); - HBFunctions.hb_get_preview(this.hbHandle, ref nativeJob, previewNumber, nativeBuffer); - - // We've used the job to get the preview. Clean up the job. - InteropUtilities.CloseJob(nativeJobPtr); - - // Copy the filled image buffer to a managed array. - byte[] managedBuffer = new byte[imageBufferSize]; - Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize); - - // We've copied the data out of unmanaged memory. Clean up that memory now. - InteropUtilities.FreeMemory(allocatedMemory); - - var bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight); - 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); - - IntPtr ptr = bitmapData.Scan0; - - for (int i = 0; i < nativeJob.height; i++) - { - Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4); - ptr = IntPtr.Add(ptr, bitmapData.Stride); - } - - bitmap.UnlockBits(bitmapData); - - using (var memoryStream = new MemoryStream()) - { - try - { - bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp); - } - finally - { - bitmap.Dispose(); - } - - var wpfBitmap = new BitmapImage(); - wpfBitmap.BeginInit(); - wpfBitmap.CacheOption = BitmapCacheOption.OnLoad; - wpfBitmap.StreamSource = memoryStream; - wpfBitmap.EndInit(); - wpfBitmap.Freeze(); - - return wpfBitmap; - } - } - - /// <summary> - /// Calculates the video bitrate for the given job and target size. - /// </summary> - /// <param name="job">The encode job.</param> - /// <param name="sizeMB">The target size in MB.</param> - /// <param name="overallSelectedLengthSeconds">The currently selected encode length. Used in preview - /// for calculating bitrate when the target size would be wrong.</param> - /// <returns>The video bitrate in kbps.</returns> - public int CalculateBitrate(EncodeJob job, int sizeMB, double overallSelectedLengthSeconds = 0) - { - long availableBytes = ((long) sizeMB) * 1024 * 1024; - - EncodingProfile profile = job.EncodingProfile; - Title title = this.GetTitle(job.Title); - - double lengthSeconds = overallSelectedLengthSeconds > 0 ? overallSelectedLengthSeconds : HandBrakeUtils.GetJobLengthSeconds(job, title); - lengthSeconds += 1.5; - - double outputFramerate; - if (profile.Framerate == 0) - { - outputFramerate = title.Framerate; - } - else - { - // Not sure what to do for VFR here hb_calc_bitrate never handled it... - // just use the peak for now. - outputFramerate = profile.Framerate; - } - - long frames = (long)(lengthSeconds * outputFramerate); - - availableBytes -= frames * HandBrakeUtils.ContainerOverheadPerFrame; - - List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title); - availableBytes -= HandBrakeUtils.GetAudioSize(job, lengthSeconds, title, outputTrackList); - - if (availableBytes < 0) - { - return 0; - } - - // Video bitrate is in kilobits per second, or where 1 kbps is 1000 bits per second. - // So 1 kbps is 125 bytes per second. - return (int)(availableBytes / (125 * lengthSeconds)); - } - - /// <summary> - /// Gives estimated file size (in MB) of the given job and video bitrate. - /// </summary> - /// <param name="job">The encode job.</param> - /// <param name="videoBitrate">The video bitrate to be used (kbps).</param> - /// <returns>The estimated file size (in MB) of the given job and video bitrate.</returns> - public double CalculateFileSize(EncodeJob job, int videoBitrate) - { - long totalBytes = 0; - - EncodingProfile profile = job.EncodingProfile; - Title title = this.GetTitle(job.Title); - - double lengthSeconds = HandBrakeUtils.GetJobLengthSeconds(job, title); - lengthSeconds += 1.5; - - double outputFramerate; - if (profile.Framerate == 0) - { - outputFramerate = title.Framerate; - } - else - { - // Not sure what to do for VFR here hb_calc_bitrate never handled it... - // just use the peak for now. - outputFramerate = profile.Framerate; - } - - long frames = (long)(lengthSeconds * outputFramerate); - - totalBytes += (long)(lengthSeconds * videoBitrate * 125); - totalBytes += frames * HandBrakeUtils.ContainerOverheadPerFrame; - - List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title); - totalBytes += HandBrakeUtils.GetAudioSize(job, lengthSeconds, title, outputTrackList); - - return (double)totalBytes / 1024 / 1024; - } - - /// <summary> - /// Starts an encode with the given job. - /// </summary> - /// <param name="jobToStart">The job to start.</param> - public void StartEncode(EncodeJob jobToStart) - { - this.StartEncode(jobToStart, false, 0, 0, 0); - } - - /// <summary> - /// Starts an encode with the given job. - /// </summary> - /// <param name="job">The job to start.</param> - /// <param name="preview">True if this is a preview encode.</param> - /// <param name="previewNumber">The preview number to start the encode at (0-based).</param> - /// <param name="previewSeconds">The number of seconds in the preview.</param> - /// <param name="overallSelectedLengthSeconds">The currently selected encode length. Used in preview - /// for calculating bitrate when the target size would be wrong.</param> - public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds, double overallSelectedLengthSeconds) - { - EncodingProfile profile = job.EncodingProfile; - if (job.ChosenAudioTracks == null) - { - throw new ArgumentException("job.ChosenAudioTracks cannot be null."); - } - - this.currentJob = job; - - IntPtr nativeJobPtr = HBFunctions.hb_job_init_by_index(this.hbHandle, this.GetTitleIndex(job.Title)); - var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(nativeJobPtr); - - this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds, overallSelectedLengthSeconds); - - this.subtitleScan = false; - if (job.Subtitles != null && job.Subtitles.SourceSubtitles != null) - { - foreach (SourceSubtitle subtitle in job.Subtitles.SourceSubtitles) - { - if (subtitle.TrackNumber == 0) - { - this.subtitleScan = true; - break; - } - } - } - - string x264Options = profile.X264Options ?? string.Empty; - IntPtr originalX264Options = Marshal.StringToHGlobalAnsi(x264Options); - this.encodeAllocatedMemory.Add(originalX264Options); - - if (!string.IsNullOrEmpty(profile.X264Profile)) - { - nativeJob.h264_profile = Marshal.StringToHGlobalAnsi(profile.X264Profile); - this.encodeAllocatedMemory.Add(nativeJob.h264_profile); - } - - if (!string.IsNullOrEmpty(profile.X264Preset)) - { - nativeJob.x264_preset = Marshal.StringToHGlobalAnsi(profile.X264Preset); - this.encodeAllocatedMemory.Add(nativeJob.x264_preset); - } - - if (profile.X264Tunes != null && profile.X264Tunes.Count > 0) - { - nativeJob.x264_tune = Marshal.StringToHGlobalAnsi(string.Join(",", profile.X264Tunes)); - this.encodeAllocatedMemory.Add(nativeJob.x264_tune); - } - - if (!string.IsNullOrEmpty(job.EncodingProfile.H264Level)) - { - nativeJob.h264_level = Marshal.StringToHGlobalAnsi(job.EncodingProfile.H264Level); - this.encodeAllocatedMemory.Add(nativeJob.h264_level); - } - - if (this.subtitleScan) - { - // If we need to scan subtitles, enqueue a pre-processing job to do that. - nativeJob.pass = -1; - nativeJob.indepth_scan = 1; - - nativeJob.advanced_opts = IntPtr.Zero; - - HBFunctions.hb_add(this.hbHandle, ref nativeJob); - } - - nativeJob.indepth_scan = 0; - - if (job.EncodingProfile.TwoPass) - { - // First pass. Apply turbo options if needed. - nativeJob.pass = 1; - string firstPassAdvancedOptions = x264Options; - if (job.EncodingProfile.TurboFirstPass) - { - if (firstPassAdvancedOptions == string.Empty) - { - firstPassAdvancedOptions = TurboX264Opts; - } - else - { - firstPassAdvancedOptions += ":" + TurboX264Opts; - } - } - - nativeJob.advanced_opts = Marshal.StringToHGlobalAnsi(firstPassAdvancedOptions); - this.encodeAllocatedMemory.Add(nativeJob.advanced_opts); - - HBFunctions.hb_add(this.hbHandle, ref nativeJob); - - // Second pass. Apply normal options. - nativeJob.pass = 2; - nativeJob.advanced_opts = originalX264Options; - - HBFunctions.hb_add(this.hbHandle, ref nativeJob); - } - else - { - // One pass job. - nativeJob.pass = 0; - nativeJob.advanced_opts = originalX264Options; - - HBFunctions.hb_add(this.hbHandle, ref nativeJob); - } - - HBFunctions.hb_start(this.hbHandle); - - // Should be safe to clean up the job we started with; a copy is in the queue now. - InteropUtilities.CloseJob(nativeJobPtr); - - this.encodePollTimer = new System.Timers.Timer(); - this.encodePollTimer.Interval = EncodePollIntervalMs; - - this.encodePollTimer.Elapsed += (o, e) => - { - this.PollEncodeProgress(); - }; - this.encodePollTimer.Start(); - } - - /// <summary> - /// Pauses the current encode. - /// </summary> - public void PauseEncode() - { - HBFunctions.hb_pause(this.hbHandle); - } - - /// <summary> - /// Resumes a paused encode. - /// </summary> - public void ResumeEncode() - { - HBFunctions.hb_resume(this.hbHandle); - } - - /// <summary> - /// Stops the current encode. - /// </summary> - public void StopEncode() - { - HBFunctions.hb_stop(this.hbHandle); - - // Also remove all jobs from the queue (in case we stopped a 2-pass encode) - var currentJobs = new List<IntPtr>(); - - int jobs = HBFunctions.hb_count(this.hbHandle); - for (int i = 0; i < jobs; i++) - { - currentJobs.Add(HBFunctions.hb_job(this.hbHandle, 0)); - } - - foreach (IntPtr job in currentJobs) - { - HBFunctions.hb_rem(this.hbHandle, job); - } - } - - /// <summary> - /// Gets the final size for a given encode job. - /// </summary> - /// <param name="job">The encode job to use.</param> - /// <param name="width">The storage width.</param> - /// <param name="height">The storage height.</param> - /// <param name="parWidth">The pixel aspect X number.</param> - /// <param name="parHeight">The pixel aspect Y number.</param> - public void GetSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight) - { - Title title = this.GetTitle(job.Title); - - if (job.EncodingProfile.Anamorphic == Anamorphic.None) - { - Size storageDimensions = CalculateNonAnamorphicOutput(job.EncodingProfile, title); - - width = storageDimensions.Width; - height = storageDimensions.Height; - - parWidth = 1; - parHeight = 1; - - return; - } - - IntPtr nativeJobPtr = HBFunctions.hb_job_init_by_index(this.hbHandle, this.GetTitleIndex(title)); - var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(nativeJobPtr); - - List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job); - InteropUtilities.FreeMemory(allocatedMemory); - - InteropUtilities.CloseJob(nativeJobPtr); - - // During the ApplyJob call, it modified nativeJob to have the correct width, height and PAR. - // We use those for the size. - width = nativeJob.width; - height = nativeJob.height; - parWidth = nativeJob.anamorphic.par_width; - parHeight = nativeJob.anamorphic.par_height; - } - - - - /// <summary> - /// Frees any resources associated with this object. - /// </summary> - public void Dispose() - { - if (this.disposed) - { - return; - } - - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Frees any resources associated with this object. - /// </summary> - /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param> - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - // Free other state (managed objects). - } - - // Free unmanaged objects. - IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr))); - Marshal.WriteIntPtr(handlePtr, this.hbHandle); - HBFunctions.hb_close(handlePtr); - Marshal.FreeHGlobal(handlePtr); - - this.disposed = true; - } - - /// <summary> - /// Calculates the output size for a non-anamorphic job. - /// </summary> - /// <param name="profile">The encoding profile for the job.</param> - /// <param name="title">The title being encoded.</param> - /// <returns>The dimensions of the final encode.</returns> - private static Size CalculateNonAnamorphicOutput(EncodingProfile profile, Title title) - { - int sourceWidth = title.Resolution.Width; - int sourceHeight = title.Resolution.Height; - - int width = profile.Width; - int height = profile.Height; - - Cropping crop; - switch (profile.CroppingType) - { - case CroppingType.Automatic: - crop = title.AutoCropDimensions; - break; - case CroppingType.Custom: - crop = profile.Cropping; - break; - default: - crop = new Cropping(); - break; - } - - sourceWidth -= crop.Left; - sourceWidth -= crop.Right; - - sourceHeight -= crop.Top; - sourceHeight -= crop.Bottom; - - double croppedAspectRatio = ((double)sourceWidth * title.ParVal.Width) / (sourceHeight * title.ParVal.Height); - - if (width == 0) - { - width = sourceWidth; - } - - if (profile.MaxWidth > 0 && width > profile.MaxWidth) - { - width = profile.MaxWidth; - } - - if (height == 0) - { - height = sourceHeight; - } - - if (profile.MaxHeight > 0 && height > profile.MaxHeight) - { - height = profile.MaxHeight; - } - - if (profile.KeepDisplayAspect) - { - if ((profile.Width == 0 && profile.Height == 0) || profile.Width == 0) - { - width = (int)((double)height * croppedAspectRatio); - if (profile.MaxWidth > 0 && width > profile.MaxWidth) - { - width = profile.MaxWidth; - height = (int)((double)width / croppedAspectRatio); - height = GetNearestValue(height, PictureAutoSizeModulus); - } - - width = GetNearestValue(width, PictureAutoSizeModulus); - } - else if (profile.Height == 0) - { - height = (int)((double)width / croppedAspectRatio); - if (profile.MaxHeight > 0 && height > profile.MaxHeight) - { - height = profile.MaxHeight; - width = (int)((double)height * croppedAspectRatio); - width = GetNearestValue(width, PictureAutoSizeModulus); - } - - height = GetNearestValue(height, PictureAutoSizeModulus); - } - } - - return new Size(width, height); - } - - /// <summary> - /// Gets the closest value to the given number divisible by the given modulus. - /// </summary> - /// <param name="number">The number to approximate.</param> - /// <param name="modulus">The modulus.</param> - /// <returns>The closest value to the given number divisible by the given modulus.</returns> - private static int GetNearestValue(int number, int modulus) - { - return modulus * ((number + modulus / 2) / modulus); - } - - /// <summary> - /// Checks the status of the ongoing scan. - /// </summary> - private void PollScanProgress() - { - var state = new hb_state_s(); - HBFunctions.hb_get_state(this.hbHandle, ref state); - - if (state.state == NativeConstants.HB_STATE_SCANNING) - { - if (this.ScanProgress != null) - { - hb_state_scanning_anon scanningState = state.param.scanning; - - this.ScanProgress(this, new ScanProgressEventArgs - { - Progress = scanningState.progress, - CurrentPreview = scanningState.preview_cur, - Previews = scanningState.preview_count, - CurrentTitle = scanningState.title_cur, - Titles = scanningState.title_count - }); - } - } - else if (state.state == NativeConstants.HB_STATE_SCANDONE) - { - this.titles = new List<Title>(); - - IntPtr titleSetPtr = HBFunctions.hb_get_title_set(this.hbHandle); - hb_title_set_s titleSet = InteropUtilities.ReadStructure<hb_title_set_s>(titleSetPtr); - this.originalTitles = titleSet.list_title.ToList<hb_title_s>(); - - foreach (hb_title_s title in this.originalTitles) - { - var newTitle = this.ConvertTitle(title); - + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Text; + using System.Windows.Media.Imaging; + + using HandBrake.Interop.EventArgs; + using HandBrake.Interop.HbLib; + using HandBrake.Interop.Interfaces; + using HandBrake.Interop.Model; + using HandBrake.Interop.Model.Encoding; + using HandBrake.Interop.SourceData; + + /// <summary> + /// A wrapper for a HandBrake instance. + /// </summary> + public class HandBrakeInstance : IHandBrakeInstance, IDisposable + { + /// <summary> + /// The modulus for picture size when auto-sizing dimensions. + /// </summary> + private const int PictureAutoSizeModulus = 2; + + /// <summary> + /// The number of MS between status polls when scanning. + /// </summary> + private const double ScanPollIntervalMs = 200; + + /// <summary> + /// The number of MS between status polls when encoding. + /// </summary> + private const double EncodePollIntervalMs = 200; + + /// <summary> + /// X264 options to add for a turbo first pass. + /// </summary> + private const string TurboX264Opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0"; + + /// <summary> + /// The native handle to the HandBrake instance. + /// </summary> + private IntPtr hbHandle; + + /// <summary> + /// The number of previews created during scan. + /// </summary> + private int previewCount; + + /// <summary> + /// The timer to poll for scan status. + /// </summary> + private System.Timers.Timer scanPollTimer; + + /// <summary> + /// The timer to poll for encode status. + /// </summary> + private System.Timers.Timer encodePollTimer; + + /// <summary> + /// The list of original titles in native structure form. + /// </summary> + private List<hb_title_s> originalTitles; + + /// <summary> + /// The list of titles on this instance. + /// </summary> + private List<Title> titles; + + /// <summary> + /// The current encode job for this instance. + /// </summary> + private EncodeJob currentJob; + + /// <summary> + /// True if the current job is scanning for subtitles. + /// </summary> + private bool subtitleScan; + + /// <summary> + /// The index of the default title. + /// </summary> + private int featureTitle; + + /// <summary> + /// A list of native memory locations allocated by this instance. + /// </summary> + private List<IntPtr> encodeAllocatedMemory; + + /// <summary> + /// A value indicating whether this object has been disposed or not. + /// </summary> + private bool disposed; + + /// <summary> + /// Finalizes an instance of the HandBrakeInstance class. + /// </summary> + ~HandBrakeInstance() + { + this.Dispose(false); + } + + /// <summary> + /// Fires for progress updates when scanning. + /// </summary> + public event EventHandler<ScanProgressEventArgs> ScanProgress; + + /// <summary> + /// Fires when a scan has completed. + /// </summary> + public event EventHandler<System.EventArgs> ScanCompleted; + + /// <summary> + /// Fires for progress updates when encoding. + /// </summary> + public event EventHandler<EncodeProgressEventArgs> EncodeProgress; + + /// <summary> + /// Fires when an encode has completed. + /// </summary> + public event EventHandler<EncodeCompletedEventArgs> EncodeCompleted; + + /// <summary> + /// Gets the list of titles on this instance. + /// </summary> + public List<Title> Titles + { + get + { + return this.titles; + } + } + + /// <summary> + /// Gets the number of previews created during scan. + /// </summary> + public int PreviewCount + { + get + { + return this.previewCount; + } + } + + /// <summary> + /// Gets the index of the default title. + /// </summary> + public int FeatureTitle + { + get + { + return this.featureTitle; + } + } + + /// <summary> + /// Gets the HandBrake version string. + /// </summary> + public string Version + { + get + { + var versionPtr = HBFunctions.hb_get_version(this.hbHandle); + return Marshal.PtrToStringAnsi(versionPtr); + } + } + + /// <summary> + /// Gets the HandBrake build number. + /// </summary> + public int Build + { + get + { + return HBFunctions.hb_get_build(this.hbHandle); + } + } + + /// <summary> + /// Initializes this instance. + /// </summary> + /// <param name="verbosity">The code for the logging verbosity to use.</param> + public void Initialize(int verbosity) + { + HandBrakeUtils.EnsureGlobalInit(); + + HandBrakeUtils.RegisterLogger(); + this.hbHandle = HBFunctions.hb_init(verbosity, update_check: 0); + } + + /// <summary> + /// Starts scanning the given path. + /// </summary> + /// <param name="path">The path to the video to scan.</param> + /// <param name="previewCount">The number of preview images to make.</param> + /// <param name="minDuration">The minimum duration of a title to show up on the scan.</param> + public void StartScan(string path, int previewCount, TimeSpan minDuration) + { + this.StartScan(path, previewCount, minDuration, 0); + } + + /// <summary> + /// Starts a scan for the given input path. + /// </summary> + /// <param name="path">The path of the video to scan.</param> + /// <param name="previewCount">The number of preview images to generate for each title while scanning.</param> + public void StartScan(string path, int previewCount) + { + this.StartScan(path, previewCount, TimeSpan.FromSeconds(10), 0); + } + + /// <summary> + /// Starts a scan of the given path. + /// </summary> + /// <param name="path">The path of the video to scan.</param> + /// <param name="previewCount">The number of preview images to generate for each title while scanning.</param> + /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param> + public void StartScan(string path, int previewCount, int titleIndex) + { + this.StartScan(path, previewCount, TimeSpan.Zero, titleIndex); + } + + /// <summary> + /// Starts a scan of the given path. + /// </summary> + /// <param name="path">The path of the video to scan.</param> + /// <param name="previewCount">The number of previews to make on each title.</param> + /// <param name="minDuration">The minimum duration of a title to show up on the scan.</param> + /// <param name="titleIndex">The title index to scan (1-based, 0 for all titles).</param> + public void StartScan(string path, int previewCount, TimeSpan minDuration, int titleIndex) + { + this.previewCount = previewCount; + + IntPtr pathPtr = InteropUtilities.CreateUtf8Ptr(path); + HBFunctions.hb_scan(this.hbHandle, pathPtr, titleIndex, previewCount, 1, (ulong)(minDuration.TotalSeconds * 90000)); + Marshal.FreeHGlobal(pathPtr); + + this.scanPollTimer = new System.Timers.Timer(); + this.scanPollTimer.Interval = ScanPollIntervalMs; + + // Lambda notation used to make sure we can view any JIT exceptions the method throws + this.scanPollTimer.Elapsed += (o, e) => + { + this.PollScanProgress(); + }; + this.scanPollTimer.Start(); + } + + /// <summary> + /// Stops an ongoing scan. + /// </summary> + public void StopScan() + { + HBFunctions.hb_scan_stop(this.hbHandle); + } + + + + /// <summary> + /// Gets an image for the given job and preview + /// </summary> + /// <remarks> + /// Only incorporates sizing and aspect ratio into preview image. + /// </remarks> + /// <param name="job">The encode job to preview.</param> + /// <param name="previewNumber">The index of the preview to get (0-based).</param> + /// <returns>An image with the requested preview.</returns> + public BitmapImage GetPreview(EncodeJob job, int previewNumber) + { + IntPtr nativeJobPtr = HBFunctions.hb_job_init_by_index(this.hbHandle, this.GetTitleIndex(job.Title)); + var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(nativeJobPtr); + + List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job); + + // There are some problems with getting previews with deinterlacing. Disabling for now. + nativeJob.deinterlace = 0; + + int outputWidth = nativeJob.width; + int outputHeight = nativeJob.height; + int imageBufferSize = outputWidth * outputHeight * 4; + IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize); + allocatedMemory.Add(nativeBuffer); + HBFunctions.hb_get_preview(this.hbHandle, ref nativeJob, previewNumber, nativeBuffer); + + // We've used the job to get the preview. Clean up the job. + InteropUtilities.CloseJob(nativeJobPtr); + + // Copy the filled image buffer to a managed array. + byte[] managedBuffer = new byte[imageBufferSize]; + Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize); + + // We've copied the data out of unmanaged memory. Clean up that memory now. + InteropUtilities.FreeMemory(allocatedMemory); + + var bitmap = new System.Drawing.Bitmap(outputWidth, outputHeight); + 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); + + IntPtr ptr = bitmapData.Scan0; + + for (int i = 0; i < nativeJob.height; i++) + { + Marshal.Copy(managedBuffer, i * nativeJob.width * 4, ptr, nativeJob.width * 4); + ptr = IntPtr.Add(ptr, bitmapData.Stride); + } + + bitmap.UnlockBits(bitmapData); + + using (var memoryStream = new MemoryStream()) + { + try + { + bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp); + } + finally + { + bitmap.Dispose(); + } + + var wpfBitmap = new BitmapImage(); + wpfBitmap.BeginInit(); + wpfBitmap.CacheOption = BitmapCacheOption.OnLoad; + wpfBitmap.StreamSource = memoryStream; + wpfBitmap.EndInit(); + wpfBitmap.Freeze(); + + return wpfBitmap; + } + } + + /// <summary> + /// Calculates the video bitrate for the given job and target size. + /// </summary> + /// <param name="job">The encode job.</param> + /// <param name="sizeMB">The target size in MB.</param> + /// <param name="overallSelectedLengthSeconds">The currently selected encode length. Used in preview + /// for calculating bitrate when the target size would be wrong.</param> + /// <returns>The video bitrate in kbps.</returns> + public int CalculateBitrate(EncodeJob job, int sizeMB, double overallSelectedLengthSeconds = 0) + { + long availableBytes = ((long)sizeMB) * 1024 * 1024; + + EncodingProfile profile = job.EncodingProfile; + Title title = this.GetTitle(job.Title); + + double lengthSeconds = overallSelectedLengthSeconds > 0 ? overallSelectedLengthSeconds : HandBrakeUtils.GetJobLengthSeconds(job, title); + lengthSeconds += 1.5; + + double outputFramerate; + if (profile.Framerate == 0) + { + outputFramerate = title.Framerate; + } + else + { + // Not sure what to do for VFR here hb_calc_bitrate never handled it... + // just use the peak for now. + outputFramerate = profile.Framerate; + } + + long frames = (long)(lengthSeconds * outputFramerate); + + availableBytes -= frames * HandBrakeUtils.ContainerOverheadPerFrame; + + List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title); + availableBytes -= HandBrakeUtils.GetAudioSize(job, lengthSeconds, title, outputTrackList); + + if (availableBytes < 0) + { + return 0; + } + + // Video bitrate is in kilobits per second, or where 1 kbps is 1000 bits per second. + // So 1 kbps is 125 bytes per second. + return (int)(availableBytes / (125 * lengthSeconds)); + } + + /// <summary> + /// Gives estimated file size (in MB) of the given job and video bitrate. + /// </summary> + /// <param name="job">The encode job.</param> + /// <param name="videoBitrate">The video bitrate to be used (kbps).</param> + /// <returns>The estimated file size (in MB) of the given job and video bitrate.</returns> + public double CalculateFileSize(EncodeJob job, int videoBitrate) + { + long totalBytes = 0; + + EncodingProfile profile = job.EncodingProfile; + Title title = this.GetTitle(job.Title); + + double lengthSeconds = HandBrakeUtils.GetJobLengthSeconds(job, title); + lengthSeconds += 1.5; + + double outputFramerate; + if (profile.Framerate == 0) + { + outputFramerate = title.Framerate; + } + else + { + // Not sure what to do for VFR here hb_calc_bitrate never handled it... + // just use the peak for now. + outputFramerate = profile.Framerate; + } + + long frames = (long)(lengthSeconds * outputFramerate); + + totalBytes += (long)(lengthSeconds * videoBitrate * 125); + totalBytes += frames * HandBrakeUtils.ContainerOverheadPerFrame; + + List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title); + totalBytes += HandBrakeUtils.GetAudioSize(job, lengthSeconds, title, outputTrackList); + + return (double)totalBytes / 1024 / 1024; + } + + /// <summary> + /// Starts an encode with the given job. + /// </summary> + /// <param name="jobToStart">The job to start.</param> + public void StartEncode(EncodeJob jobToStart) + { + this.StartEncode(jobToStart, false, 0, 0, 0); + } + + /// <summary> + /// Starts an encode with the given job. + /// </summary> + /// <param name="job">The job to start.</param> + /// <param name="preview">True if this is a preview encode.</param> + /// <param name="previewNumber">The preview number to start the encode at (0-based).</param> + /// <param name="previewSeconds">The number of seconds in the preview.</param> + /// <param name="overallSelectedLengthSeconds">The currently selected encode length. Used in preview + /// for calculating bitrate when the target size would be wrong.</param> + public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds, double overallSelectedLengthSeconds) + { + EncodingProfile profile = job.EncodingProfile; + if (job.ChosenAudioTracks == null) + { + throw new ArgumentException("job.ChosenAudioTracks cannot be null."); + } + + this.currentJob = job; + + IntPtr nativeJobPtr = HBFunctions.hb_job_init_by_index(this.hbHandle, this.GetTitleIndex(job.Title)); + var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(nativeJobPtr); + + this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds, overallSelectedLengthSeconds); + + this.subtitleScan = false; + if (job.Subtitles != null && job.Subtitles.SourceSubtitles != null) + { + foreach (SourceSubtitle subtitle in job.Subtitles.SourceSubtitles) + { + if (subtitle.TrackNumber == 0) + { + this.subtitleScan = true; + break; + } + } + } + + string x264Options = profile.X264Options ?? string.Empty; + IntPtr originalX264Options = Marshal.StringToHGlobalAnsi(x264Options); + this.encodeAllocatedMemory.Add(originalX264Options); + + if (!string.IsNullOrEmpty(profile.X264Profile)) + { + nativeJob.h264_profile = Marshal.StringToHGlobalAnsi(profile.X264Profile); + this.encodeAllocatedMemory.Add(nativeJob.h264_profile); + } + + if (!string.IsNullOrEmpty(profile.X264Preset)) + { + nativeJob.x264_preset = Marshal.StringToHGlobalAnsi(profile.X264Preset); + this.encodeAllocatedMemory.Add(nativeJob.x264_preset); + } + + if (profile.X264Tunes != null && profile.X264Tunes.Count > 0) + { + nativeJob.x264_tune = Marshal.StringToHGlobalAnsi(string.Join(",", profile.X264Tunes)); + this.encodeAllocatedMemory.Add(nativeJob.x264_tune); + } + + if (!string.IsNullOrEmpty(job.EncodingProfile.H264Level)) + { + nativeJob.h264_level = Marshal.StringToHGlobalAnsi(job.EncodingProfile.H264Level); + this.encodeAllocatedMemory.Add(nativeJob.h264_level); + } + + if (this.subtitleScan) + { + // If we need to scan subtitles, enqueue a pre-processing job to do that. + nativeJob.pass = -1; + nativeJob.indepth_scan = 1; + + nativeJob.advanced_opts = IntPtr.Zero; + + HBFunctions.hb_add(this.hbHandle, ref nativeJob); + } + + nativeJob.indepth_scan = 0; + + if (job.EncodingProfile.TwoPass) + { + // First pass. Apply turbo options if needed. + nativeJob.pass = 1; + string firstPassAdvancedOptions = x264Options; + if (job.EncodingProfile.TurboFirstPass) + { + if (firstPassAdvancedOptions == string.Empty) + { + firstPassAdvancedOptions = TurboX264Opts; + } + else + { + firstPassAdvancedOptions += ":" + TurboX264Opts; + } + } + + nativeJob.advanced_opts = Marshal.StringToHGlobalAnsi(firstPassAdvancedOptions); + this.encodeAllocatedMemory.Add(nativeJob.advanced_opts); + + HBFunctions.hb_add(this.hbHandle, ref nativeJob); + + // Second pass. Apply normal options. + nativeJob.pass = 2; + nativeJob.advanced_opts = originalX264Options; + + HBFunctions.hb_add(this.hbHandle, ref nativeJob); + } + else + { + // One pass job. + nativeJob.pass = 0; + nativeJob.advanced_opts = originalX264Options; + + HBFunctions.hb_add(this.hbHandle, ref nativeJob); + } + + HBFunctions.hb_start(this.hbHandle); + + // Should be safe to clean up the job we started with; a copy is in the queue now. + InteropUtilities.CloseJob(nativeJobPtr); + + this.encodePollTimer = new System.Timers.Timer(); + this.encodePollTimer.Interval = EncodePollIntervalMs; + + this.encodePollTimer.Elapsed += (o, e) => + { + this.PollEncodeProgress(); + }; + this.encodePollTimer.Start(); + } + + /// <summary> + /// Pauses the current encode. + /// </summary> + public void PauseEncode() + { + HBFunctions.hb_pause(this.hbHandle); + } + + /// <summary> + /// Resumes a paused encode. + /// </summary> + public void ResumeEncode() + { + HBFunctions.hb_resume(this.hbHandle); + } + + /// <summary> + /// Stops the current encode. + /// </summary> + public void StopEncode() + { + HBFunctions.hb_stop(this.hbHandle); + + // Also remove all jobs from the queue (in case we stopped a 2-pass encode) + var currentJobs = new List<IntPtr>(); + + int jobs = HBFunctions.hb_count(this.hbHandle); + for (int i = 0; i < jobs; i++) + { + currentJobs.Add(HBFunctions.hb_job(this.hbHandle, 0)); + } + + foreach (IntPtr job in currentJobs) + { + HBFunctions.hb_rem(this.hbHandle, job); + } + } + + /// <summary> + /// Gets the final size for a given encode job. + /// </summary> + /// <param name="job">The encode job to use.</param> + /// <param name="width">The storage width.</param> + /// <param name="height">The storage height.</param> + /// <param name="parWidth">The pixel aspect X number.</param> + /// <param name="parHeight">The pixel aspect Y number.</param> + public void GetSize(EncodeJob job, out int width, out int height, out int parWidth, out int parHeight) + { + Title title = this.GetTitle(job.Title); + + if (job.EncodingProfile.Anamorphic == Anamorphic.None) + { + Size storageDimensions = CalculateNonAnamorphicOutput(job.EncodingProfile, title); + + width = storageDimensions.Width; + height = storageDimensions.Height; + + parWidth = 1; + parHeight = 1; + + return; + } + + IntPtr nativeJobPtr = HBFunctions.hb_job_init_by_index(this.hbHandle, this.GetTitleIndex(title)); + var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(nativeJobPtr); + + List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job); + InteropUtilities.FreeMemory(allocatedMemory); + + InteropUtilities.CloseJob(nativeJobPtr); + + // During the ApplyJob call, it modified nativeJob to have the correct width, height and PAR. + // We use those for the size. + width = nativeJob.width; + height = nativeJob.height; + parWidth = nativeJob.anamorphic.par_width; + parHeight = nativeJob.anamorphic.par_height; + } + + + + /// <summary> + /// Frees any resources associated with this object. + /// </summary> + public void Dispose() + { + if (this.disposed) + { + return; + } + + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Frees any resources associated with this object. + /// </summary> + /// <param name="disposing">True if managed objects as well as unmanaged should be disposed.</param> + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Free other state (managed objects). + } + + // Free unmanaged objects. + IntPtr handlePtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr))); + Marshal.WriteIntPtr(handlePtr, this.hbHandle); + HBFunctions.hb_close(handlePtr); + Marshal.FreeHGlobal(handlePtr); + + this.disposed = true; + } + + /// <summary> + /// Calculates the output size for a non-anamorphic job. + /// </summary> + /// <param name="profile">The encoding profile for the job.</param> + /// <param name="title">The title being encoded.</param> + /// <returns>The dimensions of the final encode.</returns> + private static Size CalculateNonAnamorphicOutput(EncodingProfile profile, Title title) + { + int sourceWidth = title.Resolution.Width; + int sourceHeight = title.Resolution.Height; + + int width = profile.Width; + int height = profile.Height; + + Cropping crop; + switch (profile.CroppingType) + { + case CroppingType.Automatic: + crop = title.AutoCropDimensions; + break; + case CroppingType.Custom: + crop = profile.Cropping; + break; + default: + crop = new Cropping(); + break; + } + + sourceWidth -= crop.Left; + sourceWidth -= crop.Right; + + sourceHeight -= crop.Top; + sourceHeight -= crop.Bottom; + + double croppedAspectRatio = ((double)sourceWidth * title.ParVal.Width) / (sourceHeight * title.ParVal.Height); + + if (width == 0) + { + width = sourceWidth; + } + + if (profile.MaxWidth > 0 && width > profile.MaxWidth) + { + width = profile.MaxWidth; + } + + if (height == 0) + { + height = sourceHeight; + } + + if (profile.MaxHeight > 0 && height > profile.MaxHeight) + { + height = profile.MaxHeight; + } + + if (profile.KeepDisplayAspect) + { + if ((profile.Width == 0 && profile.Height == 0) || profile.Width == 0) + { + width = (int)((double)height * croppedAspectRatio); + if (profile.MaxWidth > 0 && width > profile.MaxWidth) + { + width = profile.MaxWidth; + height = (int)((double)width / croppedAspectRatio); + height = GetNearestValue(height, PictureAutoSizeModulus); + } + + width = GetNearestValue(width, PictureAutoSizeModulus); + } + else if (profile.Height == 0) + { + height = (int)((double)width / croppedAspectRatio); + if (profile.MaxHeight > 0 && height > profile.MaxHeight) + { + height = profile.MaxHeight; + width = (int)((double)height * croppedAspectRatio); + width = GetNearestValue(width, PictureAutoSizeModulus); + } + + height = GetNearestValue(height, PictureAutoSizeModulus); + } + } + + return new Size(width, height); + } + + /// <summary> + /// Gets the closest value to the given number divisible by the given modulus. + /// </summary> + /// <param name="number">The number to approximate.</param> + /// <param name="modulus">The modulus.</param> + /// <returns>The closest value to the given number divisible by the given modulus.</returns> + private static int GetNearestValue(int number, int modulus) + { + return modulus * ((number + modulus / 2) / modulus); + } + + /// <summary> + /// Checks the status of the ongoing scan. + /// </summary> + private void PollScanProgress() + { + var state = new hb_state_s(); + HBFunctions.hb_get_state(this.hbHandle, ref state); + + if (state.state == NativeConstants.HB_STATE_SCANNING) + { + if (this.ScanProgress != null) + { + hb_state_scanning_anon scanningState = state.param.scanning; + + this.ScanProgress(this, new ScanProgressEventArgs + { + Progress = scanningState.progress, + CurrentPreview = scanningState.preview_cur, + Previews = scanningState.preview_count, + CurrentTitle = scanningState.title_cur, + Titles = scanningState.title_count + }); + } + } + else if (state.state == NativeConstants.HB_STATE_SCANDONE) + { + this.titles = new List<Title>(); + + IntPtr titleSetPtr = HBFunctions.hb_get_title_set(this.hbHandle); + hb_title_set_s titleSet = InteropUtilities.ReadStructure<hb_title_set_s>(titleSetPtr); + this.originalTitles = titleSet.list_title.ToList<hb_title_s>(); + + foreach (hb_title_s title in this.originalTitles) + { + var newTitle = this.ConvertTitle(title); + // Convert the Path to UTF-8. byte[] bytes = Encoding.Default.GetBytes(title.path); string utf8Str = Encoding.UTF8.GetString(bytes); newTitle.Path = utf8Str; - - this.titles.Add(newTitle); - } - - if (this.originalTitles.Count > 0) - { - this.featureTitle = titleSet.feature; - } - else - { - this.featureTitle = 0; - } - - this.scanPollTimer.Stop(); - - if (this.ScanCompleted != null) - { - this.ScanCompleted(this, new System.EventArgs()); - } - } - } - - /// <summary> - /// Checks the status of the ongoing encode. - /// </summary> - private void PollEncodeProgress() - { - hb_state_s state = new hb_state_s(); - HBFunctions.hb_get_state(this.hbHandle, ref state); - - if (state.state == NativeConstants.HB_STATE_WORKING) - { - if (this.EncodeProgress != null) - { - int pass = 1; - int rawJobNumber = state.param.working.job_cur; - - if (this.currentJob.EncodingProfile.TwoPass) - { - if (this.subtitleScan) - { - switch (rawJobNumber) - { - case 1: - pass = -1; - break; - case 2: - pass = 1; - break; - case 3: - pass = 2; - break; - default: - break; - } - } - else - { - switch (rawJobNumber) - { - case 1: - pass = 1; - break; - case 2: - pass = 2; - break; - default: - break; - } - } - } - else - { - if (this.subtitleScan) - { - switch (rawJobNumber) - { - case 1: - pass = -1; - break; - case 2: - pass = 1; - break; - default: - break; - } - } - else - { - pass = 1; - } - } - - var progressEventArgs = new EncodeProgressEventArgs - { - FractionComplete = state.param.working.progress, - CurrentFrameRate = state.param.working.rate_cur, - AverageFrameRate = state.param.working.rate_avg, - EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds), - Pass = pass - }; - - this.EncodeProgress(this, progressEventArgs); - } - } - else if (state.state == NativeConstants.HB_STATE_WORKDONE) - { - InteropUtilities.FreeMemory(this.encodeAllocatedMemory); - this.encodePollTimer.Stop(); - - if (this.EncodeCompleted != null) - { - this.EncodeCompleted(this, new EncodeCompletedEventArgs { Error = state.param.workdone.error > 0 }); - } - } - } - - /// <summary> - /// Applies the encoding job to the native memory structure and returns a list of memory - /// locations allocated during this. - /// </summary> - /// <param name="nativeJob">The native structure to apply to job info to.</param> - /// <param name="job">The job info to apply.</param> - /// <returns>The list of memory locations allocated for the job.</returns> - private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job) - { - return this.ApplyJob(ref nativeJob, job, false, 0, 0, 0); - } - - /// <summary> - /// Applies the encoding job to the native memory structure and returns a list of memory - /// locations allocated during this. - /// </summary> - /// <param name="nativeJob">The native structure to apply to job info to.</param> - /// <param name="job">The job info to apply.</param> - /// <param name="preview">True if this is a preview encode.</param> - /// <param name="previewNumber">The preview number (0-based) to encode.</param> - /// <param name="previewSeconds">The number of seconds in the preview.</param> - /// <param name="overallSelectedLengthSeconds">The currently selected encode length. Used in preview - /// for calculating bitrate when the target size would be wrong.</param> - /// <returns>The list of memory locations allocated for the job.</returns> - private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds, double overallSelectedLengthSeconds) - { - var allocatedMemory = new List<IntPtr>(); - Title title = this.GetTitle(job.Title); - hb_title_s originalTitle = this.GetOriginalTitle(job.Title); - - EncodingProfile profile = job.EncodingProfile; - - if (preview) - { - nativeJob.start_at_preview = previewNumber + 1; - nativeJob.seek_points = this.previewCount; - - // There are 90,000 PTS per second. - nativeJob.pts_to_stop = previewSeconds * 90000; - } - else - { - switch (job.RangeType) - { - case VideoRangeType.All: - break; - case VideoRangeType.Chapters: - if (job.ChapterStart > 0 && job.ChapterEnd > 0) - { - nativeJob.chapter_start = job.ChapterStart; - nativeJob.chapter_end = job.ChapterEnd; - } - else - { - nativeJob.chapter_start = 1; - nativeJob.chapter_end = title.Chapters.Count; - } - - break; - case VideoRangeType.Seconds: - if (job.SecondsStart < 0 || job.SecondsEnd < 0 || job.SecondsStart >= job.SecondsEnd) - { - throw new ArgumentException("Seconds range " + job.SecondsStart + "-" + job.SecondsEnd + " is invalid.", "job"); - } - - // If they've selected the "full" title duration, leave off the arguments to make it clean - if (job.SecondsStart > 0 || job.SecondsEnd < title.Duration.TotalSeconds) - { - // For some reason "pts_to_stop" actually means the number of pts to stop AFTER the start point. - nativeJob.pts_to_start = (int) (job.SecondsStart * 90000); - nativeJob.pts_to_stop = (int) ((job.SecondsEnd - job.SecondsStart) * 90000); - } - break; - case VideoRangeType.Frames: - if (job.FramesStart < 0 || job.FramesEnd < 0 || job.FramesStart >= job.FramesEnd) - { - throw new ArgumentException("Frames range " + job.FramesStart + "-" + job.FramesEnd + " is invalid.", "job"); - } - - // "frame_to_stop" actually means the number of frames total to encode AFTER the start point. - nativeJob.frame_to_start = job.FramesStart; - nativeJob.frame_to_stop = job.FramesEnd - job.FramesStart; - break; - } - } - - // Chapter markers - nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0; - - List<IntPtr> nativeChapters = nativeJob.list_chapter.ToIntPtrList(); - - if (!preview && profile.IncludeChapterMarkers) - { - int numChapters = title.Chapters.Count; - - if (job.UseDefaultChapterNames) - { - for (int i = 0; i < numChapters; i++) - { - if (i < nativeChapters.Count) - { - HBFunctions.hb_chapter_set_title(nativeChapters[i], "Chapter " + (i + 1)); - } - } - } - else - { - for (int i = 0; i < numChapters; i++) - { - if (i < nativeChapters.Count && i < job.CustomChapterNames.Count) - { - IntPtr chapterNamePtr; - - if (string.IsNullOrWhiteSpace(job.CustomChapterNames[i])) - { - chapterNamePtr = InteropUtilities.CreateUtf8Ptr("Chapter " + (i + 1)); - } - else - { - chapterNamePtr = InteropUtilities.CreateUtf8Ptr(job.CustomChapterNames[i]); - } - - HBFunctions.hb_chapter_set_title__ptr(nativeChapters[i], chapterNamePtr); - Marshal.FreeHGlobal(chapterNamePtr); - } - } - } - } - - Cropping crop = GetCropping(profile, title); - - nativeJob.crop[0] = crop.Top; - nativeJob.crop[1] = crop.Bottom; - nativeJob.crop[2] = crop.Left; - nativeJob.crop[3] = crop.Right; - - var filterList = new List<hb_filter_object_s>(); - - // FILTERS: These must be added in the correct order since we cannot depend on the automatic ordering in hb_add_filter . Ordering is determined - // by the order they show up in the filters enum. - - // Detelecine - if (profile.Detelecine != Detelecine.Off) - { - string settings = null; - if (profile.Detelecine == Detelecine.Custom) - { - settings = profile.CustomDetelecine; - } - - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DETELECINE, settings, allocatedMemory); - } - - // Decomb - if (profile.Decomb != Decomb.Off) - { - string settings = null; - switch (profile.Decomb) - { - case Decomb.Default: - break; - case Decomb.Fast: - settings = "7:2:6:9:1:80"; - break; - case Decomb.Bob: - settings = "455"; - break; - case Decomb.Custom: - settings = profile.CustomDecomb; - break; - default: - break; - } - - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DECOMB, settings, allocatedMemory); - } - - // Deinterlace - if (profile.Deinterlace != Deinterlace.Off) - { - nativeJob.deinterlace = 1; - string settings = null; - - switch (profile.Deinterlace) - { - case Deinterlace.Fast: - settings = "0"; - break; - case Deinterlace.Slow: - settings = "1"; - break; - case Deinterlace.Slower: - settings = "3"; - break; - case Deinterlace.Bob: - settings = "15"; - break; - case Deinterlace.Custom: - settings = profile.CustomDeinterlace; - break; - default: - break; - } - - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DEINTERLACE, settings, allocatedMemory); - } - else - { - nativeJob.deinterlace = 0; - } - - // VFR - if (profile.Framerate == 0) - { - if (profile.ConstantFramerate) - { - // CFR with "Same as Source". Use the title rate - nativeJob.cfr = 1; - nativeJob.vrate = originalTitle.rate; - nativeJob.vrate_base = originalTitle.rate_base; - } - else - { - // Pure VFR "Same as Source" - nativeJob.cfr = 0; - } - } - else - { - // Specified framerate - if (profile.ConstantFramerate) - { - // Mark as pure CFR - nativeJob.cfr = 1; - } - else - { - // Mark as peak framerate - nativeJob.cfr = 2; - } - - nativeJob.vrate = 27000000; - nativeJob.vrate_base = Converters.FramerateToVrate(profile.Framerate); - } - - string vfrSettings = string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", nativeJob.cfr, nativeJob.vrate, nativeJob.vrate_base); - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_VFR, vfrSettings, allocatedMemory); - - // Deblock - if (profile.Deblock > 0) - { - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DEBLOCK, profile.Deblock.ToString(CultureInfo.InvariantCulture), allocatedMemory); - } - - // Denoise - if (profile.Denoise != Denoise.Off) - { - string settings = null; - switch (profile.Denoise) - { - case Denoise.Weak: - settings = "2:1:1:2:3:3"; - break; - case Denoise.Medium: - settings = "3:2:2:2:3:3"; - break; - case Denoise.Strong: - settings = "7:7:7:5:5:5"; - break; - case Denoise.Custom: - settings = profile.CustomDenoise; - break; - default: - break; - } - - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DENOISE, settings, allocatedMemory); - } - - // Crop/scale - int width = profile.Width; - int height = profile.Height; - - int cropHorizontal = crop.Left + crop.Right; - int cropVertical = crop.Top + crop.Bottom; - - if (width == 0) - { - width = title.Resolution.Width - cropHorizontal; - } - - if (profile.MaxWidth > 0 && width > profile.MaxWidth) - { - width = profile.MaxWidth; - } - - if (height == 0) - { - height = title.Resolution.Height - cropVertical; - } - - if (profile.MaxHeight > 0 && height > profile.MaxHeight) - { - height = profile.MaxHeight; - } - - // The job width can sometimes start not clean, due to interference from - // preview generation. We reset it here to allow good calculations. - nativeJob.width = width; - nativeJob.height = height; - - nativeJob.grayscale = profile.Grayscale ? 1 : 0; - - switch (profile.Anamorphic) - { - case Anamorphic.None: - nativeJob.anamorphic.mode = 0; - - Size outputSize = CalculateNonAnamorphicOutput(profile, title); - width = outputSize.Width; - height = outputSize.Height; - - nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0; - - nativeJob.width = width; - nativeJob.height = height; - - nativeJob.maxWidth = profile.MaxWidth; - nativeJob.maxHeight = profile.MaxHeight; - - break; - case Anamorphic.Strict: - nativeJob.anamorphic.mode = 1; - - nativeJob.anamorphic.par_width = title.ParVal.Width; - nativeJob.anamorphic.par_height = title.ParVal.Height; - break; - case Anamorphic.Loose: - nativeJob.anamorphic.mode = 2; - - nativeJob.modulus = profile.Modulus; - - nativeJob.width = width; - - nativeJob.maxWidth = profile.MaxWidth; - - nativeJob.anamorphic.par_width = title.ParVal.Width; - nativeJob.anamorphic.par_height = title.ParVal.Height; - break; - case Anamorphic.Custom: - nativeJob.anamorphic.mode = 3; - - nativeJob.modulus = profile.Modulus; - - if (profile.UseDisplayWidth) - { - if (profile.KeepDisplayAspect) - { - int cropWidth = title.Resolution.Width - cropHorizontal; - int cropHeight = title.Resolution.Height - cropVertical; - - double displayAspect = ((double)(cropWidth * title.ParVal.Width)) / (cropHeight * title.ParVal.Height); - int displayWidth = profile.DisplayWidth; - - if (profile.Height > 0) - { - displayWidth = (int)((double)profile.Height * displayAspect); - } - else if (displayWidth > 0) - { - height = (int)((double)displayWidth / displayAspect); - } - else - { - displayWidth = (int)((double)height * displayAspect); - } - - nativeJob.anamorphic.dar_width = displayWidth; - nativeJob.anamorphic.dar_height = height; - nativeJob.anamorphic.keep_display_aspect = 1; - } - else - { - nativeJob.anamorphic.dar_width = profile.DisplayWidth; - nativeJob.anamorphic.dar_height = height; - nativeJob.anamorphic.keep_display_aspect = 0; - } - } - else - { - nativeJob.anamorphic.par_width = profile.PixelAspectX; - nativeJob.anamorphic.par_height = profile.PixelAspectY; - nativeJob.anamorphic.keep_display_aspect = 0; - } - - nativeJob.width = width; - nativeJob.height = height; - - nativeJob.maxWidth = profile.MaxWidth; - nativeJob.maxHeight = profile.MaxHeight; - - break; - default: - break; - } - - // Need to fix up values before adding crop/scale filter - if (profile.Anamorphic != Anamorphic.None) - { - int anamorphicWidth = 0, anamorphicHeight = 0, anamorphicParWidth = 0, anamorphicParHeight = 0; - - HBFunctions.hb_set_anamorphic_size(ref nativeJob, ref anamorphicWidth, ref anamorphicHeight, ref anamorphicParWidth, ref anamorphicParHeight); - nativeJob.width = anamorphicWidth; - nativeJob.height = anamorphicHeight; - nativeJob.anamorphic.par_width = anamorphicParWidth; - nativeJob.anamorphic.par_height = anamorphicParHeight; - } - - string cropScaleSettings = string.Format( - CultureInfo.InvariantCulture, - "{0}:{1}:{2}:{3}:{4}:{5}", - nativeJob.width, - nativeJob.height, - nativeJob.crop[0], - nativeJob.crop[1], - nativeJob.crop[2], - nativeJob.crop[3]); - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_CROP_SCALE, cropScaleSettings, allocatedMemory); - - - - - - HBVideoEncoder videoEncoder = Encoders.VideoEncoders.FirstOrDefault(e => e.ShortName == profile.VideoEncoder); - if (videoEncoder == null) - { - throw new ArgumentException("Video encoder " + profile.VideoEncoder + " not recognized."); - } - - nativeJob.vcodec = videoEncoder.Id; - - - - // areBframes - // color_matrix - List<hb_audio_s> titleAudio = originalTitle.list_audio.ToList<hb_audio_s>(); - - var audioList = new List<hb_audio_s>(); - int numTracks = 0; - - List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title); - - if (!string.IsNullOrEmpty(profile.AudioEncoderFallback)) - { - HBAudioEncoder audioEncoder = Encoders.GetAudioEncoder(profile.AudioEncoderFallback); - if (audioEncoder == null) - { - throw new ArgumentException("Unrecognized fallback audio encoder: " + profile.AudioEncoderFallback); - } - - nativeJob.acodec_fallback = Encoders.GetAudioEncoder(profile.AudioEncoderFallback).Id; - } - - nativeJob.acodec_copy_mask = (int)NativeConstants.HB_ACODEC_ANY; - - foreach (Tuple<AudioEncoding, int> outputTrack in outputTrackList) - { - audioList.Add(this.ConvertAudioBack(outputTrack.Item1, titleAudio[outputTrack.Item2 - 1], numTracks++, allocatedMemory)); - } - - NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList); - nativeJob.list_audio = nativeAudioList.Ptr; - allocatedMemory.AddRange(nativeAudioList.AllocatedMemory); - - if (job.Subtitles != null) - { - if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0) - { - List<hb_subtitle_s> titleSubtitles = originalTitle.list_subtitle.ToList<hb_subtitle_s>(); - - foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles) - { - if (sourceSubtitle.TrackNumber == 0) - { - // Use subtitle search. - nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0; - nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0; - - if (!sourceSubtitle.BurnedIn) - { - nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB; - } - - nativeJob.indepth_scan = 1; - } - else - { - // Use specified subtitle. - hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1]; - var subtitleConfig = new hb_subtitle_config_s(); - - subtitleConfig.force = sourceSubtitle.Forced ? 1 : 0; - subtitleConfig.default_track = sourceSubtitle.Default ? 1 : 0; - - bool supportsBurn = nativeSubtitle.source == hb_subtitle_s_subsource.VOBSUB || nativeSubtitle.source == hb_subtitle_s_subsource.SSASUB || nativeSubtitle.source == hb_subtitle_s_subsource.PGSSUB; - if (supportsBurn && sourceSubtitle.BurnedIn) - { - subtitleConfig.dest = hb_subtitle_config_s_subdest.RENDERSUB; - } - else - { - subtitleConfig.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB; - } - - int subtitleAddSucceded = HBFunctions.hb_subtitle_add(ref nativeJob, ref subtitleConfig, sourceSubtitle.TrackNumber - 1); - if (subtitleAddSucceded == 0) - { - System.Diagnostics.Debug.WriteLine("Subtitle add failed"); - } - } - } - } - - if (job.Subtitles.SrtSubtitles != null) - { - foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles) - { - var subtitleConfig = new hb_subtitle_config_s(); - - subtitleConfig.src_codeset = srtSubtitle.CharacterCode; - subtitleConfig.src_filename = srtSubtitle.FileName; - subtitleConfig.offset = srtSubtitle.Offset; - subtitleConfig.default_track = srtSubtitle.Default ? 1 : 0; - - int srtAddSucceded = HBFunctions.hb_srt_add(ref nativeJob, ref subtitleConfig, srtSubtitle.LanguageCode); - if (srtAddSucceded == 0) - { - System.Diagnostics.Debug.WriteLine("SRT add failed"); - } - } - } - - bool hasBurnedSubtitle = job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Any(s => s.BurnedIn); - if (hasBurnedSubtitle) - { - this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_RENDER_SUB, string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}:{3}", crop.Top, crop.Bottom, crop.Left, crop.Right), allocatedMemory); - } - } - - // Construct final filter list - nativeJob.list_filter = this.ConvertFilterListToNative(filterList, allocatedMemory).Ptr; - - if (profile.ScaleMethod == ScaleMethod.Bicubic) - { - HBFunctions.hb_get_opencl_env(); - nativeJob.use_opencl = 1; - } - else - { - nativeJob.use_opencl = 0; - } - - nativeJob.qsv.decode = profile.QsvDecode ? 1 : 0; - nativeJob.use_hwd = job.DxvaDecoding ? 1 : 0; + + this.titles.Add(newTitle); + } + + if (this.originalTitles.Count > 0) + { + this.featureTitle = titleSet.feature; + } + else + { + this.featureTitle = 0; + } + + this.scanPollTimer.Stop(); + + if (this.ScanCompleted != null) + { + this.ScanCompleted(this, new System.EventArgs()); + } + } + } + + /// <summary> + /// Checks the status of the ongoing encode. + /// </summary> + private void PollEncodeProgress() + { + hb_state_s state = new hb_state_s(); + HBFunctions.hb_get_state(this.hbHandle, ref state); + + if (state.state == NativeConstants.HB_STATE_WORKING) + { + if (this.EncodeProgress != null) + { + int pass = 1; + int rawJobNumber = state.param.working.job_cur; + + if (this.currentJob.EncodingProfile.TwoPass) + { + if (this.subtitleScan) + { + switch (rawJobNumber) + { + case 1: + pass = -1; + break; + case 2: + pass = 1; + break; + case 3: + pass = 2; + break; + default: + break; + } + } + else + { + switch (rawJobNumber) + { + case 1: + pass = 1; + break; + case 2: + pass = 2; + break; + default: + break; + } + } + } + else + { + if (this.subtitleScan) + { + switch (rawJobNumber) + { + case 1: + pass = -1; + break; + case 2: + pass = 1; + break; + default: + break; + } + } + else + { + pass = 1; + } + } + + var progressEventArgs = new EncodeProgressEventArgs + { + FractionComplete = state.param.working.progress, + CurrentFrameRate = state.param.working.rate_cur, + AverageFrameRate = state.param.working.rate_avg, + EstimatedTimeLeft = new TimeSpan(state.param.working.hours, state.param.working.minutes, state.param.working.seconds), + Pass = pass + }; + + this.EncodeProgress(this, progressEventArgs); + } + } + else if (state.state == NativeConstants.HB_STATE_WORKDONE) + { + InteropUtilities.FreeMemory(this.encodeAllocatedMemory); + this.encodePollTimer.Stop(); + + if (this.EncodeCompleted != null) + { + this.EncodeCompleted(this, new EncodeCompletedEventArgs { Error = state.param.workdone.error > 0 }); + } + } + } + + /// <summary> + /// Applies the encoding job to the native memory structure and returns a list of memory + /// locations allocated during this. + /// </summary> + /// <param name="nativeJob">The native structure to apply to job info to.</param> + /// <param name="job">The job info to apply.</param> + /// <returns>The list of memory locations allocated for the job.</returns> + private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job) + { + return this.ApplyJob(ref nativeJob, job, false, 0, 0, 0); + } + + /// <summary> + /// Applies the encoding job to the native memory structure and returns a list of memory + /// locations allocated during this. + /// </summary> + /// <param name="nativeJob">The native structure to apply to job info to.</param> + /// <param name="job">The job info to apply.</param> + /// <param name="preview">True if this is a preview encode.</param> + /// <param name="previewNumber">The preview number (0-based) to encode.</param> + /// <param name="previewSeconds">The number of seconds in the preview.</param> + /// <param name="overallSelectedLengthSeconds">The currently selected encode length. Used in preview + /// for calculating bitrate when the target size would be wrong.</param> + /// <returns>The list of memory locations allocated for the job.</returns> + private List<IntPtr> ApplyJob(ref hb_job_s nativeJob, EncodeJob job, bool preview, int previewNumber, int previewSeconds, double overallSelectedLengthSeconds) + { + var allocatedMemory = new List<IntPtr>(); + Title title = this.GetTitle(job.Title); + hb_title_s originalTitle = this.GetOriginalTitle(job.Title); + + EncodingProfile profile = job.EncodingProfile; + + if (preview) + { + nativeJob.start_at_preview = previewNumber + 1; + nativeJob.seek_points = this.previewCount; + + // There are 90,000 PTS per second. + nativeJob.pts_to_stop = previewSeconds * 90000; + } + else + { + switch (job.RangeType) + { + case VideoRangeType.All: + break; + case VideoRangeType.Chapters: + if (job.ChapterStart > 0 && job.ChapterEnd > 0) + { + nativeJob.chapter_start = job.ChapterStart; + nativeJob.chapter_end = job.ChapterEnd; + } + else + { + nativeJob.chapter_start = 1; + nativeJob.chapter_end = title.Chapters.Count; + } + + break; + case VideoRangeType.Seconds: + if (job.SecondsStart < 0 || job.SecondsEnd < 0 || job.SecondsStart >= job.SecondsEnd) + { + throw new ArgumentException("Seconds range " + job.SecondsStart + "-" + job.SecondsEnd + " is invalid.", "job"); + } + + // If they've selected the "full" title duration, leave off the arguments to make it clean + if (job.SecondsStart > 0 || job.SecondsEnd < title.Duration.TotalSeconds) + { + // For some reason "pts_to_stop" actually means the number of pts to stop AFTER the start point. + nativeJob.pts_to_start = (int)(job.SecondsStart * 90000); + nativeJob.pts_to_stop = (int)((job.SecondsEnd - job.SecondsStart) * 90000); + } + break; + case VideoRangeType.Frames: + if (job.FramesStart < 0 || job.FramesEnd < 0 || job.FramesStart >= job.FramesEnd) + { + throw new ArgumentException("Frames range " + job.FramesStart + "-" + job.FramesEnd + " is invalid.", "job"); + } + + // "frame_to_stop" actually means the number of frames total to encode AFTER the start point. + nativeJob.frame_to_start = job.FramesStart; + nativeJob.frame_to_stop = job.FramesEnd - job.FramesStart; + break; + } + } + + // Chapter markers + nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0; + + List<IntPtr> nativeChapters = nativeJob.list_chapter.ToIntPtrList(); + + if (!preview && profile.IncludeChapterMarkers) + { + int numChapters = title.Chapters.Count; + + if (job.UseDefaultChapterNames) + { + for (int i = 0; i < numChapters; i++) + { + if (i < nativeChapters.Count) + { + HBFunctions.hb_chapter_set_title(nativeChapters[i], "Chapter " + (i + 1)); + } + } + } + else + { + for (int i = 0; i < numChapters; i++) + { + if (i < nativeChapters.Count && i < job.CustomChapterNames.Count) + { + IntPtr chapterNamePtr; + + if (string.IsNullOrWhiteSpace(job.CustomChapterNames[i])) + { + chapterNamePtr = InteropUtilities.CreateUtf8Ptr("Chapter " + (i + 1)); + } + else + { + chapterNamePtr = InteropUtilities.CreateUtf8Ptr(job.CustomChapterNames[i]); + } + + HBFunctions.hb_chapter_set_title__ptr(nativeChapters[i], chapterNamePtr); + Marshal.FreeHGlobal(chapterNamePtr); + } + } + } + } + + Cropping crop = GetCropping(profile, title); + + nativeJob.crop[0] = crop.Top; + nativeJob.crop[1] = crop.Bottom; + nativeJob.crop[2] = crop.Left; + nativeJob.crop[3] = crop.Right; + + var filterList = new List<hb_filter_object_s>(); + + // FILTERS: These must be added in the correct order since we cannot depend on the automatic ordering in hb_add_filter . Ordering is determined + // by the order they show up in the filters enum. + + // Detelecine + if (profile.Detelecine != Detelecine.Off) + { + string settings = null; + if (profile.Detelecine == Detelecine.Custom) + { + settings = profile.CustomDetelecine; + } + + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DETELECINE, settings, allocatedMemory); + } + + // Decomb + if (profile.Decomb != Decomb.Off) + { + string settings = null; + switch (profile.Decomb) + { + case Decomb.Default: + break; + case Decomb.Fast: + settings = "7:2:6:9:1:80"; + break; + case Decomb.Bob: + settings = "455"; + break; + case Decomb.Custom: + settings = profile.CustomDecomb; + break; + default: + break; + } + + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DECOMB, settings, allocatedMemory); + } + + // Deinterlace + if (profile.Deinterlace != Deinterlace.Off) + { + nativeJob.deinterlace = 1; + string settings = null; + + switch (profile.Deinterlace) + { + case Deinterlace.Fast: + settings = "0"; + break; + case Deinterlace.Slow: + settings = "1"; + break; + case Deinterlace.Slower: + settings = "3"; + break; + case Deinterlace.Bob: + settings = "15"; + break; + case Deinterlace.Custom: + settings = profile.CustomDeinterlace; + break; + default: + break; + } + + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DEINTERLACE, settings, allocatedMemory); + } + else + { + nativeJob.deinterlace = 0; + } + + // VFR + if (profile.Framerate == 0) + { + if (profile.ConstantFramerate) + { + // CFR with "Same as Source". Use the title rate + nativeJob.cfr = 1; + nativeJob.vrate = originalTitle.rate; + nativeJob.vrate_base = originalTitle.rate_base; + } + else + { + // Pure VFR "Same as Source" + nativeJob.cfr = 0; + } + } + else + { + // Specified framerate + if (profile.ConstantFramerate) + { + // Mark as pure CFR + nativeJob.cfr = 1; + } + else + { + // Mark as peak framerate + nativeJob.cfr = 2; + } + + nativeJob.vrate = 27000000; + nativeJob.vrate_base = Converters.FramerateToVrate(profile.Framerate); + } + + string vfrSettings = string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}", nativeJob.cfr, nativeJob.vrate, nativeJob.vrate_base); + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_VFR, vfrSettings, allocatedMemory); + + // Deblock + if (profile.Deblock > 0) + { + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DEBLOCK, profile.Deblock.ToString(CultureInfo.InvariantCulture), allocatedMemory); + } + + // Denoise + if (profile.Denoise != Denoise.Off) + { + string settings = null; + switch (profile.Denoise) + { + case Denoise.Weak: + settings = "2:1:1:2:3:3"; + break; + case Denoise.Medium: + settings = "3:2:2:2:3:3"; + break; + case Denoise.Strong: + settings = "7:7:7:5:5:5"; + break; + case Denoise.Custom: + settings = profile.CustomDenoise; + break; + default: + break; + } + + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_DENOISE, settings, allocatedMemory); + } + + // Crop/scale + int width = profile.Width; + int height = profile.Height; + + int cropHorizontal = crop.Left + crop.Right; + int cropVertical = crop.Top + crop.Bottom; + + if (width == 0) + { + width = title.Resolution.Width - cropHorizontal; + } + + if (profile.MaxWidth > 0 && width > profile.MaxWidth) + { + width = profile.MaxWidth; + } + + if (height == 0) + { + height = title.Resolution.Height - cropVertical; + } + + if (profile.MaxHeight > 0 && height > profile.MaxHeight) + { + height = profile.MaxHeight; + } + + // The job width can sometimes start not clean, due to interference from + // preview generation. We reset it here to allow good calculations. + nativeJob.width = width; + nativeJob.height = height; + + nativeJob.grayscale = profile.Grayscale ? 1 : 0; + + switch (profile.Anamorphic) + { + case Anamorphic.None: + nativeJob.anamorphic.mode = 0; + + Size outputSize = CalculateNonAnamorphicOutput(profile, title); + width = outputSize.Width; + height = outputSize.Height; + + nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0; + + nativeJob.width = width; + nativeJob.height = height; + + nativeJob.maxWidth = profile.MaxWidth; + nativeJob.maxHeight = profile.MaxHeight; + + break; + case Anamorphic.Strict: + nativeJob.anamorphic.mode = 1; + + nativeJob.anamorphic.par_width = title.ParVal.Width; + nativeJob.anamorphic.par_height = title.ParVal.Height; + break; + case Anamorphic.Loose: + nativeJob.anamorphic.mode = 2; + + nativeJob.modulus = profile.Modulus; + + nativeJob.width = width; + + nativeJob.maxWidth = profile.MaxWidth; + + nativeJob.anamorphic.par_width = title.ParVal.Width; + nativeJob.anamorphic.par_height = title.ParVal.Height; + break; + case Anamorphic.Custom: + nativeJob.anamorphic.mode = 3; + + nativeJob.modulus = profile.Modulus; + + if (profile.UseDisplayWidth) + { + if (profile.KeepDisplayAspect) + { + int cropWidth = title.Resolution.Width - cropHorizontal; + int cropHeight = title.Resolution.Height - cropVertical; + + double displayAspect = ((double)(cropWidth * title.ParVal.Width)) / (cropHeight * title.ParVal.Height); + int displayWidth = profile.DisplayWidth; + + if (profile.Height > 0) + { + displayWidth = (int)((double)profile.Height * displayAspect); + } + else if (displayWidth > 0) + { + height = (int)((double)displayWidth / displayAspect); + } + else + { + displayWidth = (int)((double)height * displayAspect); + } + + nativeJob.anamorphic.dar_width = displayWidth; + nativeJob.anamorphic.dar_height = height; + nativeJob.anamorphic.keep_display_aspect = 1; + } + else + { + nativeJob.anamorphic.dar_width = profile.DisplayWidth; + nativeJob.anamorphic.dar_height = height; + nativeJob.anamorphic.keep_display_aspect = 0; + } + } + else + { + nativeJob.anamorphic.par_width = profile.PixelAspectX; + nativeJob.anamorphic.par_height = profile.PixelAspectY; + nativeJob.anamorphic.keep_display_aspect = 0; + } + + nativeJob.width = width; + nativeJob.height = height; + + nativeJob.maxWidth = profile.MaxWidth; + nativeJob.maxHeight = profile.MaxHeight; + + break; + default: + break; + } + + // Need to fix up values before adding crop/scale filter + if (profile.Anamorphic != Anamorphic.None) + { + int anamorphicWidth = 0, anamorphicHeight = 0, anamorphicParWidth = 0, anamorphicParHeight = 0; + + HBFunctions.hb_set_anamorphic_size(ref nativeJob, ref anamorphicWidth, ref anamorphicHeight, ref anamorphicParWidth, ref anamorphicParHeight); + nativeJob.width = anamorphicWidth; + nativeJob.height = anamorphicHeight; + nativeJob.anamorphic.par_width = anamorphicParWidth; + nativeJob.anamorphic.par_height = anamorphicParHeight; + } + + string cropScaleSettings = string.Format( + CultureInfo.InvariantCulture, + "{0}:{1}:{2}:{3}:{4}:{5}", + nativeJob.width, + nativeJob.height, + nativeJob.crop[0], + nativeJob.crop[1], + nativeJob.crop[2], + nativeJob.crop[3]); + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_CROP_SCALE, cropScaleSettings, allocatedMemory); + + + + + + HBVideoEncoder videoEncoder = Encoders.VideoEncoders.FirstOrDefault(e => e.ShortName == profile.VideoEncoder); + if (videoEncoder == null) + { + throw new ArgumentException("Video encoder " + profile.VideoEncoder + " not recognized."); + } + + nativeJob.vcodec = videoEncoder.Id; + + + + // areBframes + // color_matrix + List<hb_audio_s> titleAudio = originalTitle.list_audio.ToList<hb_audio_s>(); + + var audioList = new List<hb_audio_s>(); + int numTracks = 0; + + List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title); + + if (!string.IsNullOrEmpty(profile.AudioEncoderFallback)) + { + HBAudioEncoder audioEncoder = Encoders.GetAudioEncoder(profile.AudioEncoderFallback); + if (audioEncoder == null) + { + throw new ArgumentException("Unrecognized fallback audio encoder: " + profile.AudioEncoderFallback); + } + + nativeJob.acodec_fallback = Encoders.GetAudioEncoder(profile.AudioEncoderFallback).Id; + } + + nativeJob.acodec_copy_mask = (int)NativeConstants.HB_ACODEC_ANY; + + foreach (Tuple<AudioEncoding, int> outputTrack in outputTrackList) + { + audioList.Add(this.ConvertAudioBack(outputTrack.Item1, titleAudio[outputTrack.Item2 - 1], numTracks++, allocatedMemory)); + } + + NativeList nativeAudioList = InteropUtilities.ConvertListBack<hb_audio_s>(audioList); + nativeJob.list_audio = nativeAudioList.Ptr; + allocatedMemory.AddRange(nativeAudioList.AllocatedMemory); + + if (job.Subtitles != null) + { + if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0) + { + List<hb_subtitle_s> titleSubtitles = originalTitle.list_subtitle.ToList<hb_subtitle_s>(); + + foreach (SourceSubtitle sourceSubtitle in job.Subtitles.SourceSubtitles) + { + if (sourceSubtitle.TrackNumber == 0) + { + // Use subtitle search. + nativeJob.select_subtitle_config.force = sourceSubtitle.Forced ? 1 : 0; + nativeJob.select_subtitle_config.default_track = sourceSubtitle.Default ? 1 : 0; + + if (!sourceSubtitle.BurnedIn) + { + nativeJob.select_subtitle_config.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB; + } + + nativeJob.indepth_scan = 1; + } + else + { + // Use specified subtitle. + hb_subtitle_s nativeSubtitle = titleSubtitles[sourceSubtitle.TrackNumber - 1]; + var subtitleConfig = new hb_subtitle_config_s(); + + subtitleConfig.force = sourceSubtitle.Forced ? 1 : 0; + subtitleConfig.default_track = sourceSubtitle.Default ? 1 : 0; + + bool supportsBurn = nativeSubtitle.source == hb_subtitle_s_subsource.VOBSUB || nativeSubtitle.source == hb_subtitle_s_subsource.SSASUB || nativeSubtitle.source == hb_subtitle_s_subsource.PGSSUB; + if (supportsBurn && sourceSubtitle.BurnedIn) + { + subtitleConfig.dest = hb_subtitle_config_s_subdest.RENDERSUB; + } + else + { + subtitleConfig.dest = hb_subtitle_config_s_subdest.PASSTHRUSUB; + } + + int subtitleAddSucceded = HBFunctions.hb_subtitle_add(ref nativeJob, ref subtitleConfig, sourceSubtitle.TrackNumber - 1); + if (subtitleAddSucceded == 0) + { + System.Diagnostics.Debug.WriteLine("Subtitle add failed"); + } + } + } + } + + if (job.Subtitles.SrtSubtitles != null) + { + foreach (SrtSubtitle srtSubtitle in job.Subtitles.SrtSubtitles) + { + var subtitleConfig = new hb_subtitle_config_s(); + + subtitleConfig.src_codeset = srtSubtitle.CharacterCode; + subtitleConfig.src_filename = srtSubtitle.FileName; + subtitleConfig.offset = srtSubtitle.Offset; + subtitleConfig.default_track = srtSubtitle.Default ? 1 : 0; + + int srtAddSucceded = HBFunctions.hb_srt_add(ref nativeJob, ref subtitleConfig, srtSubtitle.LanguageCode); + if (srtAddSucceded == 0) + { + System.Diagnostics.Debug.WriteLine("SRT add failed"); + } + } + } + + bool hasBurnedSubtitle = job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Any(s => s.BurnedIn); + if (hasBurnedSubtitle) + { + this.AddFilter(filterList, (int)hb_filter_ids.HB_FILTER_RENDER_SUB, string.Format(CultureInfo.InvariantCulture, "{0}:{1}:{2}:{3}", crop.Top, crop.Bottom, crop.Left, crop.Right), allocatedMemory); + } + } + + // Construct final filter list + nativeJob.list_filter = this.ConvertFilterListToNative(filterList, allocatedMemory).Ptr; + + if (profile.ScaleMethod == ScaleMethod.Bicubic) + { + HBFunctions.hb_get_opencl_env(); + nativeJob.use_opencl = 1; + } + else + { + nativeJob.use_opencl = 0; + } + + nativeJob.qsv.decode = profile.QsvDecode ? 1 : 0; + nativeJob.use_hwd = job.DxvaDecoding ? 1 : 0; #pragma warning disable 612, 618 - if (profile.OutputFormat == Container.Mp4) - { - nativeJob.mux = HBFunctions.hb_container_get_from_name("av_mp4"); - } - else if (profile.OutputFormat == Container.Mkv) - { - nativeJob.mux = HBFunctions.hb_container_get_from_name("av_mkv"); - } + if (profile.OutputFormat == Container.Mp4) + { + nativeJob.mux = HBFunctions.hb_container_get_from_name("av_mp4"); + } + else if (profile.OutputFormat == Container.Mkv) + { + nativeJob.mux = HBFunctions.hb_container_get_from_name("av_mkv"); + } #pragma warning restore 612, 618 - if (profile.ContainerName != null) - { - nativeJob.mux = HBFunctions.hb_container_get_from_name(profile.ContainerName); - } - - IntPtr outputPathPtr = InteropUtilities.CreateUtf8Ptr(job.OutputPath); - allocatedMemory.Add(outputPathPtr); - nativeJob.file = outputPathPtr; - - nativeJob.largeFileSize = profile.LargeFile ? 1 : 0; - nativeJob.mp4_optimize = profile.Optimize ? 1 : 0; - nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0; - - if (title.AngleCount > 1) - { - nativeJob.angle = job.Angle; - } - - switch (profile.VideoEncodeRateType) - { - case VideoEncodeRateType.ConstantQuality: - nativeJob.vquality = (float)profile.Quality; - nativeJob.vbitrate = 0; - break; - case VideoEncodeRateType.AverageBitrate: - nativeJob.vquality = -1; - nativeJob.vbitrate = profile.VideoBitrate; - break; - case VideoEncodeRateType.TargetSize: - nativeJob.vquality = -1; - nativeJob.vbitrate = this.CalculateBitrate(job, profile.TargetSize, overallSelectedLengthSeconds); - break; - default: - break; - } - - // frames_to_skip - - return allocatedMemory; - } - - /// <summary> - /// Gets a list of encodings and target track indices (1-based). - /// </summary> - /// <param name="job">The encode job</param> - /// <param name="title">The title the job is meant to encode.</param> - /// <returns>A list of encodings and target track indices (1-based).</returns> - private List<Tuple<AudioEncoding, int>> GetOutputTracks(EncodeJob job, Title title) - { - var list = new List<Tuple<AudioEncoding, int>>(); - - foreach (AudioEncoding encoding in job.EncodingProfile.AudioEncodings) - { - if (encoding.InputNumber == 0) - { - // Add this encoding for all chosen tracks - foreach (int chosenTrack in job.ChosenAudioTracks) - { - // In normal cases we'll never have a chosen audio track that doesn't exist but when batch encoding - // we just choose the first audio track without checking if it exists. - if (chosenTrack <= title.AudioTracks.Count) - { - list.Add(new Tuple<AudioEncoding, int>(encoding, chosenTrack)); - } - } - } - else if (encoding.InputNumber <= job.ChosenAudioTracks.Count) - { - // Add this encoding for the specified track, if it exists - int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1]; - - // In normal cases we'll never have a chosen audio track that doesn't exist but when batch encoding - // we just choose the first audio track without checking if it exists. - if (trackNumber <= title.AudioTracks.Count) - { - list.Add(new Tuple<AudioEncoding, int>(encoding, trackNumber)); - } - } - } - - return list; - } - - /// <summary> - /// Adds a filter to the given filter list. - /// </summary> - /// <param name="filterList">The list to add the filter to.</param> - /// <param name="filterType">The type of filter.</param> - /// <param name="settings">Settings for the filter.</param> - /// <param name="allocatedMemory">The list of allocated memory.</param> - private void AddFilter(List<hb_filter_object_s> filterList, int filterType, string settings, List<IntPtr> allocatedMemory) - { - IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings); - hb_filter_object_s filter = InteropUtilities.ReadStructure<hb_filter_object_s>(HBFunctions.hb_filter_init(filterType)); - filter.settings = settingsNativeString; - - allocatedMemory.Add(settingsNativeString); - filterList.Add(filter); - } - - /// <summary> - /// Converts the given filter list to a native list. - /// </summary> - /// <remarks>Sorts the list by filter ID before converting to a native list, as HB expects it that way. - /// The list memory itself will be added to the allocatedMemory list.</remarks> - /// <param name="filterList">The filter list to convert.</param> - /// <param name="allocatedMemory">The list of allocated memory to add to.</param> - /// <returns>The converted list.</returns> - private NativeList ConvertFilterListToNative(List<hb_filter_object_s> filterList, List<IntPtr> allocatedMemory) - { - var filterPtrList = new List<IntPtr>(); - - var sortedList = filterList.OrderBy(f => f.id); - foreach (var filter in sortedList) - { - IntPtr filterPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_filter_object_s))); - Marshal.StructureToPtr(filter, filterPtr, false); - - allocatedMemory.Add(filterPtr); - filterPtrList.Add(filterPtr); - } - - NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterPtrList); - allocatedMemory.AddRange(filterListNative.AllocatedMemory); - - return filterListNative; - } - - /// <summary> - /// Gets the title, given the 1-based title number. - /// </summary> - /// <param name="titleNumber">The number of the title (1-based).</param> - /// <returns>The requested Title.</returns> - private Title GetTitle(int titleNumber) - { - return this.Titles.SingleOrDefault(title => title.TitleNumber == titleNumber); - } - - /// <summary> - /// Gets the 1-based title index of the given title. - /// </summary> - /// <param name="titleNumber">The 1-based title title number.</param> - /// <returns>The 1-based title index.</returns> - private int GetTitleIndex(int titleNumber) - { - Title title = this.GetTitle(titleNumber); - return this.GetTitleIndex(title); - } - - /// <summary> - /// Gets the 1-based title index of the given title. - /// </summary> - /// <param name="title">The title to look up</param> - /// <returns>The 1-based title index of the given title.</returns> - private int GetTitleIndex(Title title) - { - return this.Titles.IndexOf(title) + 1; - } - - /// <summary> - /// Gets the native title object from the title index. - /// </summary> - /// <param name="titleIndex">The index of the title (1-based).</param> - /// <returns>Gets the native title object for the given index.</returns> - private hb_title_s GetOriginalTitle(int titleIndex) - { - List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList(); - if (matchingTitles.Count == 0) - { - throw new ArgumentException("Could not find specified title."); - } - - if (matchingTitles.Count > 1) - { - throw new ArgumentException("Multiple titles matched."); - } - - return matchingTitles[0]; - } - - /// <summary> - /// Applies an audio encoding to a native audio encoding base structure. - /// </summary> - /// <param name="encoding">The encoding to apply.</param> - /// <param name="baseStruct">The base native structure.</param> - /// <param name="outputTrack">The output track number (0-based).</param> - /// <param name="allocatedMemory">The collection of allocated memory.</param> - /// <returns>The resulting native audio structure.</returns> - private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int outputTrack, List<IntPtr> allocatedMemory) - { - hb_audio_s nativeAudio = baseStruct; - HBAudioEncoder encoder = Encoders.GetAudioEncoder(encoding.Encoder); - - nativeAudio.config.output.track = outputTrack; - nativeAudio.config.output.codec = (uint)encoder.Id; - nativeAudio.config.output.compression_level = -1; - nativeAudio.config.output.samplerate = nativeAudio.config.input.samplerate; - nativeAudio.config.output.dither_method = -1; - - if (!encoder.IsPassthrough) - { - if (encoding.SampleRateRaw != 0) - { - nativeAudio.config.output.samplerate = encoding.SampleRateRaw; - } - - HBMixdown mixdown = Encoders.GetMixdown(encoding.Mixdown); - nativeAudio.config.output.mixdown = mixdown.Id; - - if (encoding.EncodeRateType == AudioEncodeRateType.Bitrate) - { - // Disable quality targeting. - nativeAudio.config.output.quality = -3; - - if (encoding.Bitrate == 0) - { - // Bitrate of 0 means auto: choose the default for this codec, sample rate and mixdown. - nativeAudio.config.output.bitrate = HBFunctions.hb_audio_bitrate_get_default( - nativeAudio.config.output.codec, - nativeAudio.config.output.samplerate, - nativeAudio.config.output.mixdown); - } - else - { - nativeAudio.config.output.bitrate = encoding.Bitrate; - } - } - else if (encoding.EncodeRateType == AudioEncodeRateType.Quality) - { - // Bitrate of -1 signals quality targeting. - nativeAudio.config.output.bitrate = -1; - nativeAudio.config.output.quality = encoding.Quality; - } - - // If this encoder supports compression level, pass it in. - if (encoder.SupportsCompression) - { - nativeAudio.config.output.compression_level = encoding.Compression; - } - - nativeAudio.config.output.dynamic_range_compression = encoding.Drc; - nativeAudio.config.output.gain = encoding.Gain; - } - - if (!string.IsNullOrEmpty(encoding.Name)) - { - IntPtr encodingNamePtr = Marshal.StringToHGlobalAnsi(encoding.Name); - nativeAudio.config.output.name = encodingNamePtr; - allocatedMemory.Add(encodingNamePtr); - } - - nativeAudio.padding = new byte[MarshalingConstants.AudioPaddingBytes]; - - return nativeAudio; - } - - /// <summary> - /// Converts a native title to a Title object. - /// </summary> - /// <param name="title">The native title structure.</param> - /// <returns>The managed Title object.</returns> - private Title ConvertTitle(hb_title_s title) - { - var newTitle = new Title - { - TitleNumber = title.index, - Playlist = title.playlist, - Resolution = new Size(title.width, title.height), - ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height), - Duration = Converters.PtsToTimeSpan(title.duration), - DurationPts = title.duration, - AutoCropDimensions = new Cropping - { - Top = title.crop[0], - Bottom = title.crop[1], - Left = title.crop[2], - Right = title.crop[3] - }, - AspectRatio = title.aspect, - AngleCount = title.angle_count, - VideoCodecName = title.video_codec_name, - Framerate = ((double)title.rate) / title.rate_base, - FramerateNumerator = title.rate, - FramerateDenominator = title.rate_base, - Path = title.path - }; - - switch (title.type) - { - case hb_title_type_anon.HB_STREAM_TYPE: - newTitle.InputType = InputType.Stream; - break; - case hb_title_type_anon.HB_DVD_TYPE: - newTitle.InputType = InputType.Dvd; - break; - case hb_title_type_anon.HB_BD_TYPE: - newTitle.InputType = InputType.Bluray; - break; - } - - int currentSubtitleTrack = 1; - List<hb_subtitle_s> subtitleList = title.list_subtitle.ToList<hb_subtitle_s>(); - foreach (hb_subtitle_s subtitle in subtitleList) - { - var newSubtitle = new Subtitle - { - TrackNumber = currentSubtitleTrack, - Language = subtitle.lang, - LanguageCode = subtitle.iso639_2 - }; - - if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB) - { - newSubtitle.SubtitleType = SubtitleType.Picture; - } - else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB) - { - newSubtitle.SubtitleType = SubtitleType.Text; - } - - newSubtitle.SubtitleSourceInt = (int)subtitle.source; - - switch (subtitle.source) - { - case hb_subtitle_s_subsource.CC608SUB: - newSubtitle.SubtitleSource = SubtitleSource.CC608; - break; - case hb_subtitle_s_subsource.CC708SUB: - newSubtitle.SubtitleSource = SubtitleSource.CC708; - break; - case hb_subtitle_s_subsource.SRTSUB: - newSubtitle.SubtitleSource = SubtitleSource.SRT; - break; - case hb_subtitle_s_subsource.SSASUB: - newSubtitle.SubtitleSource = SubtitleSource.SSA; - break; - case hb_subtitle_s_subsource.TX3GSUB: - newSubtitle.SubtitleSource = SubtitleSource.TX3G; - break; - case hb_subtitle_s_subsource.UTF8SUB: - newSubtitle.SubtitleSource = SubtitleSource.UTF8; - break; - case hb_subtitle_s_subsource.VOBSUB: - newSubtitle.SubtitleSource = SubtitleSource.VobSub; - break; - case hb_subtitle_s_subsource.PGSSUB: - newSubtitle.SubtitleSource = SubtitleSource.PGS; - break; - default: - break; - } - - newTitle.Subtitles.Add(newSubtitle); - - currentSubtitleTrack++; - } - - int currentAudioTrack = 1; - List<hb_audio_s> audioList = title.list_audio.ToList<hb_audio_s>(); - foreach (hb_audio_s audio in audioList) - { - var newAudio = new AudioTrack - { - TrackNumber = currentAudioTrack, - Codec = Converters.NativeToAudioCodec(audio.config.input.codec), - CodecId = audio.config.input.codec, - Language = audio.config.lang.simple, - LanguageCode = audio.config.lang.iso639_2, - Description = audio.config.lang.description, - ChannelLayout = audio.config.input.channel_layout, - SampleRate = audio.config.input.samplerate, - Bitrate = audio.config.input.bitrate - }; - - newTitle.AudioTracks.Add(newAudio); - - currentAudioTrack++; - } - - List<hb_chapter_s> chapterList = title.list_chapter.ToList<hb_chapter_s>(); - foreach (hb_chapter_s chapter in chapterList) - { - var newChapter = new Chapter - { + if (profile.ContainerName != null) + { + nativeJob.mux = HBFunctions.hb_container_get_from_name(profile.ContainerName); + } + + IntPtr outputPathPtr = InteropUtilities.CreateUtf8Ptr(job.OutputPath); + allocatedMemory.Add(outputPathPtr); + nativeJob.file = outputPathPtr; + + nativeJob.largeFileSize = profile.LargeFile ? 1 : 0; + nativeJob.mp4_optimize = profile.Optimize ? 1 : 0; + nativeJob.ipod_atom = profile.IPod5GSupport ? 1 : 0; + + if (title.AngleCount > 1) + { + nativeJob.angle = job.Angle; + } + + switch (profile.VideoEncodeRateType) + { + case VideoEncodeRateType.ConstantQuality: + nativeJob.vquality = (float)profile.Quality; + nativeJob.vbitrate = 0; + break; + case VideoEncodeRateType.AverageBitrate: + nativeJob.vquality = -1; + nativeJob.vbitrate = profile.VideoBitrate; + break; + case VideoEncodeRateType.TargetSize: + nativeJob.vquality = -1; + nativeJob.vbitrate = this.CalculateBitrate(job, profile.TargetSize, overallSelectedLengthSeconds); + break; + default: + break; + } + + // frames_to_skip + + return allocatedMemory; + } + + /// <summary> + /// Gets a list of encodings and target track indices (1-based). + /// </summary> + /// <param name="job">The encode job</param> + /// <param name="title">The title the job is meant to encode.</param> + /// <returns>A list of encodings and target track indices (1-based).</returns> + private List<Tuple<AudioEncoding, int>> GetOutputTracks(EncodeJob job, Title title) + { + var list = new List<Tuple<AudioEncoding, int>>(); + + foreach (AudioEncoding encoding in job.EncodingProfile.AudioEncodings) + { + if (encoding.InputNumber == 0) + { + // Add this encoding for all chosen tracks + foreach (int chosenTrack in job.ChosenAudioTracks) + { + // In normal cases we'll never have a chosen audio track that doesn't exist but when batch encoding + // we just choose the first audio track without checking if it exists. + if (chosenTrack <= title.AudioTracks.Count) + { + list.Add(new Tuple<AudioEncoding, int>(encoding, chosenTrack)); + } + } + } + else if (encoding.InputNumber <= job.ChosenAudioTracks.Count) + { + // Add this encoding for the specified track, if it exists + int trackNumber = job.ChosenAudioTracks[encoding.InputNumber - 1]; + + // In normal cases we'll never have a chosen audio track that doesn't exist but when batch encoding + // we just choose the first audio track without checking if it exists. + if (trackNumber <= title.AudioTracks.Count) + { + list.Add(new Tuple<AudioEncoding, int>(encoding, trackNumber)); + } + } + } + + return list; + } + + /// <summary> + /// Adds a filter to the given filter list. + /// </summary> + /// <param name="filterList">The list to add the filter to.</param> + /// <param name="filterType">The type of filter.</param> + /// <param name="settings">Settings for the filter.</param> + /// <param name="allocatedMemory">The list of allocated memory.</param> + private void AddFilter(List<hb_filter_object_s> filterList, int filterType, string settings, List<IntPtr> allocatedMemory) + { + IntPtr settingsNativeString = Marshal.StringToHGlobalAnsi(settings); + hb_filter_object_s filter = InteropUtilities.ReadStructure<hb_filter_object_s>(HBFunctions.hb_filter_init(filterType)); + filter.settings = settingsNativeString; + + allocatedMemory.Add(settingsNativeString); + filterList.Add(filter); + } + + /// <summary> + /// Converts the given filter list to a native list. + /// </summary> + /// <remarks>Sorts the list by filter ID before converting to a native list, as HB expects it that way. + /// The list memory itself will be added to the allocatedMemory list.</remarks> + /// <param name="filterList">The filter list to convert.</param> + /// <param name="allocatedMemory">The list of allocated memory to add to.</param> + /// <returns>The converted list.</returns> + private NativeList ConvertFilterListToNative(List<hb_filter_object_s> filterList, List<IntPtr> allocatedMemory) + { + var filterPtrList = new List<IntPtr>(); + + var sortedList = filterList.OrderBy(f => f.id); + foreach (var filter in sortedList) + { + IntPtr filterPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_filter_object_s))); + Marshal.StructureToPtr(filter, filterPtr, false); + + allocatedMemory.Add(filterPtr); + filterPtrList.Add(filterPtr); + } + + NativeList filterListNative = InteropUtilities.CreateIntPtrList(filterPtrList); + allocatedMemory.AddRange(filterListNative.AllocatedMemory); + + return filterListNative; + } + + /// <summary> + /// Gets the title, given the 1-based title number. + /// </summary> + /// <param name="titleNumber">The number of the title (1-based).</param> + /// <returns>The requested Title.</returns> + private Title GetTitle(int titleNumber) + { + return this.Titles.SingleOrDefault(title => title.TitleNumber == titleNumber); + } + + /// <summary> + /// Gets the 1-based title index of the given title. + /// </summary> + /// <param name="titleNumber">The 1-based title title number.</param> + /// <returns>The 1-based title index.</returns> + private int GetTitleIndex(int titleNumber) + { + Title title = this.GetTitle(titleNumber); + return this.GetTitleIndex(title); + } + + /// <summary> + /// Gets the 1-based title index of the given title. + /// </summary> + /// <param name="title">The title to look up</param> + /// <returns>The 1-based title index of the given title.</returns> + private int GetTitleIndex(Title title) + { + return this.Titles.IndexOf(title) + 1; + } + + /// <summary> + /// Gets the native title object from the title index. + /// </summary> + /// <param name="titleIndex">The index of the title (1-based).</param> + /// <returns>Gets the native title object for the given index.</returns> + private hb_title_s GetOriginalTitle(int titleIndex) + { + List<hb_title_s> matchingTitles = this.originalTitles.Where(title => title.index == titleIndex).ToList(); + if (matchingTitles.Count == 0) + { + throw new ArgumentException("Could not find specified title."); + } + + if (matchingTitles.Count > 1) + { + throw new ArgumentException("Multiple titles matched."); + } + + return matchingTitles[0]; + } + + /// <summary> + /// Applies an audio encoding to a native audio encoding base structure. + /// </summary> + /// <param name="encoding">The encoding to apply.</param> + /// <param name="baseStruct">The base native structure.</param> + /// <param name="outputTrack">The output track number (0-based).</param> + /// <param name="allocatedMemory">The collection of allocated memory.</param> + /// <returns>The resulting native audio structure.</returns> + private hb_audio_s ConvertAudioBack(AudioEncoding encoding, hb_audio_s baseStruct, int outputTrack, List<IntPtr> allocatedMemory) + { + hb_audio_s nativeAudio = baseStruct; + HBAudioEncoder encoder = Encoders.GetAudioEncoder(encoding.Encoder); + + nativeAudio.config.output.track = outputTrack; + nativeAudio.config.output.codec = (uint)encoder.Id; + nativeAudio.config.output.compression_level = -1; + nativeAudio.config.output.samplerate = nativeAudio.config.input.samplerate; + nativeAudio.config.output.dither_method = -1; + + if (!encoder.IsPassthrough) + { + if (encoding.SampleRateRaw != 0) + { + nativeAudio.config.output.samplerate = encoding.SampleRateRaw; + } + + HBMixdown mixdown = Encoders.GetMixdown(encoding.Mixdown); + nativeAudio.config.output.mixdown = mixdown.Id; + + if (encoding.EncodeRateType == AudioEncodeRateType.Bitrate) + { + // Disable quality targeting. + nativeAudio.config.output.quality = -3; + + if (encoding.Bitrate == 0) + { + // Bitrate of 0 means auto: choose the default for this codec, sample rate and mixdown. + nativeAudio.config.output.bitrate = HBFunctions.hb_audio_bitrate_get_default( + nativeAudio.config.output.codec, + nativeAudio.config.output.samplerate, + nativeAudio.config.output.mixdown); + } + else + { + nativeAudio.config.output.bitrate = encoding.Bitrate; + } + } + else if (encoding.EncodeRateType == AudioEncodeRateType.Quality) + { + // Bitrate of -1 signals quality targeting. + nativeAudio.config.output.bitrate = -1; + nativeAudio.config.output.quality = encoding.Quality; + } + + // If this encoder supports compression level, pass it in. + if (encoder.SupportsCompression) + { + nativeAudio.config.output.compression_level = encoding.Compression; + } + + nativeAudio.config.output.dynamic_range_compression = encoding.Drc; + nativeAudio.config.output.gain = encoding.Gain; + } + + if (!string.IsNullOrEmpty(encoding.Name)) + { + IntPtr encodingNamePtr = Marshal.StringToHGlobalAnsi(encoding.Name); + nativeAudio.config.output.name = encodingNamePtr; + allocatedMemory.Add(encodingNamePtr); + } + + nativeAudio.padding = new byte[MarshalingConstants.AudioPaddingBytes]; + + return nativeAudio; + } + + /// <summary> + /// Converts a native title to a Title object. + /// </summary> + /// <param name="title">The native title structure.</param> + /// <returns>The managed Title object.</returns> + private Title ConvertTitle(hb_title_s title) + { + var newTitle = new Title + { + TitleNumber = title.index, + Playlist = title.playlist, + Resolution = new Size(title.width, title.height), + ParVal = new Size(title.pixel_aspect_width, title.pixel_aspect_height), + Duration = Converters.PtsToTimeSpan(title.duration), + DurationPts = title.duration, + AutoCropDimensions = new Cropping + { + Top = title.crop[0], + Bottom = title.crop[1], + Left = title.crop[2], + Right = title.crop[3] + }, + AspectRatio = title.aspect, + AngleCount = title.angle_count, + VideoCodecName = title.video_codec_name, + Framerate = ((double)title.rate) / title.rate_base, + FramerateNumerator = title.rate, + FramerateDenominator = title.rate_base, + Path = title.path + }; + + switch (title.type) + { + case hb_title_type_anon.HB_STREAM_TYPE: + newTitle.InputType = InputType.Stream; + break; + case hb_title_type_anon.HB_DVD_TYPE: + newTitle.InputType = InputType.Dvd; + break; + case hb_title_type_anon.HB_BD_TYPE: + newTitle.InputType = InputType.Bluray; + break; + } + + int currentSubtitleTrack = 1; + List<hb_subtitle_s> subtitleList = title.list_subtitle.ToList<hb_subtitle_s>(); + foreach (hb_subtitle_s subtitle in subtitleList) + { + var newSubtitle = new Subtitle + { + TrackNumber = currentSubtitleTrack, + Language = subtitle.lang, + LanguageCode = subtitle.iso639_2 + }; + + if (subtitle.format == hb_subtitle_s_subtype.PICTURESUB) + { + newSubtitle.SubtitleType = SubtitleType.Picture; + } + else if (subtitle.format == hb_subtitle_s_subtype.TEXTSUB) + { + newSubtitle.SubtitleType = SubtitleType.Text; + } + + newSubtitle.SubtitleSourceInt = (int)subtitle.source; + + switch (subtitle.source) + { + case hb_subtitle_s_subsource.CC608SUB: + newSubtitle.SubtitleSource = SubtitleSource.CC608; + break; + case hb_subtitle_s_subsource.CC708SUB: + newSubtitle.SubtitleSource = SubtitleSource.CC708; + break; + case hb_subtitle_s_subsource.SRTSUB: + newSubtitle.SubtitleSource = SubtitleSource.SRT; + break; + case hb_subtitle_s_subsource.SSASUB: + newSubtitle.SubtitleSource = SubtitleSource.SSA; + break; + case hb_subtitle_s_subsource.TX3GSUB: + newSubtitle.SubtitleSource = SubtitleSource.TX3G; + break; + case hb_subtitle_s_subsource.UTF8SUB: + newSubtitle.SubtitleSource = SubtitleSource.UTF8; + break; + case hb_subtitle_s_subsource.VOBSUB: + newSubtitle.SubtitleSource = SubtitleSource.VobSub; + break; + case hb_subtitle_s_subsource.PGSSUB: + newSubtitle.SubtitleSource = SubtitleSource.PGS; + break; + default: + break; + } + + newTitle.Subtitles.Add(newSubtitle); + + currentSubtitleTrack++; + } + + int currentAudioTrack = 1; + List<hb_audio_s> audioList = title.list_audio.ToList<hb_audio_s>(); + foreach (hb_audio_s audio in audioList) + { + var newAudio = new AudioTrack + { + TrackNumber = currentAudioTrack, + Codec = Converters.NativeToAudioCodec(audio.config.input.codec), + CodecId = audio.config.input.codec, + Language = audio.config.lang.simple, + LanguageCode = audio.config.lang.iso639_2, + Description = audio.config.lang.description, + ChannelLayout = audio.config.input.channel_layout, + SampleRate = audio.config.input.samplerate, + Bitrate = audio.config.input.bitrate + }; + + newTitle.AudioTracks.Add(newAudio); + + currentAudioTrack++; + } + + List<hb_chapter_s> chapterList = title.list_chapter.ToList<hb_chapter_s>(); + foreach (hb_chapter_s chapter in chapterList) + { + var newChapter = new Chapter + { Name = chapter.title, - ChapterNumber = chapter.index, - Duration = Converters.PtsToTimeSpan(chapter.duration), - DurationPts = chapter.duration - }; - - newTitle.Chapters.Add(newChapter); - } - - return newTitle; - } - - /// <summary> - /// Gets the cropping to use for the given encoding profile and title. - /// </summary> - /// <param name="profile">The encoding profile to use.</param> - /// <param name="title">The title being encoded.</param> - /// <returns>The cropping to use for the encode.</returns> - private static Cropping GetCropping(EncodingProfile profile, Title title) - { - Cropping crop; - switch (profile.CroppingType) - { - case CroppingType.Automatic: - crop = title.AutoCropDimensions; - break; - case CroppingType.Custom: - crop = profile.Cropping; - break; - default: - crop = new Cropping(); - break; - } - return crop; - } - } + ChapterNumber = chapter.index, + Duration = Converters.PtsToTimeSpan(chapter.duration), + DurationPts = chapter.duration + }; + + newTitle.Chapters.Add(newChapter); + } + + return newTitle; + } + + /// <summary> + /// Gets the cropping to use for the given encoding profile and title. + /// </summary> + /// <param name="profile">The encoding profile to use.</param> + /// <param name="title">The title being encoded.</param> + /// <returns>The cropping to use for the encode.</returns> + private static Cropping GetCropping(EncodingProfile profile, Title title) + { + Cropping crop; + switch (profile.CroppingType) + { + case CroppingType.Automatic: + crop = title.AutoCropDimensions; + break; + case CroppingType.Custom: + crop = profile.Cropping; + break; + default: + crop = new Cropping(); + break; + } + return crop; + } + } } -- cgit v1.2.3