// -------------------------------------------------------------------------------------------------------------------- // // This file is part of the HandBrake source code - It may be used under the terms of the GNU General Public License. // // // Defines the HandBrakeUtils type. // // -------------------------------------------------------------------------------------------------------------------- namespace HandBrake.Interop { using System; using System.Collections.Generic; using System.Runtime.InteropServices; using HandBrake.Interop.EventArgs; using HandBrake.Interop.HbLib; using HandBrake.Interop.Model; using HandBrake.Interop.Model.Encoding; using HandBrake.Interop.SourceData; /// /// HandBrake Interop Utilities /// public static class HandBrakeUtils { /// /// Estimated overhead in bytes for each frame in output container. /// internal const int ContainerOverheadPerFrame = 6; /// /// The callback for log messages from HandBrake. /// private static LoggingCallback loggingCallback; /// /// The callback for error messages from HandBrake. /// private static LoggingCallback errorCallback; /// /// True if the global initialize function has been called. /// private static bool globalInitialized; /// /// Fires when HandBrake has logged a message. /// public static event EventHandler MessageLogged; /// /// Fires when HandBrake has logged an error. /// public static event EventHandler ErrorLogged; /// /// Initializes static members of the HandBrakeUtils class. /// static HandBrakeUtils() { if (!globalInitialized) { if (HBFunctions.hb_global_init() == -1) { throw new InvalidOperationException("HB global init failed."); } globalInitialized = true; } } /// /// Ensures the HB global initialize method has been called. /// public static void EnsureGlobalInit() { // Does nothing, but invokes static ctor. } /// /// Enables or disables LibDVDNav. If disabled libdvdread will be used instead. /// /// True to enable LibDVDNav. public static void SetDvdNav(bool enableDvdNav) { HBFunctions.hb_dvd_set_dvdnav(enableDvdNav ? 1 : 0); } /// /// Call before app shutdown. Performs global cleanup. /// public static void DisposeGlobal() { HBFunctions.hb_global_close(); } /// /// Register the logger. /// public static void RegisterLogger() { // Register the logger if we have not already if (loggingCallback == null) { // Keep the callback as a member to prevent it from being garbage collected. loggingCallback = new LoggingCallback(LoggingHandler); errorCallback = new LoggingCallback(ErrorHandler); HBFunctions.hb_register_logger(loggingCallback); HBFunctions.hb_register_error_handler(errorCallback); } } /// /// Handles log messages from HandBrake. /// /// The log message (including newline). public static void LoggingHandler(string message) { if (!string.IsNullOrEmpty(message)) { string[] messageParts = message.Split(new string[] { "\n" }, StringSplitOptions.RemoveEmptyEntries); if (messageParts.Length > 0) { message = messageParts[0]; // When MP4 muxing fails (for example when the file is too big without Large File Size) // a message is logged but it isn't marked as an error. if (message.StartsWith("MP4ERROR", StringComparison.Ordinal)) { SendErrorEvent(message); return; } SendMessageEvent(message); } } } /// /// Handles errors from HandBrake. /// /// The error message. public static void ErrorHandler(string message) { if (!string.IsNullOrEmpty(message)) { // These errors happen in normal operations. Log them as messages. if (message == "dvd: ifoOpen failed" || message.Contains("avformat_seek_file failed")) { SendMessageEvent(message); return; } SendErrorEvent(message); } } /// /// Gets the standard x264 option name given the starting point. /// /// The standard x264 option name. public static string SanitizeX264OptName(string name) { IntPtr namePtr = Marshal.StringToHGlobalAnsi(name); string sanitizedName = Marshal.PtrToStringAnsi(HBFunctions.hb_x264_encopt_name(namePtr)); Marshal.FreeHGlobal(namePtr); return sanitizedName; } /// /// Checks to see if the given H.264 level is valid given the inputs. /// /// The level to check. /// The output picture width. /// The output picture height. /// The rate numerator. /// The rate denominator. /// True if x264 interlaced output is enabled. /// True if x264 fake interlacing is enabled. /// True if the level is valid. public static bool IsH264LevelValid(string level, int width, int height, int fpsNumerator, int fpsDenominator, bool interlaced, bool fakeInterlaced) { return HBFunctions.hb_check_h264_level( level, width, height, fpsNumerator, fpsDenominator, interlaced ? 1 : 0, fakeInterlaced ? 1 : 0) == 0; } /// /// Creates an X264 options string from the given settings. /// /// The x264 preset. /// The x264 tunes being used. /// The extra options string. /// The H.264 profile. /// The H.264 level. /// The width of the final picture. /// The height of the final picture. /// The full x264 options string from the given inputs. public static string CreateX264OptionsString( string preset, IList tunes, string extraOptions, string profile, string level, int width, int height) { if (width <= 0) { throw new ArgumentException("width must be positive."); } if (height <= 0) { throw new ArgumentException("height must be positive."); } IntPtr ptr = HBFunctions.hb_x264_param_unparse( preset, string.Join(",", tunes), extraOptions, profile, level, width, height); string x264Settings = Marshal.PtrToStringAnsi(ptr); return x264Settings; } /// /// Gets the total number of seconds on the given encode job. /// /// The encode job to query. /// The title being encoded. /// The total number of seconds of video to encode. 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; } /// /// Gets the number of audio samples used per frame for the given audio encoder. /// /// The encoder to query. /// The number of audio samples used per frame for the given /// audio encoder. internal static int GetAudioSamplesPerFrame(string encoderName) { switch (encoderName) { case "faac": case "ffaac": case "copy:aac": case "vorbis": return 1024; case "lame": case "copy:mp3": return 1152; case "ffac3": case "copy": case "copy:ac3": case "copy:dts": case "copy:dtshd": return 1536; } // Unknown encoder; make a guess. return 1536; } /// /// Gets the size in bytes for the audio with the given parameters. /// /// The encode job. /// The length of the encode in seconds. /// The title to encode. /// The list of tracks to encode. /// The size in bytes for the audio with the given parameters. internal static long GetAudioSize(EncodeJob job, double lengthSeconds, Title title, List> outputTrackList) { long audioBytes = 0; foreach (Tuple 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; } /// /// Sends the message logged event to any registered listeners. /// /// The message to send. private static void SendMessageEvent(string message) { if (MessageLogged != null) { MessageLogged(null, new MessageLoggedEventArgs { Message = message }); } System.Diagnostics.Debug.WriteLine(message); } /// /// Sends the error logged event to any registered listeners. /// /// The message to send private static void SendErrorEvent(string message) { if (ErrorLogged != null) { ErrorLogged(null, new MessageLoggedEventArgs { Message = message }); } System.Diagnostics.Debug.WriteLine("ERROR: " + message); } } }