// --------------------------------------------------------------------------------------------------------------------
//
// 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[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
if (messageParts.Length > 0)
{
message = messageParts[0];
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);
}
}
}