// -------------------------------------------------------------------------------------------------------------------- // // 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.Interop { using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using HandBrake.Interop.Interop.EventArgs; using HandBrake.Interop.Interop.HbLib; using HandBrake.Interop.Interop.Json.Anamorphic; using HandBrake.Interop.Interop.Json.Shared; using Newtonsoft.Json; /// /// HandBrake Interop Utilities /// public static class HandBrakeUtils { /// /// 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; private static bool initSuccess = false; private static bool initNoHardware = false; /// /// Fires when HandBrake has logged a message. /// public static event EventHandler MessageLogged; /// /// Fires when HandBrake has logged an error. /// public static event EventHandler ErrorLogged; /// /// Ensures the HB global initialize method has been called. /// public static void EnsureGlobalInit(bool initNoHardwareMode) { if (!globalInitialized) { try { if (initNoHardwareMode) { initNoHardware = true; if (HBFunctions.hb_global_init_no_hardware() == -1) { throw new InvalidOperationException("HB global init failed."); } initSuccess = true; } else { initSuccess = TryInit(); } } catch (Exception e) { initSuccess = false; } // Try without Hardware support. Bad drivers can sometimes cause issues. if (!initSuccess) { if (HBFunctions.hb_global_init_no_hardware() == -1) { throw new InvalidOperationException("HB global init failed."); } } globalInitialized = true; } } /// /// 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 = LoggingHandler; errorCallback = 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) { message = message.TrimEnd(); if (!string.IsNullOrEmpty(message)) { 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") || message.Contains("nav_get_title_list")) { SendMessageEvent(message); return; } SendErrorEvent(message); } } /// /// Gets the standard x264 option name given the starting point. /// /// /// The name. /// /// /// 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( 8, preset, string.Join(",", tunes), extraOptions, profile, level, width, height); // TODO add bit-depth support. string x264Settings = Marshal.PtrToStringAnsi(ptr); return x264Settings; } /// /// Gets the final size and PAR of the video, given anamorphic inputs. /// /// Anamorphic inputs. /// The final size and PAR of the video. public static Geometry GetAnamorphicSize(AnamorphicGeometry anamorphicGeometry) { string encode = JsonConvert.SerializeObject(anamorphicGeometry, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); IntPtr json = HBFunctions.hb_set_anamorphic_size_json(Marshal.StringToHGlobalAnsi(encode)); string result = Marshal.PtrToStringAnsi(json); return JsonConvert.DeserializeObject(result); } public static void Reduce(long den, long num, out long x, out long y) { // find the greatest common divisor of num & den by Euclid's algorithm long n = num, d = den; while (d > 0) { long t = d; d = n % d; n = t; } // at this point n is the gcd. if it's non-zero remove it from num // and den. Otherwise just return the original values. if (n > 0) { num /= n; den /= n; } x = num; y = den; } /// /// Sends the message logged event to any registered listeners. /// /// /// The message to send. /// public static void SendMessageEvent(string message) { MessageLogged?.Invoke(null, new MessageLoggedEventArgs(message)); } /// /// Sends the error logged event to any registered listeners. /// /// /// The message to send /// public static void SendErrorEvent(string message) { ErrorLogged?.Invoke(null, new MessageLoggedEventArgs(message)); } public static bool IsInitialised() { return initSuccess; } public static bool IsInitNoHardware() { return initNoHardware; } [HandleProcessCorruptedStateExceptions] private static bool TryInit() { try { if (HBFunctions.hb_global_init() == -1) { throw new InvalidOperationException("HB global init failed."); } } catch (Exception e) { return false; } return true; } } }