/* common.c

   Copyright (c) 2003-2022 HandBrake Team
   This file is part of the HandBrake source code
   Homepage: <http://handbrake.fr/>.
   It may be used under the terms of the GNU General Public License v2.
   For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include <sys/time.h>

#include "handbrake/handbrake.h"
#include "handbrake/hbffmpeg.h"
#include "x264.h"
#include "handbrake/lang.h"
#include "handbrake/common.h"
#include "handbrake/h264_common.h"
#include "handbrake/h265_common.h"
#include "handbrake/av1_common.h"
#include "handbrake/encx264.h"
#if HB_PROJECT_FEATURE_QSV
#include "handbrake/qsv_common.h"
#endif

#if HB_PROJECT_FEATURE_X265
#include "x265.h"
#endif

#ifdef SYS_MINGW
#include <windows.h>
#endif

#if HB_PROJECT_FEATURE_NVENC
#include "handbrake/nvenc_common.h"
#endif
#if HB_PROJECT_FEATURE_VCE
#include "handbrake/vce_common.h"
#endif

#ifdef __APPLE__
#include "platform/macosx/vt_common.h"
#endif

static int mixdown_get_opus_coupled_stream_count(int mixdown);

/**********************************************************************
 * Global variables
 *********************************************************************/
static hb_error_handler_t *error_handler = NULL;

/* Generic IDs for encoders, containers, etc. */
enum
{
    HB_GID_NONE = -1, // encoders must NEVER use it
    HB_GID_VCODEC_H264_MF,
    HB_GID_VCODEC_H264_NVENC,
    HB_GID_VCODEC_H264_QSV,
    HB_GID_VCODEC_H264_VCE,
    HB_GID_VCODEC_H264_VT,
    HB_GID_VCODEC_H264_X264,
    HB_GID_VCODEC_H265_MF,
    HB_GID_VCODEC_H265_NVENC,
    HB_GID_VCODEC_H265_QSV,
    HB_GID_VCODEC_H265_VCE,
    HB_GID_VCODEC_H265_VT,
    HB_GID_VCODEC_H265_X265,
    HB_GID_VCODEC_MPEG2,
    HB_GID_VCODEC_MPEG4,
    HB_GID_VCODEC_THEORA,
    HB_GID_VCODEC_VP8,
    HB_GID_VCODEC_VP9,
    HB_GID_VCODEC_AV1_SVT,
    HB_GID_VCODEC_AV1_QSV,
    HB_GID_ACODEC_AAC,
    HB_GID_ACODEC_AAC_HE,
    HB_GID_ACODEC_AAC_PASS,
    HB_GID_ACODEC_AC3,
    HB_GID_ACODEC_AC3_PASS,
    HB_GID_ACODEC_AUTO_PASS,
    HB_GID_ACODEC_DTS_PASS,
    HB_GID_ACODEC_DTSHD_PASS,
    HB_GID_ACODEC_EAC3,
    HB_GID_ACODEC_EAC3_PASS,
    HB_GID_ACODEC_FLAC,
    HB_GID_ACODEC_FLAC_PASS,
    HB_GID_ACODEC_MP2_PASS,
    HB_GID_ACODEC_MP3,
    HB_GID_ACODEC_MP3_PASS,
    HB_GID_ACODEC_TRUEHD_PASS,
    HB_GID_ACODEC_VORBIS,
    HB_GID_ACODEC_OPUS,
    HB_GID_ACODEC_OPUS_PASS,
    HB_GID_MUX_MKV,
    HB_GID_MUX_MP4,
    HB_GID_MUX_WEBM,
};

#define HB_VIDEO_CLOCK    27000000 // 27MHz clock
#define HB_VIDEO_FPS_MIN  1
#define HB_VIDEO_FPS_MAX  1000
int hb_video_rate_clock = HB_VIDEO_CLOCK;
int hb_video_rate_min   = HB_VIDEO_CLOCK / HB_VIDEO_FPS_MAX; // Min clock rate from *max* frame rate
int hb_video_rate_max   = HB_VIDEO_CLOCK / HB_VIDEO_FPS_MIN; // Max clock rate from *min* frame rate

typedef struct
{
    hb_rate_t  item;
    hb_rate_t *next;
    int enabled;
} hb_rate_internal_t;
hb_rate_t *hb_video_rates_first_item = NULL;
hb_rate_t *hb_video_rates_last_item  = NULL;
hb_rate_internal_t hb_video_rates[]  =
{
    // legacy framerates (disabled)
    { { "23.976 (NTSC Film)",  1126125, }, NULL, 0, },
    { { "25 (PAL Film/Video)", 1080000, }, NULL, 0, },
    { { "29.97 (NTSC Video)",   900900, }, NULL, 0, },
    // actual framerates
    { {  "5",                  5400000, }, NULL, 1, },
    { { "10",                  2700000, }, NULL, 1, },
    { { "12",                  2250000, }, NULL, 1, },
    { { "15",                  1800000, }, NULL, 1, },
    { { "20",                  1350000, }, NULL, 1, },
    { { "23.976",              1126125, }, NULL, 1, },
    { { "24",                  1125000, }, NULL, 1, },
    { { "25",                  1080000, }, NULL, 1, },
    { { "29.97",                900900, }, NULL, 1, },
    { { "30",                   900000, }, NULL, 1, },
    { { "48",                   562500, }, NULL, 1, },
    { { "50",                   540000, }, NULL, 1, },
    { { "59.94",                450450, }, NULL, 1, },
    { { "60",                   450000, }, NULL, 1, },
    { { "72",                   375000, }, NULL, 1, },
    { { "75",                   360000, }, NULL, 1, },
    { { "90",                   300000, }, NULL, 1, },
    { { "100",                  270000, }, NULL, 1, },
    { { "120",                  225000, }, NULL, 1, },
};
int hb_video_rates_count = sizeof(hb_video_rates) / sizeof(hb_video_rates[0]);

hb_rate_t *hb_audio_rates_first_item = NULL;
hb_rate_t *hb_audio_rates_last_item  = NULL;
hb_rate_internal_t hb_audio_rates[]  =
{
    { {  "8",      8000, }, NULL, 1, },
    { { "11.025", 11025, }, NULL, 1, },
    { { "12",     12000, }, NULL, 1, },
    { { "16",     16000, }, NULL, 1, },
    { { "22.05",  22050, }, NULL, 1, },
    { { "24",     24000, }, NULL, 1, },
    { { "32",     32000, }, NULL, 1, },
    { { "44.1",   44100, }, NULL, 1, },
    { { "48",     48000, }, NULL, 1, },
};
int hb_audio_rates_count = sizeof(hb_audio_rates) / sizeof(hb_audio_rates[0]);

hb_rate_t *hb_audio_bitrates_first_item = NULL;
hb_rate_t *hb_audio_bitrates_last_item  = NULL;
hb_rate_internal_t hb_audio_bitrates[]  =
{
    // AC3-compatible bitrates
    { {   "6",     6, }, NULL, 1, },
    { {   "12",   12, }, NULL, 1, },
    { {   "24",   24, }, NULL, 1, },
    { {   "32",   32, }, NULL, 1, },
    { {   "40",   40, }, NULL, 1, },
    { {   "48",   48, }, NULL, 1, },
    { {   "56",   56, }, NULL, 1, },
    { {   "64",   64, }, NULL, 1, },
    { {   "80",   80, }, NULL, 1, },
    { {   "96",   96, }, NULL, 1, },
    { {  "112",  112, }, NULL, 1, },
    { {  "128",  128, }, NULL, 1, },
    { {  "160",  160, }, NULL, 1, },
    { {  "192",  192, }, NULL, 1, },
    { {  "224",  224, }, NULL, 1, },
    { {  "256",  256, }, NULL, 1, },
    { {  "320",  320, }, NULL, 1, },
    { {  "384",  384, }, NULL, 1, },
    { {  "448",  448, }, NULL, 1, },
    { {  "512",  512, }, NULL, 1, },
    { {  "576",  576, }, NULL, 1, },
    { {  "640",  640, }, NULL, 1, },
    // additional bitrates
    { {  "768",  768, }, NULL, 1, },
    { {  "960",  960, }, NULL, 1, },
    { { "1152", 1152, }, NULL, 1, },
    { { "1344", 1344, }, NULL, 1, },
    { { "1536", 1536, }, NULL, 1, },
    { { "2304", 2304, }, NULL, 1, },
    { { "3072", 3072, }, NULL, 1, },
    { { "4608", 4608, }, NULL, 1, },
    { { "6144", 6144, }, NULL, 1, },
};
int hb_audio_bitrates_count = sizeof(hb_audio_bitrates) / sizeof(hb_audio_bitrates[0]);

typedef struct
{
    hb_dither_t  item;
    hb_dither_t *next;
    int enabled;
} hb_dither_internal_t;
hb_dither_t *hb_audio_dithers_first_item = NULL;
hb_dither_t *hb_audio_dithers_last_item  = NULL;
hb_dither_internal_t hb_audio_dithers[]  =
{
    { { "default",                       "auto",          SWR_DITHER_NONE - 1,            }, NULL, 1, },
    { { "none",                          "none",          SWR_DITHER_NONE,                }, NULL, 1, },
    { { "rectangular",                   "rectangular",   SWR_DITHER_RECTANGULAR,         }, NULL, 1, },
    { { "triangular",                    "triangular",    SWR_DITHER_TRIANGULAR,          }, NULL, 1, },
    { { "triangular with high pass",     "triangular_hp", SWR_DITHER_TRIANGULAR_HIGHPASS, }, NULL, 1, },
    { { "lipshitz noise shaping",        "lipshitz_ns",   SWR_DITHER_NS_LIPSHITZ,         }, NULL, 1, },
};
int hb_audio_dithers_count = sizeof(hb_audio_dithers) / sizeof(hb_audio_dithers[0]);

typedef struct
{
    hb_mixdown_t  item;
    hb_mixdown_t *next;
    int enabled;
} hb_mixdown_internal_t;
hb_mixdown_t *hb_audio_mixdowns_first_item = NULL;
hb_mixdown_t *hb_audio_mixdowns_last_item  = NULL;
hb_mixdown_internal_t hb_audio_mixdowns[]  =
{
    // legacy mixdowns, back to HB 0.9.4 whenever possible (disabled)
    { { "AC3 Passthru",       "",           HB_AMIXDOWN_NONE,      }, NULL, 0, },
    { { "DTS Passthru",       "",           HB_AMIXDOWN_NONE,      }, NULL, 0, },
    { { "DTS-HD Passthru",    "",           HB_AMIXDOWN_NONE,      }, NULL, 0, },
    { { "6-channel discrete", "6ch",        HB_AMIXDOWN_5POINT1,   }, NULL, 0, },
    // actual mixdowns
    { { "None",               "none",       HB_AMIXDOWN_NONE,      }, NULL, 1, },
    { { "Mono",               "mono",       HB_AMIXDOWN_MONO,      }, NULL, 1, },
    { { "Mono (Left Only)",   "left_only",  HB_AMIXDOWN_LEFT,      }, NULL, 1, },
    { { "Mono (Right Only)",  "right_only", HB_AMIXDOWN_RIGHT,     }, NULL, 1, },
    { { "Stereo",             "stereo",     HB_AMIXDOWN_STEREO,    }, NULL, 1, },
    { { "Dolby Surround",     "dpl1",       HB_AMIXDOWN_DOLBY,     }, NULL, 1, },
    { { "Dolby Pro Logic II", "dpl2",       HB_AMIXDOWN_DOLBYPLII, }, NULL, 1, },
    { { "5.1 Channels",       "5point1",    HB_AMIXDOWN_5POINT1,   }, NULL, 1, },
    { { "6.1 Channels",       "6point1",    HB_AMIXDOWN_6POINT1,   }, NULL, 1, },
    { { "7.1 Channels",       "7point1",    HB_AMIXDOWN_7POINT1,   }, NULL, 1, },
    { { "7.1 (5F/2R/LFE)",    "5_2_lfe",    HB_AMIXDOWN_5_2_LFE,   }, NULL, 1, },
};
int hb_audio_mixdowns_count = sizeof(hb_audio_mixdowns) / sizeof(hb_audio_mixdowns[0]);

typedef struct
{
    hb_encoder_t  item;
    hb_encoder_t *next;
    int deprecated;
    int enabled;
    int gid;
} hb_encoder_internal_t;
hb_encoder_t *hb_video_encoders_first_item = NULL;
hb_encoder_t *hb_video_encoders_last_item  = NULL;
hb_encoder_internal_t hb_video_encoders[]  =
{
    // legacy encoders, back to HB 0.9.4 whenever possible (disabled)
    { { "FFmpeg",                      "ffmpeg",           NULL,                             HB_VCODEC_FFMPEG_MPEG4,      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_VCODEC_MPEG4,  },
    { { "MPEG-4 (FFmpeg)",             "ffmpeg4",          NULL,                             HB_VCODEC_FFMPEG_MPEG4,      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_VCODEC_MPEG4,  },
    { { "MPEG-2 (FFmpeg)",             "ffmpeg2",          NULL,                             HB_VCODEC_FFMPEG_MPEG2,      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_VCODEC_MPEG2,  },
    { { "VP3 (Theora)",                "libtheora",        NULL,                             HB_VCODEC_THEORA,                            HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_VCODEC_THEORA, },
    // actual encoders
    { { "AV1 (SVT)",                   "svt_av1",          "AV1 (SVT)",                      HB_VCODEC_FFMPEG_SVT_AV1,    HB_MUX_MASK_MP4|HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_SVT,    },
    { { "AV1 10-bit (SVT)",            "svt_av1_10bit",    "AV1 10-bit (SVT)",               HB_VCODEC_FFMPEG_SVT_AV1_10BIT, HB_MUX_MASK_MP4|HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_SVT,    },
    { { "AV1 (Intel QSV)",             "qsv_av1",          "AV1 (Intel Media SDK)",          HB_VCODEC_QSV_AV1,           HB_MUX_MASK_MP4|HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_QSV,    },
    { { "AV1 10-bit (Intel QSV)",      "qsv_av1_10bit",    "AV1 10-bit (Intel Media SDK)",   HB_VCODEC_QSV_AV1_10BIT,     HB_MUX_MASK_MP4|HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_QSV,    },
    { { "H.264 (x264)",                "x264",             "H.264 (libx264)",                HB_VCODEC_X264_8BIT,                          HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_X264,  },
    { { "H.264 10-bit (x264)",         "x264_10bit",       "H.264 10-bit (libx264)",         HB_VCODEC_X264_10BIT,                         HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_X264,  },
    { { "H.264 (Intel QSV)",           "qsv_h264",         "H.264 (Intel Media SDK)",        HB_VCODEC_QSV_H264,                           HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_QSV,   },
    { { "H.264 (AMD VCE)",             "vce_h264",         "H.264 (AMD VCE)",                HB_VCODEC_FFMPEG_VCE_H264,                    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_VCE,   },
    { { "H.264 (NVEnc)",               "nvenc_h264",       "H.264 (NVEnc)",                  HB_VCODEC_FFMPEG_NVENC_H264,                  HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_NVENC, },
    { { "H.264 (MediaFoundation)",     "mf_h264",          "H.264 (MediaFoundation)",        HB_VCODEC_FFMPEG_MF_H264,                     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_MF,    },
    { { "H.264 (VideoToolbox)",        "vt_h264",          "H.264 (VideoToolbox)",           HB_VCODEC_VT_H264,                            HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_VT,    },
    { { "H.265 (x265)",                "x265",             "H.265 (libx265)",                HB_VCODEC_X265_8BIT,                            HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_VCODEC_H265_X265,  },
    { { "H.265 10-bit (x265)",         "x265_10bit",       "H.265 10-bit (libx265)",         HB_VCODEC_X265_10BIT,                           HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_VCODEC_H265_X265,  },
    { { "H.265 12-bit (x265)",         "x265_12bit",       "H.265 12-bit (libx265)",         HB_VCODEC_X265_12BIT,                           HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_VCODEC_H265_X265,  },
    { { "H.265 16-bit (x265)",         "x265_16bit",       "H.265 16-bit (libx265)",         HB_VCODEC_X265_16BIT,                           HB_MUX_AV_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_VCODEC_H265_X265,  },
    { { "H.265 (Intel QSV)",           "qsv_h265",         "H.265 (Intel Media SDK)",        HB_VCODEC_QSV_H265,                           HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_QSV,   },
    { { "H.265 10-bit (Intel QSV)",    "qsv_h265_10bit",   "H.265 10-bit (Intel Media SDK)", HB_VCODEC_QSV_H265_10BIT,                     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_QSV,   },
    { { "H.265 (AMD VCE)",             "vce_h265",         "H.265 (AMD VCE)",                HB_VCODEC_FFMPEG_VCE_H265,                    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_VCE,   },
    { { "H.265 10-bit (AMD VCE)",      "vce_h265_10bit",   "H.265 10-bit (AMD VCE)",         HB_VCODEC_FFMPEG_VCE_H265_10BIT,              HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_VCE,   },
    { { "H.265 (NVEnc)",               "nvenc_h265",       "H.265 (NVEnc)",                  HB_VCODEC_FFMPEG_NVENC_H265,                  HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_NVENC, },
    { { "H.265 10-bit (NVEnc)",        "nvenc_h265_10bit", "H.265 10-bit (NVEnc)",           HB_VCODEC_FFMPEG_NVENC_H265_10BIT,            HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_NVENC, },
    { { "H.265 (MediaFoundation)",     "mf_h265",          "H.265 (MediaFoundation)",        HB_VCODEC_FFMPEG_MF_H265,                     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_MF,    },
    { { "H.265 (VideoToolbox)",        "vt_h265",          "H.265 (VideoToolbox)",           HB_VCODEC_VT_H265,                            HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_VT,    },
    { { "H.265 10-bit (VideoToolbox)", "vt_h265_10bit",    "H.265 10-bit (VideoToolbox)",    HB_VCODEC_VT_H265_10BIT,                      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H265_VT,    },
    { { "MPEG-4",                      "mpeg4",            "MPEG-4 (libavcodec)",            HB_VCODEC_FFMPEG_MPEG4,                       HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_MPEG4,      },
    { { "MPEG-2",                      "mpeg2",            "MPEG-2 (libavcodec)",            HB_VCODEC_FFMPEG_MPEG2,                       HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_MPEG2,      },
    { { "VP8",                         "VP8",              "VP8 (libvpx)",                   HB_VCODEC_FFMPEG_VP8,                        HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_VP8,        },
    { { "VP9",                         "VP9",              "VP9 (libvpx)",                   HB_VCODEC_FFMPEG_VP9,                        HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_VP9,        },
    { { "VP9 10-bit",                  "VP9_10bit",        "VP9 10-bit (libvpx)",            HB_VCODEC_FFMPEG_VP9_10BIT,                  HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_VP9,  },
    { { "Theora",                      "theora",           "Theora (libtheora)",             HB_VCODEC_THEORA,                                             HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_THEORA,     },
};
int hb_video_encoders_count = sizeof(hb_video_encoders) / sizeof(hb_video_encoders[0]);
static int hb_video_encoder_is_enabled(int encoder, int disable_hardware)
{
    // Hardware Encoders
    if (!disable_hardware)
    {
#if HB_PROJECT_FEATURE_QSV
        if (encoder & HB_VCODEC_QSV_MASK)
        {
            return hb_qsv_video_encoder_is_enabled(hb_qsv_get_adapter_index(), encoder);
        }
#endif

        switch (encoder)
        {
#if HB_PROJECT_FEATURE_VCE
            case HB_VCODEC_FFMPEG_VCE_H264:
               return hb_vce_h264_available();
            case HB_VCODEC_FFMPEG_VCE_H265:
            case HB_VCODEC_FFMPEG_VCE_H265_10BIT:
                return hb_vce_h265_available();
#endif

#if HB_PROJECT_FEATURE_NVENC
            case HB_VCODEC_FFMPEG_NVENC_H264:
                return hb_nvenc_h264_available();
            case HB_VCODEC_FFMPEG_NVENC_H265:
            case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
                return hb_nvenc_h265_available();
#endif

#ifdef __APPLE__
            case HB_VCODEC_VT_H264:
            case HB_VCODEC_VT_H265:
            case HB_VCODEC_VT_H265_10BIT:
                return hb_vt_is_encoder_available(encoder);
#endif

#if HB_PROJECT_FEATURE_MF
            // TODO: Try to instantiate a throwaway encoder to see if a suitable MediaFoundation encoder can be found?
            // Alt, implement logic similar to ffmpeg's encoder selection, to see if one would be found.
            case HB_VCODEC_FFMPEG_MF_H264:
                return 1;
            case HB_VCODEC_FFMPEG_MF_H265:
                return 1;
#endif
        }
    }

    // Software Encoders
    switch (encoder)
    {
        // the following encoders are always enabled
        case HB_VCODEC_THEORA:
        case HB_VCODEC_FFMPEG_MPEG4:
        case HB_VCODEC_FFMPEG_MPEG2:
        case HB_VCODEC_FFMPEG_VP8:
        case HB_VCODEC_FFMPEG_VP9:
        case HB_VCODEC_FFMPEG_VP9_10BIT:
        case HB_VCODEC_FFMPEG_SVT_AV1:
        case HB_VCODEC_FFMPEG_SVT_AV1_10BIT:
            return 1;

#if HB_PROJECT_FEATURE_X265
        case HB_VCODEC_X265_8BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_X265_12BIT:
        case HB_VCODEC_X265_16BIT:
        {
            const x265_api *api;
            api = x265_api_query(hb_video_encoder_get_depth(encoder), X265_BUILD, NULL);
            return (api != NULL);
        };
#endif

        case HB_VCODEC_X264_8BIT:
        case HB_VCODEC_X264_10BIT:
        {
            const x264_api_t *api;
            api = hb_x264_api_get(hb_video_encoder_get_depth(encoder));
            return (api != NULL);
        }

        default:
            return 0;
    }
}

hb_encoder_t *hb_audio_encoders_first_item = NULL;
hb_encoder_t *hb_audio_encoders_last_item  = NULL;
hb_encoder_internal_t hb_audio_encoders[]  =
{
    // legacy encoders, back to HB 0.9.4 whenever possible (disabled)
    { { "",                   "dts",        NULL,                          HB_ACODEC_DCA_PASS,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_DTS_PASS,   },
    { { "AAC (faac)",         "faac",       NULL,                          0,                     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_AAC,        },
    { { "AAC (ffmpeg)",       "ffaac",      NULL,                          HB_ACODEC_FFAAC,       HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_AAC,        },
    { { "AC3 (ffmpeg)",       "ffac3",      NULL,                          HB_ACODEC_AC3,         HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_AC3,        },
    { { "MP3 (lame)",         "lame",       NULL,                          HB_ACODEC_LAME,        HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_MP3,        },
    { { "Vorbis (vorbis)",    "libvorbis",  NULL,                          HB_ACODEC_VORBIS,                      HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_VORBIS,     },
    { { "FLAC (ffmpeg)",      "ffflac",     NULL,                          HB_ACODEC_FFFLAC,                      HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_FLAC,       },
    { { "FLAC (24-bit)",      "ffflac24",   NULL,                          HB_ACODEC_FFFLAC24,                    HB_MUX_MASK_MKV, }, NULL, 1, 0, HB_GID_ACODEC_FLAC,       },
    // generic names
    { { "AAC",                "aac",        NULL,                          0,                     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 0, HB_GID_ACODEC_AAC,        },
    { { "HE-AAC",             "haac",       NULL,                          0,                     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 0, HB_GID_ACODEC_AAC_HE,     },
    // actual encoders
    { { "None",               "none",       "None",                        HB_ACODEC_NONE,        0,                               }, NULL, 0, 1, HB_GID_NONE,              },
    { { "AAC (CoreAudio)",    "ca_aac",     "AAC (Apple AudioToolbox)",    HB_ACODEC_CA_AAC,      HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AAC,        },
    { { "HE-AAC (CoreAudio)", "ca_haac",    "HE-AAC (Apple AudioToolbox)", HB_ACODEC_CA_HAAC,     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AAC_HE,     },
    { { "AAC (FDK)",          "fdk_aac",    "AAC (libfdk_aac)",            HB_ACODEC_FDK_AAC,     HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AAC,        },
    { { "HE-AAC (FDK)",       "fdk_haac",   "HE-AAC (libfdk_aac)",         HB_ACODEC_FDK_HAAC,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AAC_HE,     },
    { { "AAC (avcodec)",      "av_aac",     "AAC (libavcodec)",            HB_ACODEC_FFAAC,       HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AAC,        },
    { { "AAC Passthru",       "copy:aac",   "AAC Passthru",                HB_ACODEC_AAC_PASS,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AAC_PASS,   },
    { { "AC3",                "ac3",        "AC3 (libavcodec)",            HB_ACODEC_AC3,         HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AC3,        },
    { { "AC3 Passthru",       "copy:ac3",   "AC3 Passthru",                HB_ACODEC_AC3_PASS,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AC3_PASS,   },
    { { "E-AC3",              "eac3",       "E-AC3 (libavcodec)",          HB_ACODEC_FFEAC3,      HB_MUX_MASK_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_ACODEC_EAC3,       },
    { { "E-AC3 Passthru",     "copy:eac3",  "E-AC3 Passthru",              HB_ACODEC_EAC3_PASS,   HB_MUX_MASK_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_ACODEC_EAC3_PASS,  },
    { { "TrueHD Passthru",    "copy:truehd","TrueHD Passthru",             HB_ACODEC_TRUEHD_PASS, HB_MUX_MASK_MP4|HB_MUX_AV_MKV,   }, NULL, 0, 1, HB_GID_ACODEC_TRUEHD_PASS,},
    { { "DTS Passthru",       "copy:dts",   "DTS Passthru",                HB_ACODEC_DCA_PASS,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_DTS_PASS,   },
    { { "DTS-HD Passthru",    "copy:dtshd", "DTS-HD Passthru",             HB_ACODEC_DCA_HD_PASS, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_DTSHD_PASS, },
    { { "MP2 Passthru",       "copy:mp2",   "MP2 Passthru",                HB_ACODEC_MP2_PASS,                    HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_MP2_PASS,   },
    { { "MP3",                "mp3",        "MP3 (libmp3lame)",            HB_ACODEC_LAME,        HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_MP3,        },
    { { "MP3 Passthru",       "copy:mp3",   "MP3 Passthru",                HB_ACODEC_MP3_PASS,    HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_MP3_PASS,   },
    { { "Vorbis",             "vorbis",     "Vorbis (libvorbis)",          HB_ACODEC_VORBIS,     HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_VORBIS,     },
    { { "FLAC 16-bit",        "flac16",     "FLAC 16-bit (libavcodec)",    HB_ACODEC_FFFLAC,                      HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_FLAC,       },
    { { "FLAC 24-bit",        "flac24",     "FLAC 24-bit (libavcodec)",    HB_ACODEC_FFFLAC24,                    HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_FLAC,       },
    { { "FLAC Passthru",      "copy:flac",  "FLAC Passthru",               HB_ACODEC_FLAC_PASS,                   HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_FLAC_PASS,  },
    { { "Opus",               "opus",       "Opus (libopus)",              HB_ACODEC_OPUS,       HB_MUX_MASK_MP4|HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_OPUS,       },
    { { "Opus Passthru",      "copy:opus",  "Opus Passthru",               HB_ACODEC_OPUS_PASS,  HB_MUX_MASK_MP4|HB_MUX_MASK_WEBM|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_OPUS_PASS,  },
    { { "Auto Passthru",      "copy",       "Auto Passthru",               HB_ACODEC_AUTO_PASS,   HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_ACODEC_AUTO_PASS,  },
};
int hb_audio_encoders_count = sizeof(hb_audio_encoders) / sizeof(hb_audio_encoders[0]);
static int hb_audio_encoder_is_enabled(int encoder)
{
    if (encoder & HB_ACODEC_PASS_FLAG)
    {
        // Passthru encoders are always enabled
        return 1;
    }
    switch (encoder)
    {
#ifdef __APPLE__
        case HB_ACODEC_CA_AAC:
        case HB_ACODEC_CA_HAAC:
            return 1;
#endif

#if HB_PROJECT_FEATURE_FFMPEG_AAC
        case HB_ACODEC_FFAAC:
            return avcodec_find_encoder_by_name("aac") != NULL;
#endif

        case HB_ACODEC_FDK_AAC:
        case HB_ACODEC_FDK_HAAC:
            return avcodec_find_encoder_by_name("libfdk_aac") != NULL;

        case HB_ACODEC_AC3:
            return avcodec_find_encoder(AV_CODEC_ID_AC3) != NULL;

        case HB_ACODEC_FFEAC3:
            return avcodec_find_encoder(AV_CODEC_ID_EAC3) != NULL;

        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
            return avcodec_find_encoder(AV_CODEC_ID_FLAC) != NULL;

        case HB_ACODEC_OPUS:
            return avcodec_find_encoder(AV_CODEC_ID_OPUS) != NULL;

        // the following encoders are always enabled
        case HB_ACODEC_LAME:
        case HB_ACODEC_VORBIS:
        case HB_ACODEC_NONE:
            return 1;

        default:
            return 0;
    }
}

typedef struct
{
    hb_container_t  item;
    hb_container_t *next;
    int enabled;
    int gid;
} hb_container_internal_t;
hb_container_t *hb_containers_first_item = NULL;
hb_container_t *hb_containers_last_item  = NULL;
hb_container_internal_t hb_containers[]  =
{
    // legacy muxers, back to HB 0.9.4 whenever possible (disabled)
    { { "M4V file",            "m4v",     NULL,                     "m4v",  0,              }, NULL, 0, HB_GID_MUX_MP4,  },
    { { "MP4 file",            "mp4",     NULL,                     "mp4",  0,              }, NULL, 0, HB_GID_MUX_MP4,  },
    { { "MKV file",            "mkv",     NULL,                     "mkv",  0,              }, NULL, 0, HB_GID_MUX_MKV,  },
    // actual muxers
    { { "MPEG-4 (avformat)",   "av_mp4",  "MPEG-4 (libavformat)",   "mp4",  HB_MUX_AV_MP4,  }, NULL, 1, HB_GID_MUX_MP4,  },
    { { "MPEG-4 (mp4v2)",      "mp4v2",   "MPEG-4 (libmp4v2)",      "mp4",  HB_MUX_MP4V2,   }, NULL, 1, HB_GID_MUX_MP4,  },
    { { "Matroska (avformat)", "av_mkv",  "Matroska (libavformat)", "mkv",  HB_MUX_AV_MKV,  }, NULL, 1, HB_GID_MUX_MKV,  },
    { { "Matroska (libmkv)",   "libmkv",  "Matroska (libmkv)",      "mkv",  HB_MUX_LIBMKV,  }, NULL, 1, HB_GID_MUX_MKV,  },
    { { "WebM (avformat)",     "av_webm", "WebM (libavformat)",     "webm", HB_MUX_AV_WEBM, }, NULL, 1, HB_GID_MUX_WEBM, },
};
int hb_containers_count = sizeof(hb_containers) / sizeof(hb_containers[0]);
static int hb_container_is_enabled(int format)
{
    switch (format)
    {
        case HB_MUX_AV_MP4:
        case HB_MUX_AV_MKV:
        case HB_MUX_AV_WEBM:
            return 1;

        default:
            return 0;
    }
}

void hb_common_global_init(int disable_hardware)
{
    static int common_init_done = 0;
    if (common_init_done)
        return;

    int i, j;

    // video framerates
    for (i = 0; i < hb_video_rates_count; i++)
    {
        if (hb_video_rates[i].enabled)
        {
            if (hb_video_rates_first_item == NULL)
            {
                hb_video_rates_first_item = &hb_video_rates[i].item;
            }
            else
            {
                ((hb_rate_internal_t*)hb_video_rates_last_item)->next =
                    &hb_video_rates[i].item;
            }
            hb_video_rates_last_item = &hb_video_rates[i].item;
        }
    }
    // fallbacks are static for now (no setup required)

    // audio samplerates
    for (i = 0; i < hb_audio_rates_count; i++)
    {
        if (hb_audio_rates[i].enabled)
        {
            if (hb_audio_rates_first_item == NULL)
            {
                hb_audio_rates_first_item = &hb_audio_rates[i].item;
            }
            else
            {
                ((hb_rate_internal_t*)hb_audio_rates_last_item)->next =
                    &hb_audio_rates[i].item;
            }
            hb_audio_rates_last_item = &hb_audio_rates[i].item;
        }
    }
    // fallbacks are static for now (no setup required)

    // audio bitrates
    for (i = 0; i < hb_audio_bitrates_count; i++)
    {
        if (hb_audio_bitrates[i].enabled)
        {
            if (hb_audio_bitrates_first_item == NULL)
            {
                hb_audio_bitrates_first_item = &hb_audio_bitrates[i].item;
            }
            else
            {
                ((hb_rate_internal_t*)hb_audio_bitrates_last_item)->next =
                    &hb_audio_bitrates[i].item;
            }
            hb_audio_bitrates_last_item = &hb_audio_bitrates[i].item;
        }
    }
    // fallbacks are static for now (no setup required)

    // audio dithers
    for (i = 0; i < hb_audio_dithers_count; i++)
    {
        if (hb_audio_dithers[i].enabled)
        {
            if (hb_audio_dithers_first_item == NULL)
            {
                hb_audio_dithers_first_item = &hb_audio_dithers[i].item;
            }
            else
            {
                ((hb_dither_internal_t*)hb_audio_dithers_last_item)->next =
                    &hb_audio_dithers[i].item;
            }
            hb_audio_dithers_last_item = &hb_audio_dithers[i].item;
        }
    }
    // fallbacks are static for now (no setup required)

    // audio mixdowns
    for (i = 0; i < hb_audio_mixdowns_count; i++)
    {
        if (hb_audio_mixdowns[i].enabled)
        {
            if (hb_audio_mixdowns_first_item == NULL)
            {
                hb_audio_mixdowns_first_item = &hb_audio_mixdowns[i].item;
            }
            else
            {
                ((hb_mixdown_internal_t*)hb_audio_mixdowns_last_item)->next =
                    &hb_audio_mixdowns[i].item;
            }
            hb_audio_mixdowns_last_item = &hb_audio_mixdowns[i].item;
        }
    }
    // fallbacks are static for now (no setup required)

    // video encoders
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (hb_video_encoders[i].enabled)
        {
            // we still need to check
            hb_video_encoders[i].enabled =
                hb_video_encoder_is_enabled(hb_video_encoders[i].item.codec, disable_hardware);
        }
        if (hb_video_encoders[i].enabled)
        {
            if (hb_video_encoders_first_item == NULL)
            {
                hb_video_encoders_first_item = &hb_video_encoders[i].item;
            }
            else
            {
                ((hb_encoder_internal_t*)hb_video_encoders_last_item)->next =
                    &hb_video_encoders[i].item;
            }
            hb_video_encoders_last_item = &hb_video_encoders[i].item;
        }
    }
    // setup fallbacks
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (!hb_video_encoders[i].enabled)
        {
            if ((hb_video_encoders[i].item.codec) &&
                (hb_video_encoder_is_enabled(hb_video_encoders[i].item.codec, disable_hardware)))
            {
                // we have a specific fallback and it's enabled
                continue;
            }
            for (j = 0; j < hb_video_encoders_count; j++)
            {
                if (hb_video_encoders[j].enabled &&
                    hb_video_encoders[j].gid == hb_video_encoders[i].gid)
                {
                    hb_video_encoders[i].item.codec = hb_video_encoders[j].item.codec;
                    break;
                }
            }
        }
    }

    // audio encoders
    for (i = 0; i < hb_audio_encoders_count; i++)
    {
        if (hb_audio_encoders[i].enabled)
        {
            // we still need to check
            hb_audio_encoders[i].enabled =
                hb_audio_encoder_is_enabled(hb_audio_encoders[i].item.codec);
        }
        if (hb_audio_encoders[i].enabled)
        {
            if (hb_audio_encoders_first_item == NULL)
            {
                hb_audio_encoders_first_item = &hb_audio_encoders[i].item;
            }
            else
            {
                ((hb_encoder_internal_t*)hb_audio_encoders_last_item)->next =
                    &hb_audio_encoders[i].item;
            }
            hb_audio_encoders_last_item = &hb_audio_encoders[i].item;
        }
    }
    // setup fallbacks
    for (i = 0; i < hb_audio_encoders_count; i++)
    {
        if (!hb_audio_encoders[i].enabled)
        {
            if ((hb_audio_encoders[i].item.codec & HB_ACODEC_MASK) &&
                (hb_audio_encoder_is_enabled(hb_audio_encoders[i].item.codec)))
            {
                // we have a specific fallback and it's enabled
                continue;
            }
            for (j = 0; j < hb_audio_encoders_count; j++)
            {
                if (hb_audio_encoders[j].enabled &&
                    hb_audio_encoders[j].gid == hb_audio_encoders[i].gid)
                {
                    hb_audio_encoders[i].item.codec = hb_audio_encoders[j].item.codec;
                    break;
                }
            }
            if (hb_audio_encoders[i].gid == HB_GID_ACODEC_AAC_HE)
            {
                // try to find an AAC fallback if no HE-AAC encoder is available
                for (j = 0; j < hb_audio_encoders_count; j++)
                {
                    if (hb_audio_encoders[j].enabled &&
                        hb_audio_encoders[j].gid == HB_GID_ACODEC_AAC)
                    {
                        hb_audio_encoders[i].item.codec = hb_audio_encoders[j].item.codec;
                        break;
                    }
                }
            }
        }
    }

    // video containers
    for (i = 0; i < hb_containers_count; i++)
    {
        if (hb_containers[i].enabled)
        {
            // we still need to check
            hb_containers[i].enabled =
                hb_container_is_enabled(hb_containers[i].item.format);
        }
        if (hb_containers[i].enabled)
        {
            if (hb_containers_first_item == NULL)
            {
                hb_containers_first_item = &hb_containers[i].item;
            }
            else
            {
                ((hb_container_internal_t*)hb_containers_last_item)->next =
                    &hb_containers[i].item;
            }
            hb_containers_last_item = &hb_containers[i].item;
        }
    }
    // setup fallbacks
    for (i = 0; i < hb_containers_count; i++)
    {
        if (!hb_containers[i].enabled)
        {
            if ((hb_containers[i].item.format & HB_MUX_MASK) &&
                (hb_container_is_enabled(hb_containers[i].item.format)))
            {
                // we have a specific fallback and it's enabled
                continue;
            }
            for (j = 0; j < hb_containers_count; j++)
            {
                if (hb_containers[j].enabled &&
                    hb_containers[j].gid == hb_containers[i].gid)
                {
                    hb_containers[i].item.format = hb_containers[j].item.format;
                    break;
                }
            }
        }
    }

    // we're done, yay!
    common_init_done = 1;
}

int hb_video_framerate_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_video_rates_count; i++)
    {
        if (!strcasecmp(hb_video_rates[i].item.name, name))
        {
            return hb_video_rates[i].item.rate;
        }
    }

fail:
    return -1;
}

const char* hb_video_framerate_get_name(int framerate)
{
    if (framerate > hb_video_rates_first_item->rate ||
        framerate < hb_video_rates_last_item ->rate)
        goto fail;

    const hb_rate_t *video_framerate = NULL;
    while ((video_framerate = hb_video_framerate_get_next(video_framerate)) != NULL)
    {
        if (video_framerate->rate == framerate)
        {
            return video_framerate->name;
        }
    }

fail:
    return NULL;
}

const char* hb_video_framerate_sanitize_name(const char *name)
{
    return hb_video_framerate_get_name(hb_video_framerate_get_from_name(name));
}

void hb_video_framerate_get_limits(int *low, int *high, int *clock)
{
    *low   = hb_video_rate_min;
    *high  = hb_video_rate_max;
    *clock = hb_video_rate_clock;
}

const hb_rate_t* hb_video_framerate_get_next(const hb_rate_t *last)
{
    if (last == NULL)
    {
        return hb_video_rates_first_item;
    }
    return ((hb_rate_internal_t*)last)->next;
}

int hb_video_framerate_get_close(hb_rational_t *framerate, double thresh)
{
    double            fps_in;
    const hb_rate_t * rate = NULL;
    int               result = -1;
    double            closest = thresh;

    fps_in = (double)framerate->num / framerate->den;
    while ((rate = hb_video_framerate_get_next(rate)) != NULL)
    {
        double fps = (double)hb_video_rate_clock / rate->rate;
        if (ABS(fps - fps_in) < closest)
        {
            result = rate->rate;
            closest = ABS(fps - fps_in);
        }
    }
    return result;
}

int hb_audio_samplerate_is_supported(int samplerate, uint32_t codec)
{
    switch (codec)
    {
        case HB_ACODEC_AC3:
        case HB_ACODEC_FFEAC3:
        case HB_ACODEC_CA_HAAC:
            // ca_haac can't do samplerates < 32 kHz
            // libav's E-AC-3 encoder can't do samplerates < 32 kHz
            // AC-3 < 32 kHz suffers from poor hardware compatibility
            if (samplerate < 32000)
                return 0;
            else
                return 1;
        case HB_ACODEC_FDK_HAAC:
            // fdk_haac can't do samplerates < 16 kHz
            if (samplerate < 16000)
                return 0;
             else
                return 1;
        case HB_ACODEC_OPUS:
            switch (samplerate)
            {
                // Opus only supports samplerates 8kHz, 12kHz, 16kHz,
                // 24kHz, 48kHz
                case 8000:
                case 12000:
                case 16000:
                case 24000:
                case 48000:
                    return 1;
                default:
                    return 0;
            }
        default:
            return 1;
    }
}

int hb_audio_samplerate_get_sr_shift(int samplerate)
{
    /* sr_shift: 0 -> 48000, 44100, 32000 Hz
     *           1 -> 24000, 22050, 16000 Hz
     *           2 -> 12000, 11025,  8000 Hz
     *
     * also, since samplerates are sanitized downwards:
     *
     * (samplerate < 32000) implies (samplerate <= 24000)
     */
    return ((samplerate < 16000) ? 2 : (samplerate < 32000) ? 1 : 0);
}

int hb_audio_samplerate_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_audio_rates_count; i++)
    {
        if (!strcasecmp(hb_audio_rates[i].item.name, name))
        {
            return hb_audio_rates[i].item.rate;
        }
    }

    // maybe the samplerate was specified in Hz
    i = atoi(name);
    if (i >= hb_audio_rates_first_item->rate &&
        i <= hb_audio_rates_last_item ->rate)
    {
        return hb_audio_samplerate_find_closest(i, HB_ACODEC_INVALID);
    }

fail:
    return -1;
}

const char* hb_audio_samplerate_get_name(int samplerate)
{
    if (samplerate < hb_audio_rates_first_item->rate ||
        samplerate > hb_audio_rates_last_item ->rate)
        goto fail;

    const hb_rate_t *audio_samplerate = NULL;
    while ((audio_samplerate = hb_audio_samplerate_get_next(audio_samplerate)) != NULL)
    {
        if (audio_samplerate->rate == samplerate)
        {
            return audio_samplerate->name;
        }
    }

fail:
    return NULL;
}

const hb_rate_t* hb_audio_samplerate_get_next(const hb_rate_t *last)
{
    if (last == NULL)
    {
        return hb_audio_rates_first_item;
    }
    return ((hb_rate_internal_t*)last)->next;
}

const hb_rate_t* hb_audio_samplerate_get_next_for_codec(const hb_rate_t *last,
                                                        uint32_t codec)
{
    while ((last = hb_audio_samplerate_get_next(last)) != NULL)
        if (hb_audio_samplerate_is_supported(last->rate, codec))
            return last;

    // None found or end of list
    return NULL;
}

int hb_audio_samplerate_find_closest(int samplerate, uint32_t codec)
{
    const hb_rate_t * rate, * prev, * next;

    rate = prev = next = hb_audio_samplerate_get_next_for_codec(NULL, codec);

    if (rate == NULL)
    {
        // This codec doesn't support any samplerate
        return 0;
    }

    while (rate != NULL && next->rate < samplerate)
    {
        rate = hb_audio_samplerate_get_next_for_codec(rate, codec);
        if (rate != NULL)
        {
            prev = next;
            next = rate;
        }
    }

    int delta_prev = samplerate - prev->rate;
    int delta_next = next->rate - samplerate;
    if (delta_prev <= delta_next)
    {
        return prev->rate;
    }
    else
    {
        return next->rate;
    }
}

// Given an input bitrate, find closest match in the set of allowed bitrates
static int hb_audio_bitrate_find_closest(int bitrate)
{
    // Check if bitrate mode was disabled
    if (bitrate <= 0)
        return bitrate;

    int closest_bitrate            = hb_audio_bitrates_first_item->rate;
    const hb_rate_t *audio_bitrate = NULL;
    while ((audio_bitrate = hb_audio_bitrate_get_next(audio_bitrate)) != NULL)
    {
        if (bitrate == audio_bitrate->rate)
        {
            // valid bitrate
            closest_bitrate = audio_bitrate->rate;
            break;
        }
        if (bitrate > audio_bitrate->rate)
        {
            // bitrates are sanitized downwards
            closest_bitrate = audio_bitrate->rate;
        }
    }
    return closest_bitrate;
}

// Given an input bitrate, sanitize it.
// Check low and high limits and make sure it is in the set of allowed bitrates.
int hb_audio_bitrate_get_best(uint32_t codec, int bitrate, int samplerate,
                              int mixdown)
{
    int low, high;
    hb_audio_bitrate_get_limits(codec, samplerate, mixdown, &low, &high);
    if (bitrate > high)
        bitrate = high;
    if (bitrate < low)
        bitrate = low;
    return hb_audio_bitrate_find_closest(bitrate);
}

// Get the default bitrate for a given codec/samplerate/mixdown triplet.
int hb_audio_bitrate_get_default(uint32_t codec, int samplerate, int mixdown)
{
    if ((codec & HB_ACODEC_PASS_FLAG) || !(codec & HB_ACODEC_MASK))
        goto fail;

    int bitrate, nchannels, nlfe, sr_shift;
    /* full-bandwidth channels, sr_shift */
    nlfe      = hb_mixdown_get_low_freq_channel_count(mixdown);
    nchannels = hb_mixdown_get_discrete_channel_count(mixdown) - nlfe;
    sr_shift  = hb_audio_samplerate_get_sr_shift(samplerate);

    switch (codec)
    {
        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
            goto fail;

        // 96, 224, 640 Kbps
        case HB_ACODEC_AC3:
            bitrate = (nchannels * 128) - (32 * (nchannels < 5));
            break;

        // Our E-AC-3 encoder is pretty similar to our AC-3 encoder but it does
        // allow for higher bitrates, should some users want a bit more quality
        // at the expense of compression efficiency - still, let's remain
        // compatible with passthru over S/PDIF by default: 384, 768, 1536 Kbps
        case HB_ACODEC_FFEAC3:
            bitrate = (nchannels * 384) - (128 * (nchannels - 2) * (nchannels > 2));
            break;

        case HB_ACODEC_CA_HAAC:
        case HB_ACODEC_FDK_HAAC:
            bitrate = nchannels * 32;
            break;

        case HB_ACODEC_OPUS:
        {
            int coupled = mixdown_get_opus_coupled_stream_count(mixdown);
            int uncoupled = nchannels + nlfe - 2 * coupled;

            bitrate = coupled * 96 + uncoupled * 64;
        } break;

        default:
            bitrate = nchannels * 80;
            break;
    }
    // sample_rate adjustment
    bitrate >>= sr_shift;
    return hb_audio_bitrate_get_best(codec, bitrate, samplerate, mixdown);

fail:
    return -1;
}

/* Get the bitrate low and high limits for a codec/samplerate/mixdown triplet.
 *
 * Encoder    1.0 channel    2.0 channels    5.1 channels    6.1 channels    7.1 channels
 * --------------------------------------------------------------------------------------
 *
 * ffaac
 * -----
 * supported samplerates: 8 - 48 kHz
 * libavcodec/aacenc.c defines a maximum bitrate:
 * -> 6144 * samplerate / 1024 bps (per channel, incl. LFE).
 * But output bitrates don't go as high as the theoretical maximums:
 * 12 kHz        61  (72)       123 (144)
 * 24 kHz       121 (144)       242 (288)
 * 48 kHz       236 (288)       472 (576)
 * Also, ffaac isn't a great encoder, so you don't want to allow too low a bitrate.
 * Limits: minimum of  32 Kbps per channel
 *         maximum of 192 Kbps per channel at 32 kHz, adjusted for sr_shift
 *         maximum of 256 Kbps per channel at 44.1-48 kHz, adjusted for sr_shift
 *
 * vorbis
 * ------
 * supported samplerates: 8 - 48 kHz
 * lib/modes/setup_*.h provides a range of allowed bitrates for various configurations.
 * for each samplerate, the highest minimums and lowest maximums are:
 *  8 kHz        Minimum  8 Kbps, maximum  32 Kbps (per channel, incl. LFE).
 * 12 kHz        Minimum 14 Kbps, maximum  44 Kbps (per channel, incl. LFE).
 * 16 kHz        Minimum 16 Kbps, maximum  86 Kbps (per channel, incl. LFE).
 * 24 kHz        Minimum 22 Kbps, maximum  86 Kbps (per channel, incl. LFE).
 * 32 kHz        Minimum 26 Kbps, maximum 190 Kbps (per channel, incl. LFE).
 * 48 kHz        Minimum 28 Kbps, maximum 240 Kbps (per channel, incl. LFE).
 * Limits: minimum of 14/22/28 Kbps per channel (8-12, 16-24, 32-48 kHz)
 *         maximum of 32/86/190/240 Kbps per channel (8-12, 16-24, 32, 44.1-48 kHz)
 *
 * lame
 * ----
 * supported samplerates: 8 - 48 kHz
 * lame_init_params() allows the following bitrates:
 * 12 kHz        Minimum  8 Kbps, maximum  64 Kbps
 * 24 kHz        Minimum  8 Kbps, maximum 160 Kbps
 * 48 kHz        Minimum 32 Kbps, maximum 320 Kbps
 * Limits: minimum of 8/8/32 Kbps (8-12, 16-24, 32-48 kHz)
 *         maximum of 64/160/320 Kbps (8-12, 16-24, 32-48 kHz)
 *
 * ffac3
 * -----
 * supported samplerates: 32 - 48 kHz (< 32 kHz disabled for compatibility reasons)
 * Dolby's encoder has a min. of 224 Kbps for 5 full-bandwidth channels (5.0, 5.1)
 * The maximum AC3 bitrate is 640 Kbps
 * Limits: minimum of 224/5 Kbps per full-bandwidth channel, maximum of 640 Kbps
 *
 * ffeac3
 * ------
 * supported samplerates: 32 - 48 kHz (< 32 kHz not supported by libav encoder)
 * Dolby's encoder has a min. of 224 Kbps for 5 full-bandwidth channels (5.0, 5.1)
 * The maximum bitrate is 128 bits per sample per second
 * Limits: minimum of 224/5 Kbps per full-bandwidth channel
 *         maximum of 128/1000 * samplerate Kbps
 *
 * ca_aac
 * ------
 * supported samplerates: 8 - 48 kHz
 * Core Audio API provides a range of allowed bitrates:
 *  8 kHz         8 -  24        16 -  48        40 - 112        48 - 144        56 - 160
 * 12 kHz        12 -  32        24 -  64        64 - 160        72 - 192        96 - 224
 * 16 kHz        12 -  48        24 -  96        64 - 224        72 - 288        96 - 320
 * 24 kHz        16 -  64        32 - 128        80 - 320        96 - 384       112 - 448
 * 32 kHz        24 -  96        48 - 192       128 - 448       144 - 576       192 - 640
 * 48 kHz        32 - 256        64 - 320       160 - 768       192 - 960       224 - 960
 * Limits:
 *  8 kHz -> minimum of  8 Kbps and maximum of  24 Kbps per full-bandwidth channel
 * 12 kHz -> minimum of 12 Kbps and maximum of  32 Kbps per full-bandwidth channel
 * 16 kHz -> minimum of 12 Kbps and maximum of  48 Kbps per full-bandwidth channel
 * 24 kHz -> minimum of 16 Kbps and maximum of  64 Kbps per full-bandwidth channel
 * 32 kHz -> minimum of 24 Kbps and maximum of  96 Kbps per full-bandwidth channel
 * 48 kHz -> minimum of 32 Kbps and maximum of 160 Kbps per full-bandwidth channel
 * 48 kHz ->                        maximum of +96 Kbps for Mono
 * Note: encCoreAudioInit() will sanitize any mistake made here.
 *
 * ca_haac
 * -------
 * supported samplerates: 32 - 48 kHz
 * Core Audio API provides a range of allowed bitrates:
 * 32 kHz         12 - 40         24 - 80        64 - 192          N/A           96 - 256
 * 48 kHz         16 - 40         32 - 80        80 - 192          N/A          112 - 256
 * Limits: minimum of 12 Kbps per full-bandwidth channel (<= 32 kHz)
 *         minimum of 16 Kbps per full-bandwidth channel ( > 32 kHz)
 *         maximum of 40 Kbps per full-bandwidth channel
 * Note: encCoreAudioInit() will sanitize any mistake made here.
 *
 * fdk_aac
 * -------
 * supported samplerates: 8 - 48 kHz
 * libfdk limits the bitrate to the following values:
 *  8 kHz              48              96             240
 * 12 kHz              72             144             360
 * 16 kHz              96             192             480
 * 24 kHz             144             288             720
 * 32 kHz             192             384             960
 * 48 kHz             288             576            1440
 * Limits: minimum of samplerate * 2/3 Kbps per full-bandwidth channel (see ca_aac)
 *         maximum of samplerate * 6.0 Kbps per full-bandwidth channel
 *
 * fdk_haac
 * --------
 * supported samplerates: 16 - 48 kHz
 * libfdk limits the bitrate to the following values:
 * 16 kHz         8 -  48        16 -  96        45 - 199
 * 24 kHz         8 -  63        16 - 127        45 - 266
 * 32 kHz         8 -  63        16 - 127        45 - 266
 * 48 kHz        12 -  63        16 - 127        50 - 266
 * Limits: minimum of 12 Kbps per full-bandwidth channel  (<= 32 kHz) (see ca_haac)
 *         minimum of 16 Kbps per full-bandwidth channel  ( > 32 kHz) (see ca_haac)
 *         maximum of 48,  96 or 192 Kbps (1.0, 2.0, 5.1) (<= 16 kHz)
 *         maximum of 64, 128 or 256 Kbps (1.0, 2.0, 5.1) ( > 16 kHz)
 */
void hb_audio_bitrate_get_limits(uint32_t codec, int samplerate, int mixdown,
                                 int *low, int *high)
{
    /*
     * samplerate == 0 means "auto" (same as source) and the UIs know the source
     * samplerate -- except where there isn't a source (audio defaults panel);
     * but we have enough info to return the global bitrate limits for this
     * mixdown, since the first/last samplerate are known to us and non-zero.
     */
    if (samplerate == 0)
    {
        int dummy;
        hb_audio_bitrate_get_limits(codec, hb_audio_rates_first_item->rate, mixdown, low, &dummy);
        hb_audio_bitrate_get_limits(codec, hb_audio_rates_last_item->rate, mixdown, &dummy, high);
        return;
    }

    /* samplerate, sr_shift */
    int sr_shift;
    samplerate = hb_audio_samplerate_find_closest(samplerate, codec);
    sr_shift = hb_audio_samplerate_get_sr_shift(samplerate);

    /* LFE, full-bandwidth channels */
    int lfe_count, nchannels;
    lfe_count = hb_mixdown_get_low_freq_channel_count(mixdown);
    nchannels = hb_mixdown_get_discrete_channel_count(mixdown) - lfe_count;

    switch (codec)
    {
        // Bitrates don't apply to "lossless" audio
        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
            *low = *high = -1;
            return;

        case HB_ACODEC_AC3:
            *low  = 224 * nchannels / 5;
            *high = 640;
            break;

        case HB_ACODEC_FFEAC3:
            *low  = 224 * nchannels  /    5;
            *high = 128 * samplerate / 1000;
            break;

        case HB_ACODEC_CA_AAC:
        {
            switch (samplerate)
            {
                case 8000:
                    *low  = nchannels *  8;
                    *high = nchannels * 24;
                    break;

                case 11025:
                case 12000:
                    *low  = nchannels * 12;
                    *high = nchannels * 32;
                    break;

                case 16000:
                    *low  = nchannels * 12;
                    *high = nchannels * 48;
                    break;

                case 22050:
                case 24000:
                    *low  = nchannels * 16;
                    *high = nchannels * 64;
                    break;

                case 32000:
                    *low  = nchannels * 24;
                    *high = nchannels * 96;
                    break;

                case 44100:
                case 48000:
                default:
                    *low  = nchannels * 32;
                    *high = nchannels * (160 + (96 * (nchannels == 1)));
                    break;
            } break;
        }

        case HB_ACODEC_CA_HAAC:
            *low  = nchannels * (12 + (4 * (samplerate >= 44100)));
            *high = nchannels * 40;
            break;

        case HB_ACODEC_FDK_AAC:
            *low  = nchannels * samplerate * 2 / 3000;
            *high = nchannels * samplerate * 6 / 1000;
            break;

        case HB_ACODEC_FDK_HAAC:
            *low  = (nchannels * (12 + (4 * (samplerate >= 44100))));
            *high = (nchannels - (nchannels > 2)) * (48 +
                                                     (16 *
                                                      (samplerate >= 22050)));
            break;

        case HB_ACODEC_FFAAC:
            *low  = ((nchannels + lfe_count) * 32);
            *high = ((nchannels + lfe_count) *
                     ((192 + (64 * ((samplerate << sr_shift) >= 44100)))
                      >> sr_shift));
            break;

        case HB_ACODEC_LAME:
            *low  =  8 + (24 * (sr_shift < 1));
            *high = 64 + (96 * (sr_shift < 2)) + (160 * (sr_shift < 1));
            break;

        case HB_ACODEC_VORBIS:
            *low  = (nchannels + lfe_count) * (14 +
                                               (8 * (sr_shift < 2)) +
                                               (6 * (sr_shift < 1)));
            *high = (nchannels + lfe_count) * (32 +
                                               ( 54 * (sr_shift < 2)) +
                                               (104 * (sr_shift < 1)) +
                                               ( 50 * (samplerate >= 44100)));
            break;

        case HB_ACODEC_OPUS:
            *low  = (nchannels + lfe_count) * 6;
            *high = (nchannels + lfe_count) * 256;
            break;

        // Bitrates don't apply to passthrough audio, but may apply if we
        // fall back to an encoder when the source can't be passed through.
        default:
            *low  = hb_audio_bitrates_first_item->rate;
            *high = hb_audio_bitrates_last_item ->rate;
            break;
    }

    // sanitize max. bitrate
    if (*high < hb_audio_bitrates_first_item->rate)
        *high = hb_audio_bitrates_first_item->rate;
    if (*high > hb_audio_bitrates_last_item ->rate)
        *high = hb_audio_bitrates_last_item ->rate;
}

const hb_rate_t* hb_audio_bitrate_get_next(const hb_rate_t *last)
{
    if (last == NULL)
    {
        return hb_audio_bitrates_first_item;
    }
    return ((hb_rate_internal_t*)last)->next;
}

// Get limits and hints for the UIs.
//
// granularity sets the minimum step increments that should be used
// (it's ok to round up to some nice multiple if you like)
//
// direction says whether 'low' limit is highest or lowest
// quality (direction 0 == lowest value is worst quality)
void hb_video_quality_get_limits(uint32_t codec, float *low, float *high,
                                 float *granularity, int *direction)
{
#if HB_PROJECT_FEATURE_QSV
    if (codec & HB_VCODEC_QSV_MASK)
    {
        return hb_qsv_video_quality_get_limits(codec, low, high, granularity,
                                               direction);
    }
#endif

    switch (codec)
    {
        /*
         * H.264/H.265: *low
         * = 51 - (QP_MAX_SPEC)
         * = 51 - (51 + QP_BD_OFFSET)
         * =  0 - (QP_BD_OFFSET)
         * =  0 - (6*(BIT_DEPTH-8))     (libx264)
         * =  0 - (6*(X265_DEPTH-8))    (libx265)
         */
        case HB_VCODEC_X264_8BIT:
        case HB_VCODEC_X265_8BIT:
        case HB_VCODEC_FFMPEG_NVENC_H264:
        case HB_VCODEC_FFMPEG_NVENC_H265:
            *direction   = 1;
            *granularity = 0.1;
            *low         = 0.;
            *high        = 51.;
            break;
        case HB_VCODEC_X264_10BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
            *direction   = 1;
            *granularity = 0.1;
            *low         = -12.;
            *high        = 51.;
            break;
        case HB_VCODEC_X265_12BIT:
            *direction   = 1;
            *granularity = 0.1;
            *low         = -24.;
            *high        = 51.;
            break;
        case HB_VCODEC_X265_16BIT:
            *direction   = 1;
            *granularity = 0.1;
            *low         = -48.;
            *high        = 51.;
            break;

        case HB_VCODEC_THEORA:
            *direction   = 0;
            *granularity = 1.;
            *low         = 0.;
            *high        = 63.;
            break;

        case HB_VCODEC_FFMPEG_VP8:
        case HB_VCODEC_FFMPEG_VP9:
        case HB_VCODEC_FFMPEG_VP9_10BIT:
        case HB_VCODEC_FFMPEG_SVT_AV1:
        case HB_VCODEC_FFMPEG_SVT_AV1_10BIT:
            *direction   = 1;
            *granularity = 1.;
            *low         = 0.;
            *high        = 63.;
            break;

        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            *direction   = 0;
            *granularity = 1.;
            *low         = 0.;
            *high        = 100.;
            break;

        case HB_VCODEC_FFMPEG_MF_H264:
        case HB_VCODEC_FFMPEG_MF_H265:
            *direction   = 0;
            *granularity = 1;
            *low         = 0;
            *high        = 100;
            break;

        case HB_VCODEC_FFMPEG_MPEG2:
        case HB_VCODEC_FFMPEG_MPEG4:
        default:
            *direction   = 1;
            *granularity = 1.;
            *low         = 1.;
            *high        = 31.;
            break;
    }
}

const char* hb_video_quality_get_name(uint32_t codec)
{
#if HB_PROJECT_FEATURE_QSV
    if (codec & HB_VCODEC_QSV_MASK)
    {
        return hb_qsv_video_quality_get_name(codec);
    }
#endif

    switch (codec)
    {
        case HB_VCODEC_X264_8BIT:
        case HB_VCODEC_X264_10BIT:
        case HB_VCODEC_X265_8BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_X265_12BIT:
        case HB_VCODEC_X265_16BIT:
        case HB_VCODEC_FFMPEG_SVT_AV1:
        case HB_VCODEC_FFMPEG_SVT_AV1_10BIT:
            return "RF";

        case HB_VCODEC_FFMPEG_VP8:
        case HB_VCODEC_FFMPEG_VP9:
        case HB_VCODEC_FFMPEG_VP9_10BIT:
        case HB_VCODEC_FFMPEG_NVENC_H264:
        case HB_VCODEC_FFMPEG_NVENC_H265:
        case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
        case HB_VCODEC_FFMPEG_VCE_H264:
        case HB_VCODEC_FFMPEG_VCE_H265:
        case HB_VCODEC_FFMPEG_VCE_H265_10BIT:
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return "CQ";

        case HB_VCODEC_FFMPEG_MF_H264:
        case HB_VCODEC_FFMPEG_MF_H265:
            return "Quality";

        default:
            return "QP";
    }
}

int hb_video_quality_is_supported(uint32_t codec)
{
    switch (codec)
    {
#ifdef __APPLE__
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return hb_vt_is_constant_quality_available(codec);
#endif

        default:
            return 1;
    }
}

int hb_video_twopass_is_supported(uint32_t codec)
{
    switch (codec)
    {
#ifdef __APPLE__
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return hb_vt_is_two_pass_available(codec);
#endif

        case HB_VCODEC_FFMPEG_MF_H264:
        case HB_VCODEC_FFMPEG_MF_H265:
        case HB_VCODEC_FFMPEG_VCE_H264:
        case HB_VCODEC_FFMPEG_VCE_H265:
        case HB_VCODEC_FFMPEG_VCE_H265_10BIT:
        case HB_VCODEC_FFMPEG_NVENC_H264:
        case HB_VCODEC_FFMPEG_NVENC_H265:
        case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
        case HB_VCODEC_QSV_H264:
        case HB_VCODEC_QSV_H265:
        case HB_VCODEC_QSV_H265_10BIT:
        case HB_VCODEC_QSV_AV1:
        case HB_VCODEC_QSV_AV1_10BIT:
        case HB_VCODEC_FFMPEG_SVT_AV1:
        case HB_VCODEC_FFMPEG_SVT_AV1_10BIT:
            return 0;

        default:
            return 1;
    }
}

int hb_video_encoder_is_supported(int encoder)
{
    const hb_encoder_t *video_encoder = NULL;
    while ((video_encoder = hb_video_encoder_get_next(video_encoder)) != NULL)
    {
        if (video_encoder->codec == encoder)
        {
            return 1;
        }
    }
    return 0;
}

int hb_video_encoder_pix_fmt_is_supported(int encoder, int pix_fmt, const char *profile)
{
    const int *pix_fmts = hb_video_encoder_get_pix_fmts(encoder, profile);
    while (*pix_fmts != AV_PIX_FMT_NONE)
    {
        if (pix_fmt == *pix_fmts)
        {
            return 1;
        }
        pix_fmts++;
    }
    return 0;
}

int hb_video_encoder_get_depth(int encoder)
{
    switch (encoder)
    {
#if HB_PROJECT_FEATURE_QSV
        case HB_VCODEC_QSV_H265_10BIT:
        case HB_VCODEC_QSV_AV1_10BIT:
#endif
#ifdef __APPLE__
        case HB_VCODEC_VT_H265_10BIT:
#endif
        case HB_VCODEC_X264_10BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
        case HB_VCODEC_FFMPEG_VCE_H265_10BIT:
            return 10;
        case HB_VCODEC_X265_12BIT:
            return 12;
        case HB_VCODEC_X265_16BIT:
            return 16;
        default:
            return 8;
    }
}

const char* const* hb_video_encoder_get_presets(int encoder)
{
#if HB_PROJECT_FEATURE_QSV
    if (encoder & HB_VCODEC_QSV_MASK)
    {
        return hb_qsv_preset_get_names();
    }
#endif

    if (encoder & HB_VCODEC_FFMPEG_MASK)
    {
        return hb_av_preset_get_names(encoder);
    }

    switch (encoder)
    {
        case HB_VCODEC_X264_8BIT:
        case HB_VCODEC_X264_10BIT:
            return x264_preset_names;

#if HB_PROJECT_FEATURE_X265
        case HB_VCODEC_X265_8BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_X265_12BIT:
        case HB_VCODEC_X265_16BIT:
            return x265_preset_names;
#endif
#ifdef __APPLE__
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return hb_vt_preset_get_names(encoder);
#endif
        default:
            return NULL;
    }
}

const char* const* hb_video_encoder_get_tunes(int encoder)
{
    if (encoder & HB_VCODEC_FFMPEG_MASK)
    {
        return hb_av_tune_get_names(encoder);
    }

    switch (encoder)
    {
        case HB_VCODEC_X264_8BIT:
        case HB_VCODEC_X264_10BIT:
            return x264_tune_names;

#if HB_PROJECT_FEATURE_X265
        case HB_VCODEC_X265_8BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_X265_12BIT:
        case HB_VCODEC_X265_16BIT:
            return x265_tune_names;
#endif
        default:
            return NULL;
    }
}

const char* const* hb_video_encoder_get_profiles(int encoder)
{
#if HB_PROJECT_FEATURE_QSV
    if (encoder & HB_VCODEC_QSV_MASK)
    {
        return hb_qsv_profile_get_names(encoder);
    }
#endif

    switch (encoder)
    {
        case HB_VCODEC_X264_8BIT:
            return hb_x264_profile_names_8bit;
        case HB_VCODEC_X264_10BIT:
            return hb_x264_profile_names_10bit;

        case HB_VCODEC_X265_8BIT:
            return hb_x265_profile_names_8bit;
        case HB_VCODEC_X265_10BIT:
            return hb_x265_profile_names_10bit;
        case HB_VCODEC_X265_12BIT:
            return hb_x265_profile_names_12bit;
        case HB_VCODEC_X265_16BIT:
            return hb_h265_profile_names_16bit;

#if HB_PROJECT_FEATURE_VCE
        case HB_VCODEC_FFMPEG_VCE_H264:
            return hb_vce_h264_profile_names;
        case HB_VCODEC_FFMPEG_VCE_H265:
            return hb_vce_h265_profile_names;
        case HB_VCODEC_FFMPEG_VCE_H265_10BIT:
            return hb_vce_h265_10bit_profile_names;
#endif
#if __APPLE__
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return hb_vt_profile_get_names(encoder);
#endif
        case HB_VCODEC_FFMPEG_NVENC_H264:
        case HB_VCODEC_FFMPEG_NVENC_H265:
        case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
        case HB_VCODEC_FFMPEG_MF_H264:
        case HB_VCODEC_FFMPEG_MF_H265:
        case HB_VCODEC_FFMPEG_SVT_AV1:
        case HB_VCODEC_FFMPEG_SVT_AV1_10BIT:
            return hb_av_profile_get_names(encoder);
        default:
            return NULL;
    }
}

const char* const* hb_video_encoder_get_levels(int encoder)
{
#if HB_PROJECT_FEATURE_QSV
    if (encoder & HB_VCODEC_QSV_MASK)
    {
        return hb_qsv_level_get_names(encoder);
    }
#endif

    switch (encoder)
    {
        case HB_VCODEC_X264_8BIT:
        case HB_VCODEC_X264_10BIT:
        case HB_VCODEC_FFMPEG_NVENC_H264:
        case HB_VCODEC_FFMPEG_MF_H264:
            return hb_h264_level_names;

#if HB_PROJECT_FEATURE_VCE
     case HB_VCODEC_FFMPEG_VCE_H264:
            return hb_vce_h264_level_names; // Not quite the same as x264
#endif

        case HB_VCODEC_X265_8BIT:
        case HB_VCODEC_X265_10BIT:
        case HB_VCODEC_X265_12BIT:
        case HB_VCODEC_X265_16BIT:
        case HB_VCODEC_FFMPEG_NVENC_H265:
        case HB_VCODEC_FFMPEG_NVENC_H265_10BIT:
        case HB_VCODEC_FFMPEG_VCE_H265:
        case HB_VCODEC_FFMPEG_VCE_H265_10BIT:
        case HB_VCODEC_FFMPEG_MF_H265:
            return hb_h265_level_names;

#ifdef __APPLE__
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return hb_vt_level_get_names(encoder);
#endif

        case HB_VCODEC_FFMPEG_SVT_AV1:
        case HB_VCODEC_FFMPEG_SVT_AV1_10BIT:
            return hb_av1_level_names;

        default:
            return NULL;
    }
}

static const enum AVPixelFormat standard_pix_fmts[] =
{
    AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_422_pix_fmts[] =
{
    AV_PIX_FMT_YUV422P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_444_pix_fmts[] =
{
    AV_PIX_FMT_YUV444P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_10bit_pix_fmts[] =
{
    AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_422_10bit_pix_fmts[] =
{
    AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_444_10bit_pix_fmts[] =
{
    AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_12bit_pix_fmts[] =
{
    AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_422_12bit_pix_fmts[] =
{
    AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P, AV_PIX_FMT_NONE
};

static const enum AVPixelFormat standard_444_12bit_pix_fmts[] =
{
    AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P, AV_PIX_FMT_NONE
};

const int* hb_video_encoder_get_pix_fmts(int encoder, const char *profile)
{
#if HB_PROJECT_FEATURE_QSV
    if (encoder & HB_VCODEC_QSV_MASK)
    {
        return hb_qsv_get_pix_fmts(encoder);
    }
#endif

    if (encoder & HB_VCODEC_FFMPEG_MASK)
    {
        return hb_av_get_pix_fmts(encoder);
    }

    switch (encoder)
    {
        case HB_VCODEC_X264_8BIT:
        {
            if (profile && !strcasecmp(profile, "high422"))
            {
                return standard_422_pix_fmts;
            }
            else if (profile && !strcasecmp(profile, "high444"))
            {
                return standard_444_pix_fmts;
            }
            else
            {
                return standard_pix_fmts;
            }
        }
        case HB_VCODEC_X264_10BIT:
        {
            if (profile && !strcasecmp(profile, "high422"))
            {
                return standard_422_10bit_pix_fmts;
            }
            else if (profile && !strcasecmp(profile, "high444"))
            {
                return standard_444_10bit_pix_fmts;
            }
            else
            {
                return standard_10bit_pix_fmts;
            }
        }
#if HB_PROJECT_FEATURE_X265
        case HB_VCODEC_X265_8BIT:
        {
            if (profile && (!strcasecmp(profile, "main444-8") ||
                            !strcasecmp(profile, "main444-intra")))
            {
                return standard_444_pix_fmts;
            }
            else
            {
                return standard_pix_fmts;
            }
        }
        case HB_VCODEC_X265_10BIT:
        {
            if (profile && (!strcasecmp(profile, "main422-10") ||
                            !strcasecmp(profile, "main422-10-intra")))
            {
                return standard_422_10bit_pix_fmts;
            }
            else if (profile && (!strcasecmp(profile, "main444-10") ||
                                 !strcasecmp(profile, "main444-10-intra")))
            {
                return standard_444_10bit_pix_fmts;
            }
            else
            {
                return standard_10bit_pix_fmts;
            }
        }
        case HB_VCODEC_X265_12BIT:
        {
            if (profile && (!strcasecmp(profile, "main422-12") ||
                            !strcasecmp(profile, "main422-12-intra")))
            {
                return standard_422_12bit_pix_fmts;
            }
            else if (profile && (!strcasecmp(profile, "main444-12") ||
                                 !strcasecmp(profile, "main444-12-intra")))
            {
                return standard_444_12bit_pix_fmts;
            }

            else
            {
                return standard_12bit_pix_fmts;
            }
        }
#endif
#if __APPLE__
        case HB_VCODEC_VT_H264:
        case HB_VCODEC_VT_H265:
        case HB_VCODEC_VT_H265_10BIT:
            return hb_vt_get_pix_fmts(encoder);
#endif

        default:
            return standard_pix_fmts;
    }
}

// Get limits and hints for the UIs.
//
// granularity sets the minimum step increments that should be used
// (it's ok to round up to some nice multiple if you like)
//
// direction says whether 'low' limit is highest or lowest
// quality (direction 0 == lowest value is worst quality)
void hb_audio_quality_get_limits(uint32_t codec, float *low, float *high,
                                 float *granularity, int *direction)
{
    switch (codec)
    {
        case HB_ACODEC_FFAAC:
            *direction   = 0;
            *granularity = 1.;
            *low         = 1.;
            *high        = 5.;
            break;

        case HB_ACODEC_FDK_HAAC:
        case HB_ACODEC_FDK_AAC:
            *direction   = 0;
            *granularity = 1.;
            *low         = 1.;
            *high        = 5.;
            break;

        case HB_ACODEC_LAME:
            *direction   = 1;
            *granularity = 0.5;
            *low         = 0.;
            *high        = 10.;
            break;

        case HB_ACODEC_VORBIS:
            *direction   = 0;
            *granularity = 0.5;
            *low         = -2.;
            *high        = 10.;
            break;

        case HB_ACODEC_CA_AAC:
            *direction   = 0;
            *granularity = 9.;
            *low         = 1.;
            *high        = 127.;
            break;

        default:
            *direction   = 0;
            *granularity = 1.;
            *low = *high = HB_INVALID_AUDIO_QUALITY;
            break;
    }
}

float hb_audio_quality_get_best(uint32_t codec, float quality)
{
    int direction;
    float low, high, granularity;
    hb_audio_quality_get_limits(codec, &low, &high, &granularity, &direction);
    if (quality > high)
        quality = high;
    if (quality < low)
        quality = low;
    return quality;
}

float hb_audio_quality_get_default(uint32_t codec)
{
    switch (codec)
    {
        case HB_ACODEC_FFAAC:
            return 3.;

        case HB_ACODEC_FDK_HAAC:
        case HB_ACODEC_FDK_AAC:
            return 3.;

        case HB_ACODEC_LAME:
            return 2.;

        case HB_ACODEC_VORBIS:
            return 5.;

        case HB_ACODEC_CA_AAC:
            return 91.;

        default:
            return HB_INVALID_AUDIO_QUALITY;
    }
}

// Get limits and hints for the UIs.
//
// granularity sets the minimum step increments that should be used
// (it's ok to round up to some nice multiple if you like)
//
// direction says whether 'low' limit is highest or lowest
// compression level (direction 0 == lowest value is worst compression level)
void hb_audio_compression_get_limits(uint32_t codec, float *low, float *high,
                                     float *granularity, int *direction)
{
    switch (codec)
    {
        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
            *direction   = 0;
            *granularity = 1.;
            *high        = 12.;
            *low         = 0.;
            break;

        case HB_ACODEC_LAME:
            *direction   = 1;
            *granularity = 1.;
            *high        = 9.;
            *low         = 0.;
            break;

        case HB_ACODEC_OPUS:
            *direction   = 0;
            *granularity = 1.;
            *high        = 10.;
            *low         = 0.;
            break;

        default:
            *direction   = 0;
            *granularity = 1.;
            *low = *high = -1.;
            break;
    }
}

float hb_audio_compression_get_best(uint32_t codec, float compression)
{
    int direction;
    float low, high, granularity;
    hb_audio_compression_get_limits(codec, &low, &high, &granularity, &direction);
    if( compression > high )
        compression = high;
    if( compression < low )
        compression = low;
    return compression;
}

float hb_audio_compression_get_default(uint32_t codec)
{
    switch (codec)
    {
        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
            return 5.;

        case HB_ACODEC_LAME:
            return 2.;

        case HB_ACODEC_OPUS:
            return 10.;

        default:
            return -1.;
    }
}

int hb_audio_dither_get_default()
{
    // "auto"
    return hb_audio_dithers_first_item->method;
}

int hb_audio_dither_get_default_method()
{
    /*
     * input could be s16 (possibly already dithered) converted to flt, so
     * let's use a "low-risk" dither algorithm (standard triangular).
     */
    return SWR_DITHER_TRIANGULAR;
}

int hb_audio_dither_is_supported(uint32_t codec, int depth)
{
    // Since dithering is performed by swresample, all codecs are supported
    switch (codec)
    {
        case HB_ACODEC_FFFLAC:
            if (depth == 0 || depth > 16)
                return 1;
    }
    return 0;
}

int hb_audio_dither_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for ( i = 0; i < hb_audio_dithers_count; i++)
    {
        if (!strcasecmp(hb_audio_dithers[i].item.short_name,  name) ||
            !strcasecmp(hb_audio_dithers[i].item.description, name))
        {
            return hb_audio_dithers[i].item.method;
        }
    }

fail:
    return hb_audio_dither_get_default();
}

const char* hb_audio_dither_get_description(int method)
{
    if (method < hb_audio_dithers_first_item->method ||
        method > hb_audio_dithers_last_item ->method)
        goto fail;

    const hb_dither_t *audio_dither = NULL;
    while ((audio_dither = hb_audio_dither_get_next(audio_dither)) != NULL)
    {
        if (audio_dither->method == method)
        {
            return audio_dither->description;
        }
    }

fail:
    return NULL;
}

const hb_dither_t* hb_audio_dither_get_next(const hb_dither_t *last)
{
    if (last == NULL)
    {
        return hb_audio_dithers_first_item;
    }
    return ((hb_dither_internal_t*)last)->next;
}

static int mixdown_get_opus_coupled_stream_count(int mixdown)
{
    switch (mixdown)
    {
        case HB_AMIXDOWN_7POINT1:
        case HB_AMIXDOWN_6POINT1:
            return 3;

        case HB_AMIXDOWN_5POINT1:
            return 2;

        case HB_AMIXDOWN_MONO:
        case HB_AMIXDOWN_LEFT:
        case HB_AMIXDOWN_RIGHT:
            return 0;

        case HB_AMIXDOWN_NONE:
        case HB_INVALID_AMIXDOWN:
        case HB_AMIXDOWN_5_2_LFE:
            // The 5F/2R/LFE configuration is currently not supported by Opus,
            // so don't set coupled streams.
            return 0;

        default:
            return 1;
    }
}

int hb_mixdown_is_supported(int mixdown, uint32_t codec, uint64_t layout)
{
    return (hb_mixdown_has_codec_support(mixdown, codec) &&
            hb_mixdown_has_remix_support(mixdown, layout));
}

int hb_mixdown_has_codec_support(int mixdown, uint32_t codec)
{
    // Passthru, only "None" mixdown is supported
    if (codec & HB_ACODEC_PASS_FLAG)
        return (mixdown == HB_AMIXDOWN_NONE);

    // Not passthru, "None" mixdown never supported
    if (mixdown == HB_AMIXDOWN_NONE)
        return 0;

    switch (codec)
    {
        case HB_ACODEC_VORBIS:
        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
        case HB_ACODEC_OPUS:
        case HB_ACODEC_CA_AAC:
        case HB_ACODEC_CA_HAAC:
        case HB_ACODEC_FFAAC:
            return (mixdown <= HB_AMIXDOWN_7POINT1);

        case HB_ACODEC_LAME:
            return (mixdown <= HB_AMIXDOWN_DOLBYPLII);

        case HB_ACODEC_FDK_AAC:
        case HB_ACODEC_FDK_HAAC:
            return ((mixdown <= HB_AMIXDOWN_5POINT1) ||
                    (mixdown == HB_AMIXDOWN_7POINT1));

        default:
            return (mixdown <= HB_AMIXDOWN_5POINT1);
    }
}

int hb_mixdown_has_remix_support(int mixdown, uint64_t layout)
{
    /*
     * Where there isn't a source (e.g. audio defaults panel), we have no input
     * layout; assume remix support, as the mixdown will be sanitized later on.
     */
    if (!layout)
    {
        return 1;
    }
    switch (mixdown)
    {
        // stereo + front left/right of center
        case HB_AMIXDOWN_5_2_LFE:
            return ((layout & AV_CH_FRONT_LEFT_OF_CENTER) &&
                    (layout & AV_CH_FRONT_RIGHT_OF_CENTER) &&
                    (layout & AV_CH_LAYOUT_STEREO) == AV_CH_LAYOUT_STEREO);

        // 7.0 or better
        case HB_AMIXDOWN_7POINT1:
            return ((layout & AV_CH_LAYOUT_7POINT0) == AV_CH_LAYOUT_7POINT0);

        // 6.0 or better
        case HB_AMIXDOWN_6POINT1:
            return ((layout & AV_CH_LAYOUT_7POINT0) == AV_CH_LAYOUT_7POINT0 ||
                    (layout & AV_CH_LAYOUT_6POINT0) == AV_CH_LAYOUT_6POINT0 ||
                    (layout & AV_CH_LAYOUT_HEXAGONAL) == AV_CH_LAYOUT_HEXAGONAL);

        // stereo + either of front center, side or back left/right, back center
        case HB_AMIXDOWN_5POINT1:
            return ((layout & AV_CH_LAYOUT_2_1) == AV_CH_LAYOUT_2_1 ||
                    (layout & AV_CH_LAYOUT_2_2) == AV_CH_LAYOUT_2_2 ||
                    (layout & AV_CH_LAYOUT_QUAD) == AV_CH_LAYOUT_QUAD ||
                    (layout & AV_CH_LAYOUT_SURROUND) == AV_CH_LAYOUT_SURROUND);

        // stereo + either of side or back left/right, back center
        // also, allow Dolby Surround output if the input is already Dolby
        case HB_AMIXDOWN_DOLBY:
        case HB_AMIXDOWN_DOLBYPLII:
            return ((layout & AV_CH_LAYOUT_2_1) == AV_CH_LAYOUT_2_1 ||
                    (layout & AV_CH_LAYOUT_2_2) == AV_CH_LAYOUT_2_2 ||
                    (layout & AV_CH_LAYOUT_QUAD) == AV_CH_LAYOUT_QUAD ||
                    (layout == AV_CH_LAYOUT_STEREO_DOWNMIX &&
                     mixdown == HB_AMIXDOWN_DOLBY));

        // more than 1 channel
        case HB_AMIXDOWN_STEREO:
            return (hb_layout_get_discrete_channel_count(layout) > 1);

        // regular stereo (not Dolby)
        case HB_AMIXDOWN_LEFT:
        case HB_AMIXDOWN_RIGHT:
            return (layout & AV_CH_LAYOUT_STEREO);

        // mono remix always supported
        // HB_AMIXDOWN_NONE always supported (for Passthru)
        case HB_AMIXDOWN_MONO:
        case HB_AMIXDOWN_NONE:
            return 1;

        // unknown mixdown, should never happen
        default:
            return 0;
    }
}

int hb_mixdown_get_discrete_channel_count(int amixdown)
{
    switch (amixdown)
    {
        case HB_AMIXDOWN_5_2_LFE:
        case HB_AMIXDOWN_7POINT1:
            return 8;

        case HB_AMIXDOWN_6POINT1:
            return 7;

        case HB_AMIXDOWN_5POINT1:
            return 6;

        case HB_AMIXDOWN_MONO:
        case HB_AMIXDOWN_LEFT:
        case HB_AMIXDOWN_RIGHT:
            return 1;

        case HB_AMIXDOWN_NONE:
            return 0;

        default:
            return 2;
    }
}

int hb_mixdown_get_low_freq_channel_count(int amixdown)
{
    switch (amixdown)
    {
        case HB_AMIXDOWN_5POINT1:
        case HB_AMIXDOWN_6POINT1:
        case HB_AMIXDOWN_7POINT1:
        case HB_AMIXDOWN_5_2_LFE:
            return 1;

        default:
            return 0;
    }
}

int hb_mixdown_get_best(uint32_t codec, uint64_t layout, int mixdown)
{
    // Passthru, only "None" mixdown is supported
    if (codec & HB_ACODEC_PASS_FLAG)
        return HB_AMIXDOWN_NONE;

    int best_mixdown                  = HB_INVALID_AMIXDOWN;
    const hb_mixdown_t *audio_mixdown = hb_mixdown_get_next(NULL);
    // test all non-None mixdowns while the value is <= the requested mixdown
    // HB_INVALID_AMIXDOWN means the highest supported mixdown was requested
    while ((audio_mixdown = hb_mixdown_get_next(audio_mixdown)) != NULL)
    {
        if ((mixdown == HB_INVALID_AMIXDOWN || audio_mixdown->amixdown <= mixdown) &&
            (hb_mixdown_is_supported(audio_mixdown->amixdown, codec, layout)))
        {
            best_mixdown = audio_mixdown->amixdown;
        }
    }
    return best_mixdown;
}

int hb_mixdown_get_default(uint32_t codec, uint64_t layout)
{
    int mixdown;
    switch (codec)
    {
        // the FLAC encoder defaults to the best mixdown up to 7.1
        case HB_ACODEC_FFFLAC:
        case HB_ACODEC_FFFLAC24:
        case HB_ACODEC_OPUS:
        case HB_ACODEC_CA_AAC:
        case HB_ACODEC_CA_HAAC:
        case HB_ACODEC_FFAAC:
        case HB_ACODEC_FDK_AAC:
        case HB_ACODEC_FDK_HAAC:
            mixdown = HB_AMIXDOWN_7POINT1;
            break;

        // the (E-)AC-3 encoder defaults to the best mixdown up to 5.1
        case HB_ACODEC_AC3:
        case HB_ACODEC_FFEAC3:
            mixdown = HB_AMIXDOWN_5POINT1;
            break;

        // other encoders default to the best mixdown up to DPLII
        default:
            mixdown = HB_AMIXDOWN_DOLBYPLII;
            break;
    }

    // return the best available mixdown up to the selected default
    return hb_mixdown_get_best(codec, layout, mixdown);
}

hb_mixdown_t* hb_mixdown_get_from_mixdown(int mixdown)
{
    int i;
    for (i = 0; i < hb_audio_mixdowns_count; i++)
    {
        if (hb_audio_mixdowns[i].item.amixdown == mixdown)
        {
            return &hb_audio_mixdowns[i].item;
        }
    }

    return NULL;
}

int hb_mixdown_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_audio_mixdowns_count; i++)
    {
        if (!strcasecmp(hb_audio_mixdowns[i].item.name,       name) ||
            !strcasecmp(hb_audio_mixdowns[i].item.short_name, name))
        {
            return hb_audio_mixdowns[i].item.amixdown;
        }
    }

fail:
    return HB_INVALID_AMIXDOWN;
}

const char* hb_mixdown_get_name(int mixdown)
{
    if (mixdown < hb_audio_mixdowns_first_item->amixdown ||
        mixdown > hb_audio_mixdowns_last_item ->amixdown)
        goto fail;

    const hb_mixdown_t *audio_mixdown = NULL;
    while ((audio_mixdown = hb_mixdown_get_next(audio_mixdown)) != NULL)
    {
        if (audio_mixdown->amixdown == mixdown)
        {
            return audio_mixdown->name;
        }
    }

fail:
    return NULL;
}

const char* hb_mixdown_get_short_name(int mixdown)
{
    if (mixdown < hb_audio_mixdowns_first_item->amixdown ||
        mixdown > hb_audio_mixdowns_last_item ->amixdown)
        goto fail;

    const hb_mixdown_t *audio_mixdown = NULL;
    while ((audio_mixdown = hb_mixdown_get_next(audio_mixdown)) != NULL)
    {
        if (audio_mixdown->amixdown == mixdown)
        {
            return audio_mixdown->short_name;
        }
    }

fail:
    return NULL;
}

const char* hb_mixdown_sanitize_name(const char *name)
{
    return hb_mixdown_get_name(hb_mixdown_get_from_name(name));
}

const hb_mixdown_t* hb_mixdown_get_next(const hb_mixdown_t *last)
{
    if (last == NULL)
    {
        return hb_audio_mixdowns_first_item;
    }
    return ((hb_mixdown_internal_t*)last)->next;
}

void hb_layout_get_name(char *name, int size, int64_t layout)
{
    AVChannelLayout ch_layout = {0};
    av_channel_layout_from_mask(&ch_layout, layout);
    av_channel_layout_describe(&ch_layout, name, size);
    av_channel_layout_uninit(&ch_layout);
}

int hb_layout_get_discrete_channel_count(int64_t layout)
{
    int nb_channels = 0;
    AVChannelLayout ch_layout = {0};
    av_channel_layout_from_mask(&ch_layout, layout);
    nb_channels = ch_layout.nb_channels;
    av_channel_layout_uninit(&ch_layout);
    return nb_channels;
}

int hb_layout_get_low_freq_channel_count(int64_t layout)
{
    return !!(layout & AV_CH_LOW_FREQUENCY) +
           !!(layout & AV_CH_LOW_FREQUENCY_2);
}

int hb_video_encoder_get_default(int muxer)
{
    if (!(muxer & HB_MUX_MASK))
        goto fail;

    const hb_encoder_t *video_encoder = NULL;
    while ((video_encoder = hb_video_encoder_get_next(video_encoder)) != NULL)
    {
        if (video_encoder->muxers & muxer)
        {
            return video_encoder->codec;
        }
    }

fail:
    return HB_VCODEC_INVALID;
}

hb_encoder_t * hb_video_encoder_get_from_codec(int codec)
{
    int i;
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (hb_video_encoders[i].item.codec == codec)
        {
            return &hb_video_encoders[i].item;
        }
    }

    return NULL;
}

int hb_video_encoder_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (!strcasecmp(hb_video_encoders[i].item.name,       name) ||
            !strcasecmp(hb_video_encoders[i].item.short_name, name))
        {
            return hb_video_encoders[i].item.codec;
        }
    }

fail:
    return HB_VCODEC_INVALID;
}

const char* hb_video_encoder_get_name(int encoder)
{
    int i;
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (hb_video_encoders[i].item.codec == encoder && hb_video_encoders[i].deprecated == 0)
        {
            return hb_video_encoders[i].item.name;
        }
    }

    return NULL;
}

const char* hb_video_encoder_get_short_name(int encoder)
{
    int i;
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (hb_video_encoders[i].item.codec == encoder && hb_video_encoders[i].deprecated == 0)
        {
            return hb_video_encoders[i].item.short_name;
        }
    }

    return NULL;
}

const char* hb_video_encoder_get_long_name(int encoder)
{
    int i;
    for (i = 0; i < hb_video_encoders_count; i++)
    {
        if (hb_video_encoders[i].item.codec == encoder && hb_video_encoders[i].deprecated == 0)
        {
            return hb_video_encoders[i].item.long_name;
        }
    }

    return NULL;
}

const char* hb_video_encoder_sanitize_name(const char *name)
{
    return hb_video_encoder_get_name(hb_video_encoder_get_from_name(name));
}

const hb_encoder_t* hb_video_encoder_get_next(const hb_encoder_t *last)
{
    if (last == NULL)
    {
        return hb_video_encoders_first_item;
    }
    return ((hb_encoder_internal_t*)last)->next;
}

// for a valid passthru, return the matching encoder for that codec (if any),
// else return -1 (i.e. drop the track)
int hb_audio_encoder_get_fallback_for_passthru(int passthru)
{
    int gid;
    const hb_encoder_t *audio_encoder = NULL;
    switch (passthru)
    {
        case HB_ACODEC_AAC_PASS:
            gid = HB_GID_ACODEC_AAC;
            break;

        case HB_ACODEC_AC3_PASS:
            gid = HB_GID_ACODEC_AC3;
            break;

        case HB_ACODEC_EAC3_PASS:
            gid = HB_GID_ACODEC_EAC3;
            break;

        case HB_ACODEC_FLAC_PASS:
            gid = HB_GID_ACODEC_FLAC;
            break;

        case HB_ACODEC_MP3_PASS:
            gid = HB_GID_ACODEC_MP3;
            break;

        default:
            return HB_ACODEC_INVALID;
            break;
    }
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        if (((hb_encoder_internal_t*)audio_encoder)->gid == gid)
        {
            return audio_encoder->codec;
        }
    }

    // passthru tracks are often the second audio from the same source track
    // if we don't have an encoder matching the passthru codec, return INVALID
    // dropping the track, as well as ensuring that there is at least one
    // audio track in the output is then up to the UIs
    return HB_ACODEC_INVALID;
}

int hb_audio_encoder_get_default(int muxer)
{
    if (!(muxer & HB_MUX_MASK))
        goto fail;

    int codec                         = 0;
    const hb_encoder_t *audio_encoder = NULL;
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        // default encoder should not be passthru
        if ((audio_encoder->muxers & muxer) &&
            (audio_encoder->codec  & HB_ACODEC_PASS_FLAG) == 0)
        {
            codec = audio_encoder->codec;
            break;
        }
    }

    // Lame is better than our low-end AAC encoders
    // if the container is MKV, use the former
    // AAC is still used when the container is MP4 (for better compatibility)
    if (codec == HB_ACODEC_FFAAC && (muxer & HB_MUX_MASK_MKV) == muxer)
    {
        return HB_ACODEC_LAME;
    }
    else
    {
        return codec;
    }

fail:
    return HB_ACODEC_INVALID;
}

hb_encoder_t* hb_audio_encoder_get_from_codec(int codec)
{
    int i;
    for (i = 0; i < hb_audio_encoders_count; i++)
    {
        if (hb_audio_encoders[i].item.codec == codec)
        {
            return &hb_audio_encoders[i].item;
        }
    }

    return NULL;
}

int hb_audio_encoder_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_audio_encoders_count; i++)
    {
        if (!strcasecmp(hb_audio_encoders[i].item.name,       name) ||
            !strcasecmp(hb_audio_encoders[i].item.short_name, name))
        {
            return hb_audio_encoders[i].item.codec;
        }
    }

fail:
    return HB_ACODEC_INVALID;
}

const char* hb_audio_encoder_get_name(int encoder)
{
    if (!(encoder & HB_ACODEC_ANY))
        goto fail;

    const hb_encoder_t *audio_encoder = NULL;
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        if (audio_encoder->codec == encoder)
        {
            return audio_encoder->name;
        }
    }

fail:
    return NULL;
}

const char* hb_audio_encoder_get_short_name(int encoder)
{
    if (!(encoder & HB_ACODEC_ANY))
        goto fail;

    const hb_encoder_t *audio_encoder = NULL;
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        if (audio_encoder->codec == encoder)
        {
            return audio_encoder->short_name;
        }
    }

fail:
    return NULL;
}

const char* hb_audio_encoder_get_long_name(int encoder)
{
    if (!(encoder & HB_ACODEC_ANY))
        goto fail;

    const hb_encoder_t *audio_encoder = NULL;
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        if (audio_encoder->codec == encoder)
        {
            return audio_encoder->long_name;
        }
    }

fail:
    return NULL;
}

const char* hb_audio_encoder_sanitize_name(const char *name)
{
    return hb_audio_encoder_get_name(hb_audio_encoder_get_from_name(name));
}

const hb_encoder_t* hb_audio_encoder_get_next(const hb_encoder_t *last)
{
    if (last == NULL)
    {
        return  hb_audio_encoders_first_item;
    }
    return ((hb_encoder_internal_t*)last)->next;
}

void hb_autopassthru_apply_settings(hb_job_t *job)
{
    hb_audio_t *audio;
    int i, already_printed;
    for (i = already_printed = 0; i < hb_list_count(job->list_audio);)
    {
        audio = hb_list_item(job->list_audio, i);
        if (audio->config.out.codec == HB_ACODEC_AUTO_PASS)
        {
            if (!already_printed)
                hb_autopassthru_print_settings(job);
            already_printed = 1;
            audio->config.out.codec = hb_autopassthru_get_encoder(audio->config.in.codec,
                                                                  job->acodec_copy_mask,
                                                                  job->acodec_fallback,
                                                                  job->mux);
            if (audio->config.out.codec == HB_ACODEC_NONE ||
                audio->config.out.codec == HB_ACODEC_INVALID)
            {
                hb_log("Auto Passthru: passthru not possible and no valid fallback specified, dropping track %d",
                       audio->config.out.track );
                hb_list_rem(job->list_audio, audio);
                hb_audio_close(&audio);
                continue;
            }
            if (!(audio->config.out.codec & HB_ACODEC_PASS_FLAG))
            {
                hb_log("Auto Passthru: passthru not possible for track %d, using fallback",
                       audio->config.out.track);
                if (audio->config.out.mixdown <= 0)
                {
                    audio->config.out.mixdown =
                        hb_mixdown_get_default(audio->config.out.codec,
                                               audio->config.in.channel_layout);
                }
                else
                {
                    audio->config.out.mixdown =
                        hb_mixdown_get_best(audio->config.out.codec,
                                            audio->config.in.channel_layout,
                                            audio->config.out.mixdown);
                }
                if (audio->config.out.samplerate <= 0)
                    audio->config.out.samplerate = audio->config.in.samplerate;
                audio->config.out.samplerate =
                    hb_audio_samplerate_find_closest(
                        audio->config.out.samplerate, audio->config.out.codec);
                int quality_not_allowed =
                    hb_audio_quality_get_default(audio->config.out.codec)
                            == HB_INVALID_AUDIO_QUALITY;
                if (audio->config.out.bitrate > 0)
                {
                    // Use best bitrate
                    audio->config.out.bitrate =
                        hb_audio_bitrate_get_best(audio->config.out.codec,
                                                  audio->config.out.bitrate,
                                                  audio->config.out.samplerate,
                                                  audio->config.out.mixdown);
                }
                else if (quality_not_allowed ||
                         audio->config.out.quality != HB_INVALID_AUDIO_QUALITY)
                {
                    // Use default bitrate
                    audio->config.out.bitrate =
                        hb_audio_bitrate_get_default(audio->config.out.codec,
                                                 audio->config.out.samplerate,
                                                 audio->config.out.mixdown);
                }
                else
                {
                    // Use best quality
                    audio->config.out.quality =
                        hb_audio_quality_get_best(audio->config.out.codec,
                                                  audio->config.out.quality);
                }
                if (audio->config.out.compression_level < 0)
                {
                    audio->config.out.compression_level =
                        hb_audio_compression_get_default(
                                        audio->config.out.codec);
                }
                else
                {
                    audio->config.out.compression_level =
                        hb_audio_compression_get_best(audio->config.out.codec,
                                        audio->config.out.compression_level);
                }
            }
            else
            {
                const hb_encoder_t *audio_encoder = NULL;
                while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
                {
                    if (audio_encoder->codec == audio->config.out.codec)
                    {
                        hb_log("Auto Passthru: using %s for track %d",
                               audio_encoder->name,
                               audio->config.out.track);
                        break;
                    }
                }
            }
        }
        /* Adjust output track number, in case we removed one.
         * Output tracks sadly still need to be in sequential order.
         * Note: out.track starts at 1, i starts at 0 */
        audio->config.out.track = ++i;
    }
}

void hb_autopassthru_print_settings(hb_job_t *job)
{
    char *mask = NULL, *tmp;
    const char *fallback = NULL;
    const hb_encoder_t *audio_encoder = NULL;
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        if ((audio_encoder->codec &  HB_ACODEC_PASS_FLAG) &&
            (audio_encoder->codec != HB_ACODEC_AUTO_PASS) &&
            (audio_encoder->codec & (job->acodec_copy_mask &
                                     HB_ACODEC_PASS_MASK)))
        {
            if (mask != NULL)
            {
                tmp = hb_strncat_dup(mask, ", ", 2);
                if (tmp != NULL)
                {
                    free(mask);
                    mask = tmp;
                }
            }
            // passthru name without " Passthru"
            tmp = hb_strncat_dup(mask,  audio_encoder->name,
                                 strlen(audio_encoder->name) - 9);
            if (tmp != NULL)
            {
                free(mask);
                mask = tmp;
            }
        }
        else if ((audio_encoder->codec & HB_ACODEC_PASS_FLAG) == 0 &&
                 (audio_encoder->codec == job->acodec_fallback))
        {
            fallback = audio_encoder->name;
        }
    }
    if (mask == NULL)
        hb_log("Auto Passthru: no codecs allowed");
    else
        hb_log("Auto Passthru: allowed codecs are %s", mask);
    if (fallback == NULL)
        hb_log("Auto Passthru: no valid fallback specified");
    else
        hb_log("Auto Passthru: fallback is %s", fallback);
}

int hb_autopassthru_get_encoder(int in_codec, int copy_mask, int fallback,
                                int muxer)
{
    int out_codec_result_set = 0;
    int fallback_result_set  = 0;
    int out_codec_result = HB_ACODEC_INVALID;
    int fallback_result  = HB_ACODEC_INVALID;
    const hb_encoder_t *audio_encoder = NULL;
    int out_codec = (copy_mask & in_codec) | HB_ACODEC_PASS_FLAG;

    // sanitize fallback encoder and selected passthru
    // note: invalid fallbacks are caught in hb_autopassthru_apply_settings
    while ((audio_encoder = hb_audio_encoder_get_next(audio_encoder)) != NULL)
    {
        if (!out_codec_result_set && audio_encoder->codec == out_codec)
        {
            out_codec_result_set = 1;
            if (audio_encoder->muxers & muxer)
                out_codec_result = out_codec;
        }
        else if (!fallback_result_set && audio_encoder->codec == fallback)
        {
            fallback_result_set  = 1;
            if ((audio_encoder->muxers & muxer) || fallback == HB_ACODEC_NONE)
                fallback_result = fallback;
        }
        if (out_codec_result_set && fallback_result_set)
        {
            break;
        }
    }
    return (out_codec_result != HB_ACODEC_INVALID) ? out_codec_result :
                                                     fallback_result;
}

const char* hb_audio_decoder_get_name(int codec, int codec_param)
{
    if (codec & HB_ACODEC_FF_MASK)
    {
        const AVCodec *codec;

        codec = avcodec_find_decoder(codec_param);
        if (codec != NULL)
        {
            return codec->name;
        }
    }
    else
    {
        switch (codec)
        {
            case HB_ACODEC_LPCM:
                return "pcm_dvd";
            default:
                break;
        }
    }
    return "unknown";
}

hb_container_t* hb_container_get_from_format(int format)
{
    int i;
    for (i = 0; i < hb_containers_count; i++)
    {
        if (hb_containers[i].item.format == format)
        {
            return &hb_containers[i].item;
        }
    }

    return NULL;
}

int hb_container_get_from_name(const char *name)
{
    if (name == NULL || *name == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_containers_count; i++)
    {
        if (!strcasecmp(hb_containers[i].item.name,       name) ||
            !strcasecmp(hb_containers[i].item.short_name, name))
        {
            return hb_containers[i].item.format;
        }
    }

fail:
    return HB_MUX_INVALID;
}

int hb_container_get_from_extension(const char *extension)
{
    if (extension == NULL || *extension == '\0')
        goto fail;

    int i;
    for (i = 0; i < hb_containers_count; i++)
    {
        if (!strcasecmp(hb_containers[i].item.default_extension, extension))
        {
            return hb_containers[i].item.format;
        }
    }

fail:
    return HB_MUX_INVALID;
}

const char* hb_container_get_name(int format)
{
    if (!(format & HB_MUX_MASK))
        goto fail;

    const hb_container_t *container = NULL;
    while ((container = hb_container_get_next(container)) != NULL)
    {
        if (container->format == format)
        {
            return container->name;
        }
    }

fail:
    return NULL;
}

const char* hb_container_get_short_name(int format)
{
    if (!(format & HB_MUX_MASK))
        goto fail;

    const hb_container_t *container = NULL;
    while ((container = hb_container_get_next(container)) != NULL)
    {
        if (container->format == format)
        {
            return container->short_name;
        }
    }

fail:
    return NULL;
}

const char* hb_container_get_long_name(int format)
{
    if (!(format & HB_MUX_MASK))
        goto fail;

    const hb_container_t *container = NULL;
    while ((container = hb_container_get_next(container)) != NULL)
    {
        if (container->format == format)
        {
            return container->long_name;
        }
    }

fail:
    return NULL;
}

const char* hb_container_get_default_extension(int format)
{
    if (!(format & HB_MUX_MASK))
        goto fail;

    const hb_container_t *container = NULL;
    while ((container = hb_container_get_next(container)) != NULL)
    {
        if (container->format == format)
        {
            return container->default_extension;
        }
    }

fail:
    return NULL;
}

const char* hb_container_sanitize_name(const char *name)
{
    return hb_container_get_name(hb_container_get_from_name(name));
}

const hb_container_t* hb_container_get_next(const hb_container_t *last)
{
    if (last == NULL)
    {
        return  hb_containers_first_item;
    }
    return ((hb_container_internal_t*)last)->next;
}

/**********************************************************************
 * hb_reduce
 **********************************************************************
 * Given a numerator (num) and a denominator (den), reduce them to an
 * equivalent fraction and store the result in x and y.
 *********************************************************************/
void hb_reduce( int *x, int *y, int num, int den )
{
    // find the greatest common divisor of num & den by Euclid's algorithm
    int n = num, d = den;
    while ( d )
    {
        int 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 )
    {
        *x = num / n;
        *y = den / n;
    }
    else
    {
        *x = num;
        *y = den;
    }
}

void hb_limit_rational( int *x, int *y, int64_t num, int64_t den, int limit )
{
    hb_reduce64( &num, &den, num, den );
    if ( num < limit && den < limit )
    {
        *x = num;
        *y = den;
        return;
    }

    if ( num > den )
    {
        double div = (double)limit / num;
        num = limit;
        den *= div;
    }
    else
    {
        double div = (double)limit / den;
        den = limit;
        num *= div;
    }
    *x = num;
    *y = den;
}

/**********************************************************************
 * hb_reduce64
 **********************************************************************
 * Given a numerator (num) and a denominator (den), reduce them to an
 * equivalent fraction and store the result in x and y.
 *********************************************************************/
void hb_reduce64( int64_t *x, int64_t *y, int64_t num, int64_t den )
{
    // find the greatest common divisor of num & den by Euclid's algorithm
    int64_t n = num, d = den;
    while ( d )
    {
        int64_t 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 )
    {
        num /= n;
        den /= n;
    }

    *x = num;
    *y = den;

}

void hb_limit_rational64( int64_t *x, int64_t *y, int64_t num, int64_t den, int64_t limit )
{
    hb_reduce64( &num, &den, num, den );
    if ( num < limit && den < limit )
    {
        *x = num;
        *y = den;
        return;
    }

    if ( num > den )
    {
        double div = (double)limit / num;
        num = limit;
        den *= div;
    }
    else
    {
        double div = (double)limit / den;
        den = limit;
        num *= div;
    }
    *x = num;
    *y = den;
}

/**********************************************************************
 * hb_buffer_list implementation
 *********************************************************************/
void hb_buffer_list_append(hb_buffer_list_t *list, hb_buffer_t *buf)
{
    int count = 1;
    int size = 0;
    hb_buffer_t *end = buf;

    if (buf == NULL)
    {
        return;
    }

    // Input buffer may be a list of buffers, find the end.
    size += buf->size;
    while (end != NULL && end->next != NULL)
    {
        end = end->next;
        size += end->size;
        count++;
    }
    if (list->tail == NULL)
    {
        list->head = buf;
        list->tail = end;
    }
    else
    {
        list->tail->next = buf;
        list->tail = end;
    }
    list->count += count;
    list->size += size;
}

void hb_buffer_list_prepend(hb_buffer_list_t *list, hb_buffer_t *buf)
{
    int count = 1;
    int size = 0;
    hb_buffer_t *end = buf;

    if (buf == NULL)
    {
        return;
    }

    // Input buffer may be a list of buffers, find the end.
    size += buf->size;
    while (end != NULL && end->next != NULL)
    {
        end = end->next;
        size += end->size;
        count++;
    }
    if (list->tail == NULL)
    {
        list->head = buf;
        list->tail = end;
    }
    else
    {
        end->next = list->head;
        list->head = buf;
    }
    list->count += count;
    list->size += size;
}

hb_buffer_t* hb_buffer_list_rem_head(hb_buffer_list_t *list)
{
    if (list == NULL)
    {
        return NULL;
    }
    hb_buffer_t *head = list->head;
    if (list->head != NULL)
    {
        if (list->head == list->tail)
        {
            list->tail = NULL;
        }
        list->head = list->head->next;
        list->count--;
        list->size -= head->size;
    }
    if (head != NULL)
    {
        head->next = NULL;
    }
    return head;
}

hb_buffer_t* hb_buffer_list_rem_tail(hb_buffer_list_t *list)
{
    if (list == NULL)
    {
        return NULL;
    }
    hb_buffer_t *tail = list->tail;

    if (list->head == list->tail)
    {
        list->head = list->tail = NULL;
        list->count = 0;
        list->size = 0;
    }
    else if (list->tail != NULL)
    {
        hb_buffer_t *end = list->head;
        while (end->next != list->tail)
        {
            end = end->next;
        }
        end->next = NULL;
        list->tail = end;
        list->count--;
        list->size -= tail->size;
    }
    if (tail != NULL)
    {
        tail->next = NULL;
    }
    return tail;
}

hb_buffer_t* hb_buffer_list_rem(hb_buffer_list_t *list, hb_buffer_t * b)
{
    hb_buffer_t * a;

    if (list == NULL)
    {
        return NULL;
    }
    if (b == list->head)
    {
        return hb_buffer_list_rem_head(list);
    }
    a = list->head;
    while (a != NULL && a->next != b)
    {
        a = a->next;
    }
    if (a == NULL)
    {
        // Buffer is not in the list
        return NULL;
    }
    list->count--;
    list->size -= b->size;
    a->next = b->next;
    if (list->tail == b)
    {
        list->tail = a;
    }
    b->next = NULL;

    return b;
}

hb_buffer_t* hb_buffer_list_head(hb_buffer_list_t *list)
{
    if (list == NULL)
    {
        return NULL;
    }
    return list->head;
}

hb_buffer_t* hb_buffer_list_tail(hb_buffer_list_t *list)
{
    if (list == NULL)
    {
        return NULL;
    }
    return list->tail;
}

hb_buffer_t* hb_buffer_list_set(hb_buffer_list_t *list, hb_buffer_t *buf)
{
    int count = 0;
    int size = 0;

    if (list == NULL)
    {
        return NULL;
    }

    hb_buffer_t *head = list->head;
    hb_buffer_t *end = buf;
    if (end != NULL)
    {
        count++;
        size += end->size;
        while (end->next != NULL)
        {
            end = end->next;
            count++;
            size += end->size;
        }
    }
    list->head = buf;
    list->tail = end;
    list->count = count;
    list->size = size;
    return head;
}

hb_buffer_t* hb_buffer_list_clear(hb_buffer_list_t *list)
{
    if (list == NULL)
    {
        return NULL;
    }
    hb_buffer_t *head = list->head;
    list->head = list->tail = NULL;
    list->count = 0;
    list->size = 0;
    return head;
}

void hb_buffer_list_close(hb_buffer_list_t *list)
{
    hb_buffer_t *buf = hb_buffer_list_clear(list);
    hb_buffer_close(&buf);
}

int hb_buffer_list_count(hb_buffer_list_t *list)
{
    if (list == NULL) return 0;
    return list->count;
}

int hb_buffer_list_size(hb_buffer_list_t *list)
{
    return list->size;
}

/**********************************************************************
 * hb_list implementation
 **********************************************************************
 * Basic and slow, but enough for what we need
 *********************************************************************/

#define HB_LIST_DEFAULT_SIZE 20

struct hb_list_s
{
    /* Pointers to items in the list */
    void ** items;

    /* How many (void *) allocated in 'items' */
    int     items_alloc;

    /* How many valid pointers in 'items' */
    int     items_count;
};

/**********************************************************************
 * hb_list_init
 **********************************************************************
 * Allocates an empty list ready for HB_LIST_DEFAULT_SIZE items
 *********************************************************************/
hb_list_t * hb_list_init()
{
    hb_list_t * l;

    l              = calloc( sizeof( hb_list_t ), 1 );
    l->items       = calloc( HB_LIST_DEFAULT_SIZE * sizeof( void * ), 1 );
    l->items_alloc = HB_LIST_DEFAULT_SIZE;

    return l;
}

/**********************************************************************
 * hb_list_count
 **********************************************************************
 * Returns the number of items currently in the list
 *********************************************************************/
int hb_list_count( const hb_list_t * l )
{
    if (l == NULL) return 0;
    return l->items_count;
}

/**********************************************************************
 * hb_list_add
 **********************************************************************
 * Adds an item at the end of the list, making it bigger if necessary.
 * Can safely be called with a NULL pointer to add, it will be ignored.
 *********************************************************************/
void hb_list_add( hb_list_t * l, void * p )
{
    if( !p )
    {
        return;
    }

    if( l->items_count == l->items_alloc )
    {
        /* We need a bigger boat */
        l->items_alloc += HB_LIST_DEFAULT_SIZE;
        l->items        = realloc( l->items,
                                   l->items_alloc * sizeof( void * ) );
    }

    l->items[l->items_count] = p;
    (l->items_count)++;
}

/**********************************************************************
 * hb_list_insert
 **********************************************************************
 * Adds an item at the specified position in the list, making it bigger
 * if necessary.
 * Can safely be called with a NULL pointer to add, it will be ignored.
 *********************************************************************/
void hb_list_insert( hb_list_t * l, int pos, void * p )
{
    if( !p )
    {
        return;
    }

    if( l->items_count == l->items_alloc )
    {
        /* We need a bigger boat */
        l->items_alloc += HB_LIST_DEFAULT_SIZE;
        l->items        = realloc( l->items,
                                   l->items_alloc * sizeof( void * ) );
    }

    if ( l->items_count != pos )
    {
        /* Shift all items after it sizeof( void * ) bytes earlier */
        memmove( &l->items[pos+1], &l->items[pos],
                 ( l->items_count - pos ) * sizeof( void * ) );
    }


    l->items[pos] = p;
    (l->items_count)++;
}

/**********************************************************************
 * hb_list_rem
 **********************************************************************
 * Remove an item from the list. Bad things will happen if called
 * with a NULL pointer or if the item is not in the list.
 *********************************************************************/
void hb_list_rem( hb_list_t * l, void * p )
{
    int i;

    /* Find the item in the list */
    for( i = 0; i < l->items_count; i++ )
    {
        if( l->items[i] == p )
        {
            /* Shift all items after it sizeof( void * ) bytes earlier */
            memmove( &l->items[i], &l->items[i+1],
                     ( l->items_count - i - 1 ) * sizeof( void * ) );

            (l->items_count)--;
            break;
        }
    }
}

/**********************************************************************
 * hb_list_item
 **********************************************************************
 * Returns item at position i, or NULL if there are not that many
 * items in the list
 *********************************************************************/
void * hb_list_item( const hb_list_t * l, int i )
{
    if( l == NULL || i < 0 || i >= l->items_count )
    {
        return NULL;
    }

    return l->items[i];
}

/**********************************************************************
 * hb_list_bytes
 **********************************************************************
 * Assuming all items are of type hb_buffer_t, returns the total
 * number of bytes in the list
 *********************************************************************/
int hb_list_bytes( hb_list_t * l )
{
    hb_buffer_t * buf;
    int           ret;
    int           i;

    ret = 0;
    for( i = 0; i < hb_list_count( l ); i++ )
    {
        buf  = hb_list_item( l, i );
        ret += buf->size - buf->offset;
    }

    return ret;
}

/**********************************************************************
 * hb_list_seebytes
 **********************************************************************
 * Assuming all items are of type hb_buffer_t, copy <size> bytes from
 * the list to <dst>, keeping the list unmodified.
 *********************************************************************/
void hb_list_seebytes( hb_list_t * l, uint8_t * dst, int size )
{
    hb_buffer_t * buf;
    int           copied;
    int           copying;
    int           i;

    for( i = 0, copied = 0; copied < size; i++ )
    {
        buf     = hb_list_item( l, i );
        copying = MIN( buf->size - buf->offset, size - copied );
        memcpy( &dst[copied], &buf->data[buf->offset], copying );
        copied += copying;
    }
}

/**********************************************************************
 * hb_list_getbytes
 **********************************************************************
 * Assuming all items are of type hb_buffer_t, copy <size> bytes from
 * the list to <dst>. What's copied is removed from the list.
 * The variable pointed by <pts> is set to the PTS of the buffer the
 * first byte has been got from.
 * The variable pointed by <pos> is set to the position of that byte
 * in that buffer.
 *********************************************************************/
void hb_list_getbytes( hb_list_t * l, uint8_t * dst, int size,
                       uint64_t * pts, uint64_t * pos )
{
    hb_buffer_t * buf;
    int           copied;
    int           copying;
    uint8_t       has_pts;

    /* So we won't have to deal with NULL pointers */
     uint64_t dummy1, dummy2;

    if( !pts ) pts = &dummy1;
    if( !pos ) pos = &dummy2;

    for( copied = 0, has_pts = 0; copied < size;  )
    {
        buf     = hb_list_item( l, 0 );
        copying = MIN( buf->size - buf->offset, size - copied );
        memcpy( &dst[copied], &buf->data[buf->offset], copying );

        if( !has_pts )
        {
            *pts    = buf->s.start;
            *pos    = buf->offset;
            has_pts = 1;
        }

        buf->offset += copying;
        if( buf->offset >= buf->size )
        {
            hb_list_rem( l, buf );
            hb_buffer_close( &buf );
        }

        copied += copying;
    }
}

/**********************************************************************
 * hb_list_empty
 **********************************************************************
 * Assuming all items are of type hb_buffer_t, close them all and
 * close the list.
 *********************************************************************/
void hb_list_empty( hb_list_t ** _l )
{
    hb_list_t * l = *_l;
    hb_buffer_t * b;

    while( ( b = hb_list_item( l, 0 ) ) )
    {
        hb_list_rem( l, b );
        hb_buffer_close( &b );
    }

    hb_list_close( _l );
}

/**********************************************************************
 * hb_list_close
 **********************************************************************
 * Free memory allocated by hb_list_init. Does NOT free contents of
 * items still in the list.
 *********************************************************************/
void hb_list_close( hb_list_t ** _l )
{
    hb_list_t * l = *_l;

    if (l == NULL)
    {
        return;
    }

    free( l->items );
    free( l );

    *_l = NULL;
}

int global_verbosity_level; //Necessary for hb_deep_log
/**********************************************************************
 * hb_valog
 **********************************************************************
 * If verbose mode is one, print message with timestamp. Messages
 * longer than 180 characters are stripped ;p
 *********************************************************************/
void hb_valog( hb_debug_level_t level, const char * prefix, const char * log, va_list args)
{
    char      * string;
    time_t      _now;
    struct tm * now;
    char        preamble[362];

    if( global_verbosity_level < level )
    {
        /* Hiding message */
        return;
    }

    /* Get the time */
    _now = time( NULL );
    now  = localtime( &_now );
    if ( prefix && *prefix )
    {
        // limit the prefix length
        snprintf( preamble, 361, "[%02d:%02d:%02d] %s %s\n",
                 now->tm_hour, now->tm_min, now->tm_sec, prefix, log );
    }
    else
    {
        snprintf( preamble, 361, "[%02d:%02d:%02d] %s\n",
                  now->tm_hour, now->tm_min, now->tm_sec, log );
    }

    string = hb_strdup_vaprintf(preamble, args);

#ifdef SYS_MINGW
    wchar_t     *wstring; /* 360 chars + \n + \0 */
    int          len;

    len = strlen(string) + 1;
    wstring = malloc(2 * len);

    // Convert internal utf8 to "console output code page".
    //
    // This is just bizarre windows behavior.  You would expect that
    // printf would automatically convert a wide character string to
    // the current "console output code page" when using the "%ls" format
    // specifier.  But it doesn't... so we must do it.
    if (!MultiByteToWideChar(CP_UTF8, 0, string, -1, wstring, len))
    {
        free(string);
        free(wstring);
        return;
    }
    free(string);
    string = malloc(2 * len);
    if (!WideCharToMultiByte(GetConsoleOutputCP(), 0, wstring, -1, string, len,
                             NULL, NULL))
    {
        free(string);
        free(wstring);
        return;
    }
    free(wstring);
#endif

    /* Print it */
    fprintf( stderr, "%s", string );
    free(string);
}

/**********************************************************************
 * hb_log
 **********************************************************************
 * If verbose mode is one, print message with timestamp. Messages
 * longer than 180 characters are stripped ;p
 *********************************************************************/
void hb_log( char * log, ... )
{
    va_list     args;

    va_start( args, log );
    hb_valog( 0, NULL, log, args );
    va_end( args );
}

/**********************************************************************
 * hb_deep_log
 **********************************************************************
 * If verbose mode is >= level, print message with timestamp. Messages
 * longer than 360 characters are stripped ;p
 *********************************************************************/
void hb_deep_log( hb_debug_level_t level, char * log, ... )
{
    va_list     args;

    va_start( args, log );
    hb_valog( level, NULL, log, args );
    va_end( args );
}

/**********************************************************************
 * hb_error
 **********************************************************************
 * Using whatever output is available display this error.
 *********************************************************************/
void hb_error( char * log, ... )
{
    char        string[181]; /* 180 chars + \0 */
    char        rep_string[181];
    static char last_string[181];
    static int  last_error_count = 0;
    static uint64_t last_series_error_time = 0;
    static hb_lock_t *mutex = 0;
    va_list     args;
    uint64_t time_now;

    /* Convert the message to a string */
    va_start( args, log );
    vsnprintf( string, 180, log, args );
    va_end( args );

    if( !mutex )
    {
        mutex = hb_lock_init();
    }

    hb_lock( mutex );

    time_now = hb_get_date();

    if( strcmp( string, last_string) == 0 )
    {
        /*
         * The last error and this one are the same, don't log it
         * just count it instead, unless it was more than one second
         * ago.
         */
        last_error_count++;
        if( last_series_error_time + ( 1000 * 1 ) > time_now )
        {
            hb_unlock( mutex );
            return;
        }
    }

    /*
     * A new error, or the same one more than 10sec since the last one
     * did we have any of the same counted up?
     */
    if( last_error_count > 0 )
    {
        /*
         * Print out the last error to ensure context for the last
         * repeated message.
         */
        if( error_handler )
        {
            error_handler( last_string );
        } else {
            hb_log( "%s", last_string );
        }

        if( last_error_count > 1 )
        {
            /*
             * Only print out the repeat message for more than 2 of the
             * same, since we just printed out two of them already.
             */
            snprintf( rep_string, 180, "Last error repeated %d times",
                      last_error_count - 1 );

            if( error_handler )
            {
                error_handler( rep_string );
            } else {
                hb_log( "%s", rep_string );
            }
        }

        last_error_count = 0;
    }

    last_series_error_time = time_now;

    strcpy( last_string, string );

    /*
     * Got the error in a single string, send it off to be dispatched.
     */
    if( error_handler )
    {
        error_handler( string );
    } else {
        hb_log( "%s", string );
    }

    hb_unlock( mutex );
}

void hb_register_error_handler( hb_error_handler_t * handler )
{
    error_handler = handler;
}

static void hb_update_str( char **dst, const char *src )
{
    if ( dst )
    {
        free( *dst );
        *dst = NULL;
        if ( src )
        {
            *dst = strdup( src );
        }
    }
}

/**********************************************************************
 * hb_title_init
 **********************************************************************
 *
 *********************************************************************/
hb_title_t * hb_title_init( char * path, int index )
{
    hb_title_t * t;

    t = calloc( sizeof( hb_title_t ), 1 );

    t->index              = index;
    t->playlist           = -1;
    t->list_audio         = hb_list_init();
    t->list_chapter       = hb_list_init();
    t->list_subtitle      = hb_list_init();
    t->list_attachment    = hb_list_init();
    t->metadata           = hb_metadata_init();
    t->path               = strdup(path);
    // default to decoding mpeg2
    t->video_id           = 0xE0;
    t->video_codec        = WORK_DECAVCODECV;
    t->video_codec_param  = AV_CODEC_ID_MPEG2VIDEO;
    t->video_timebase.num = 1;
    t->video_timebase.den = 90000;
    t->angle_count        = 1;
    t->geometry.par.num   = 0;
    t->geometry.par.den   = 1;
    t->color_prim         = -1;
    t->color_transfer     = -1;
    t->color_matrix       = -1;

    return t;
}

/**********************************************************************
 * hb_title_close
 **********************************************************************
 *
 *********************************************************************/
void hb_title_close( hb_title_t ** _t )
{
    hb_title_t * t = *_t;
    hb_audio_t * audio;
    hb_chapter_t * chapter;
    hb_subtitle_t * subtitle;
    hb_attachment_t * attachment;

    while( ( chapter = hb_list_item( t->list_chapter, 0 ) ) )
    {
        hb_list_rem( t->list_chapter, chapter );
        hb_chapter_close( &chapter );
    }
    hb_list_close( &t->list_chapter );

    while( ( audio = hb_list_item( t->list_audio, 0 ) ) )
    {
        hb_list_rem( t->list_audio, audio );
        hb_audio_close( &audio );
    }
    hb_list_close( &t->list_audio );

    while( ( subtitle = hb_list_item( t->list_subtitle, 0 ) ) )
    {
        hb_list_rem( t->list_subtitle, subtitle );
        hb_subtitle_close( &subtitle );
    }
    hb_list_close( &t->list_subtitle );

    while( ( attachment = hb_list_item( t->list_attachment, 0 ) ) )
    {
        hb_list_rem( t->list_attachment, attachment );
        hb_attachment_close( &attachment );
    }
    hb_list_close( &t->list_attachment );

    hb_metadata_close( &t->metadata );

    free((char*)t->name);
    free((char*)t->path);
    free(t->video_codec_name);
    free(t->container_name);

    free( t );
    *_t = NULL;
}

static void job_setup(hb_job_t * job, hb_title_t * title)
{
    if ( job == NULL || title == NULL )
        return;

    job->title = title;

    /* Set defaults settings */
    job->chapter_start = 1;
    job->chapter_end   = hb_list_count( title->list_chapter );
    job->list_chapter = hb_chapter_list_copy( title->list_chapter );

    /* Autocrop by default. Gnark gnark */
    memcpy( job->crop, title->crop, 4 * sizeof( int ) );

    hb_geometry_t          srcGeo, resultGeo;
    hb_geometry_settings_t uiGeo;

    srcGeo = title->geometry;

    memset(&uiGeo, 0, sizeof(uiGeo));
    memcpy(uiGeo.crop, title->crop, 4 * sizeof( int ));
    uiGeo.geometry.width  = srcGeo.width  - uiGeo.crop[2] - uiGeo.crop[3];
    uiGeo.geometry.height = srcGeo.height - uiGeo.crop[0] - uiGeo.crop[1];
    uiGeo.mode = HB_ANAMORPHIC_NONE;
    uiGeo.keep = HB_KEEP_DISPLAY_ASPECT;

    hb_set_anamorphic_size2(&srcGeo, &uiGeo, &resultGeo);
    job->width  = resultGeo.width;
    job->height = resultGeo.height;
    job->par    = resultGeo.par;

    job->vcodec     = HB_VCODEC_FFMPEG_MPEG4;
    job->vquality   = HB_INVALID_VIDEO_QUALITY;
    job->vbitrate   = 1000;
    job->twopass    = 0;
    job->pass_id    = HB_PASS_ENCODE;
    job->vrate      = title->vrate;

    job->input_pix_fmt   = AV_PIX_FMT_YUV420P;
    job->output_pix_fmt  = AV_PIX_FMT_YUV420P;
    job->color_prim      = title->color_prim;
    job->color_transfer  = title->color_transfer;
    job->color_matrix    = title->color_matrix;
    job->color_range     = title->color_range;
    job->chroma_location = title->chroma_location;
    job->color_prim_override     = HB_COLR_PRI_UNDEF;
    job->color_transfer_override = HB_COLR_TRA_UNDEF;
    job->color_matrix_override   = HB_COLR_MAT_UNDEF;
    job->mastering      = title->mastering;
    job->coll           = title->coll;

    job->mux = HB_MUX_MP4;

    job->list_audio = hb_list_init();
    job->list_subtitle = hb_list_init();
    job->list_filter = hb_list_init();

    job->list_attachment = hb_attachment_list_copy( title->list_attachment );
    job->metadata = hb_metadata_copy( title->metadata );

#if HB_PROJECT_FEATURE_QSV
    job->qsv.ctx = NULL;
    if (!job->indepth_scan)
    {
        job->qsv.ctx = hb_qsv_context_init();
    }
    job->qsv.enc_info.is_init_done = 0;
    job->qsv.decode                = !!(title->video_decode_support &
                                        HB_DECODE_SUPPORT_QSV);
#endif

}

int hb_output_color_prim(hb_job_t * job)
{
    if (job->color_prim_override != HB_COLR_PRI_UNDEF)
        return job->color_prim_override;
    else
        return job->color_prim;
}

int hb_output_color_transfer(hb_job_t * job)
{
    if (job->color_transfer_override != HB_COLR_TRA_UNDEF)
        return job->color_transfer_override;
    else
        return job->color_transfer;
}

int hb_output_color_matrix(hb_job_t * job)
{
    if (job->color_matrix_override != HB_COLR_MAT_UNDEF)
        return job->color_matrix_override;
    else
        return job->color_matrix;
}

static void job_clean( hb_job_t * job )
{
    if (job)
    {
        hb_chapter_t *chapter;
        hb_audio_t *audio;
        hb_subtitle_t *subtitle;
        hb_filter_object_t *filter;
        hb_attachment_t *attachment;

        free((void*)job->json);
        job->json = NULL;
        free(job->encoder_preset);
        job->encoder_preset = NULL;
        free(job->encoder_tune);
        job->encoder_tune = NULL;
        free(job->encoder_options);
        job->encoder_options = NULL;
        free(job->encoder_profile);
        job->encoder_profile = NULL;
        free(job->encoder_level);
        job->encoder_level = NULL;
        free(job->file);
        job->file = NULL;

        // clean up chapter list
        while( ( chapter = hb_list_item( job->list_chapter, 0 ) ) )
        {
            hb_list_rem( job->list_chapter, chapter );
            hb_chapter_close( &chapter );
        }
        hb_list_close( &job->list_chapter );

        // clean up audio list
        while( ( audio = hb_list_item( job->list_audio, 0 ) ) )
        {
            hb_list_rem( job->list_audio, audio );
            hb_audio_close( &audio );
        }
        hb_list_close( &job->list_audio );

        // clean up subtitle list
        while( ( subtitle = hb_list_item( job->list_subtitle, 0 ) ) )
        {
            hb_list_rem( job->list_subtitle, subtitle );
            hb_subtitle_close( &subtitle );
        }
        hb_list_close( &job->list_subtitle );

        // clean up filter list
        while( ( filter = hb_list_item( job->list_filter, 0 ) ) )
        {
            hb_list_rem( job->list_filter, filter );
            hb_filter_close( &filter );
        }
        hb_list_close( &job->list_filter );

        // clean up attachment list
        while( ( attachment = hb_list_item( job->list_attachment, 0 ) ) )
        {
            hb_list_rem( job->list_attachment, attachment );
            hb_attachment_close( &attachment );
        }
        hb_list_close( &job->list_attachment );

        // clean up metadata
        hb_metadata_close( &job->metadata );
    }
}

hb_title_t * hb_find_title_by_index( hb_handle_t *h, int title_index )
{
    hb_title_set_t *title_set = hb_get_title_set( h );
    int ii;

    for (ii = 0; ii < hb_list_count(title_set->list_title); ii++)
    {
        hb_title_t *title = hb_list_item(title_set->list_title, ii);
        if (title_index == title->index)
        {
            return title;
        }
    }
    return NULL;
}

/*
 * Create a pristine job structure from a title
 * title_index is 1 based
 */
hb_job_t * hb_job_init_by_index( hb_handle_t * h, int title_index )
{
    hb_title_t * title = hb_find_title_by_index(h, title_index);
    if (title == NULL)
        return NULL;
    return hb_job_init(title);
}

hb_job_t * hb_job_init( hb_title_t * title )
{
    hb_job_t * job;

    if ( title == NULL )
        return NULL;

    job = calloc( sizeof( hb_job_t ), 1 );
    job_setup(job, title);

    return job;
}

/**********************************************************************
 * hb_job_close
 **********************************************************************
 *
 *********************************************************************/
void hb_job_close( hb_job_t ** _j )
{
    if (_j && *_j)
    {
        job_clean(*_j);
        free( *_j );
        _j = NULL;
    }
}

void hb_job_set_encoder_preset(hb_job_t *job, const char *preset)
{
    if (job != NULL)
    {
        if (preset == NULL || preset[0] == 0)
        {
            preset = NULL;
        }
        hb_update_str(&job->encoder_preset, preset);
    }
}

void hb_job_set_encoder_tune(hb_job_t *job, const char *tune)
{
    if (job != NULL)
    {
        if (tune == NULL || tune[0] == 0)
        {
            tune = NULL;
        }
        hb_update_str(&job->encoder_tune, tune);
    }
}

void hb_job_set_encoder_options(hb_job_t *job, const char *options)
{
    if (job != NULL)
    {
        if (options == NULL || options[0] == 0)
        {
            options = NULL;
        }
        hb_update_str(&job->encoder_options, options);
    }
}

void hb_job_set_encoder_profile(hb_job_t *job, const char *profile)
{
    if (job != NULL)
    {
        if (profile == NULL || profile[0] == 0)
        {
            profile = NULL;
        }
        hb_update_str(&job->encoder_profile, profile);
    }
}

void hb_job_set_encoder_level(hb_job_t *job, const char *level)
{
    if (job != NULL)
    {
        if (level == NULL || level[0] == 0)
        {
            level = NULL;
        }
        hb_update_str(&job->encoder_level, level);
    }
}

void hb_job_set_file(hb_job_t *job, const char *file)
{
    if (job != NULL)
    {
        hb_update_str(&job->file, file);
    }
}

hb_filter_object_t * hb_filter_copy( hb_filter_object_t * filter )
{
    if( filter == NULL )
        return NULL;

    hb_filter_object_t * filter_copy = malloc( sizeof( hb_filter_object_t ) );
    memcpy( filter_copy, filter, sizeof( hb_filter_object_t ) );
    if( filter->settings )
        filter_copy->settings = hb_value_dup(filter->settings);
    filter_copy->sub_filter = hb_filter_copy(filter->sub_filter);
    return filter_copy;
}

/**********************************************************************
 * hb_filter_list_copy
 **********************************************************************
 *
 *********************************************************************/
hb_list_t *hb_filter_list_copy(const hb_list_t *src)
{
    hb_list_t *list = hb_list_init();
    hb_filter_object_t *filter = NULL;
    int i;

    if( src )
    {
        for( i = 0; i < hb_list_count(src); i++ )
        {
            if( ( filter = hb_list_item( src, i ) ) )
            {
                hb_list_add( list, hb_filter_copy(filter) );
            }
        }
    }
    return list;
}

hb_dict_t * hb_filter_dict_find(const hb_value_array_t * list, int filter_id)
{
    hb_dict_t * filter = NULL;
    int ii;

    if (list == NULL)
    {
        return NULL;
    }
    for (ii = 0; ii < hb_value_array_len(list); ii++)
    {
        filter = hb_value_array_get(list, ii);
        if (hb_dict_get_int(filter, "ID") == filter_id)
        {
            return filter;
        }
    }

    return NULL;
}

hb_filter_object_t * hb_filter_find(const hb_list_t *list, int filter_id)
{
    hb_filter_object_t *filter = NULL;
    int ii;

    if (list == NULL)
    {
        return NULL;
    }
    for (ii = 0; ii < hb_list_count(list); ii++)
    {
        filter = hb_list_item(list, ii);
        if (filter->id == filter_id)
        {
            return filter;
        }
    }

    return NULL;
}

/**
 * Gets a filter object with the given type
 * @param filter_id The type of filter to get.
 * @returns The requested filter object.
 */
hb_filter_object_t * hb_filter_get( int filter_id )
{
    hb_filter_object_t * filter;
    switch( filter_id )
    {
        case HB_FILTER_DETELECINE:
            filter = &hb_filter_detelecine;
            break;

        case HB_FILTER_COMB_DETECT:
            filter = &hb_filter_comb_detect;
            break;

        case HB_FILTER_DECOMB:
            filter = &hb_filter_decomb;
            break;

        case HB_FILTER_YADIF:
            filter = &hb_filter_yadif;
            break;

        case HB_FILTER_BWDIF:
            filter = &hb_filter_bwdif;
            break;

        case HB_FILTER_COLORSPACE:
            filter = &hb_filter_colorspace;
            break;

        case HB_FILTER_VFR:
            filter = &hb_filter_vfr;
            break;

        case HB_FILTER_DEBLOCK:
            filter = &hb_filter_deblock;
            break;

        case HB_FILTER_DENOISE:
            filter = &hb_filter_denoise;
            break;

        case HB_FILTER_NLMEANS:
            filter = &hb_filter_nlmeans;
            break;

        case HB_FILTER_CHROMA_SMOOTH:
            filter = &hb_filter_chroma_smooth;
            break;

        case HB_FILTER_RENDER_SUB:
            filter = &hb_filter_render_sub;
            break;

        case HB_FILTER_CROP_SCALE:
            filter = &hb_filter_crop_scale;
            break;

        case HB_FILTER_LAPSHARP:
            filter = &hb_filter_lapsharp;
            break;

        case HB_FILTER_UNSHARP:
            filter = &hb_filter_unsharp;
            break;

        case HB_FILTER_AVFILTER:
            filter = &hb_filter_avfilter;
            break;

        case HB_FILTER_PAD:
            filter = &hb_filter_pad;
            break;

        case HB_FILTER_ROTATE:
            filter = &hb_filter_rotate;
            break;

        case HB_FILTER_GRAYSCALE:
            filter = &hb_filter_grayscale;
            break;

        case HB_FILTER_FORMAT:
            filter = &hb_filter_format;
            break;

#if HB_PROJECT_FEATURE_QSV
#if !HB_QSV_ONEVPL
        case HB_FILTER_QSV:
            filter = &hb_filter_qsv;
            break;

        case HB_FILTER_QSV_PRE:
            filter = &hb_filter_qsv_pre;
            break;

        case HB_FILTER_QSV_POST:
            filter = &hb_filter_qsv_post;
            break;
#endif
#endif

        case HB_FILTER_MT_FRAME:
            filter = &hb_filter_mt_frame;
            break;

        default:
            filter = NULL;
            break;
    }
    return filter;
}

hb_filter_object_t * hb_filter_init( int filter_id )
{
    switch (filter_id)
    {
        case HB_FILTER_UNSHARP:
        case HB_FILTER_LAPSHARP:
        case HB_FILTER_CHROMA_SMOOTH:
        {
            hb_filter_object_t * wrapper;

            wrapper = hb_filter_copy(hb_filter_get(HB_FILTER_MT_FRAME));
            wrapper->sub_filter = hb_filter_copy(hb_filter_get(filter_id));
            wrapper->id = filter_id;
            wrapper->name = wrapper->sub_filter->name;
            return wrapper;
        } break;

        default:
            return hb_filter_copy(hb_filter_get(filter_id));
    }
}

/**********************************************************************
 * hb_filter_close
 **********************************************************************
 *
 *********************************************************************/
void hb_filter_close( hb_filter_object_t ** _f )
{
    hb_filter_object_t * f = *_f;

    if (f == NULL)
    {
        return;
    }
    hb_filter_close(&f->sub_filter);
    hb_value_free(&f->settings);

    free( f );
    *_f = NULL;
}

/**********************************************************************
 * hb_filter_info_close
 **********************************************************************
 *
 *********************************************************************/
void hb_filter_info_close( hb_filter_info_t ** _fi )
{
    hb_filter_info_t * fi = *_fi;

    if (fi != NULL)
    {
        free(fi->human_readable_desc);
    }

    free( fi );
    *_fi = NULL;
}

static char * append_string(char * dst, const char * src)
{
    int dst_len = 0, src_len, len;

    if (src == NULL)
    {
        return dst;
    }

    src_len = len = strlen(src) + 1;
    if (dst != NULL)
    {
        dst_len = strlen(dst);
        len += dst_len;
    }
    char * tmp = realloc(dst, len);
    if (tmp == NULL)
    {
        // Failed to allocate required space
        return dst;
    }
    dst = tmp;
    memcpy(dst + dst_len, src, src_len);
    return dst;
}

static char * stringify_array(int filter_id, hb_value_array_t * array)
{
    char * result = strdup("");
    int    ii;
    int    len = hb_value_array_len(array);
    int    first = 1;

    if (hb_value_array_len(array) == 0)
    {
        return result;
    }
    for (ii = 0; ii < len; ii++)
    {
        hb_value_t * val = hb_value_array_get(array, ii);
        char       * str = hb_filter_settings_string(filter_id, val);
        if (str != NULL)
        {
            if (!first)
            {
                result = append_string(result, ",");
            }
            first = 0;
            if (hb_value_type(val) == HB_VALUE_TYPE_DICT)
            {
                result = append_string(result, str);
            }
            else if (hb_value_type(val) == HB_VALUE_TYPE_ARRAY)
            {
                result = append_string(result, "[");
                result = append_string(result, str);
                result = append_string(result, "]");
            }
            else
            {
                result = append_string(result, str);
            }
            free(str);
        }
    }

    return result;
}

static char * stringify_dict(int filter_id, hb_dict_t * dict)
{
    char            * result = strdup("");
    const char      * key;
    char           ** keys = NULL;
    hb_value_t      * val;
    hb_dict_iter_t    iter;
    int               first = 1;

    if (hb_dict_elements(dict) == 0)
    {
        return result;
    }
    // Check for dict containing rational value
    if (hb_dict_elements(dict) == 2)
    {
        hb_value_t *num_val = hb_dict_get(dict, "Num");
        hb_value_t *den_val = hb_dict_get(dict, "Den");
        if (num_val != NULL && den_val != NULL &&
            hb_value_type(num_val) == HB_VALUE_TYPE_INT &&
            hb_value_type(den_val) == HB_VALUE_TYPE_INT)
        {
            int num = hb_value_get_int(num_val);
            int den = hb_value_get_int(den_val);
            char * str = hb_strdup_printf("%d/%d", num, den);
            result = append_string(result, str);
            free(str);
            return result;
        }
    }
    hb_filter_object_t * filter = hb_filter_get(filter_id);
    if (filter != NULL)
    {
        keys = hb_filter_get_keys(filter_id);
        if (keys != NULL && keys[0] == NULL)
        {
            hb_str_vfree(keys);
            keys = NULL;
        }
    }

    int done, ii = 0;
    iter = hb_dict_iter_init(dict);
    if (keys == NULL)
    {
        done = !hb_dict_iter_next_ex(dict, &iter, &key, NULL);
    }
    else
    {
        done = (key = keys[ii]) == NULL;
    }
    while (!done)
    {
        val = hb_dict_get(dict, key);
        if (val != NULL)
        {
            if (!first)
            {
                result = append_string(result, ":");
            }
            first = 0;
            result = append_string(result, key);
            int elements = 1;

            if (hb_value_type(val) == HB_VALUE_TYPE_NULL)
            {
                elements = 0;
            }
            else if (hb_value_type(val) == HB_VALUE_TYPE_DICT)
            {
                elements = hb_dict_elements(val);
            }
            else if (hb_value_type(val) == HB_VALUE_TYPE_ARRAY)
            {
                elements = hb_value_array_len(val);
            }
            if (elements != 0)
            {
                char * str = hb_filter_settings_string(filter_id, val);
                if (str != NULL)
                {
                    result = append_string(result, "=");
                    if (hb_value_type(val) == HB_VALUE_TYPE_DICT)
                    {
                        result = append_string(result, "'");
                        result = append_string(result, str);
                        result = append_string(result, "'");
                    }
                    else if (hb_value_type(val) == HB_VALUE_TYPE_ARRAY)
                    {
                        result = append_string(result, "[");
                        result = append_string(result, str);
                        result = append_string(result, "]");
                    }
                    else
                    {
                        result = append_string(result, str);
                    }
                    free(str);
                }
            }
        }
        ii++;
        if (keys == NULL)
        {
            done = !hb_dict_iter_next_ex(dict, &iter, &key, NULL);
        }
        else
        {
            done = (key = keys[ii]) == NULL;
        }
    }
    hb_str_vfree(keys);

    return result;
}

char * hb_filter_settings_string(int filter_id, hb_value_t * value)
{
    if (value == NULL || hb_value_type(value) == HB_VALUE_TYPE_NULL)
    {
        return strdup("");
    }
    if (hb_value_type(value) == HB_VALUE_TYPE_DICT)
    {
        return stringify_dict(filter_id, value);
    }
    if (hb_value_type(value) == HB_VALUE_TYPE_ARRAY)
    {
        return stringify_array(filter_id, value);
    }
    return hb_value_get_string_xform(value);
}

char * hb_filter_settings_string_json(int filter_id, const char * json)
{
    hb_value_t * value  = hb_value_json(json);
    char       * result = hb_filter_settings_string(filter_id, value);
    hb_value_free(&value);

    return result;
}

hb_dict_t * hb_parse_filter_settings(const char * settings_str)
{
    hb_dict_t  * dict = hb_dict_init();
    char      ** settings_list = hb_str_vsplit(settings_str, ':');
    int          ii;

    for (ii = 0; settings_list[ii] != NULL; ii++)
    {
        char * key, * value;
        char ** settings_pair = hb_str_vsplit(settings_list[ii], '=');
        if (settings_pair[0] == NULL || settings_pair[1] == NULL)
        {
            // Parse error. Not key=value pair.
            hb_str_vfree(settings_list);
            hb_str_vfree(settings_pair);
            hb_value_free(&dict);
            hb_log("hb_parse_filter_settings: Error parsing (%s)",
                   settings_str);
            return NULL;
        }
        key   = settings_pair[0];
        value = settings_pair[1];

        int last = strlen(value) - 1;
        // Check if value was quoted dictionary and remove quotes
        // and parse the sub-dictionary.  This should only happen
        // for avfilter settings.
        if (last > 0 && value[0] == '\'' && value[last] == '\'')
        {
            char * str = strdup(value + 1);
            str[last - 1] = 0;
            hb_dict_t * sub_dict = hb_parse_filter_settings(str);
            free(str);
            if (sub_dict == NULL)
            {
                // Parse error. Not key=value pair.
                hb_str_vfree(settings_list);
                hb_str_vfree(settings_pair);
                hb_value_free(&dict);
                hb_log("hb_parse_filter_settings: Error parsing (%s)",
                       settings_str);
                return NULL;
            }
            hb_dict_case_set(dict, key, sub_dict);
        }
        // Check if value was quoted string and remove quotes
        else if (last > 0 && value[0] == '"' && value[last] == '"')
        {
            char * str = strdup(value + 1);
            str[last - 1] = 0;
            hb_dict_case_set(dict, key, hb_value_string(str));
            free(str);
        }
        else
        {
            hb_dict_case_set(dict, key, hb_value_string(value));
        }

        hb_str_vfree(settings_pair);
    }
    hb_str_vfree(settings_list);

    return dict;
}

char * hb_parse_filter_settings_json(const char * settings_str)
{
    hb_dict_t * dict = hb_parse_filter_settings(settings_str);
    char      * result = hb_value_get_json(dict);
    hb_value_free(&dict);

    return result;
}

/**********************************************************************
 * hb_chapter_copy
 **********************************************************************
 *
 *********************************************************************/
hb_chapter_t *hb_chapter_copy(const hb_chapter_t *src)
{
    hb_chapter_t *chap = NULL;

    if ( src )
    {
        chap = calloc( 1, sizeof(*chap) );
        memcpy( chap, src, sizeof(*chap) );
        if ( src->title )
        {
            chap->title = strdup( src->title );
        }
    }
    return chap;
}

/**********************************************************************
 * hb_chapter_list_copy
 **********************************************************************
 *
 *********************************************************************/
hb_list_t *hb_chapter_list_copy(const hb_list_t *src)
{
    hb_list_t *list = hb_list_init();
    hb_chapter_t *chapter = NULL;
    int i;

    if( src )
    {
        for( i = 0; i < hb_list_count(src); i++ )
        {
            if( ( chapter = hb_list_item( src, i ) ) )
            {
                hb_list_add( list, hb_chapter_copy(chapter) );
            }
        }
    }
    return list;
}

/**********************************************************************
 * hb_chapter_close
 **********************************************************************
 *
 *********************************************************************/
void hb_chapter_close(hb_chapter_t **chap)
{
    if ( chap && *chap )
    {
        free((*chap)->title);
        free(*chap);
        *chap = NULL;
    }
}

/**********************************************************************
 * hb_chapter_set_title
 **********************************************************************
 *
 *********************************************************************/
void hb_chapter_set_title( hb_chapter_t *chapter, const char *title )
{
    if ( chapter )
    {
        hb_update_str( &chapter->title, title );
    }
}

/**********************************************************************
 * hb_chapter_set_title_by_index
 **********************************************************************
 * Applies information from the given job to the official job instance.
 * @param job Handle to hb_job_t.
 * @param chapter The chapter to apply the name to (1-based).
 * @param title to apply.
 *********************************************************************/
void hb_chapter_set_title_by_index( hb_job_t * job, int chapter_index, const char * title )
{
    hb_chapter_t * chapter = hb_list_item( job->list_chapter, chapter_index - 1 );
    hb_chapter_set_title( chapter, title );
}

/**********************************************************************
 * hb_audio_copy
 **********************************************************************
 *
 *********************************************************************/
hb_audio_t *hb_audio_copy(const hb_audio_t *src)
{
    hb_audio_t *audio = NULL;

    if( src )
    {
        audio = calloc(1, sizeof(*audio));
        memcpy(audio, src, sizeof(*audio));
        if ( src->config.out.name )
        {
            audio->config.out.name = strdup(src->config.out.name);
        }
        if ( src->config.in.name )
        {
            audio->config.in.name = strdup(src->config.in.name);
        }
    }
    return audio;
}

/**********************************************************************
 * hb_audio_list_copy
 **********************************************************************
 *
 *********************************************************************/
hb_list_t *hb_audio_list_copy(const hb_list_t *src)
{
    hb_list_t *list = hb_list_init();
    hb_audio_t *audio = NULL;
    int i;

    if( src )
    {
        for( i = 0; i < hb_list_count(src); i++ )
        {
            if( ( audio = hb_list_item( src, i ) ) )
            {
                hb_list_add( list, hb_audio_copy(audio) );
            }
        }
    }
    return list;
}

/**********************************************************************
 * hb_audio_close
 **********************************************************************
 *
 *********************************************************************/
void hb_audio_close( hb_audio_t **audio )
{
    if ( audio && *audio )
    {
        free((char*)(*audio)->config.in.name);
        free((char*)(*audio)->config.out.name);
        free(*audio);
        *audio = NULL;
    }
}

/**********************************************************************
 * hb_audio_new
 **********************************************************************
 *
 *********************************************************************/
void hb_audio_config_init(hb_audio_config_t * audiocfg)
{
    /* Set read-only parameters to invalid values */
    audiocfg->in.codec = 0;
    audiocfg->in.codec_param = 0;
    audiocfg->in.reg_desc = 0;
    audiocfg->in.stream_type = 0;
    audiocfg->in.substream_type = 0;
    audiocfg->in.version = 0;
    audiocfg->in.flags = 0;
    audiocfg->in.mode = 0;
    audiocfg->in.samplerate = -1;
    audiocfg->in.sample_bit_depth = 0;
    audiocfg->in.samples_per_frame = -1;
    audiocfg->in.bitrate = -1;
    audiocfg->in.matrix_encoding = AV_MATRIX_ENCODING_NONE;
    audiocfg->in.channel_layout = 0;
    audiocfg->in.channel_map = NULL;
    audiocfg->lang.description[0] = 0;
    audiocfg->lang.simple[0] = 0;
    audiocfg->lang.iso639_2[0] = 0;

    /* Initialize some sensible defaults */
    audiocfg->in.track = audiocfg->out.track = 0;
    audiocfg->out.codec = hb_audio_encoder_get_default(HB_MUX_MP4); // default container
    audiocfg->out.samplerate = -1;
    audiocfg->out.samples_per_frame = -1;
    audiocfg->out.bitrate = -1;
    audiocfg->out.quality = HB_INVALID_AUDIO_QUALITY;
    audiocfg->out.compression_level = -1;
    audiocfg->out.mixdown = HB_INVALID_AMIXDOWN;
    audiocfg->out.dynamic_range_compression = 0;
    audiocfg->out.gain = 0;
    audiocfg->out.normalize_mix_level = 0;
    audiocfg->out.dither_method = hb_audio_dither_get_default();
    audiocfg->out.name = NULL;
}

/**********************************************************************
 * hb_audio_add
 **********************************************************************
 *
 *********************************************************************/
int hb_audio_add(const hb_job_t * job, const hb_audio_config_t * audiocfg)
{
    hb_title_t *title = job->title;
    hb_audio_t *audio;

    audio = hb_audio_copy( hb_list_item( title->list_audio, audiocfg->in.track ) );
    if( audio == NULL )
    {
        /* We fail! */
        return 0;
    }

    if( (audiocfg->in.bitrate != -1) && (audiocfg->in.codec != 0xDEADBEEF) )
    {
        /* This most likely means the client didn't call hb_audio_config_init
         * so bail. */
        hb_audio_close(&audio);
        return 0;
    }

    /* Set the job's "in track" to the value passed in audiocfg.
     * HandBrakeCLI assumes this value is preserved in the jobs
     * audio list, but in.track in the title's audio list is not
     * required to be the same. */
    // "track" in title->list_audio is an index into the source's tracks.
    // "track" in job->list_audio is an index into title->list_audio
    audio->config.in.track = audiocfg->in.track;

    /* Really shouldn't ignore the passed out track, but there is currently no
     * way to handle duplicates or out-of-order track numbers. */
    audio->config.out = audiocfg->out;
    audio->config.out.track = hb_list_count(job->list_audio) + 1;
    if (audiocfg->out.name && *audiocfg->out.name)
    {
        audio->config.out.name = strdup(audiocfg->out.name);
    }

    hb_list_add(job->list_audio, audio);
    return 1;
}

hb_audio_config_t * hb_list_audio_config_item(hb_list_t * list, int i)
{
    hb_audio_t *audio = NULL;

    if( (audio = hb_list_item(list, i)) )
        return &(audio->config);

    return NULL;
}

/**********************************************************************
 * hb_subtitle_copy
 **********************************************************************
 *
 *********************************************************************/
hb_subtitle_t *hb_subtitle_copy(const hb_subtitle_t *src)
{
    hb_subtitle_t *subtitle = NULL;

    if( src )
    {
        subtitle = calloc(1, sizeof(*subtitle));
        memcpy(subtitle, src, sizeof(*subtitle));
        if ( src->extradata )
        {
            subtitle->extradata = malloc( src->extradata_size );
            memcpy( subtitle->extradata, src->extradata, src->extradata_size );
        }
        if (src->name != NULL)
        {
            subtitle->name = strdup(src->name);
        }
        if (src->config.name != NULL)
        {
            subtitle->config.name = strdup(src->config.name);
        }
        if (src->config.src_filename)
        {
            subtitle->config.src_filename = strdup(src->config.src_filename);
        }
    }
    return subtitle;
}

/**********************************************************************
 * hb_subtitle_list_copy
 **********************************************************************
 *
 *********************************************************************/
hb_list_t *hb_subtitle_list_copy(const hb_list_t *src)
{
    hb_list_t *list = hb_list_init();
    hb_subtitle_t *subtitle = NULL;
    int i;

    if( src )
    {
        for( i = 0; i < hb_list_count(src); i++ )
        {
            if( ( subtitle = hb_list_item( src, i ) ) )
            {
                hb_list_add( list, hb_subtitle_copy(subtitle) );
            }
        }
    }
    return list;
}

/**********************************************************************
 * hb_subtitle_close
 **********************************************************************
 *
 *********************************************************************/
void hb_subtitle_close( hb_subtitle_t **_sub )
{
    hb_subtitle_t * sub = *_sub;
    if ( _sub && sub )
    {
        free((char*)sub->name);
        free((char*)sub->config.name);
        free((char*)sub->config.src_filename);
        free(sub->extradata);
        free(sub);
        *_sub = NULL;
    }
}

/**********************************************************************
 * hb_subtitle_add
 **********************************************************************
 *
 *********************************************************************/
int hb_subtitle_add_ssa_header(hb_subtitle_t *subtitle, const char *font,
                               int fs, int w, int h)
{
    // Free any pre-existing extradata
    free(subtitle->extradata);

    float shadow_size = fs / 36.0;
    float outline_size = fs / 30.0;

    // SRT subtitles are represented internally as SSA
    // Create an SSA header
    const char * ssa_header =
        "[Script Info]\r\n"
        "ScriptType: v4.00+\r\n"
        "Collisions: Normal\r\n"
        "PlayResX: %d\r\n"
        "PlayResY: %d\r\n"
        "Timer: 100.0\r\n"
        "WrapStyle: 0\r\n"
        "ScaledBorderAndShadow: yes\r\n"
        "\r\n"
        "[V4+ Styles]\r\n"
        "Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\r\n"
        "Style: Default,%s,%d,&H00FFFFFF,&H00FFFFFF,&H000F0F0F,&H000F0F0F,0,0,0,0,100,100,0,0.00,1,%.2f,%.2f,2,20,20,20,0\r\n";

    subtitle->extradata = (uint8_t*)hb_strdup_printf(ssa_header, w, h, font, fs, outline_size, shadow_size);
    if (subtitle->extradata == NULL)
    {
        hb_error("hb_subtitle_add_ssa_header: malloc failed");
        return 0;
    }
    subtitle->extradata_size = strlen((char*)subtitle->extradata) + 1;

    return 1;
}

int hb_subtitle_add(const hb_job_t * job, const hb_subtitle_config_t * subtitlecfg, int track)
{
    hb_title_t *title = job->title;
    hb_subtitle_t *subtitle;

    subtitle = hb_subtitle_copy( hb_list_item( title->list_subtitle, track ) );
    if( subtitle == NULL )
    {
        /* We fail! */
        return 0;
    }

    // "track" in title->list_audio is an index into the source's tracks.
    // "track" in job->list_audio is an index into title->list_audio
    subtitle->track = track;
    subtitle->config = *subtitlecfg;
    if (subtitlecfg->name != NULL && subtitlecfg->name[0] != 0)
    {
        subtitle->config.name = strdup(subtitlecfg->name);
    }
    else
    {
        subtitle->config.name = NULL;
    }
    subtitle->config.src_filename = NULL;
    subtitle->out_track = hb_list_count(job->list_subtitle) + 1;
    hb_list_add(job->list_subtitle, subtitle);
    return 1;
}

int hb_import_subtitle_add( const hb_job_t * job,
                const hb_subtitle_config_t * subtitlecfg,
                const char *lang_code, int source )
{
    hb_subtitle_t *subtitle;
    iso639_lang_t *lang = NULL;

    subtitle = calloc( 1, sizeof( *subtitle ) );
    if (subtitle == NULL)
    {
        hb_error("hb_srt_add: malloc failed");
        return 0;
    }

    subtitle->id           = (hb_list_count(job->list_subtitle) << 8) |
                             HB_SUBTITLE_IMPORT_TAG;
    subtitle->format       = TEXTSUB;
    subtitle->source       = source;
    subtitle->codec        = WORK_DECSRTSUB;
    subtitle->codec        = source == IMPORTSRT ? WORK_DECSRTSUB :
                                                   WORK_DECSSASUB;
    subtitle->codec_param  = source == IMPORTSRT ? AV_CODEC_ID_SUBRIP :
                                                   AV_CODEC_ID_ASS;
    subtitle->timebase.num = 1;
    subtitle->timebase.den = 90000;

    lang = lang_for_code2(lang_code);
    if (lang == NULL)
    {
        hb_log("hb_srt_add: unknown language code (%s)", lang_code);
        lang = lang_for_code2("und");
    }
    snprintf(subtitle->lang, sizeof(subtitle->lang), "%s [%s]",
             strlen(lang->native_name) ? lang->native_name : lang->eng_name,
             hb_subsource_name(subtitle->source));
    strcpy(subtitle->iso639_2, lang->iso639_2);

    subtitle->config = *subtitlecfg;
    if (subtitlecfg->name != NULL && subtitlecfg->name[0] != 0)
    {
        subtitle->config.name = strdup(subtitlecfg->name);
    }
    else
    {
        subtitle->config.name = NULL;
    }
    subtitle->config.src_filename = strdup(subtitlecfg->src_filename);
    hb_list_add(job->list_subtitle, subtitle);

    return 1;
}

int hb_srt_add( const hb_job_t * job,
                const hb_subtitle_config_t * subtitlecfg,
                const char *lang_code )
{
    return hb_import_subtitle_add(job, subtitlecfg, lang_code, IMPORTSRT);
}

int hb_subtitle_can_force( int source )
{
    return source == VOBSUB || source == PGSSUB;
}

int hb_subtitle_can_burn( int source )
{
    return source == VOBSUB    || source == PGSSUB    || source == SSASUB  ||
           source == CC608SUB  || source == UTF8SUB   || source == TX3GSUB ||
           source == IMPORTSRT || source == IMPORTSSA || source == DVBSUB;
}

int hb_subtitle_can_pass( int source, int mux )
{
    switch (mux)
    {
        case HB_MUX_AV_MKV:
            switch( source )
            {
                case DVBSUB:
                case PGSSUB:
                case VOBSUB:
                case SSASUB:
                case UTF8SUB:
                case TX3GSUB:
                case CC608SUB:
                case CC708SUB:
                case IMPORTSRT:
                case IMPORTSSA:
                    return 1;

                default:
                    return 0;
            } break;

        case HB_MUX_AV_MP4:
            switch( source )
            {
                case VOBSUB:
                case SSASUB:
                case UTF8SUB:
                case TX3GSUB:
                case CC608SUB:
                case CC708SUB:
                case IMPORTSRT:
                case IMPORTSSA:
                    return 1;

                default:
                    return 0;
            } break;

        // webm can't support subtitles unless they're burned.
        // there's ambiguity in the spec about WebVTT... TODO
        case HB_MUX_AV_WEBM:
            return 0;
        default:
            // Internal error. Should never get here.
            hb_error("internal error.  Bad mux %d\n", mux);
            return 0;
    }
}

int hb_audio_can_apply_drc(uint32_t codec, uint32_t codec_param, int encoder)
{
    if (encoder & HB_ACODEC_PASS_FLAG)
    {
        // can't apply DRC to passthrough audio
        return 0;
    }
    else if (codec & HB_ACODEC_FF_MASK)
    {
        return (codec_param == AV_CODEC_ID_AC3 ||
                codec_param == AV_CODEC_ID_EAC3);
    }
    else if (codec == HB_ACODEC_AC3)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}

int hb_audio_can_apply_drc2(hb_handle_t *h, int title_idx, int audio_idx, int encoder)
{
    hb_title_t *title = hb_find_title_by_index(h, title_idx);
    if (title == NULL)
        return 0;

    hb_audio_t *audio = hb_list_item(title->list_audio, audio_idx);
    if (audio == NULL)
        return 0;

    return hb_audio_can_apply_drc(audio->config.in.codec,
                                  audio->config.in.codec_param, encoder);
}

/**********************************************************************
 * hb_metadata_init
 **********************************************************************
 *
 *********************************************************************/
hb_metadata_t *hb_metadata_init()
{
    hb_metadata_t *metadata = calloc( 1, sizeof(*metadata) );
    if (metadata != NULL)
    {
        metadata->dict = hb_dict_init();
    }
    return metadata;
}

/**********************************************************************
 * hb_metadata_copy
 **********************************************************************
 *
 *********************************************************************/
hb_metadata_t *hb_metadata_copy( const hb_metadata_t *src )
{
    hb_metadata_t *metadata = NULL;

    if ( src )
    {
        metadata = calloc( 1, sizeof(*metadata) );
        if (src->dict != NULL)
        {
            metadata->dict = hb_value_dup(src->dict);
        }
        if ( src->list_coverart )
        {
            int ii;
            for ( ii = 0; ii < hb_list_count( src->list_coverart ); ii++ )
            {
                hb_coverart_t *art = hb_list_item( src->list_coverart, ii );
                hb_metadata_add_coverart(
                        metadata, art->data, art->size, art->type );
            }
        }
    }
    return metadata;
}

/**********************************************************************
 * hb_metadata_close
 **********************************************************************
 *
 *********************************************************************/
void hb_metadata_close( hb_metadata_t **_m )
{
    if ( _m && *_m )
    {
        hb_metadata_t *m = *_m;
        hb_coverart_t *art;

        if (m->dict != NULL)
        {
            hb_value_free(&m->dict);
        }

        if ( m->list_coverart )
        {
            while( ( art = hb_list_item( m->list_coverart, 0 ) ) )
            {
                hb_list_rem( m->list_coverart, art );
                free( art->data );
                free( art );
            }
            hb_list_close( &m->list_coverart );
        }

        free( m );
        *_m = NULL;
    }
}

/**********************************************************************
 * hb_metadata_set_*
 **********************************************************************
 *
 *********************************************************************/
void hb_update_meta_dict(hb_dict_t * dict, const char * key, const char * value)
{
    if (value != NULL)
    {
        hb_dict_set_string(dict, key, value);
    }
    else
    {
        hb_dict_remove(dict, key);
    }
}

void hb_metadata_add_coverart( hb_metadata_t *metadata, const uint8_t *data, int size, int type )
{
    if ( metadata )
    {
        if ( metadata->list_coverart == NULL )
        {
            metadata->list_coverart = hb_list_init();
        }
        hb_coverart_t *art = calloc( 1, sizeof(hb_coverart_t) );
        art->data = malloc( size );
        memcpy( art->data, data, size );
        art->size = size;
        art->type = type;
        hb_list_add( metadata->list_coverart, art );
    }
}

void hb_metadata_rem_coverart( hb_metadata_t *metadata, int idx )
{
    if ( metadata )
    {
        hb_coverart_t *art = hb_list_item( metadata->list_coverart, idx );
        if ( art )
        {
            hb_list_rem( metadata->list_coverart, art );
            free( art->data );
            free( art );
        }
    }
}

char * hb_strdup_vaprintf( const char * fmt, va_list args )
{
    int       len;
    int       size = 256;
    char    * str;
    char    * tmp;
    va_list   copy;

    str = malloc( size );
    if ( str == NULL )
        return NULL;

    while (1)
    {
        // vsnprintf modifies it's va_list.  Since we may need to do this
        // more than once, use a copy of the va_list.
        va_copy(copy, args);

        /* Try to print in the allocated space. */
        len = vsnprintf( str, size, fmt, copy );

        /* If that worked, return the string. */
        if ( len > -1 && len < size )
        {
            return str;
        }

        /* Else try again with more space. */
        if ( len > -1 )     /* glibc 2.1 */
            size = len + 1; /* precisely what is needed */
        else                /* glibc 2.0 */
            size *= 2;      /* twice the old size */
        tmp = realloc( str, size );
        if ( tmp == NULL )
        {
            free( str );
            return NULL;
        }
        else
            str = tmp;
    }

    return str;
}

char * hb_strdup_printf( const char * fmt, ... )
{
    char    * str;
    va_list   args;

    va_start( args, fmt );
    str = hb_strdup_vaprintf( fmt, args );
    va_end( args );

    return str;
}

char * hb_strncat_dup( const char * s1, const char * s2, size_t n )
{
    size_t len;
    char * str;

    len = 0;
    if( s1 )
        len += strlen( s1 );
    if( s2 )
        len += MAX( strlen( s2 ), n );
    if( !len )
        return NULL;

    str = malloc( len + 1 );
    if( !str )
        return NULL;

    if( s1 )
        strcpy( str, s1 );
    else
        strcpy( str, "" );

    if (s2)
    {
        strncat( str, s2, n );
    }

    return str;
}

/**********************************************************************
 * hb_attachment_copy
 **********************************************************************
 *
 *********************************************************************/
hb_attachment_t *hb_attachment_copy(const hb_attachment_t *src)
{
    hb_attachment_t *attachment = NULL;

    if( src )
    {
        attachment = calloc(1, sizeof(*attachment));
        memcpy(attachment, src, sizeof(*attachment));
        if ( src->name )
        {
            attachment->name = strdup( src->name );
        }
        if ( src->data )
        {
            attachment->data = malloc( src->size );
            memcpy( attachment->data, src->data, src->size );
        }
    }
    return attachment;
}

/**********************************************************************
 * hb_attachment_list_copy
 **********************************************************************
 *
 *********************************************************************/
hb_list_t *hb_attachment_list_copy(const hb_list_t *src)
{
    hb_list_t *list = hb_list_init();
    hb_attachment_t *attachment = NULL;
    int i;

    if( src )
    {
        for( i = 0; i < hb_list_count(src); i++ )
        {
            if( ( attachment = hb_list_item( src, i ) ) )
            {
                hb_list_add( list, hb_attachment_copy(attachment) );
            }
        }
    }
    return list;
}

/**********************************************************************
 * hb_attachment_close
 **********************************************************************
 *
 *********************************************************************/
void hb_attachment_close( hb_attachment_t **attachment )
{
    if ( attachment && *attachment )
    {
        free((*attachment)->data);
        free((*attachment)->name);
        free(*attachment);
        *attachment = NULL;
    }
}

/**********************************************************************
 * hb_yuv2rgb
 **********************************************************************
 * Converts a YCrCb pixel to an RGB pixel.
 *
 * This conversion is lossy (due to rounding and clamping).
 *
 * Algorithm:
 *   http://en.wikipedia.org/w/index.php?title=YCbCr&oldid=361987695#Technical_details
 *********************************************************************/
int hb_yuv2rgb(int yuv)
{
    double y, Cr, Cb;
    int r, g, b;

    y  = (yuv >> 16) & 0xff;
    Cr = (yuv >>  8) & 0xff;
    Cb = (yuv      ) & 0xff;

    r = 1.164 * (y - 16)                      + 1.596 * (Cr - 128);
    g = 1.164 * (y - 16) - 0.392 * (Cb - 128) - 0.813 * (Cr - 128);
    b = 1.164 * (y - 16) + 2.017 * (Cb - 128);

    r = (r < 0) ? 0 : r;
    g = (g < 0) ? 0 : g;
    b = (b < 0) ? 0 : b;

    r = (r > 255) ? 255 : r;
    g = (g > 255) ? 255 : g;
    b = (b > 255) ? 255 : b;

    return (r << 16) | (g << 8) | b;
}

/**********************************************************************
 * hb_rgb2yuv
 **********************************************************************
 * Converts an RGB pixel to a limited range Bt.601 YCrCb pixel.
 *
 * This conversion is lossy (due to rounding and clamping).
 *
 * Algorithm:
 *   http://en.wikipedia.org/w/index.php?title=YCbCr&oldid=361987695#Technical_details
 *********************************************************************/
int hb_rgb2yuv(int rgb)
{
    double r, g, b;
    int y, Cr, Cb;

    r = (rgb >> 16) & 0xff;
    g = (rgb >>  8) & 0xff;
    b = (rgb      ) & 0xff;

    y  =  16. + ( 0.257 * r) + (0.504 * g) + (0.098 * b);
    Cb = 128. + (-0.148 * r) - (0.291 * g) + (0.439 * b);
    Cr = 128. + ( 0.439 * r) - (0.368 * g) - (0.071 * b);

    y = (y < 0) ? 0 : y;
    Cb = (Cb < 0) ? 0 : Cb;
    Cr = (Cr < 0) ? 0 : Cr;

    y = (y > 255) ? 255 : y;
    Cb = (Cb > 255) ? 255 : Cb;
    Cr = (Cr > 255) ? 255 : Cr;

    return (y << 16) | (Cr << 8) | Cb;
}

int hb_rgb2yuv_bt709(int rgb)
{
    double r, g, b;
    int y, Cr, Cb;

    r = (rgb >> 16) & 0xff;
    g = (rgb >>  8) & 0xff;
    b = (rgb      ) & 0xff;

    y  =  16. + ( 0.1826 * r) + (0.6142 * g) + (0.0620 * b);
    Cb = 128. + (-0.1006 * r) - (0.3386 * g) + (0.4392 * b);
    Cr = 128. + ( 0.4392 * r) - (0.3986 * g) - (0.0403 * b);

    y = (y < 0) ? 0 : y;
    Cb = (Cb < 0) ? 0 : Cb;
    Cr = (Cr < 0) ? 0 : Cr;

    y = (y > 255) ? 255 : y;
    Cb = (Cb > 255) ? 255 : Cb;
    Cr = (Cr > 255) ? 255 : Cr;

    return (y << 16) | (Cr << 8) | Cb;
}

const char * hb_subsource_name( int source )
{
    switch (source)
    {
        case VOBSUB:
            return "VOBSUB";
        case IMPORTSRT:
            return "SRT";
        case CC608SUB:
            return "CC608";
        case CC708SUB:
            return "CC708";
        case UTF8SUB:
            return "UTF-8";
        case TX3GSUB:
            return "TX3G";
        case IMPORTSSA:
        case SSASUB:
            return "SSA";
        case PGSSUB:
            return "PGS";
        case DVBSUB:
            return "DVB";
        default:
            return "Unknown";
    }
}

void hb_hexdump( hb_debug_level_t level, const char * label, const uint8_t * data, int len )
{
    int ii;
    char line[80], ascii[19], *p;

    ascii[18] = 0;
    ascii[0] = '|';
    ascii[17] = '|';
    memset(&ascii[1], '.', 16);
    p = line;
    if( label )
        hb_deep_log(level, "++++ %s ++++", label);
    else
        hb_deep_log(level, "++++++++++++");
    for( ii = 0; ii < len; ii++ )
    {
        if( ( ii & 0x0f ) == 0x0f )
        {
            p += snprintf( p, sizeof(line), "%02x", data[ii] );
            hb_deep_log( level, "    %-50s%20s", line, ascii );
            memset(&ascii[1], '.', 16);
            p = line;
        }
        else if( ( ii & 0x07 ) == 0x07 )
        {
            p += snprintf( p, sizeof(line), "%02x  ", data[ii] );
        }
        else
        {
            p += snprintf( p, sizeof(line), "%02x ", data[ii] );
        }
        if( isgraph( data[ii] ) )
            ascii[(ii & 0x0f) + 1] = data[ii];
        else
            ascii[(ii & 0x0f) + 1] = '.';
    }
    if( p != line )
    {
        hb_deep_log( level, "    %-50s%20s", line, ascii );
    }
}

int hb_str_vlen(char **strv)
{
    int i;
    if (strv == NULL)
        return 0;
    for (i = 0; strv[i]; i++);
    return i;
}

static const char* strchr_quote(const char *pos, char c, char q)
{
    if (pos == NULL)
        return NULL;

    while (*pos != 0 && *pos != c)
    {
        if (*pos == q)
        {
            pos = strchr_quote(pos+1, q, 0);
            if (pos == NULL)
                return NULL;
            pos++;
        }
        else if (*pos == '\\' && *(pos+1) != 0)
            pos += 2;
        else
            pos++;
    }
    if (*pos != c)
        return NULL;
    return pos;
}

static char *strndup_quote(const char *str, char q, int len)
{
    if (str == NULL)
        return NULL;

    char * res;
    int str_len = strlen( str );
    int src = 0, dst = 0;
    res = malloc( len > str_len ? str_len + 1 : len + 1 );
    if ( res == NULL ) return res;

    while (str[src] != 0 && src < len)
    {
        if (str[src] == q)
            src++;
        else
            res[dst++] = str[src++];
    }
    res[dst] = '\0';
    return res;
}

char** hb_str_vsplit( const char *str, char delem )
{
    const char *  pos;
    const char *  end;
    char       ** ret;
    int           count, i;
    char          quote = '"';

    if (delem == '"')
    {
        quote = '\'';
    }
    if ( str == NULL || str[0] == 0 )
    {
        ret = malloc( sizeof(char*) );
        if ( ret == NULL ) return ret;
        *ret = NULL;
        return ret;
    }

    // Find number of elements in the string
    count = 1;
    pos = str;
    while ( ( pos = strchr_quote( pos, delem, quote ) ) != NULL )
    {
        count++;
        pos++;
    }

    ret = calloc( ( count + 1 ), sizeof(char*) );
    if ( ret == NULL ) return ret;

    pos = str;
    for ( i = 0; i < count - 1; i++ )
    {
        end = strchr_quote( pos, delem, quote );
        ret[i] = strndup_quote(pos, quote, end - pos);
        pos = end + 1;
    }
    ret[i] = strndup_quote(pos, quote, strlen(pos));

    return ret;
}

void hb_str_vfree( char **strv )
{
    int i;

    if (strv == NULL)
        return;

    for ( i = 0; strv[i]; i++ )
    {
        free( strv[i] );
    }
    free( strv );
}

hb_chapter_queue_t * hb_chapter_queue_init(void)
{
    hb_chapter_queue_t * q;

    q = calloc(1, sizeof(*q));
    if (q != NULL)
    {
        q->list_chapter = hb_list_init();
        if (q->list_chapter == NULL)
        {
            free(q);
            q = NULL;
        }
    }
    return q;
}

void hb_chapter_queue_close(hb_chapter_queue_t **_q)
{
    hb_chapter_queue_t      * q = *_q;
    hb_chapter_queue_item_t * item;

    if (q == NULL)
    {
        return;
    }
    while ((item = hb_list_item(q->list_chapter, 0)) != NULL)
    {
        hb_list_rem(q->list_chapter, item);
        free(item);
    }
    hb_list_close(&q->list_chapter);
    free(q);
    *_q = NULL;
}

void hb_chapter_enqueue(hb_chapter_queue_t *q, hb_buffer_t *buf)
{
    /*
     * Chapter markers are sometimes so close we can get a new
     * one before the previous goes through the encoding queue.
     *
     * Dropping markers can cause weird side-effects downstream,
     * including but not limited to missing chapters in the
     * output, so we need to save it somehow.
     */
    hb_chapter_queue_item_t *item = malloc(sizeof(hb_chapter_queue_item_t));
    if (item != NULL)
    {
        item->start = buf->s.start;
        item->new_chap = buf->s.new_chap;
        // Make sure work_loop doesn't also copy the chapter mark
        buf->s.new_chap = 0;
        hb_list_add(q->list_chapter, item);
    }
}

void hb_chapter_dequeue(hb_chapter_queue_t *q, hb_buffer_t *buf)
{
    hb_chapter_queue_item_t *item = hb_list_item(q->list_chapter, 0);
    if (item != NULL)
    {
        if (buf->s.start < item->start)
        {
            // Have not reached the next chapter yet.
            return;
        }

        // we're done with this chapter
        hb_list_rem(q->list_chapter, item);
        buf->s.new_chap = item->new_chap;
        free(item);
    }
}

int hb_get_bit_depth(int format)
{
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(format);
    int i, min, max;

    if (!desc || !desc->nb_components)
    {
        return -1;
    }

    min = INT_MAX, max = -INT_MAX;
    for (i = 0; i < desc->nb_components; i++)
    {
        min = FFMIN(desc->comp[i].depth, min);
        max = FFMAX(desc->comp[i].depth, max);
    }

    return max;
}

int hb_get_chroma_sub_sample(int format, int *h_shift, int *v_shift)
{
    return av_pix_fmt_get_chroma_sub_sample(format, h_shift, v_shift);
}

static int pix_fmt_is_supported(hb_job_t *job, int pix_fmt)
{
    const int title_bit_depth = hb_get_bit_depth(job->title->pix_fmt);
    const int pix_fmt_bit_depth = hb_get_bit_depth(pix_fmt);

    if (pix_fmt_bit_depth > title_bit_depth)
    {
        return 0;
    }

    const AVPixFmtDescriptor *title_desc = av_pix_fmt_desc_get(job->title->pix_fmt);
    const AVPixFmtDescriptor *pix_fmt_desc = av_pix_fmt_desc_get(pix_fmt);

    if (pix_fmt_desc->log2_chroma_w < title_desc->log2_chroma_w ||
        pix_fmt_desc->log2_chroma_h < title_desc->log2_chroma_h)
    {
        return 0;
    }

    if (job->title->video_decode_support & HB_DECODE_SUPPORT_QSV)
    {
        // Allow formats supported by QSV pipeline via system memory
        // at that stage only, video memory formats will be reassigned later if allowed
        if (pix_fmt != AV_PIX_FMT_YUV420P10 &&
            pix_fmt != AV_PIX_FMT_YUV420P)
        {
            return 0;
        }
    }
#if HB_PROJECT_FEATURE_NVENC
    else if (hb_nvdec_is_enabled(job))
    {
        if (pix_fmt != AV_PIX_FMT_YUV420P10LE &&
            pix_fmt != AV_PIX_FMT_NV12)
        {
            return 0;
        }
    }
#endif
    else
    {
        // Allow biplanar formats only if
        // hardware decoding is enabled.
        if (pix_fmt == AV_PIX_FMT_P010 ||
            pix_fmt == AV_PIX_FMT_NV12)
        {
            return 0;
        }
    }

    for (int i = 0; i < hb_list_count(job->list_filter); i++)
    {
        hb_filter_object_t *filter = hb_list_item(job->list_filter, i);

        switch (filter->id)
        {
            case HB_FILTER_DETELECINE:
            case HB_FILTER_COMB_DETECT:
            case HB_FILTER_DECOMB:
            case HB_FILTER_YADIF:
            case HB_FILTER_BWDIF:
            case HB_FILTER_DENOISE:
            case HB_FILTER_NLMEANS:
            case HB_FILTER_CHROMA_SMOOTH:
            case HB_FILTER_LAPSHARP:
            case HB_FILTER_UNSHARP:
            case HB_FILTER_GRAYSCALE:
            case HB_FILTER_RENDER_SUB:
               if (pix_fmt == AV_PIX_FMT_P010 ||
                   pix_fmt == AV_PIX_FMT_NV12)
               {
                   return 0;
               }
        }
    }

    return 1;
}

static const enum AVPixelFormat pipeline_pix_fmts[] =
{
    AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P,
    AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P,
    AV_PIX_FMT_YUV420P12, AV_PIX_FMT_P010, AV_PIX_FMT_YUV420P10,
    AV_PIX_FMT_NV12, AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE
};

int hb_get_best_pix_fmt(hb_job_t * job)
{
    const int *pix_fmts = pipeline_pix_fmts;

    while (*pix_fmts != AV_PIX_FMT_NONE)
    {
        if (pix_fmt_is_supported(job, *pix_fmts))
        {
            return *pix_fmts;
        }
        pix_fmts++;
    }

    return AV_PIX_FMT_YUV420P;
}
