/* Copyright 2018 Streampunk Media Ltd.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

/* -LICENSE-START-
** Copyright (c) 2015 Blackmagic Design
**
** Permission is hereby granted, free of charge, to any person or organization
** obtaining a copy of the software and accompanying documentation covered by
** this license (the "Software") to use, reproduce, display, distribute,
** execute, and transmit the Software, and to prepare derivative works of the
** Software, and to permit third-parties to whom the Software is furnished to
** do so, all subject to the following:
**
** The copyright notices in the Software and this entire statement, including
** the above license grant, this restriction and the following disclaimer,
** must be included in all copies of the Software, in whole or in part, and
** all derivative works of the Software, unless such copies or derivative
** works are solely in the form of machine-executable object code generated by
** a source language processor.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
** DEALINGS IN THE SOFTWARE.
** -LICENSE-END-
*/

#define _WINSOCKAPI_

#include "DeckLinkAPI.h"
#include <stdio.h>

#ifdef WIN32
#include <tchar.h>
#include <conio.h>
#include <objbase.h>		// Necessary for COM
#include <comdef.h>
#endif

#ifdef __APPLE__
#include <CoreFoundation/CFString.h>
#endif

#define NAPI_EXPERIMENTAL
#include "macadam_util.h"
#include "capture_promise.h"
#include "playback_promise.h"
#include "timecode.h"
#include "node_api.h"

// List of known pixel formats and their matching display names
const BMDPixelFormat gKnownPixelFormats[] = {bmdFormat8BitYUV, bmdFormat10BitYUV, bmdFormat8BitARGB, bmdFormat8BitBGRA, bmdFormat10BitRGB, bmdFormat12BitRGB, bmdFormat12BitRGBLE, bmdFormat10BitRGBXLE, bmdFormat10BitRGBX, (BMDPixelFormat) 0};
const char* gKnownPixelFormatNames[] = {"8-bit YUV", "10-bit YUV", "8-bit ARGB", "8-bit BGRA", "10-bit RGB", "12-bit RGB", "12-bit RGBLE", "10-bit RGBXLE", "10-bit RGBX", NULL};

const BMDDeckLinkConfigurationID knownConfigValues[] = {
  /* Serial port Flags */
  bmdDeckLinkConfigSwapSerialRxTx,

  /* Video Input/Output Integers */
  bmdDeckLinkConfigHDMI3DPackingFormat,
  bmdDeckLinkConfigBypass,
  bmdDeckLinkConfigClockTimingAdjustment,
  bmdDeckLinkConfigDuplexMode,

  /* Audio Input/Output Flags */
  bmdDeckLinkConfigAnalogAudioConsumerLevels,

  /* Video output flags */
  bmdDeckLinkConfigFieldFlickerRemoval,
  bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion,
  bmdDeckLinkConfig444SDIVideoOutput,
  bmdDeckLinkConfigBlackVideoOutputDuringCapture,
  bmdDeckLinkConfigLowLatencyVideoOutput,
  bmdDeckLinkConfigDownConversionOnAllAnalogOutput,
  bmdDeckLinkConfigSMPTELevelAOutput,
  bmdDeckLinkConfigRec2020Output,	// Ensure output is Rec.2020 colorspace
  bmdDeckLinkConfigQuadLinkSDIVideoOutputSquareDivisionSplit,

  /* Video Output Flags */
  bmdDeckLinkConfigOutput1080pAsPsF,

  /* Video Output Integers */
  bmdDeckLinkConfigVideoOutputConnection,
  bmdDeckLinkConfigVideoOutputConversionMode,
  bmdDeckLinkConfigAnalogVideoOutputFlags,
  bmdDeckLinkConfigReferenceInputTimingOffset,
  bmdDeckLinkConfigVideoOutputIdleOperation,
  bmdDeckLinkConfigDefaultVideoOutputMode,
  bmdDeckLinkConfigDefaultVideoOutputModeFlags,
  bmdDeckLinkConfigSDIOutputLinkConfiguration,

  /* Video Output Floats */
  bmdDeckLinkConfigVideoOutputComponentLumaGain,
  bmdDeckLinkConfigVideoOutputComponentChromaBlueGain,
  bmdDeckLinkConfigVideoOutputComponentChromaRedGain,
  bmdDeckLinkConfigVideoOutputCompositeLumaGain,
  bmdDeckLinkConfigVideoOutputCompositeChromaGain,
  bmdDeckLinkConfigVideoOutputSVideoLumaGain,
  bmdDeckLinkConfigVideoOutputSVideoChromaGain,

  /* Video Input Flags */

  bmdDeckLinkConfigVideoInputScanning,	// Applicable to H264 Pro Recorder only
  bmdDeckLinkConfigUseDedicatedLTCInput,	// Use timecode from LTC input instead of SDI stream
  bmdDeckLinkConfigSDIInput3DPayloadOverride,

  /* Video Input Flags */
  bmdDeckLinkConfigCapture1080pAsPsF,

  /* Video Input Integers */
  bmdDeckLinkConfigVideoInputConnection,
  bmdDeckLinkConfigAnalogVideoInputFlags,
  bmdDeckLinkConfigVideoInputConversionMode,
  bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame,
  bmdDeckLinkConfigVANCSourceLine1Mapping,
  bmdDeckLinkConfigVANCSourceLine2Mapping,
  bmdDeckLinkConfigVANCSourceLine3Mapping,
  bmdDeckLinkConfigCapturePassThroughMode,

  /* Video Input Floats */
  bmdDeckLinkConfigVideoInputComponentLumaGain,
  bmdDeckLinkConfigVideoInputComponentChromaBlueGain,
  bmdDeckLinkConfigVideoInputComponentChromaRedGain,
  bmdDeckLinkConfigVideoInputCompositeLumaGain,
  bmdDeckLinkConfigVideoInputCompositeChromaGain,
  bmdDeckLinkConfigVideoInputSVideoLumaGain,
  bmdDeckLinkConfigVideoInputSVideoChromaGain,

  /* Audio Input Flags */
  bmdDeckLinkConfigMicrophonePhantomPower,

  /* Audio Input Integers */
  bmdDeckLinkConfigAudioInputConnection,

  /* Audio Input Floats */
  bmdDeckLinkConfigAnalogAudioInputScaleChannel1,
  bmdDeckLinkConfigAnalogAudioInputScaleChannel2,
  bmdDeckLinkConfigAnalogAudioInputScaleChannel3,
  bmdDeckLinkConfigAnalogAudioInputScaleChannel4,
  bmdDeckLinkConfigDigitalAudioInputScale,
  bmdDeckLinkConfigMicrophoneInputGain,

  /* Audio Output Integers */
  bmdDeckLinkConfigAudioOutputAESAnalogSwitch,

  /* Audio Output Floats */
  bmdDeckLinkConfigAnalogAudioOutputScaleChannel1,
  bmdDeckLinkConfigAnalogAudioOutputScaleChannel2,
  bmdDeckLinkConfigAnalogAudioOutputScaleChannel3,
  bmdDeckLinkConfigAnalogAudioOutputScaleChannel4,
  bmdDeckLinkConfigDigitalAudioOutputScale,
  bmdDeckLinkConfigHeadphoneVolume,

  /* Device Information Strings */
  bmdDeckLinkConfigDeviceInformationLabel,
  bmdDeckLinkConfigDeviceInformationSerialNumber,
  bmdDeckLinkConfigDeviceInformationCompany,
  bmdDeckLinkConfigDeviceInformationPhone,
  bmdDeckLinkConfigDeviceInformationEmail,
  bmdDeckLinkConfigDeviceInformationDate,

  /* Deck Control Integers */
  bmdDeckLinkConfigDeckControlConnection,
  (BMDDeckLinkConfigurationID) 0
};

const char* knownConfigNames[] = {
  /* Serial port Flags */
  "swapSerialRxTx", // bmdDeckLinkConfigSwapSerialRxTx

  /* Video Input/Output Integers */
  "HDMI3DPackingFormat", // bmdDeckLinkConfigHDMI3DPackingFormat
  "bypass", // bmdDeckLinkConfigBypass
  "clockTimingAdjustment", // bmdDeckLinkConfigClockTimingAdjustment
  "duplexMode", // bmdDeckLinkConfigDuplexMode

  /* Audio Input/Output Flags */
  "analogAudioConsumerLevels", // bmdDeckLinkConfigAnalogAudioConsumerLevels

  /* Video output flags */
  "fieldFlickerRemoval", // bmdDeckLinkConfigFieldFlickerRemoval
  "HD1080p24ToHD1080i5994Conversion", // bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion
  "SDIVideoOutput444", // bmdDeckLinkConfig444SDIVideoOutput
  "blackVideoOutputDuringCapture", // bmdDeckLinkConfigBlackVideoOutputDuringCapture
  "lowLatencyVideoOutput", // bmdDeckLinkConfigLowLatencyVideoOutput
  "downConversionOnAllAnalogOutput", // bmdDeckLinkConfigDownConversionOnAllAnalogOutput
  "SMPTELevelAOutput", //bmdDeckLinkConfigSMPTELevelAOutput
  "rec2020Output", // bmdDeckLinkConfigRec2020Output - Ensure output is Rec.2020 colorspace
  "quadLinkSDIVideoOutputSquareDivisionSplit", // bmdDeckLinkConfigQuadLinkSDIVideoOutputSquareDivisionSplit

  /* Video Output Flags */
  "output1080pAsPsF", // bmdDeckLinkConfigOutput1080pAsPsF

  /* Video Output Integers */
  "videoOutputConnection", // bmdDeckLinkConfigVideoOutputConnection
  "videoOutputConversionMode", // bmdDeckLinkConfigVideoOutputConversionMode
  "videoOutputFlags", // bmdDeckLinkConfigAnalogVideoOutputFlags
  "referenceInputTimingOffset", // bmdDeckLinkConfigReferenceInputTimingOffset
  "videoOutputIdleOperation", // bmdDeckLinkConfigVideoOutputIdleOperation
  "videoOutputMode", // bmdDeckLinkConfigDefaultVideoOutputMode
  "defaultVideoModeOutputFlags", // bmdDeckLinkConfigDefaultVideoOutputModeFlags
  "SDIOutputLinkConfiguration", // bmdDeckLinkConfigSDIOutputLinkConfiguration

  /* Video Output Floats */
  "videoOutputComponentLumaGain", // bmdDeckLinkConfigVideoOutputComponentLumaGain
  "videoOutputComponentChromaBlueGain", // bmdDeckLinkConfigVideoOutputComponentChromaBlueGain
  "videoOutputComponentChromaRedGain", // bmdDeckLinkConfigVideoOutputComponentChromaRedGain
  "videoOutputCompositeLumaGain", // bmdDeckLinkConfigVideoOutputCompositeLumaGain
  "videoOutputCompositeChromaGain", // bmdDeckLinkConfigVideoOutputCompositeChromaGain
  "videoOutputSVideoLumaGain", // bmdDeckLinkConfigVideoOutputSVideoLumaGain
  "videoOutputSVideoChromaGain", // bmdDeckLinkConfigVideoOutputSVideoChromaGain

  /* Video Input Flags */
  "videoInputScanning", // bmdDeckLinkConfigVideoInputScanning - applicable to H264 Pro Recorder only
  "useDedicatedLTCInput", // bmdDeckLinkConfigUseDedicatedLTCInput - use timecode from LTC input instead of SDI stream
  "SDIInput3DPayloadOverride", // bmdDeckLinkConfigSDIInput3DPayloadOverride

  /* Video Input Flags */
  "capture1080pAsPsF", // bmdDeckLinkConfigCapture1080pAsPsF

  /* Video Input Integers */
  "videoInputConnection", // bmdDeckLinkConfigVideoInputConnection
  "analogVideoInputFlags", // bmdDeckLinkConfigAnalogVideoInputFlags
  "videoInputConversionMode", // bmdDeckLinkConfigVideoInputConversionMode
  "pulldown32SequenceInitialTimecodeFrame", // bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame
  "VANCSourceLine1Mapping", // bmdDeckLinkConfigVANCSourceLine1Mapping
  "VANCSourceLine2Mapping", // bmdDeckLinkConfigVANCSourceLine2Mapping
  "VANCSourceLine3Mapping", // bmdDeckLinkConfigVANCSourceLine3Mapping
  "capturePassThroughMode", // bmdDeckLinkConfigCapturePassThroughMode

  /* Video Input Floats */
  "videoInputComponentLumaGain", // bmdDeckLinkConfigVideoInputComponentLumaGain
  "videoInputComponentChromaBlueGain", // bmdDeckLinkConfigVideoInputComponentChromaBlueGain
  "videoInputComponentChromaRedGain", // bmdDeckLinkConfigVideoInputComponentChromaRedGain
  "videoInputCompositeLumaGain", // bmdDeckLinkConfigVideoInputCompositeLumaGain
  "videoInputCompositeChromaGain", // bmdDeckLinkConfigVideoInputCompositeChromaGain
  "videoInputSVideoLumaGain", // bmdDeckLinkConfigVideoInputSVideoLumaGain
  "videoInputSVideoChromaGain", // bmdDeckLinkConfigVideoInputSVideoChromaGain

  /* Audio Input Flags */
  "microphonePhantomPower", // bmdDeckLinkConfigMicrophonePhantomPower

  /* Audio Input Integers */
  "audioInputConnection", // bmdDeckLinkConfigAudioInputConnection

  /* Audio Input Floats */
  "analogAudioInputScaleChannel1", // bmdDeckLinkConfigAnalogAudioInputScaleChannel1
  "analogAudioInputScaleChannel2", // bmdDeckLinkConfigAnalogAudioInputScaleChannel2
  "analogAudioInputScaleChannel3", // bmdDeckLinkConfigAnalogAudioInputScaleChannel3
  "analogAudioInputScaleChannel4", // bmdDeckLinkConfigAnalogAudioInputScaleChannel4
  "digitalAudioInputScale", // bmdDeckLinkConfigDigitalAudioInputScale
  "microphoneInputGain", // bmdDeckLinkConfigMicrophoneInputGain

  /* Audio Output Integers */
  "audioOutputAESAnalogSwitch", // bmdDeckLinkConfigAudioOutputAESAnalogSwitch

  /* Audio Output Floats */
  "analogAudioOutputScaleChannel1", // bmdDeckLinkConfigAnalogAudioOutputScaleChannel1
  "analogAudioOutputScaleChannel2", // bmdDeckLinkConfigAnalogAudioOutputScaleChannel2
  "analogAudioOutputScaleChannel3", // bmdDeckLinkConfigAnalogAudioOutputScaleChannel3
  "analogAudioOutputScaleChannel4", // bmdDeckLinkConfigAnalogAudioOutputScaleChannel4
  "digitalAudioOutputScale", // bmdDeckLinkConfigDigitalAudioOutputScale
  "headphoneVolume", // bmdDeckLinkConfigHeadphoneVolume

  /* Device Information Strings */
  "deviceInformationLabel", // bmdDeckLinkConfigDeviceInformationLabel
  "deviceInformationSerialNumber", // bmdDeckLinkConfigDeviceInformationSerialNumber
  "deviceInformationCompany", // bmdDeckLinkConfigDeviceInformationCompany
  "deviceInformationPhone", // bmdDeckLinkConfigDeviceInformationPhone
  "deviceInformationEmail", // bmdDeckLinkConfigDeviceInformationEmail
  "deviceInformationDate", // bmdDeckLinkConfigDeviceInformationDate

  /* Deck Control Integers */
  "deckControlConnection", // bmdDeckLinkConfigDeckControlConnection
  nullptr
};

const MacadamConfigType knownConfigTypes[] = {
  /* Serial port Flags */
  macadamFlag, // bmdDeckLinkConfigSwapSerialRxTx

  /* Video Input/Output Integers */
  macadamInt64, // bmdDeckLinkConfigHDMI3DPackingFormat
  macadamInt64, // bmdDeckLinkConfigBypass
  macadamInt64, // bmdDeckLinkConfigClockTimingAdjustment
  macadamInt64, // bmdDeckLinkConfigDuplexMode

  /* Audio Input/Output Flags */
  macadamFlag, // bmdDeckLinkConfigAnalogAudioConsumerLevels

  /* Video output flags */
  macadamFlag, // bmdDeckLinkConfigFieldFlickerRemoval
  macadamFlag, // bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion
  macadamFlag, // bmdDeckLinkConfig444SDIVideoOutput
  macadamFlag, // bmdDeckLinkConfigBlackVideoOutputDuringCapture
  macadamFlag, // bmdDeckLinkConfigLowLatencyVideoOutput
  macadamFlag, // bmdDeckLinkConfigDownConversionOnAllAnalogOutput
  macadamFlag, // bmdDeckLinkConfigSMPTELevelAOutput
  macadamFlag, // bmdDeckLinkConfigRec2020Output - ensure output is Rec.2020 colorspace
  macadamFlag, // bmdDeckLinkConfigQuadLinkSDIVideoOutputSquareDivisionSplit

  /* Video Output Flags */
  macadamFlag, // bmdDeckLinkConfigOutput1080pAsPsF

  /* Video Output Integers */
  macadamInt64, // bmdDeckLinkConfigVideoOutputConnection
  macadamInt64, // bmdDeckLinkConfigVideoOutputConversionMode
  macadamInt64, // bmdDeckLinkConfigAnalogVideoOutputFlags
  macadamInt64, // bmdDeckLinkConfigReferenceInputTimingOffset
  macadamInt64, // bmdDeckLinkConfigVideoOutputIdleOperation
  macadamInt64, // bmdDeckLinkConfigDefaultVideoOutputMode
  macadamInt64, // bmdDeckLinkConfigDefaultVideoOutputModeFlags
  macadamInt64, // bmdDeckLinkConfigSDIOutputLinkConfiguration

  /* Video Output Floats */
  macadamFloat, // bmdDeckLinkConfigVideoOutputComponentLumaGain
  macadamFloat, // bmdDeckLinkConfigVideoOutputComponentChromaBlueGain
  macadamFloat, // bmdDeckLinkConfigVideoOutputComponentChromaRedGain
  macadamFloat, // bmdDeckLinkConfigVideoOutputCompositeLumaGain
  macadamFloat, // bmdDeckLinkConfigVideoOutputCompositeChromaGain
  macadamFloat, // bmdDeckLinkConfigVideoOutputSVideoLumaGain
  macadamFloat, // bmdDeckLinkConfigVideoOutputSVideoChromaGain

  /* Video Input Flags */

  macadamFlag, // bmdDeckLinkConfigVideoInputScanning - applicable to H264 Pro Recorder only
  macadamFlag, // bmdDeckLinkConfigUseDedicatedLTCInput - use timecode from LTC input instead of SDI stream
  macadamFlag, // bmdDeckLinkConfigSDIInput3DPayloadOverride

  /* Video Input Flags */
  macadamFlag, // bmdDeckLinkConfigCapture1080pAsPsF

  /* Video Input Integers */
  macadamInt64, // bmdDeckLinkConfigVideoInputConnection
  macadamInt64, // bmdDeckLinkConfigAnalogVideoInputFlags
  macadamInt64, // bmdDeckLinkConfigVideoInputConversionMode
  macadamInt64, // bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame
  macadamInt64, // bmdDeckLinkConfigVANCSourceLine1Mapping
  macadamInt64, // bmdDeckLinkConfigVANCSourceLine2Mapping
  macadamInt64, // bmdDeckLinkConfigVANCSourceLine3Mapping
  macadamInt64, // bmdDeckLinkConfigCapturePassThroughMode

  /* Video Input Floats */
  macadamFloat, // bmdDeckLinkConfigVideoInputComponentLumaGain
  macadamFloat, // bmdDeckLinkConfigVideoInputComponentChromaBlueGain
  macadamFloat, // bmdDeckLinkConfigVideoInputComponentChromaRedGain
  macadamFloat, // bmdDeckLinkConfigVideoInputCompositeLumaGain
  macadamFloat, // bmdDeckLinkConfigVideoInputCompositeChromaGain
  macadamFloat, // bmdDeckLinkConfigVideoInputSVideoLumaGain
  macadamFloat, // bmdDeckLinkConfigVideoInputSVideoChromaGain

  /* Audio Input Flags */
  macadamFlag, // bmdDeckLinkConfigMicrophonePhantomPower

  /* Audio Input Integers */
  macadamInt64, // bmdDeckLinkConfigAudioInputConnection

  /* Audio Input Floats */
  macadamFloat, // bmdDeckLinkConfigAnalogAudioInputScaleChannel1
  macadamFloat, // bmdDeckLinkConfigAnalogAudioInputScaleChannel2
  macadamFloat, // bmdDeckLinkConfigAnalogAudioInputScaleChannel3
  macadamFloat, // bmdDeckLinkConfigAnalogAudioInputScaleChannel4
  macadamFloat, // bmdDeckLinkConfigDigitalAudioInputScale
  macadamFloat, // bmdDeckLinkConfigMicrophoneInputGain

  /* Audio Output Integers */
  macadamInt64, // bmdDeckLinkConfigAudioOutputAESAnalogSwitch

  /* Audio Output Floats */
  macadamFloat, // bmdDeckLinkConfigAnalogAudioOutputScaleChannel1
  macadamFloat, // bmdDeckLinkConfigAnalogAudioOutputScaleChannel2
  macadamFloat, // bmdDeckLinkConfigAnalogAudioOutputScaleChannel3
  macadamFloat, // bmdDeckLinkConfigAnalogAudioOutputScaleChannel4
  macadamFloat, // bmdDeckLinkConfigDigitalAudioOutputScale
  macadamFloat, // bmdDeckLinkConfigHeadphoneVolume

  /* Device Information Strings */
  macadamString, // bmdDeckLinkConfigDeviceInformationLabel
  macadamString, // bmdDeckLinkConfigDeviceInformationSerialNumber
  macadamString, // bmdDeckLinkConfigDeviceInformationCompany
  macadamString, // bmdDeckLinkConfigDeviceInformationPhone
  macadamString, // bmdDeckLinkConfigDeviceInformationEmail
  macadamString, // bmdDeckLinkConfigDeviceInformationDate

  /* Deck Control Integers */
  macadamInt64, // bmdDeckLinkConfigDeckControlConnection

  (MacadamConfigType) 0
};

napi_status queryOutputDisplayModes(napi_env env, IDeckLink* deckLink, napi_value result);
napi_status queryInputDisplayModes(napi_env env, IDeckLink* deckLink, napi_value result);

napi_value deckLinkVersion(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value result;

  IDeckLinkIterator* deckLinkIterator;
  HRESULT hresult;
  IDeckLinkAPIInformation*	deckLinkAPIInformation;
  #ifdef WIN32
  CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
  #else
  deckLinkIterator = CreateDeckLinkIteratorInstance();
  #endif
  if (deckLinkIterator == nullptr) NAPI_THROW_ERROR("Unable to load DeckLinkAPI.");

  hresult = deckLinkIterator->QueryInterface(IID_IDeckLinkAPIInformation, (void**)&deckLinkAPIInformation);
  if (hresult != S_OK) NAPI_THROW_ERROR("Error connecting to DeckLinkAPI.");

  char deckVer [80];
  int64_t	deckLinkVersion;
  int	dlVerMajor, dlVerMinor, dlVerPoint;

  // We can also use the BMDDeckLinkAPIVersion flag with GetString
  deckLinkAPIInformation->GetInt(BMDDeckLinkAPIVersion, &deckLinkVersion);

  dlVerMajor = (deckLinkVersion & 0xFF000000) >> 24;
  dlVerMinor = (deckLinkVersion & 0x00FF0000) >> 16;
  dlVerPoint = (deckLinkVersion & 0x0000FF00) >> 8;

  sprintf(deckVer, "DeckLinkAPI version: %d.%d.%d", dlVerMajor, dlVerMinor, dlVerPoint);

  deckLinkAPIInformation->Release();
  deckLinkIterator->Release();

  status = napi_create_string_utf8(env, deckVer, NAPI_AUTO_LENGTH, &result);
  CHECK_STATUS;
  return result;
}

napi_value getFirstDevice(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value result;

  status = napi_get_undefined(env, &result);
  CHECK_STATUS;

  IDeckLinkIterator* deckLinkIterator;
  HRESULT	hresult;
  IDeckLink* deckLink = nullptr;
  IDeckLinkAttributes* deckLinkAttributes = nullptr;

  #ifdef WIN32
  CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
  #else
  deckLinkIterator = CreateDeckLinkIteratorInstance();
  #endif
  if (deckLinkIterator == nullptr) NAPI_THROW_ERROR("Unable to load DeckLinkAPI.");

  if (deckLinkIterator->Next(&deckLink) != S_OK) {
    status = napi_get_undefined(env, &result);
    if (checkStatus(env, status, __FILE__, __LINE__ - 1) != napi_ok) {
      deckLinkIterator->Release();
      return result;
    }
  }

  #ifdef WIN32
  BSTR deviceNameBSTR = NULL;
  hresult = deckLink->GetModelName(&deviceNameBSTR);
  if (hresult == S_OK) {
    _bstr_t deviceName(deviceNameBSTR, false);
    status = napi_create_string_utf8(env, (char*) deviceName, NAPI_AUTO_LENGTH, &result);
    // delete deviceName;
    CHECK_BAIL;
  }
  #elif __APPLE__
  CFStringRef deviceNameCFString = NULL;
  hresult = deckLink->GetModelName(&deviceNameCFString);
  if (hresult == S_OK) {
    char deviceName [64];
    CFStringGetCString(deviceNameCFString, deviceName, sizeof(deviceName), kCFStringEncodingMacRoman);
    CFRelease(deviceNameCFString);
    status = napi_create_string_utf8(env, deviceName, NAPI_AUTO_LENGTH, &result);
    CHECK_BAIL;
  }
  #else
  char* deviceName;
  hresult = deckLink->GetModelName((const char **) &deviceName);
  if (hresult == S_OK) {
    status = napi_create_string_utf8(env, deviceName, NAPI_AUTO_LENGTH, &result);
    free(deviceName);
    CHECK_BAIL;
  }
  #endif

bail:
  if (deckLink != nullptr) deckLink->Release();
  deckLinkIterator->Release();
  if (deckLinkAttributes != nullptr) deckLinkAttributes->Release();

  return result;
}

napi_value getDeviceInfo(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value result;

  status = napi_create_array(env, &result);
  CHECK_STATUS;

  IDeckLinkIterator* deckLinkIterator;
  HRESULT	hresult;
  IDeckLink* deckLink = nullptr;
  IDeckLinkAttributes* deckLinkAttributes = nullptr;
  #ifdef WIN32
  CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
  #else
  deckLinkIterator = CreateDeckLinkIteratorInstance();
  #endif
  if (deckLinkIterator == nullptr) NAPI_THROW_ERROR("Unable to load DeckLinkAPI.");

  uint32_t index = 0;
  while (deckLinkIterator->Next(&deckLink) == S_OK) {
    napi_value item, param = nullptr;
    status = napi_create_object(env, &item);
    CHECK_BAIL;
    #ifdef WIN32
    BSTR deviceNameBSTR = NULL;
    hresult = deckLink->GetModelName(&deviceNameBSTR);
    if (hresult == S_OK) {
      _bstr_t deviceName(deviceNameBSTR, false);
      status = napi_create_string_utf8(env, (char*) deviceName, NAPI_AUTO_LENGTH, &param);
      // delete deviceName;
      CHECK_BAIL;
    }
    #elif __APPLE__
    CFStringRef deviceNameCFString = NULL;
    hresult = deckLink->GetModelName(&deviceNameCFString);
    if (hresult == S_OK) {
      char deviceName [64];
      CFStringGetCString(deviceNameCFString, deviceName, sizeof(deviceName), kCFStringEncodingMacRoman);
      CFRelease(deviceNameCFString);
      status = napi_create_string_utf8(env, deviceName, NAPI_AUTO_LENGTH, &param);
      CHECK_BAIL;
    }
    #else
    char* deviceName;
    hresult = deckLink->GetModelName((const char **) &deviceName);
    if (hresult == S_OK) {
      status = napi_create_string_utf8(env, deviceName, NAPI_AUTO_LENGTH, &param);
      free(deviceName);
      CHECK_BAIL;
    }
    #endif
    if (param != nullptr) {
      status = napi_set_named_property(env, item, "modelName", param);
      CHECK_BAIL;
    }
    param = nullptr;

    #ifdef WIN32
    BSTR displayNameBSTR = NULL;
    hresult = deckLink->GetDisplayName(&displayNameBSTR);
    if (hresult == S_OK) {
      _bstr_t displayName(displayNameBSTR, false);
      status = napi_create_string_utf8(env, (char*) displayName, NAPI_AUTO_LENGTH, &param);
      // delete displayName;
      CHECK_BAIL;
    }
    #elif __APPLE__
    CFStringRef displayNameCFString = NULL;
    hresult = deckLink->GetDisplayName(&displayNameCFString);
    if (hresult == S_OK) {
      char displayName [64];
      CFStringGetCString(displayNameCFString, displayName, sizeof(displayName), kCFStringEncodingMacRoman);
      CFRelease(displayNameCFString);
      status = napi_create_string_utf8(env, displayName, NAPI_AUTO_LENGTH, &param);
      CHECK_BAIL;
    }
    #else
    char* displayName;
    hresult = deckLink->GetDisplayName((const char **) &displayName);
    if (hresult == S_OK) {
      status = napi_create_string_utf8(env, displayName, NAPI_AUTO_LENGTH, &param);
      free(displayName);
      CHECK_BAIL;
    }
    #endif
    if (param != nullptr) {
      status = napi_set_named_property(env, item, "displayName", param);
      CHECK_BAIL;
    }
    param = nullptr;

    // Query the DeckLink for its attributes interface
    hresult = deckLink->QueryInterface(IID_IDeckLinkAttributes, (void**)&deckLinkAttributes);
    if (hresult == S_OK) {
      bool supported;
      #ifdef WIN32
      BOOL supportedWin;
      BSTR name;
      #elif __APPLE__
      CFStringRef name;
      #else
      char* name;
      #endif
      int64_t value;
      double valuef;

      #ifdef WIN32
      hresult = deckLinkAttributes->GetString(BMDDeckLinkVendorName, &name);
      if (hresult == S_OK) {
        _bstr_t vendorName(name, false);
        status = napi_create_string_utf8(env, (char*) vendorName, NAPI_AUTO_LENGTH, &param);
        // delete portName;
        CHECK_BAIL;
      #elif __APPLE__
      hresult = deckLinkAttributes->GetString(BMDDeckLinkVendorName, &name);
      if (hresult == S_OK) {
        char vendorName[64];
        CFStringGetCString(name, vendorName, sizeof(vendorName), kCFStringEncodingMacRoman);
        // CFRelease(name); ocumentation says do not free
        status = napi_create_string_utf8(env, vendorName, NAPI_AUTO_LENGTH, &param);
        CHECK_BAIL;
      #else
      hresult = deckLinkAttributes->GetString(BMDDeckLinkVendorName, (const char **) &name);
      if (hresult == S_OK) {
        status = napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &param);
        // free(name); Documentation says do not free
        CHECK_BAIL;
      #endif
        status = napi_set_named_property(env, item, "vendorName", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetString(BMDDeckLinkDeviceHandle, &name);
      if (hresult == S_OK) {
        _bstr_t deviceHandle(name, false);
        status = napi_create_string_utf8(env, (char*) deviceHandle, NAPI_AUTO_LENGTH, &param);
        // delete portName;
        CHECK_BAIL;
      #elif __APPLE__
      hresult = deckLinkAttributes->GetString(BMDDeckLinkDeviceHandle, &name);
      if (hresult == S_OK) {
        char deviceHandle[64];
        CFStringGetCString(name, deviceHandle, sizeof(deviceHandle), kCFStringEncodingMacRoman);
        CFRelease(name);
        status = napi_create_string_utf8(env, deviceHandle, NAPI_AUTO_LENGTH, &param);
        CHECK_BAIL;
      #else
      hresult = deckLinkAttributes->GetString(BMDDeckLinkDeviceHandle, (const char **) &name);
      if (hresult == S_OK) {
        status = napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &param);
        free(name);
        CHECK_BAIL;
      #endif
        status = napi_set_named_property(env, item, "deviceHandle", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasSerialPort, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasSerialPort, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "hasSerialPort", param);
        CHECK_BAIL;
        if (supported == true) {
          #ifdef WIN32
          hresult = deckLinkAttributes->GetString(BMDDeckLinkSerialPortDeviceName, &name);
          if (hresult == S_OK) {
            _bstr_t portName(name, false);
            status = napi_create_string_utf8(env, (char*) portName, NAPI_AUTO_LENGTH, &param);
            // delete portName;
            CHECK_BAIL;
          #elif __APPLE__
          hresult = deckLinkAttributes->GetString(BMDDeckLinkSerialPortDeviceName, &name);
          if (hresult == S_OK) {
            char portName[64];
            CFStringGetCString(name, portName, sizeof(portName), kCFStringEncodingMacRoman);
            CFRelease(name);
            status = napi_create_string_utf8(env, portName, NAPI_AUTO_LENGTH, &param);
            CHECK_BAIL;
          #else
          hresult = deckLinkAttributes->GetString(BMDDeckLinkSerialPortDeviceName, (const char **) &name);
          if (hresult == S_OK) {
            status = napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &param);
            free(name);
            CHECK_BAIL;
          #endif
            status = napi_set_named_property(env, item, "serialPortDeviceName", param);
            CHECK_BAIL;
          }
        }
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkPersistentID, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "persistentID", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkTopologicalID, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "topologicalID", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkNumberOfSubDevices, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "numberOfSubDevices", param);
        CHECK_BAIL;
      }

      if (value > 0) {
        hresult = deckLinkAttributes->GetInt(BMDDeckLinkSubDeviceIndex, &value);
        if (hresult == S_OK) {
          status = napi_create_int64(env, value, &param);
          CHECK_BAIL;
          status = napi_set_named_property(env, item, "subDeviceIndex", param);
          CHECK_BAIL;
        }
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkMaximumAudioChannels, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "maximumAudioChannels", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkMaximumAnalogAudioInputChannels, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "maximumAnalogAudioInputChannels", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkMaximumAnalogAudioOutputChannels, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "maximumAnalogAudioOutputChannels", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsInputFormatDetection", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasReferenceInput, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasReferenceInput, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "hasReferenceInput", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsFullDuplex, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsFullDuplex, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsFullDuplex", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsExternalKeying, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsExternalKeying", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsInternalKeying", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsHDKeying, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsHDKeying, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsHDKeying", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasAnalogVideoOutputGain, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasAnalogVideoOutputGain, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "hasAnalogVideoOutputGain", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkCanOnlyAdjustOverallVideoOutputGain, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkCanOnlyAdjustOverallVideoOutputGain, &supported);
      #endif
  	  if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "canOnlyAdjustOverallVideoOutputGain", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetFloat(BMDDeckLinkVideoInputGainMinimum, &valuef);
      if (hresult == S_OK) {
        status = napi_create_double(env, valuef, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "videoInputGainMinimum", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetFloat(BMDDeckLinkVideoInputGainMaximum, &valuef);
      if (hresult == S_OK) {
        status = napi_create_double(env, valuef, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "videoInputGainMaximum", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetFloat(BMDDeckLinkVideoOutputGainMinimum, &valuef);
      if (hresult == S_OK) {
        status = napi_create_double(env, valuef, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "videoOutputGainMinimum", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetFloat(BMDDeckLinkVideoOutputGainMaximum, &valuef);
      if (hresult == S_OK) {
        status = napi_create_double(env, valuef, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "videoOutputGainMaximum", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetFloat(BMDDeckLinkMicrophoneInputGainMinimum, &valuef);
      if (hresult == S_OK) {
        status = napi_create_double(env, valuef, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "microphoneInputGainMinimum", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetFloat(BMDDeckLinkMicrophoneInputGainMaximum, &valuef);
      if (hresult == S_OK) {
        status = napi_create_double(env, valuef, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "microphoneInputGainMaximum", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasVideoInputAntiAliasingFilter, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasVideoInputAntiAliasingFilter, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "hasVideoInputAntiAliasingFilter", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasBypass, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasBypass, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "hasLinkBypass", param);
        CHECK_BAIL;
      }

      /* #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDesktopDisplay_v10_6, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDesktopDisplay_v10_6, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsDesktopDisplay", param);
        CHECK_BAIL;
      } */

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsClockTimingAdjustment, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsClockTimingAdjustment, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsClockTimingAdjustment", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsFullFrameReferenceInputTimingOffset, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsFullFrameReferenceInputTimingOffset, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsFullFrameReferenceInputTimingOffset", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsSMPTELevelAOutput, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsSMPTELevelAOutput, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsSMPTELevelAOutput", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDualLinkSDI, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDualLinkSDI, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsDualLinkSDI", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsQuadLinkSDI, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsQuadLinkSDI, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsQuadLinkSDI", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsIdleOutput, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsIdleOutput, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsIdleOutput", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasLTCTimecodeInput, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkHasLTCTimecodeInput, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "hasLTCTimecodeInput", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDuplexModeConfiguration, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsDuplexModeConfiguration, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsDuplexModeConfiguration", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsHDRMetadata, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsHDRMetadata, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsHDRMetadata", param);
        CHECK_BAIL;
      }

      #ifdef WIN32
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsColorspaceMetadata, &supportedWin);
      supported = (supportedWin == TRUE);
      #else
      hresult = deckLinkAttributes->GetFlag(BMDDeckLinkSupportsColorspaceMetadata, &supported);
      #endif
      if (hresult == S_OK) {
        status = napi_get_boolean(env, supported, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "supportsColorspaceMetadata", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkDeviceInterface, &value);
      if (hresult == S_OK) {
        switch (value) {
          case bmdDeviceInterfacePCI:
            status = napi_create_string_utf8(env, "PCI", NAPI_AUTO_LENGTH, &param);
            CHECK_BAIL;
            break;
          case bmdDeviceInterfaceUSB:
            status = napi_create_string_utf8(env, "USB", NAPI_AUTO_LENGTH, &param);
            CHECK_BAIL;
            break;
          case bmdDeviceInterfaceThunderbolt:
            status = napi_create_string_utf8(env, "Thunderbolt", NAPI_AUTO_LENGTH, &param);
            CHECK_BAIL;
            break;
        }

        status = napi_set_named_property(env, item, "deviceInterface", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkVideoIOSupport, &value);
      if (hresult == S_OK) {
        napi_value connb, connj;
        uint32_t indexi = 0;
        status = napi_create_array(env, &connb);
        CHECK_BAIL;

        if (value & bmdDeviceSupportsCapture) {
          status = napi_create_string_utf8(env, "Capture", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdDeviceSupportsPlayback) {
          status = napi_create_string_utf8(env, "Playback", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        status = napi_set_named_property(env, item, "deviceSupports", connb);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkDeckControlConnections, &value);
      if (hresult == S_OK) {
        napi_value connb, connj;
        uint32_t indexi = 0;
        status = napi_create_array(env, &connb);
        CHECK_BAIL;

        if (value & bmdDeckControlConnectionRS422Remote1) {
          status = napi_create_string_utf8(env, "ConnectionRS422Remote1", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdDeckControlConnectionRS422Remote2) {
          status = napi_create_string_utf8(env, "ConnectionRS422Remote2", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        status = napi_set_named_property(env, item, "controlConnections", connb);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkVideoInputConnections, &value);
      if (hresult == S_OK) {
        napi_value connb, connj;
        uint32_t indexi = 0;
        status = napi_create_array(env, &connb);
        CHECK_BAIL;

        if (value & bmdVideoConnectionSDI) {
          status = napi_create_string_utf8(env, "SDI", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionHDMI) {
          status = napi_create_string_utf8(env, "HDMI", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionOpticalSDI) {
          status = napi_create_string_utf8(env, "Optical SDI", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionComponent) {
          status = napi_create_string_utf8(env, "Component", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionComposite){
          status = napi_create_string_utf8(env, "Composite", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionSVideo) {
          status = napi_create_string_utf8(env, "S-Video", NAPI_AUTO_LENGTH, &connj);
          CHECK_BAIL;
          status = napi_set_element(env, connb, indexi++, connj);
          CHECK_BAIL;
        }

        status = napi_set_named_property(env, item, "videoInputConnections", connb);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkAudioInputConnections, &value);
      if (hresult == S_OK) {
        napi_value conna, conni;
        uint32_t indexo = 0;
        status = napi_create_array(env, &conna);
        CHECK_BAIL;

        if (value & bmdAudioConnectionEmbedded) {
          status = napi_create_string_utf8(env, "Embedded", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAESEBU) {
          status = napi_create_string_utf8(env, "AESEBU", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAnalog) {
          status = napi_create_string_utf8(env, "Analog", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAnalogXLR) {
          status = napi_create_string_utf8(env, "AnalogXLR", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAnalogRCA) {
          status = napi_create_string_utf8(env, "AnalogRCA", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionMicrophone){
          status = napi_create_string_utf8(env, "Microphone", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionHeadphones) {
          status = napi_create_string_utf8(env, "Headphones", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        status = napi_set_named_property(env, item, "audioInputConnections", conna);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkAudioInputRCAChannelCount, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "audioInputRCAChannelCount", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkAudioInputXLRChannelCount, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "audioInputXLRChannelCount", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkVideoOutputConnections, &value);
      if (hresult == S_OK) {
        napi_value conna, conni;
        uint32_t indexo = 0;
        status = napi_create_array(env, &conna);
        CHECK_BAIL;

        if (value & bmdVideoConnectionSDI) {
          status = napi_create_string_utf8(env, "SDI", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionHDMI) {
          status = napi_create_string_utf8(env, "HDMI", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionOpticalSDI) {
          status = napi_create_string_utf8(env, "Optical SDI", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionComponent) {
          status = napi_create_string_utf8(env, "Component", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionComposite){
          status = napi_create_string_utf8(env, "Composite", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdVideoConnectionSVideo) {
          status = napi_create_string_utf8(env, "S-Video", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        status = napi_set_named_property(env, item, "videoOutputConnections", conna);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkAudioOutputConnections, &value);
      if (hresult == S_OK) {
        napi_value conna, conni;
        uint32_t indexo = 0;
        status = napi_create_array(env, &conna);
        CHECK_BAIL;

        if (value & bmdAudioConnectionEmbedded) {
          status = napi_create_string_utf8(env, "Embedded", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAESEBU) {
          status = napi_create_string_utf8(env, "AESEBU", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAnalog) {
          status = napi_create_string_utf8(env, "Analog", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAnalogXLR) {
          status = napi_create_string_utf8(env, "AnalogXLR", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionAnalogRCA) {
          status = napi_create_string_utf8(env, "AnalogRCA", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionMicrophone){
          status = napi_create_string_utf8(env, "Microphone", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        if (value & bmdAudioConnectionHeadphones) {
          status = napi_create_string_utf8(env, "Headphones", NAPI_AUTO_LENGTH, &conni);
          CHECK_BAIL;
          status = napi_set_element(env, conna, indexo++, conni);
          CHECK_BAIL;
        }

        status = napi_set_named_property(env, item, "audioOutputConnections", conna);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkAudioOutputRCAChannelCount, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "audioOutputRCAChannelCount", param);
        CHECK_BAIL;
      }

      hresult = deckLinkAttributes->GetInt(BMDDeckLinkAudioOutputXLRChannelCount, &value);
      if (hresult == S_OK) {
        status = napi_create_int64(env, value, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, item, "audioOutputXLRChannelCount", param);
        CHECK_BAIL;
      }

    } // Get deckLinkAttributes

    status = queryInputDisplayModes(env, deckLink, item);
    CHECK_BAIL;

    status = queryOutputDisplayModes(env, deckLink, item);
    CHECK_BAIL;

    status = napi_set_element(env, result, index++, item);
    CHECK_BAIL;
  } // end while look

bail:
  if (deckLink != nullptr) deckLink->Release();
  deckLinkIterator->Release();
  if (deckLinkAttributes != nullptr) deckLinkAttributes->Release();

  return result;
}

napi_status queryOutputDisplayModes(napi_env env, IDeckLink* deckLink, napi_value result) {

  IDeckLinkOutput* deckLinkIO = nullptr;
  IDeckLinkDisplayModeIterator* displayModeIterator = nullptr;
  IDeckLinkDisplayMode* displayMode = nullptr;
  HRESULT hresult;
  napi_status status = napi_ok;
  napi_value modes, modeobj, item, itemPart;
  uint32_t modeIndex = 0, partIndex = 0;

  #if defined(WIN32) || defined(__APPLE__)
  char modeName[64];
  #else
  char * modeName;
  #endif
  int modeWidth;
  int	modeHeight;
  BMDTimeValue frameRateDuration;
  BMDTimeScale frameRateScale;
  int	pixelFormatIndex = 0; // index into the gKnownPixelFormats / gKnownFormatNames arrays
  BMDDisplayModeSupport	displayModeSupport;

  status = napi_create_array(env, &modes);
  CHECK_BAIL;
  status = napi_set_named_property(env, result, "outputDisplayModes", modes);
  CHECK_BAIL;

  // Query the DeckLink for its configuration interface
  hresult = deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&deckLinkIO);
  if (hresult != S_OK) { goto bail; }

  // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output
  hresult = deckLinkIO->GetDisplayModeIterator(&displayModeIterator);
  if (hresult != S_OK) { goto bail; }

  // List all supported output display modes
  while (displayModeIterator->Next(&displayMode) == S_OK) {

    status = napi_create_object(env, &modeobj);
    CHECK_BAIL;

    #ifdef WIN32
    BSTR			displayModeBSTR = NULL;
    hresult = displayMode->GetName(&displayModeBSTR);
    if (hresult == S_OK)
    {
      _bstr_t	modeNameWin(displayModeBSTR, false);
      strcpy(modeName, (const char*) modeNameWin);
    }
    #elif __APPLE__
    CFStringRef	displayModeCFString = NULL;
    hresult = displayMode->GetName(&displayModeCFString);
  	if (hresult == S_OK) {
  	  CFStringGetCString(displayModeCFString, modeName, sizeof(modeName), kCFStringEncodingMacRoman);
  	  CFRelease(displayModeCFString);
    }
    #else
    hresult = displayMode->GetName((const char **) &modeName);
    #endif

    status = napi_create_string_utf8(env, modeName, NAPI_AUTO_LENGTH, &item);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "name", item);
    CHECK_BAIL;

		modeWidth = displayMode->GetWidth();
    status = napi_create_int64(env, modeWidth, &item);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "width", item);
    CHECK_BAIL;

		modeHeight = displayMode->GetHeight();
    status = napi_create_int64(env, modeHeight, &item);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "height", item);
    CHECK_BAIL;

	  displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
		// printf(" %-20s \t %d x %d \t %7g FPS\t", displayModeString, modeWidth, modeHeight, (double)frameRateScale / (double)frameRateDuration);
    status = napi_create_array(env, &item);
    CHECK_BAIL;
    status = napi_create_int64(env, frameRateDuration, &itemPart);
    CHECK_BAIL;
    status = napi_set_element(env, item, 0, itemPart);
    CHECK_BAIL;
    status = napi_create_int64(env, frameRateScale, &itemPart);
    CHECK_BAIL;
    status = napi_set_element(env, item, 1, itemPart);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "frameRate", item);
    CHECK_BAIL;

    status = napi_create_array(env, &item);
    CHECK_BAIL;

    partIndex = 0;
    pixelFormatIndex = 0;

		while ((gKnownPixelFormats[pixelFormatIndex] != 0) &&
        (gKnownPixelFormatNames[pixelFormatIndex] != NULL)) {
			if ((deckLinkIO->DoesSupportVideoMode(
        displayMode->GetDisplayMode(), gKnownPixelFormats[pixelFormatIndex],
        bmdVideoOutputFlagDefault, &displayModeSupport, NULL) == S_OK)
					&& (displayModeSupport != bmdDisplayModeNotSupported)) {

				status = napi_create_string_utf8(env, gKnownPixelFormatNames[pixelFormatIndex],
          NAPI_AUTO_LENGTH, &itemPart);
        CHECK_BAIL;
        status = napi_set_element(env, item, partIndex++, itemPart);
        CHECK_BAIL;
			}

			pixelFormatIndex++;
		}
    status = napi_set_named_property(env, modeobj, "videoModes", item);
    CHECK_BAIL;

    status = napi_set_element(env, modes, modeIndex++, modeobj);
    CHECK_BAIL;

    displayMode->Release();
  }

bail:
  if (deckLinkIO != nullptr) deckLinkIO->Release();
  if (displayModeIterator != nullptr) displayModeIterator->Release();
  if (displayMode != nullptr) displayMode->Release();

  return status;
}

napi_status queryInputDisplayModes(napi_env env, IDeckLink* deckLink, napi_value result) {

  IDeckLinkInput* deckLinkIO = nullptr;
  IDeckLinkDisplayModeIterator* displayModeIterator = nullptr;
  IDeckLinkDisplayMode* displayMode = nullptr;
  HRESULT hresult;
  napi_status status = napi_ok;
  napi_value modes, modeobj, item, itemPart;
  uint32_t modeIndex = 0, partIndex = 0;

  #if defined(WIN32) || defined(__APPLE__)
  char modeName[64];
  #else
  char * modeName;
  #endif
  int	modeWidth;
  int	modeHeight;
  BMDTimeValue frameRateDuration;
  BMDTimeScale frameRateScale;
  int	pixelFormatIndex = 0; // index into the gKnownPixelFormats / gKnownFormatNames arrays
  BMDDisplayModeSupport	displayModeSupport;

  status = napi_create_array(env, &modes);
  CHECK_BAIL;
  status = napi_set_named_property(env, result, "inputDisplayModes", modes);
  CHECK_BAIL;

  // Query the DeckLink for its configuration interface
  hresult = deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkIO);
  if (hresult != S_OK) { goto bail; }

  // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output
  hresult = deckLinkIO->GetDisplayModeIterator(&displayModeIterator);
  if (hresult != S_OK) { goto bail; }

  // List all supported output display modes
  while (displayModeIterator->Next(&displayMode) == S_OK) {

    status = napi_create_object(env, &modeobj);
    CHECK_BAIL;

    #ifdef WIN32
    BSTR			displayModeBSTR = NULL;
    hresult = displayMode->GetName(&displayModeBSTR);
    if (hresult == S_OK)
    {
      _bstr_t displayMode(displayModeBSTR, false);
      strcpy(modeName, (const char *) displayMode);
    }
    #elif __APPLE__
    CFStringRef	displayModeCFString = NULL;
    hresult = displayMode->GetName(&displayModeCFString);
  	if (hresult == S_OK) {
  	  CFStringGetCString(displayModeCFString, modeName, sizeof(modeName), kCFStringEncodingMacRoman);
  	  CFRelease(displayModeCFString);
    }
    #else
    hresult = displayMode->GetName((const char **) &modeName);
    #endif

    status = napi_create_string_utf8(env, modeName, NAPI_AUTO_LENGTH, &item);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "name", item);
    CHECK_BAIL;

		modeWidth = displayMode->GetWidth();
    status = napi_create_int64(env, modeWidth, &item);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "width", item);
    CHECK_BAIL;

		modeHeight = displayMode->GetHeight();
    status = napi_create_int64(env, modeHeight, &item);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "height", item);
    CHECK_BAIL;

	  displayMode->GetFrameRate(&frameRateDuration, &frameRateScale);
		// printf(" %-20s \t %d x %d \t %7g FPS\t", displayModeString, modeWidth, modeHeight, (double)frameRateScale / (double)frameRateDuration);
    status = napi_create_array(env, &item);
    CHECK_BAIL;
    status = napi_create_int64(env, frameRateDuration, &itemPart);
    CHECK_BAIL;
    status = napi_set_element(env, item, 0, itemPart);
    CHECK_BAIL;
    status = napi_create_int64(env, frameRateScale, &itemPart);
    CHECK_BAIL;
    status = napi_set_element(env, item, 1, itemPart);
    CHECK_BAIL;
    status = napi_set_named_property(env, modeobj, "frameRate", item);
    CHECK_BAIL;

    status = napi_create_array(env, &item);
    CHECK_BAIL;

    partIndex = 0;
    pixelFormatIndex = 0;

		while ((gKnownPixelFormats[pixelFormatIndex] != 0) &&
        (gKnownPixelFormatNames[pixelFormatIndex] != NULL)) {
			if ((deckLinkIO->DoesSupportVideoMode(
        displayMode->GetDisplayMode(), gKnownPixelFormats[pixelFormatIndex],
        bmdVideoOutputFlagDefault, &displayModeSupport, NULL) == S_OK)
					&& (displayModeSupport != bmdDisplayModeNotSupported)) {

				status = napi_create_string_utf8(env, gKnownPixelFormatNames[pixelFormatIndex],
          NAPI_AUTO_LENGTH, &itemPart);
        CHECK_BAIL;
        status = napi_set_element(env, item, partIndex++, itemPart);
        CHECK_BAIL;
			}

			pixelFormatIndex++;
		}
    status = napi_set_named_property(env, modeobj, "videoModes", item);
    CHECK_BAIL;

    status = napi_set_element(env, modes, modeIndex++, modeobj);
    CHECK_BAIL;

    displayMode->Release();
  }

bail:
  if (deckLinkIO != nullptr) deckLinkIO->Release();
  if (displayModeIterator != nullptr) displayModeIterator->Release();
  if (displayMode != nullptr) displayMode->Release();

  return status;
}

napi_value getDeviceConfig(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value result, param;
  napi_valuetype type;
  uint32_t deviceIndex = 0;
  uint32_t configIndex = 0;

  status = napi_create_object(env, &result);
  CHECK_STATUS;

  IDeckLinkIterator* deckLinkIterator;
  HRESULT	hresult;
  IDeckLink* deckLink = nullptr;
  IDeckLinkConfiguration* deckLinkConfig = nullptr;

  int64_t intValue;
  double floatValue;
  #ifdef WIN32
  BOOL flag;
  BSTR stringValueBSTR;
  #elif __APPLE__
  bool flag;
  CFStringRef stringValueCFStr;
  #else
  bool flag;
  char* stringValue;
  #endif

  size_t argc = 1;
  napi_value argv[1];
  status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
  CHECK_STATUS;

  if (argc >= 1) {
    status = napi_typeof(env, argv[0], &type);
    CHECK_STATUS;
    if (type != napi_number) NAPI_THROW_ERROR("Argument must be a device index.");
    status = napi_get_value_uint32(env, argv[0], &deviceIndex);
    CHECK_STATUS;
  }

  #ifdef WIN32
  CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
  #else
  deckLinkIterator = CreateDeckLinkIteratorInstance();
  #endif
  if (deckLinkIterator == nullptr) NAPI_THROW_ERROR("Unable to load DeckLinkAPI.");

  for ( uint32_t x = 0 ; x <= deviceIndex ; x++ ) {
    if (deckLinkIterator->Next(&deckLink) != S_OK) {
      deckLinkIterator->Release();
      NAPI_THROW_ERROR("Device index exceeds the number of installed devices.");
    }
  }

  deckLinkIterator->Release();

  hresult = deckLink->QueryInterface(IID_IDeckLinkConfiguration, (void**)&deckLinkConfig);
  if (hresult != S_OK) {
    napi_throw_error(env, nullptr, "Failed to get deck link configuration for device.");
    goto bail;
  }

  status = napi_create_string_utf8(env, "configuration", NAPI_AUTO_LENGTH, &param);
  CHECK_BAIL;
  status = napi_set_named_property(env, result, "type", param);
  CHECK_BAIL;

  status = napi_create_uint32(env, deviceIndex, &param);
  CHECK_BAIL;
  status = napi_set_named_property(env, result, "deviceIndex", param);
  CHECK_BAIL;

  while ( (knownConfigNames[configIndex] != nullptr) &&
            (knownConfigValues[configIndex] != 0) &&
            (knownConfigTypes[configIndex] != 0) ) {

    switch (knownConfigTypes[configIndex]) {
      case macadamFlag:
        hresult = deckLinkConfig->GetFlag(knownConfigValues[configIndex], &flag);
        switch (hresult) {
          case S_OK:
            #ifdef WIN32
            status = napi_get_boolean(env, flag == TRUE ? true : false, &param);
            #else
            status = napi_get_boolean(env, flag, &param);
            #endif
            CHECK_BAIL;
            break;
          case E_NOTIMPL:
            status = napi_get_null(env, &param);
            CHECK_BAIL;
            break;
          case E_FAIL:
          case E_INVALIDARG:
          default:
            status = napi_get_undefined(env, &param);
            CHECK_BAIL;
            break;
        }
        status = napi_set_named_property(env, result, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case macadamInt64:
        hresult = deckLinkConfig->GetInt(knownConfigValues[configIndex], &intValue);
        switch (hresult) {
          case S_OK:
            status = napi_create_int64(env, intValue, &param);
            CHECK_BAIL;
            break;
          case E_NOTIMPL:
            status = napi_get_null(env, &param);
            CHECK_BAIL;
            break;
          case E_FAIL:
          case E_INVALIDARG:
          default:
            status = napi_get_undefined(env, &param);
            CHECK_BAIL;
            break;
        }
        status = napi_set_named_property(env, result, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case macadamFloat:
        hresult = deckLinkConfig->GetFloat(knownConfigValues[configIndex], &floatValue);
        switch (hresult) {
          case S_OK:
            status = napi_create_double(env, floatValue, &param);
            CHECK_BAIL;
            break;
          case E_NOTIMPL:
            status = napi_get_null(env, &param);
            CHECK_BAIL;
            break;
          case E_FAIL:
          case E_INVALIDARG:
          default:
            status = napi_get_undefined(env, &param);
            CHECK_BAIL;
            break;
        }
        status = napi_set_named_property(env, result, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case macadamString:
        #ifdef WIN32
        hresult = deckLinkConfig->GetString(knownConfigValues[configIndex], &stringValueBSTR);
        if (hresult == S_OK) {
          _bstr_t stringValue(stringValueBSTR, false);
          status = napi_create_string_utf8(env, (char*) stringValue, NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
        }
        #elif __APPLE__
        hresult = deckLinkConfig->GetString(knownConfigValues[configIndex], &stringValueCFStr);
        if (hresult == S_OK) {
          char stringValue [256];
          CFStringGetCString(stringValueCFStr, stringValue, sizeof(stringValue), kCFStringEncodingMacRoman);
          CFRelease(stringValueCFStr);
          status = napi_create_string_utf8(env, stringValue, NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
        }
        #else
        hresult = deckLinkConfig->GetString(knownConfigValues[configIndex], (const char **) &stringValue);
        if (hresult == S_OK) {
          status = napi_create_string_utf8(env, stringValue, NAPI_AUTO_LENGTH, &param);
          free(stringValue);
          CHECK_BAIL;
        }
        #endif
        switch (hresult) {
          case S_OK:
            break;
          case E_NOTIMPL:
            status = napi_get_null(env, &param);
            CHECK_BAIL;
            break;
          case E_FAIL:
          case E_INVALIDARG:
          default:
            status = napi_get_undefined(env, &param);
            CHECK_BAIL;
            break;
        }
        status = napi_set_named_property(env, result, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      default:
        printf("DEBUG: Unexpected config parameter type %i named %s.\n",
          knownConfigTypes[configIndex], knownConfigNames[configIndex]);
        break;
    }
    configIndex++;
  }

  bail:
    if (deckLinkConfig != nullptr) { deckLinkConfig->Release(); }
    if (deckLink != nullptr) { deckLink->Release(); }
    return result;
}

napi_value setDeviceConfig(napi_env env, napi_callback_info info) {
  napi_status status;
  napi_value result = nullptr, param, errors;
  napi_valuetype type;
  uint32_t deviceIndex = 0;
  uint32_t configIndex = 0;
  bool isArray, hasProperty;
  HRESULT hresult = S_OK;

  IDeckLink* deckLink = nullptr;
  IDeckLinkIterator* deckLinkIterator = nullptr;
  IDeckLinkConfiguration* deckLinkConfig = nullptr;

  int64_t intValue;
  double floatValue;
  size_t bufSize;
  char* buf;
  #ifdef WIN32
  BOOL flag;
  #elif __APPLE__
  bool flag;
  CFStringRef stringValueCFStr;
  #else
  bool flag;
  #endif

  size_t argc = 1;
  napi_value argv[1];
  status = napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
  CHECK_STATUS;

  if (argc != 1)
    NAPI_THROW_ERROR("Properties to set must be provided as a single object.");

  status = napi_typeof(env, argv[0], &type);
  CHECK_STATUS;
  status = napi_is_array(env, argv[0], &isArray);
  CHECK_STATUS;
  if ((type != napi_object) || isArray)
    NAPI_THROW_ERROR("Configuration properties must be provided as an object.");

  status = napi_get_named_property(env, argv[0], "deviceIndex", &param);
  CHECK_STATUS;
  status = napi_typeof(env, param, &type);
  CHECK_STATUS;
  if (type == napi_number) {
    status = napi_get_value_uint32(env, param, &deviceIndex);
    CHECK_STATUS;
  }

  #ifdef WIN32
  CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void**)&deckLinkIterator);
  #else
  deckLinkIterator = CreateDeckLinkIteratorInstance();
  #endif
  if (deckLinkIterator == nullptr) NAPI_THROW_ERROR("Unable to load DeckLinkAPI.");

  for ( uint32_t x = 0 ; x <= deviceIndex ; x++ ) {
    if (deckLinkIterator->Next(&deckLink) != S_OK) {
      deckLinkIterator->Release();
      NAPI_THROW_ERROR("Device index exceeds the number of installed devices.");
    }
  }

  deckLinkIterator->Release();

  hresult = deckLink->QueryInterface(IID_IDeckLinkConfiguration, (void**)&deckLinkConfig);
  if (hresult != S_OK) {
    napi_throw_error(env, nullptr, "Failed to get deck link configuration for device.");
    goto bail;
  }

  status = napi_create_object(env, &result);
  CHECK_BAIL;
  status = napi_create_string_utf8(env, "setConfiguration", NAPI_AUTO_LENGTH, &param);
  CHECK_BAIL;
  status = napi_set_named_property(env, result, "type", param);
  CHECK_BAIL;

  status = napi_create_uint32(env, deviceIndex, &param);
  CHECK_BAIL;
  status = napi_set_named_property(env, result, "deviceIndex", param);
  CHECK_BAIL;

  status = napi_create_object(env, &errors);
  CHECK_BAIL;

  while ( (knownConfigNames[configIndex] != nullptr) &&
            (knownConfigValues[configIndex] != 0) &&
            (knownConfigTypes[configIndex] != 0) ) {

    status = napi_has_named_property(env, argv[0], knownConfigNames[configIndex], &hasProperty);
    CHECK_BAIL;
    if (!hasProperty) { configIndex++; continue; }

    status = napi_get_named_property(env, argv[0], knownConfigNames[configIndex], &param);
    CHECK_BAIL;
    status = napi_typeof(env, param, &type);
    CHECK_BAIL;
    switch (knownConfigTypes[configIndex]) {
      case macadamFlag:
        if (type != napi_boolean) {
          status = napi_create_string_utf8(env,
            "Cannot set configuration property as the given value is not of Boolean type.",
            NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
          status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
          CHECK_BAIL;
          configIndex++; continue;
        }
        #ifdef WIN32
        bool cFlag;
        status = napi_get_value_bool(env, param, &cFlag);
        flag = cFlag == TRUE ? true : false;
        #else
        status = napi_get_value_bool(env, param, &flag);
        #endif
        CHECK_BAIL;
        hresult = deckLinkConfig->SetFlag(knownConfigValues[configIndex], flag);
        break;
      case macadamInt64:
        if (type != napi_number) {
          status = napi_create_string_utf8(env,
            "Cannot set configuration property as the given value is not a number.",
            NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
          status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
          CHECK_BAIL;
          configIndex++; continue;
        }
        status = napi_get_value_int64(env, param, &intValue);
        CHECK_BAIL;
        hresult = deckLinkConfig->SetInt(knownConfigValues[configIndex], intValue);
        break;
      case macadamFloat:
        if (type != napi_number) {
          status = napi_create_string_utf8(env,
            "Cannot set configuration property as the given value is not a number.",
            NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
          status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
          CHECK_BAIL;
          configIndex++; continue;
        }
        status = napi_get_value_double(env, param, &floatValue);
        CHECK_BAIL;
        hresult = deckLinkConfig->SetFloat(knownConfigValues[configIndex], floatValue);
        break;
      case macadamString: {
        if (type != napi_string) {
          status = napi_create_string_utf8(env,
            "Canoot set configuration property as the given value is not a string.",
            NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
          configIndex++; continue;
        }

        status = napi_get_value_string_utf8(env, param, nullptr, 0, &bufSize);
        CHECK_BAIL;
        buf = (char*) malloc(sizeof(char) * (bufSize + 1));
        status = napi_get_value_string_utf8(env, param, buf, bufSize + 1, &bufSize);
        CHECK_BAIL;
        #ifdef WIN32
        bstr_t stringValueBSTR(buf);
        hresult = deckLinkConfig->SetString(knownConfigValues[configIndex], stringValueBSTR);
        // delete stringValueBSTR;
        #elif __APPLE__
        stringValueCFStr = CFStringCreateWithCString(nullptr, buf, kCFStringEncodingMacRoman);
        if (stringValueCFStr == nullptr) {
          status = napi_create_string_utf8(env,
            "Failed to create CFString.",
            NAPI_AUTO_LENGTH, &param);
          CHECK_BAIL;
          configIndex++; continue;
        }
        hresult = deckLinkConfig->SetString(knownConfigValues[configIndex], stringValueCFStr);
        CFRelease(stringValueCFStr);
        #else
        hresult = deckLinkConfig->SetString(knownConfigValues[configIndex], buf);
        #endif
        free(buf);

        break;
      }
      default:
        hresult = E_UNEXPECTED;
        break;
    }
    switch (hresult) {
      case S_OK:
        status = napi_set_named_property(env, result, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case E_FAIL:
        status = napi_create_string_utf8(env,
          "General failure (E_FAIL) when trying to set configuration property.",
          NAPI_AUTO_LENGTH, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case E_INVALIDARG:
        status = napi_create_string_utf8(env,
          "No known configuration property of this type (E_INVALIDARG).",
          NAPI_AUTO_LENGTH, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case E_NOTIMPL:
        status = napi_create_string_utf8(env,
          "The configuration property is known but not supported by this DeckLink hardware (E_NOTIMPL).",
          NAPI_AUTO_LENGTH, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
      case E_UNEXPECTED:
      default:
        status = napi_create_string_utf8(env,
          "Unexpected property type or other unexpected behaviour (E_UNEXPECTED).",
          NAPI_AUTO_LENGTH, &param);
        CHECK_BAIL;
        status = napi_set_named_property(env, errors, knownConfigNames[configIndex], param);
        CHECK_BAIL;
        break;
    }
    configIndex++;
  }

  status = napi_set_named_property(env, result, "errors", errors);
  CHECK_BAIL;

  bail:
    if (deckLinkConfig != nullptr) {
      hresult = deckLinkConfig->WriteConfigurationToPreferences();
      deckLinkConfig->Release();
      if (hresult != S_OK) {
        if (deckLink != nullptr) { deckLink->Release(); }
        NAPI_THROW_ERROR("Failed to store DeckLink configuration. Insufficient permissions.");
      }
    }
    if (deckLink != nullptr) { deckLink->Release(); }
    return result;
}

napi_value Init(napi_env env, napi_value exports) {
  napi_status status;
  napi_property_descriptor desc[] = {
    DECLARE_NAPI_METHOD("deckLinkVersion", deckLinkVersion),
    DECLARE_NAPI_METHOD("getFirstDevice", getFirstDevice),
    DECLARE_NAPI_METHOD("getDeviceInfo", getDeviceInfo),
    DECLARE_NAPI_METHOD("getDeviceConfig", getDeviceConfig),
    DECLARE_NAPI_METHOD("setDeviceConfig", setDeviceConfig),
    DECLARE_NAPI_METHOD("capture", capture),
    DECLARE_NAPI_METHOD("playback", playback),
    DECLARE_NAPI_METHOD("timecodeTest", timecodeTest)
   };
  status = napi_define_properties(env, exports, 8, desc);
  CHECK_STATUS;

  #ifdef WIN32
  HRESULT result;
  result = CoInitializeEx(NULL, COINIT_MULTITHREADED);
	if (FAILED(result))
	{
		fprintf(stderr, "Initialization of COM failed - result = %08x.\n", result);
	}
  #endif

  return exports;
}

NAPI_MODULE(nodencl, Init)
