Exemple #1
0
        /// <summary>
        /// The clone.
        /// </summary>
        /// <returns>
        /// The <see cref="EncodeJob"/>.
        /// </returns>
        public EncodeJob Clone()
        {
            var clone = new EncodeJob
            {
                SourceType             = this.SourceType,
                SourcePath             = this.SourcePath,
                Title                  = this.Title,
                Angle                  = this.Angle,
                RangeType              = this.RangeType,
                ChapterStart           = this.ChapterStart,
                ChapterEnd             = this.ChapterEnd,
                SecondsStart           = this.SecondsStart,
                SecondsEnd             = this.SecondsEnd,
                FramesStart            = this.FramesStart,
                FramesEnd              = this.FramesEnd,
                ChosenAudioTracks      = new List <int>(this.ChosenAudioTracks),
                Subtitles              = this.Subtitles,
                UseDefaultChapterNames = this.UseDefaultChapterNames,
                OutputPath             = this.OutputPath,
                EncodingProfile        = this.EncodingProfile,
                Length                 = this.Length
            };

            return(clone);
        }
        /// <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>
        /// 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;
        }
Exemple #4
0
		public void StartEncode(EncodeJob job, bool preview, int previewNumber, int previewSeconds, double overallSelectedLengthSeconds, int verbosity, int previewCount, bool useDvdNav)
		{
			CurrentEncoder = this;
			this.callback = OperationContext.Current.GetCallbackChannel<IHandBrakeEncoderCallback>();

			try
			{
				if (this.callback == null)
				{
					throw new ArgumentException("Could not get callback channel.");
				}

				HandBrakeUtils.MessageLogged += (o, e) =>
					{
						this.StopOnException(() =>
							{
								this.callback.OnMessageLogged(e.Message);
							});
					};

				HandBrakeUtils.ErrorLogged += (o, e) =>
					{
						this.StopOnException(() =>
							{
								this.callback.OnErrorLogged(e.Message);
							});
					};

				HandBrakeUtils.SetDvdNav(useDvdNav);

				this.instance = new HandBrakeInstance();
				this.instance.Initialize(verbosity);

				this.instance.ScanCompleted += (o, e) =>
					{
						try
						{
							Title encodeTitle = this.instance.Titles.FirstOrDefault(title => title.TitleNumber == job.Title);

							if (encodeTitle != null)
							{
								lock (this.encodeLock)
								{
									this.instance.StartEncode(job, preview, previewNumber, previewSeconds, overallSelectedLengthSeconds, previewCount);
									this.callback.OnEncodeStarted();
									this.state = EncodeState.Encoding;
								}
							}
							else
							{
								this.callback.OnEncodeComplete(true);
								this.CleanUpAndSignalCompletion();
							}
						}
						catch (Exception exception)
						{
							this.callback.OnException(exception.ToString());
							this.CleanUpAndSignalCompletion();
						}
					};

				this.instance.EncodeProgress += (o, e) =>
					{
						this.StopOnException(() =>
							{
								this.callback.OnEncodeProgress(e.AverageFrameRate, e.CurrentFrameRate, e.EstimatedTimeLeft, e.FractionComplete, e.Pass);
							});
					};

				this.instance.EncodeCompleted += (o, e) =>
					{
						this.state = EncodeState.Finished;

						try
						{
							this.callback.OnEncodeComplete(e.Error);
						}
						catch (CommunicationException exception)
						{
							WorkerLogger.Log("Got exception when reporting completion: " + exception, isError: true);
						}
						finally
						{
							this.CleanUpAndSignalCompletion();
						}
					};

				this.instance.StartScan(job.SourcePath, previewCount, job.Title);
				this.state = EncodeState.Scanning;
			}
			catch (Exception exception)
			{
				this.callback.OnException(exception.ToString());
				throw;
			}
		}
        /// <summary>
        /// Gets the size in bytes for the audio with the given parameters.
        /// </summary>
        /// <param name="job">The encode job.</param>
        /// <param name="lengthSeconds">The length of the encode in seconds.</param>
        /// <param name="title">The title to encode.</param>
        /// <param name="outputTrackList">The list of tracks to encode.</param>
        /// <returns>The size in bytes for the audio with the given parameters.</returns>
        internal static long GetAudioSize(EncodeJob job, double lengthSeconds, Title title, List<Tuple<AudioEncoding, int>> outputTrackList)
        {
            long audioBytes = 0;

            foreach (Tuple<AudioEncoding, int> outputTrack in outputTrackList)
            {
                AudioEncoding encoding = outputTrack.Item1;
                AudioTrack track = title.AudioTracks[outputTrack.Item2 - 1];

                int samplesPerFrame = HandBrakeUtils.GetAudioSamplesPerFrame(encoding.Encoder);
                int audioBitrate;

                HBAudioEncoder audioEncoder = Encoders.GetAudioEncoder(encoding.Encoder);

                if (audioEncoder.IsPassthrough)
                {
                    // Input bitrate is in bits/second.
                    audioBitrate = track.Bitrate / 8;
                }
                else if (encoding.EncodeRateType == AudioEncodeRateType.Quality)
                {
                    // Can't predict size of quality targeted audio encoding.
                    audioBitrate = 0;
                }
                else
                {
                    int outputBitrate;
                    if (encoding.Bitrate > 0)
                    {
                        outputBitrate = encoding.Bitrate;
                    }
                    else
                    {
                        outputBitrate = Encoders.GetDefaultBitrate(
                            audioEncoder,
                            encoding.SampleRateRaw == 0 ? track.SampleRate : encoding.SampleRateRaw,
                            Encoders.SanitizeMixdown(Encoders.GetMixdown(encoding.Mixdown), audioEncoder, track.ChannelLayout));
                    }

                    // Output bitrate is in kbps.
                    audioBitrate = outputBitrate * 1000 / 8;
                }

                audioBytes += (long)(lengthSeconds * audioBitrate);

                // Audio overhead
                audioBytes += encoding.SampleRateRaw * ContainerOverheadPerFrame / samplesPerFrame;
            }

            return audioBytes;
        }
        /// <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.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");
                        }

                        // 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;
                }
            }

            nativeJob.chapter_markers = profile.IncludeChapterMarkers ? 1 : 0;

            Cropping crop;

            if (profile.CustomCropping)
            {
                crop = profile.Cropping;
            }
            else
            {
                crop = title.AutoCropDimensions;
            }

            nativeJob.crop[0] = crop.Top;
            nativeJob.crop[1] = crop.Bottom;
            nativeJob.crop[2] = crop.Left;
            nativeJob.crop[3] = crop.Right;

            if (profile.Deinterlace != Deinterlace.Off)
            {
                nativeJob.deinterlace = 1;
                string settings = null;

                switch (profile.Deinterlace)
                {
                    case Deinterlace.Fast:
                        settings = "-1";
                        break;
                    case Deinterlace.Slow:
                        settings = "2";
                        break;
                    case Deinterlace.Slower:
                        settings = "0";
                        break;
                    case Deinterlace.Custom:
                        settings = profile.CustomDeinterlace;
                        break;
                    default:
                        break;
                }

                this.AddFilter(nativeJob, NativeConstants.HB_FILTER_DEINTERLACE, settings, allocatedMemory);
            }
            else
            {
                nativeJob.deinterlace = 0;
            }

            if (profile.Detelecine != Detelecine.Off)
            {
                string settings = null;
                if (profile.Detelecine == Detelecine.Custom)
                {
                    settings = profile.CustomDetelecine;
                }

                this.AddFilter(nativeJob, NativeConstants.HB_FILTER_DETELECINE, settings, allocatedMemory);
            }

            if (profile.Decomb != Decomb.Off)
            {
                string settings = null;
                if (profile.Decomb == Decomb.Custom)
                {
                    settings = profile.CustomDecomb;
                }

                this.AddFilter(nativeJob, NativeConstants.HB_FILTER_DECOMB, settings, allocatedMemory);
            }

            if (profile.Deblock > 0)
            {
                this.AddFilter(nativeJob, NativeConstants.HB_FILTER_DEBLOCK, profile.Deblock.ToString(), allocatedMemory);
            }

            if (profile.Denoise != Denoise.Off)
            {
                string settings = null;
                switch (profile.Denoise)
                {
                    case Denoise.Weak:
                        settings = "2:1:2:3";
                        break;
                    case Denoise.Medium:
                        settings = "3:2:2:3";
                        break;
                    case Denoise.Strong:
                        settings = "7:7:5:5";
                        break;
                    case Denoise.Custom:
                        settings = profile.CustomDenoise;
                        break;
                    default:
                        break;
                }

                this.AddFilter(nativeJob, NativeConstants.HB_FILTER_DENOISE, settings, allocatedMemory);
            }

            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;
            }

            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;
                    break;
                case Anamorphic.Strict:
                    nativeJob.anamorphic.mode = 1;
                    break;
                case Anamorphic.Loose:
                    nativeJob.anamorphic.mode = 2;
                    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)cropHeight * displayAspect);
                            }

                            nativeJob.anamorphic.dar_width = displayWidth;
                            nativeJob.anamorphic.dar_height = height;
                            nativeJob.anamorphic.keep_display_aspect = 1;
                        }

                        nativeJob.anamorphic.dar_width = profile.DisplayWidth;
                        nativeJob.anamorphic.dar_height = height;
                        nativeJob.anamorphic.keep_display_aspect = profile.KeepDisplayAspect ? 1 : 0;
                    }
                    else
                    {
                        nativeJob.anamorphic.par_width = profile.PixelAspectX;
                        nativeJob.anamorphic.par_height = profile.PixelAspectY;
                        nativeJob.anamorphic.keep_display_aspect = 0;
                    }

                    break;
                default:
                    break;
            }

            nativeJob.width = width;
            nativeJob.height = height;

            nativeJob.maxWidth = profile.MaxWidth;
            nativeJob.maxHeight = profile.MaxHeight;

            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;

            if (profile.Framerate == 0)
            {
                nativeJob.cfr = 0;
            }
            else
            {
                if (profile.PeakFramerate)
                {
                    nativeJob.cfr = 2;
                }
                else
                {
                    nativeJob.cfr = 1;
                }

                nativeJob.vrate = 27000000;
                nativeJob.vrate_base = Converters.FramerateToVrate(profile.Framerate);
            }

            // vfr
            // areBframes
            // color_matrix
            List<hb_audio_s> titleAudio = InteropUtilities.ConvertList<hb_audio_s>(originalTitle.list_audio);

            var audioList = new List<hb_audio_s>();
            int numTracks = 0;

            List<Tuple<AudioEncoding, int>> outputTrackList = this.GetOutputTracks(job, title);

            if (profile.AudioEncoderFallback != null)
            {
                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.ListPtr;
            allocatedMemory.AddRange(nativeAudioList.AllocatedMemory);

            // Create a new empty list
            int totalSubtitles = 0;
            if (job.Subtitles != null)
            {
                if (job.Subtitles.SourceSubtitles != null)
                {
                    totalSubtitles += job.Subtitles.SourceSubtitles.Count;
                }

                if (job.Subtitles.SrtSubtitles != null)
                {
                    totalSubtitles += job.Subtitles.SrtSubtitles.Count;
                }
            }

            NativeList nativeSubtitleList = InteropUtilities.CreateNativeList(totalSubtitles + 2);
            nativeJob.list_subtitle = nativeSubtitleList.ListPtr;
            allocatedMemory.AddRange(nativeSubtitleList.AllocatedMemory);

            if (job.Subtitles != null)
            {
                if (job.Subtitles.SourceSubtitles != null && job.Subtitles.SourceSubtitles.Count > 0)
                {
                    List<hb_subtitle_s> titleSubtitles = InteropUtilities.ConvertList<hb_subtitle_s>(originalTitle.list_subtitle);

                    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;
                            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");
                        }
                    }
                }
            }

            if (profile.OutputFormat == Container.Mp4)
            {
                nativeJob.mux = NativeConstants.HB_MUX_MP4;
            }
            else
            {
                nativeJob.mux = NativeConstants.HB_MUX_MKV;
            }

            nativeJob.file = job.OutputPath;

            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>
        /// 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)
        {
            this.currentJob = job;
            hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
            this.encodeAllocatedMemory = this.ApplyJob(ref nativeJob, job, preview, previewNumber, previewSeconds, overallSelectedLengthSeconds);

            if (!preview && job.EncodingProfile.IncludeChapterMarkers)
            {
                Title title = this.GetTitle(job.Title);
                int numChapters = title.Chapters.Count;

                if (job.UseDefaultChapterNames)
                {
                    for (int i = 0; i < numChapters; i++)
                    {
                        HBFunctions.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, "Chapter " + (i + 1));
                    }
                }
                else
                {
                    for (int i = 0; i < numChapters; i++)
                    {
                        HBFunctions.hb_set_chapter_name(this.hbHandle, job.Title, i + 1, job.CustomChapterNames[i]);
                    }
                }
            }

            this.subtitleScan = false;
            if (job.Subtitles.SourceSubtitles != null)
            {
                foreach (SourceSubtitle subtitle in job.Subtitles.SourceSubtitles)
                {
                    if (subtitle.TrackNumber == 0)
                    {
                        this.subtitleScan = true;
                        break;
                    }
                }
            }

            string x264Options = job.EncodingProfile.X264Options ?? string.Empty;
            IntPtr originalX264Options = Marshal.StringToHGlobalAnsi(x264Options);
            this.encodeAllocatedMemory.Add(originalX264Options);

            if (!string.IsNullOrEmpty(job.EncodingProfile.X264Profile))
            {
                nativeJob.x264_profile = Marshal.StringToHGlobalAnsi(job.EncodingProfile.X264Profile);
                this.encodeAllocatedMemory.Add(nativeJob.x264_profile);
            }

            if (!string.IsNullOrEmpty(job.EncodingProfile.X264Preset))
            {
                nativeJob.x264_preset = Marshal.StringToHGlobalAnsi(job.EncodingProfile.X264Preset);
                this.encodeAllocatedMemory.Add(nativeJob.x264_preset);
            }

            if (!string.IsNullOrEmpty(job.EncodingProfile.X264Tune))
            {
                nativeJob.x264_tune = Marshal.StringToHGlobalAnsi(job.EncodingProfile.X264Tune);
                this.encodeAllocatedMemory.Add(nativeJob.x264_tune);
            }

            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);

            this.encodePollTimer = new System.Timers.Timer();
            this.encodePollTimer.Interval = EncodePollIntervalMs;

            this.encodePollTimer.Elapsed += (o, e) =>
            {
                this.PollEncodeProgress();
            };
            this.encodePollTimer.Start();
        }
        /// <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)
        {
            if (job.EncodingProfile.Anamorphic == Anamorphic.None)
            {
                Title title = this.GetTitle(job.Title);
                Size storageDimensions = CalculateNonAnamorphicOutput(job.EncodingProfile, title);

                width = storageDimensions.Width;
                height = storageDimensions.Height;

                parWidth = 1;
                parHeight = 1;

                return;
            }

            var nativeJob = InteropUtilities.ReadStructure<hb_job_s>(this.GetOriginalTitle(job.Title).job);
            List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job);

            int refWidth = 0;
            int refHeight = 0;
            int refParWidth = 0;
            int refParHeight = 0;
            HBFunctions.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
            //HBFunctions.hb_set_anamorphic_size_by_index(this.hbHandle, job.Title, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
            HBFunctions.hb_set_anamorphic_size(ref nativeJob, ref refWidth, ref refHeight, ref refParWidth, ref refParHeight);
            InteropUtilities.FreeMemory(allocatedMemory);

            width = refWidth;
            height = refHeight;
            parWidth = refParWidth;
            parHeight = refParHeight;
        }
        /// <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");
            }
            #pragma warning restore 612, 618

            if (profile.ContainerName != null)
            {
                nativeJob.mux = HBFunctions.hb_container_get_from_name(profile.ContainerName);
            }

            if (job.OutputPath == null)
            {
                nativeJob.file = IntPtr.Zero;
            }
            else
            {
                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>
        /// 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>
        /// 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>
        /// 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>
 /// Starts an encode with the given job.
 /// </summary>
 /// <param name="jobToStart">
 /// The job to start.
 /// </param>
 /// <param name="scanPreviewCount">
 /// The scan Preview Count.
 /// </param>
 public void StartEncode(EncodeJob jobToStart, int scanPreviewCount)
 {
     this.StartEncode(jobToStart, false, 0, 0, 0, scanPreviewCount);
 }
        /*
         * TODO: This conversion class needs to be finished off before libencode will work.
         */
        /// <summary>
        /// Get an EncodeJob model for a LibHB Encode.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        /// <returns>
        /// An Interop.EncodeJob model.
        /// </returns>
        public static EncodeJob GetEncodeJob(QueueTask task)
        {
            // Sanity Checking
            if (task == null || task.Task == null)
            {
                return null;
            }

            // The current Job Configuration
            EncodeTask work = task.Task;

            // Which will be converted to this EncodeJob Model.
            EncodeJob job = new EncodeJob();
            EncodingProfile profile = new EncodingProfile();
            job.EncodingProfile = profile;

            switch (work.Anamorphic)
            {
                case Model.Encoding.Anamorphic.Custom:
                    profile.Anamorphic = Interop.Model.Encoding.Anamorphic.Custom;
                    break;
                case Model.Encoding.Anamorphic.Strict:
                    profile.Anamorphic = Interop.Model.Encoding.Anamorphic.Strict;
                    break;
                case Model.Encoding.Anamorphic.Loose:
                    profile.Anamorphic = Interop.Model.Encoding.Anamorphic.Loose;
                    break;
                case Model.Encoding.Anamorphic.None:
                    profile.Anamorphic = Interop.Model.Encoding.Anamorphic.None;
                    break;
            }

            profile.AudioEncodings = new List<AudioEncoding>();
            foreach (AudioTrack track in work.AudioTracks)
            {
                AudioEncoding newTrack = new AudioEncoding
                    {
                        Bitrate = track.Bitrate,
                        Drc = track.DRC,
                        Gain = track.Gain,
                        //Encoder = track.Encoder,
                        // InputNumber = track.Track,
                        //Mixdown = track.MixDown,
                        //SampleRateRaw = track.SampleRate
                    };

                profile.AudioEncodings.Add(newTrack);
            }

            profile.Cropping = new HandBrake.Interop.Model.Cropping
                {
                    Top = work.Cropping.Top,
                    Bottom = work.Cropping.Bottom,
                    Left = work.Cropping.Left,
                    Right = work.Cropping.Right
                };

            profile.CustomCropping = true;
            profile.CustomDecomb = work.CustomDecomb;
            profile.CustomDeinterlace = work.CustomDeinterlace;
            profile.CustomDenoise = work.CustomDenoise;
            profile.CustomDetelecine = work.CustomDetelecine;
            profile.Deblock = work.Deblock;
            profile.Decomb = work.Decomb;
            profile.Deinterlace = work.Deinterlace;
            profile.Denoise = work.Denoise;
            profile.Detelecine = work.Detelecine;
            profile.DisplayWidth = work.DisplayWidth.HasValue
                                       ? int.Parse(Math.Round(work.DisplayWidth.Value, 0).ToString())
                                       : 0;
            profile.Framerate = work.Framerate.HasValue ? work.Framerate.Value : 0;
            profile.Grayscale = work.Grayscale;
            profile.Height = work.Height.HasValue ? work.Height.Value : 0;
            profile.IPod5GSupport = work.IPod5GSupport;
            profile.IncludeChapterMarkers = work.IncludeChapterMarkers;
            profile.KeepDisplayAspect = work.KeepDisplayAspect;
            profile.LargeFile = work.LargeFile;
            profile.MaxHeight = work.MaxHeight.HasValue ? work.MaxHeight.Value : 0;
            profile.MaxWidth = work.MaxWidth.HasValue ? work.MaxWidth.Value : 0;
            profile.Modulus = work.Modulus.HasValue ? work.Modulus.Value : 16;
            profile.Optimize = work.OptimizeMP4;
            switch (work.OutputFormat)
            {
                case OutputFormat.Mp4:
                case OutputFormat.M4V:
                    profile.OutputFormat = Interop.Model.Encoding.OutputFormat.Mp4;
                    break;
                case OutputFormat.Mkv:
                    profile.OutputFormat = Interop.Model.Encoding.OutputFormat.Mkv;
                    break;
            }
            profile.PeakFramerate = work.FramerateMode == FramerateMode.PFR;
            profile.PixelAspectX = work.PixelAspectX;
            profile.PixelAspectY = work.PixelAspectY;

            switch (work.OutputFormat)
            {
                case OutputFormat.Mp4:
                    profile.PreferredExtension = Interop.Model.Encoding.OutputExtension.Mp4;
                    break;
                case OutputFormat.M4V:
                    profile.PreferredExtension = Interop.Model.Encoding.OutputExtension.M4v;
                    break;
            }
            profile.Quality = work.Quality.HasValue ? work.Quality.Value : 0;
            profile.UseDisplayWidth = true;
            profile.VideoBitrate = work.VideoBitrate.HasValue ? work.VideoBitrate.Value : 0;
            profile.VideoEncodeRateType = work.VideoEncodeRateType;

            switch (work.VideoEncoder)
            {
                case VideoEncoder.X264:
                    profile.VideoEncoder = Interop.Model.Encoding.VideoEncoder.X264;
                    break;
                case VideoEncoder.FFMpeg:
                    profile.VideoEncoder = Interop.Model.Encoding.VideoEncoder.FFMpeg;
                    break;
                case VideoEncoder.FFMpeg2:
                    profile.VideoEncoder = Interop.Model.Encoding.VideoEncoder.FFMpeg; // TODO Fix This.
                    break;
                case VideoEncoder.Theora:
                    profile.VideoEncoder = Interop.Model.Encoding.VideoEncoder.Theora;
                    break;

            }

            profile.Width = work.Width.HasValue ? work.Width.Value : 0;
            profile.X264Options = work.AdvancedEncoderOptions;

            if (work.PointToPointMode == PointToPointMode.Chapters)
            {
                job.ChapterStart = work.StartPoint;
                job.ChapterEnd = work.EndPoint;
            }

            job.Angle = work.Angle;
            job.EncodingProfile = profile;
            if (work.PointToPointMode == PointToPointMode.Frames)
            {
                job.FramesEnd = work.EndPoint;
                job.FramesStart = work.StartPoint;
            }

            job.OutputPath = work.Destination;
            switch (work.PointToPointMode)
            {
                case PointToPointMode.Chapters:
                    job.RangeType = VideoRangeType.Chapters;
                    break;
                case PointToPointMode.Seconds:
                    job.RangeType = VideoRangeType.Seconds;
                    break;
                case PointToPointMode.Frames:
                    job.RangeType = VideoRangeType.Frames;
                    break;
            }

            if (work.PointToPointMode == PointToPointMode.Seconds)
            {
                job.SecondsEnd = work.EndPoint;
                job.SecondsStart = work.StartPoint;
            }

            job.SourcePath = work.Source;
            // job.SourceType = work.Type;
            job.Title = work.Title;
            job.Subtitles = new Subtitles { SourceSubtitles = new List<SourceSubtitle>(), SrtSubtitles = new List<SrtSubtitle>() };
            foreach (SubtitleTrack track in work.SubtitleTracks)
            {
                // TODO
            }

            return job;
        }
        /// <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>
        /// 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)
        {
            hb_title_s title = this.GetOriginalTitle(job.Title);

            hb_job_s nativeJob = InteropUtilities.ReadStructure<hb_job_s>(title.job);
            List<IntPtr> allocatedMemory = this.ApplyJob(ref nativeJob, job);

            // There are some problems with getting previews with deinterlacing. Disabling for now.
            nativeJob.deinterlace = 0;

            // Create a new job pointer from our modified job object
            IntPtr newJob = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(hb_job_s)));
            Marshal.StructureToPtr(nativeJob, newJob, false);
            allocatedMemory.Add(newJob);

            int outputWidth = nativeJob.width;
            int outputHeight = nativeJob.height;
            int imageBufferSize = outputWidth * outputHeight * 4;
            IntPtr nativeBuffer = Marshal.AllocHGlobal(imageBufferSize);
            allocatedMemory.Add(nativeBuffer);
            HBFunctions.hb_set_job(this.hbHandle, job.Title, ref nativeJob);
            HBFunctions.hb_get_preview(this.hbHandle, ref title, previewNumber, nativeBuffer);

            // Copy the filled image buffer to a managed array.
            byte[] managedBuffer = new byte[imageBufferSize];
            Marshal.Copy(nativeBuffer, managedBuffer, 0, imageBufferSize);

            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>
        /// Get an EncodeJob model for a LibHB Encode.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        /// <returns>
        /// An Interop.EncodeJob model.
        /// </returns>
        public static EncodeJob GetEncodeJob(EncodeTask task)
        {
            // The current Job Configuration
            EncodeTask work = task;

            // Which will be converted to this EncodeJob Model.
            EncodeJob job = new EncodeJob();
            EncodingProfile profile = new EncodingProfile();
            job.EncodingProfile = profile;

            // Audio Settings
            profile.AudioEncodings = new List<AudioEncoding>();
            job.ChosenAudioTracks = new List<int>();
            foreach (AudioTrack track in work.AudioTracks)
            {
                AudioEncoding newTrack = new AudioEncoding
                    {
                        Bitrate = track.Bitrate,
                        Drc = track.DRC,
                        Gain = track.Gain,
                        Encoder = Converters.GetCliAudioEncoder(track.Encoder),
                        InputNumber = track.Track.HasValue ? track.Track.Value : 0,
                        Mixdown = Converters.GetCliMixDown(track.MixDown),
                        SampleRateRaw = GetSampleRateRaw(track.SampleRate),
                    };

                profile.AudioEncodings.Add(newTrack);
                if (track.Track != null)
                {
                    job.ChosenAudioTracks.Add(track.Track.Value);
                }
            }

            // Title Settings
            job.OutputPath = work.Destination;
            job.SourcePath = work.Source;
            job.Title = work.Title;
            // job.SourceType = work.Type;
            switch (work.PointToPointMode)
            {
                case PointToPointMode.Chapters:
                    job.RangeType = VideoRangeType.Chapters;
                    break;
                case PointToPointMode.Seconds:
                    job.RangeType = VideoRangeType.Seconds;
                    break;
                case PointToPointMode.Frames:
                    job.RangeType = VideoRangeType.Frames;
                    break;
            }

            if (work.PointToPointMode == PointToPointMode.Seconds)
            {
                job.SecondsEnd = work.EndPoint;
                job.SecondsStart = work.StartPoint;
            }
            if (work.PointToPointMode == PointToPointMode.Chapters)
            {
                job.ChapterStart = work.StartPoint;
                job.ChapterEnd = work.EndPoint;
            }
            if (work.PointToPointMode == PointToPointMode.Frames)
            {
                job.FramesEnd = work.EndPoint;
                job.FramesStart = work.StartPoint;
            }

            job.Angle = work.Angle;
            job.EncodingProfile = profile;

            // Output Settings
            profile.IPod5GSupport = work.IPod5GSupport;
            profile.Optimize = work.OptimizeMP4;
            switch (work.OutputFormat)
            {
                case OutputFormat.Mp4:
                case OutputFormat.M4V:
                    profile.OutputFormat = Container.Mp4;
                    break;
                case OutputFormat.Mkv:
                    profile.OutputFormat = Container.Mkv;
                    break;
            }

            // Picture Settings
            profile.Anamorphic = work.Anamorphic;
            profile.Cropping = new Cropping
                {
                    Top = work.Cropping.Top,
                    Bottom = work.Cropping.Bottom,
                    Left = work.Cropping.Left,
                    Right = work.Cropping.Right
                };
            profile.CroppingType = CroppingType.Custom; // TODO deal with this better
            profile.DisplayWidth = work.DisplayWidth.HasValue
                           ? int.Parse(Math.Round(work.DisplayWidth.Value, 0).ToString())
                           : 0;
            profile.PixelAspectX = work.PixelAspectX;
            profile.PixelAspectY = work.PixelAspectY;
            profile.Height = work.Height.HasValue ? work.Height.Value : 0;
            profile.KeepDisplayAspect = work.KeepDisplayAspect;
            profile.MaxHeight = work.MaxHeight.HasValue ? work.MaxHeight.Value : 0;
            profile.MaxWidth = work.MaxWidth.HasValue ? work.MaxWidth.Value : 0;
            profile.Modulus = work.Modulus.HasValue ? work.Modulus.Value : 16;
            profile.UseDisplayWidth = true;
            profile.Width = work.Width.HasValue ? work.Width.Value : 0;

            // Filter Settings
            profile.CustomDecomb = work.CustomDecomb;
            profile.CustomDeinterlace = work.CustomDeinterlace;
            profile.CustomDenoise = work.CustomDenoise;
            profile.CustomDetelecine = work.CustomDetelecine;
            if (work.Deblock > 4)
                profile.Deblock = work.Deblock;
            profile.Decomb = work.Decomb;
            profile.Deinterlace = work.Deinterlace;
            profile.Denoise = work.Denoise;
            profile.Detelecine = work.Detelecine;
            profile.Grayscale = work.Grayscale;

            // Video Settings
            profile.Framerate = work.Framerate.HasValue ? work.Framerate.Value : 0;
            profile.ConstantFramerate = work.FramerateMode == FramerateMode.CFR;
            profile.Quality = work.Quality.HasValue ? work.Quality.Value : 0;
            profile.VideoBitrate = work.VideoBitrate.HasValue ? work.VideoBitrate.Value : 0;
            profile.VideoEncodeRateType = work.VideoEncodeRateType;
            profile.VideoEncoder = Converters.GetVideoEncoder(work.VideoEncoder);

            profile.H264Level = work.H264Level;
            profile.X264Profile = work.H264Profile.ToString().ToLower().Replace(" ", string.Empty); // TODO change these away from strings.
            profile.X264Preset = work.X264Preset.ToString().ToLower().Replace(" ", string.Empty);
            profile.X264Tunes = new List<string>();

            if (work.X264Tune != x264Tune.None)
            {
                profile.X264Tunes.Add(work.X264Tune.ToString().ToLower().Replace(" ", string.Empty));
            }

            if (work.FastDecode)
            {
                profile.X264Tunes.Add("fastdecode");
            }

            // Chapter Markers
            profile.IncludeChapterMarkers = work.IncludeChapterMarkers;
            job.CustomChapterNames = work.ChapterNames.Select(item => item.ChapterName).ToList();
            job.UseDefaultChapterNames = work.IncludeChapterMarkers;

            // Advanced Settings
            profile.X264Options = work.AdvancedEncoderOptions;

            // Subtitles
            job.Subtitles = new Subtitles { SourceSubtitles = new List<SourceSubtitle>(), SrtSubtitles = new List<SrtSubtitle>() };
            foreach (SubtitleTrack track in work.SubtitleTracks)
            {
                if (track.IsSrtSubtitle)
                {
                    job.Subtitles.SrtSubtitles.Add(
                        new SrtSubtitle
                            {
                                CharacterCode = track.SrtCharCode,
                                Default = track.Default,
                                FileName = track.SrtFileName,
                                LanguageCode = track.SrtLang,
                                Offset = track.SrtOffset
                            });
                }
                else
                {
                    if (track.SourceTrack != null)
                    {
                        job.Subtitles.SourceSubtitles.Add(
                            new SourceSubtitle
                                {
                                    BurnedIn = track.Burned,
                                    Default = track.Default,
                                    Forced = track.Forced,
                                    TrackNumber = track.SourceTrack.TrackNumber
                                });
                    }
                }
            }

            return job;
        }
 /// <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>
        /// Gets the size in bytes for the audio with the given parameters.
        /// </summary>
        /// <param name="job">The encode job.</param>
        /// <param name="lengthSeconds">The length of the encode in seconds.</param>
        /// <param name="title">The title to encode.</param>
        /// <param name="outputTrackList">The list of tracks to encode.</param>
        /// <returns>The size in bytes for the audio with the given parameters.</returns>
        internal static long GetAudioSize(EncodeJob job, double lengthSeconds, Title title, List<Tuple<AudioEncoding, int>> outputTrackList)
        {
            long audioBytes = 0;

            foreach (Tuple<AudioEncoding, int> outputTrack in outputTrackList)
            {
                AudioEncoding encoding = outputTrack.Item1;
                AudioTrack track = title.AudioTracks[outputTrack.Item2 - 1];

                int samplesPerFrame = HandBrakeUtils.GetAudioSamplesPerFrame(encoding.Encoder);
                int audioBitrate;

                if (Utilities.IsPassthrough(encoding.Encoder))
                {
                    // Input bitrate is in bits/second.
                    audioBitrate = track.Bitrate / 8;
                }
                else
                {
                    // Output bitrate is in kbps.
                    audioBitrate = encoding.Bitrate * 1000 / 8;
                }

                audioBytes += (long)(lengthSeconds * audioBitrate);

                // Audio overhead
                audioBytes += encoding.SampleRateRaw * ContainerOverheadPerFrame / samplesPerFrame;
            }

            return audioBytes;
        }
 /// <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);
 }
Exemple #21
0
        static void instance_ScanCompleted(object sender, EventArgs e) {
            Console.WriteLine("Scan complete");

            EncodingProfile profile = GetHandBrakeEncodingProfile();

            var job = new EncodeJob {
                EncodingProfile = profile,
                RangeType = VideoRangeType.All,
                Title = 1,
                SourcePath = SourceFile,

                ChosenAudioTracks = new List<int> { 1 },
                OutputPath = @"C:\Users\michael.de.keyser\Source\subtitle extract\hbOutput.mp4",
                Subtitles = new Subtitles {
                    SourceSubtitles = new List<SourceSubtitle>() {
                                new SourceSubtitle { TrackNumber = 1, BurnedIn = false, Default = false, Forced = false },
                                new SourceSubtitle { TrackNumber = 2, BurnedIn = false, Default = false, Forced = false }
                            },
                    SrtSubtitles = new List<SrtSubtitle>()
                }
            };

            instance.EncodeProgress += (o, args) => {
                Console.WriteLine(args.FractionComplete);
            };
            instance.EncodeCompleted += (o, args) => {
                Console.WriteLine("Encode completed.");
            };
            instance.StartEncode(job, 10);
        }
Exemple #22
0
        /// <summary>
        /// The clone.
        /// </summary>
        /// <returns>
        /// The <see cref="EncodeJob"/>.
        /// </returns>
        public EncodeJob Clone()
        {
            var clone = new EncodeJob
            {
                SourceType             = this.SourceType,
                SourcePath             = this.SourcePath,
                Title                  = this.Title,
                Angle                  = this.Angle,
                RangeType              = this.RangeType,
                ChapterStart           = this.ChapterStart,
                ChapterEnd             = this.ChapterEnd,
                SecondsStart           = this.SecondsStart,
                SecondsEnd             = this.SecondsEnd,
                FramesStart            = this.FramesStart,
                FramesEnd              = this.FramesEnd,
                Subtitles              = this.Subtitles,
                UseDefaultChapterNames = this.UseDefaultChapterNames,
                DxvaDecoding           = this.DxvaDecoding,
                OutputPath             = this.OutputPath,

                ContainerName         = this.ContainerName,
                PreferredExtension    = this.PreferredExtension,
                IncludeChapterMarkers = this.IncludeChapterMarkers,
                Optimize      = this.Optimize,
                IPod5GSupport = this.IPod5GSupport,

                Width             = this.Width,
                Height            = this.Height,
                MaxWidth          = this.MaxWidth,
                MaxHeight         = this.MaxHeight,
                ScaleMethod       = this.ScaleMethod,
                Cropping          = this.Cropping.Clone(),
                Anamorphic        = this.Anamorphic,
                UseDisplayWidth   = this.UseDisplayWidth,
                DisplayWidth      = this.DisplayWidth,
                KeepDisplayAspect = this.KeepDisplayAspect,
                PixelAspectX      = this.PixelAspectX,
                PixelAspectY      = this.PixelAspectY,
                Modulus           = this.Modulus,
                Rotation          = this.Rotation,
                FlipHorizontal    = this.FlipHorizontal,
                FlipVertical      = this.FlipVertical,

                Deinterlace       = this.Deinterlace,
                CustomDeinterlace = this.CustomDeinterlace,
                Decomb            = this.Decomb,
                CustomDecomb      = this.CustomDecomb,
                Detelecine        = this.Detelecine,
                CustomDetelecine  = this.CustomDetelecine,
                Denoise           = this.Denoise,
                DenoisePreset     = this.DenoisePreset,
                DenoiseTune       = this.DenoiseTune,
                UseCustomDenoise  = this.UseCustomDenoise,
                CustomDenoise     = this.CustomDenoise,
                Deblock           = this.Deblock,
                Grayscale         = this.Grayscale,

                VideoEncoder        = this.VideoEncoder,
                VideoOptions        = this.VideoOptions,
                VideoProfile        = this.VideoProfile,
                VideoPreset         = this.VideoPreset,
                VideoTunes          = this.VideoTunes,
                VideoLevel          = this.VideoLevel,
                QsvDecode           = this.QsvDecode,
                VideoEncodeRateType = this.VideoEncodeRateType,
                Quality             = this.Quality,
                TargetSize          = this.TargetSize,
                VideoBitrate        = this.VideoBitrate,
                TwoPass             = this.TwoPass,
                TurboFirstPass      = this.TurboFirstPass,
                Framerate           = this.Framerate,
                ConstantFramerate   = this.ConstantFramerate,

                AudioEncodings       = new List <AudioEncoding>(this.AudioEncodings),
                AudioEncoderFallback = this.AudioEncoderFallback
            };

            return(clone);
        }
        /*
         * TODO: This conversion class needs to be finished off before libencode will work.
         */
        /// <summary>
        /// Get an EncodeJob model for a LibHB Encode.
        /// </summary>
        /// <param name="task">
        /// The task.
        /// </param>
        /// <returns>
        /// An Interop.EncodeJob model.
        /// </returns>
        public static EncodeJob GetEncodeJob(QueueTask task)
        {
            // Sanity Checking
            if (task == null || task.Task == null)
            {
                return null;
            }

            // The current Job Configuration
            EncodeTask work = task.Task;

            // Which will be converted to this EncodeJob Model.
            EncodeJob job = new EncodeJob();

            EncodingProfile profile = new EncodingProfile();
            job.EncodingProfile = profile;

            profile.Anamorphic = work.Anamorphic;

            profile.AudioEncodings = new List<AudioEncoding>();
            job.ChosenAudioTracks = new List<int>();
            foreach (AudioTrack track in work.AudioTracks)
            {
                AudioEncoding newTrack = new AudioEncoding
                    {
                        Bitrate = track.Bitrate,
                        Drc = track.DRC,
                        Gain = track.Gain,
                        Encoder = Converters.GetCliAudioEncoder(track.Encoder),
                        InputNumber = track.Track.HasValue ? track.Track.Value : 0,
                        Mixdown = Converters.GetCliMixDown(track.MixDown),
                        SampleRateRaw = GetSampleRateRaw(track.SampleRate),
                    };

                profile.AudioEncodings.Add(newTrack);
                if (track.Track != null)
                {
                    job.ChosenAudioTracks.Add(track.Track.Value);
                }
            }
            profile.Cropping = new Cropping
                {
                    Top = work.Cropping.Top,
                    Bottom = work.Cropping.Bottom,
                    Left = work.Cropping.Left,
                    Right = work.Cropping.Right
                };

            profile.CroppingType = CroppingType.Custom; // TODO deal with this better
            profile.CustomDecomb = work.CustomDecomb;
            profile.CustomDeinterlace = work.CustomDeinterlace;
            profile.CustomDenoise = work.CustomDenoise;
            profile.CustomDetelecine = work.CustomDetelecine;
            profile.Deblock = work.Deblock;
            profile.Decomb = work.Decomb;
            profile.Deinterlace = work.Deinterlace;
            profile.Denoise = work.Denoise;
            profile.Detelecine = work.Detelecine;
            profile.DisplayWidth = work.DisplayWidth.HasValue
                                       ? int.Parse(Math.Round(work.DisplayWidth.Value, 0).ToString())
                                       : 0;
            profile.Framerate = work.Framerate.HasValue ? work.Framerate.Value : 0;
            profile.Grayscale = work.Grayscale;
            profile.Height = work.Height.HasValue ? work.Height.Value : 0;
            profile.IPod5GSupport = work.IPod5GSupport;
            profile.IncludeChapterMarkers = work.IncludeChapterMarkers;
            profile.KeepDisplayAspect = work.KeepDisplayAspect;
            profile.LargeFile = work.LargeFile;
            profile.MaxHeight = work.MaxHeight.HasValue ? work.MaxHeight.Value : 0;
            profile.MaxWidth = work.MaxWidth.HasValue ? work.MaxWidth.Value : 0;
            profile.Modulus = work.Modulus.HasValue ? work.Modulus.Value : 16;
            profile.Optimize = work.OptimizeMP4;
            switch (work.OutputFormat)
            {
                case OutputFormat.Mp4:
                case OutputFormat.M4V:
                    profile.OutputFormat = Container.Mp4;
                    break;
                case OutputFormat.Mkv:
                    profile.OutputFormat = Container.Mkv;
                    break;
            }
            profile.ConstantFramerate = work.FramerateMode == FramerateMode.CFR;
            profile.PixelAspectX = work.PixelAspectX;
            profile.PixelAspectY = work.PixelAspectY;

            switch (work.OutputFormat)
            {
                case OutputFormat.Mp4:
                    profile.PreferredExtension = OutputExtension.Mp4;
                    break;
                case OutputFormat.M4V:
                    profile.PreferredExtension = OutputExtension.M4v;
                    break;
            }
            profile.Quality = work.Quality.HasValue ? work.Quality.Value : 0;
            profile.UseDisplayWidth = true;
            profile.VideoBitrate = work.VideoBitrate.HasValue ? work.VideoBitrate.Value : 0;
            profile.VideoEncodeRateType = work.VideoEncodeRateType;
            profile.VideoEncoder = Converters.GetVideoEncoder(work.VideoEncoder);
            profile.Width = work.Width.HasValue ? work.Width.Value : 0;
            profile.X264Options = work.AdvancedEncoderOptions;

            if (work.PointToPointMode == PointToPointMode.Chapters)
            {
                job.ChapterStart = work.StartPoint;
                job.ChapterEnd = work.EndPoint;
            }

            job.Angle = work.Angle;
            job.EncodingProfile = profile;
            if (work.PointToPointMode == PointToPointMode.Frames)
            {
                job.FramesEnd = work.EndPoint;
                job.FramesStart = work.StartPoint;
            }

            job.CustomChapterNames = work.ChapterNames.Select(item => item.ChapterName).ToList();
            job.UseDefaultChapterNames = work.IncludeChapterMarkers;

            job.OutputPath = work.Destination;
            switch (work.PointToPointMode)
            {
                case PointToPointMode.Chapters:
                    job.RangeType = VideoRangeType.Chapters;
                    break;
                case PointToPointMode.Seconds:
                    job.RangeType = VideoRangeType.Seconds;
                    break;
                case PointToPointMode.Frames:
                    job.RangeType = VideoRangeType.Frames;
                    break;
            }

            if (work.PointToPointMode == PointToPointMode.Seconds)
            {
                job.SecondsEnd = work.EndPoint;
                job.SecondsStart = work.StartPoint;
            }

            job.SourcePath = work.Source;
            // job.SourceType = work.Type;
            job.Title = work.Title;

            // TODO Setup subtitles
            job.Subtitles = new Subtitles { SourceSubtitles = new List<SourceSubtitle>(), SrtSubtitles = new List<SrtSubtitle>() };
            //foreach (SubtitleTrack track in work.SubtitleTracks)
            //{
            //    // TODO
            //}

            return job;
        }
        /// <summary>
        /// Gets the total number of seconds on the given encode job.
        /// </summary>
        /// <param name="job">The encode job to query.</param>
        /// <param name="title">The title being encoded.</param>
        /// <returns>The total number of seconds of video to encode.</returns>
        internal static double GetJobLengthSeconds(EncodeJob job, Title title)
        {
            switch (job.RangeType)
            {
                case VideoRangeType.All:
                    return title.Duration.TotalSeconds;
                case VideoRangeType.Chapters:
                    TimeSpan duration = TimeSpan.Zero;
                    for (int i = job.ChapterStart; i <= job.ChapterEnd; i++)
                    {
                        duration += title.Chapters[i - 1].Duration;
                    }

                    return duration.TotalSeconds;
                case VideoRangeType.Seconds:
                    return job.SecondsEnd - job.SecondsStart;
                case VideoRangeType.Frames:
                    return (job.FramesEnd - job.FramesStart) / title.Framerate;
            }

            return 0;
        }
Exemple #25
0
        public EncodeJob Clone()
        {
            EncodeJob clone = new EncodeJob
            {
                SourceType = this.SourceType,
                SourcePath = this.SourcePath,
                Title = this.Title,
                Angle = this.Angle,
                RangeType = this.RangeType,
                ChapterStart = this.ChapterStart,
                ChapterEnd = this.ChapterEnd,
                SecondsStart = this.SecondsStart,
                SecondsEnd = this.SecondsEnd,
                FramesStart = this.FramesStart,
                FramesEnd = this.FramesEnd,
                ChosenAudioTracks = new List<int>(this.ChosenAudioTracks),
                Subtitles = this.Subtitles,
                UseDefaultChapterNames = this.UseDefaultChapterNames,
                OutputPath = this.OutputPath,
                EncodingProfile = this.EncodingProfile,
                Length = this.Length
            };

            return clone;
        }