public static extern int hb_subtitle_add(ref hb_job_s job, ref hb_subtitle_config_s subtitleConfig, int track);
public static extern int hb_srt_add(ref hb_job_s job, ref hb_subtitle_config_s subtitleConfig, string lang);
/// <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> /// 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; }