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