A Python library for outputting video frames to Blackmagic DeckLink devices using the official DeckLink SDK. This library provides a simple interface for displaying static frames, solid colors, and dynamic content from NumPy arrays.
Written by Nick Shaw, www.antlerpost.com, with a lot of help from Claude Code!
- Static Frame Output: Display static images from NumPy arrays
- Solid Color Output: Display solid colors for testing and calibration
- Dynamic Updates: Update currently displayed frame
- Multiple Resolutions: Support for all display modes supported by your DeckLink device (SD, HD, 2K, 4K, 8K, and PC modes)
- 10-bit Y'CbCr Output: 10-bit Y'CbCr 4:2:2 (v210) (default for uint16/float data)
- 10 and 12-bit R'G'B' output: 10 and 12-bit R'G'B' 4:4:4
- HDR Support: SMPTE ST 2086 / CEA-861.3 HDR static metadata
- Y'CbCr matrix control: Rec.601 (SD only), Rec.709 (HD+), and Rec.2020 (HD+) matrix support
- Cross-Platform: Works on Windows, macOS, and Linux (this is in theory – only macOS build fully tested so far)
- Python 3.7 or higher
- Blackmagic DeckLink device (DeckLink, UltraStudio, or Intensity series)
- Blackmagic Desktop Video software installed
Python dependencies (NumPy >= 1.19.0, pybind11 >= 2.6.0) are automatically installed (if needed) during the build process.
SDK v14.1 headers for all platforms are included in the repository - no separate download needed.
All Platforms (macOS, Windows, Linux):
_vendor/decklink_sdk/Mac/include/- macOS headers_vendor/decklink_sdk/Win/include/- Windows headers_vendor/decklink_sdk/Linux/include/- Linux headers
The build system (CMake + scikit-build-core) automatically uses the correct platform-specific headers.
# Clone or download the library files
git clone https://github.com/nick-shaw/blackmagic-output.git
cd blackmagic-output
# Initialize submodules (required for advanced example using T-Pat test patterns)
git submodule update --init --recursive
# Install in development mode (this also installs numpy and pybind11 dependencies)
pip install -e .
# If upgrading from a previous development version, force reinstall:
pip install --force-reinstall -e .For examples and additional functionality:
pip install imageio pillow jsonschemaNote: While imageio / PIL can load 16-bit TIFF files correctly, 16-bit PNG files are often converted to 8-bit during loading due to PIL limitations. For reliable 16-bit workflows, use TIFF format.
import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode
# Create a simple test image (1080p R'G'B', normalized float)
frame = np.ones((1080, 1920, 3), dtype=np.float32)
frame[:, :] = [1.0, 0.0, 0.0] # Red frame
# Display the frame
with BlackmagicOutput() as output:
# Initialize device (uses first available device)
output.initialize()
# Display static frame at 1080p25
output.display_static_frame(frame, DisplayMode.HD1080p25)
# Keep displaying (Enter to stop)
input("Press Enter to stop...")Note: The explicit initialize() call is optional - display_static_frame() will automatically initialize the first available device (device_index=0) if not already initialized. Explicit initialization is useful for device selection, better error handling, and timing control:
# Simpler alternative - auto-initialization (uses first device)
with BlackmagicOutput() as output:
output.display_static_frame(frame, DisplayMode.HD1080p25)
input("Press Enter to stop...")The library provides two APIs:
- High-level Python wrapper (
blackmagic_output.BlackmagicOutput) - Convenient API for most cases - Low-level direct access (
decklink_output.DeckLinkOutput) - For more fine-grained control
Convenient Python wrapper for most video output operations.
get_available_devices() -> List[str]
Get list of available DeckLink device names.
initialize(device_index=0) -> bool
Initialize the specified DeckLink device.
device_index: Index of device to use (default: 0)- Returns: True if successful
Note: Explicit initialization is optional. Methods like display_static_frame() and display_solid_color() will automatically initialize the first available device (device_index=0) if not already initialized. Explicit initialization is recommended when you need:
- To select a specific device (when multiple DeckLink devices are present)
- Separate error handling for device initialization vs. frame display
- Control over initialization timing (e.g., to avoid delays during first frame display)
- To verify device availability before preparing frame data
get_supported_display_modes() -> List[dict]
Get list of supported display modes for the initialized device.
- Returns: List of dictionaries, each containing:
display_mode: DisplayMode enum valuename: Human-readable mode name (e.g., "1080p25", "2160p59.94")width: Frame width in pixelsheight: Frame height in pixelsframerate: Frame rate in frames per second
- Raises: RuntimeError if device not initialized
Example:
from blackmagic_output import BlackmagicOutput
with BlackmagicOutput() as output:
output.initialize()
modes = output.get_supported_display_modes()
for mode in modes:
print(f"{mode['name']}: {mode['width']}x{mode['height']} @ {mode['framerate']:.2f} fps")is_pixel_format_supported(display_mode, pixel_format) -> bool
Check if a pixel format is supported for a given display mode.
display_mode: Display mode to checkpixel_format: Pixel format to check- Returns: True if the mode / format combination is supported
display_static_frame(frame_data, display_mode, pixel_format=PixelFormat.YUV10, matrix=None, hdr_metadata=None, input_narrow_range=False, output_narrow_range=True) -> bool
Display a static frame continuously.
frame_data: NumPy array with image data:- RGB: shape (height, width, 3), dtype uint8 / uint16 / float32 / float64
- BGRA: shape (height, width, 4), dtype uint8
display_mode: Video resolution and frame ratepixel_format: Pixel format (default: YUV10, automatically uses BGRA for uint8 data)matrix: Optional R'G'B' to Y'CbCr conversion matrix (Matrix.Rec601,Matrix.Rec709orMatrix.Rec2020). Only used with YUV10 format. If not specified, auto-detects based on resolution: SD modes (NTSC, PAL) use Rec.601, HD and higher use Rec.709hdr_metadata: Optional HDR metadata dict with keys:'eotf': Eotf enum (SDR, PQ, or HLG)'custom': Optional HdrMetadataCustom object for custom metadata values
input_narrow_range: Whether to interpret integerframe_dataas narrow range (float is always interpreted as full range). Default: Falseoutput_narrow_range: Whether to output a narrow range signal. Default: True- Returns: True if successful
display_solid_color(color, display_mode, pixel_format=PixelFormat.YUV10, matrix=None, hdr_metadata=None, input_narrow_range=False, output_narrow_range=True, patch=None, background_color=None) -> bool
Display a solid color continuously.
color: R'G'B' tuple (r, g, b) with values:- Integer values (0-1023): Interpreted as 10-bit values
- Float values (0.0-1.0): Interpreted as normalized full range values
display_mode: Video resolution and frame ratepixel_format: Pixel format (default: YUV10)matrix: RGB to Y'CbCr conversion matrix (Rec601, Rec709 or Rec2020). Only applies when pixel_format is YUV10. If not specified, auto-detects based on resolution: SD modes (NTSC, PAL) use Rec.601, HD and higher use Rec.709hdr_metadata: Optional HDR metadata dict with 'eotf' (and optional 'custom') keysinput_narrow_range: Whether to interpret integercolorvalues as narrow range (float is always interpreted as full range). Default: Falseoutput_narrow_range: Whether to output a narrow range signal. Default: Truepatch: Optional tuple (center_x, center_y, width, height) with normalized coordinates (0.0-1.0):- center_x, center_y: Center position of the patch (0.5, 0.5 = center of screen)
- width, height: Patch dimensions (1.0, 1.0 = full screen)
- If None, displays full screen solid color. Default: None
background_color: R'G'B' tuple for background when using patch parameter. Uses same format ascolorparameter (respectinginput_narrow_range). If None, defaults to black. Default: None- Returns: True if successful
update_frame(frame_data) -> bool
Update currently displayed frame with new data.
frame_data: New frame data as NumPy array- Returns: True if successful
get_display_mode_info(display_mode) -> dict
Get information about a display mode.
- Returns: Dictionary with 'width', 'height', 'framerate'
get_current_output_info() -> dict
Get information about the current output configuration.
- Returns: Dictionary with 'display_mode_name', 'pixel_format_name', 'width', 'height', 'framerate', 'rgb444_mode_enabled'
stop() -> bool
Stop video output.
- Returns: True if successful
Stops displaying frames but keeps the device initialized and ready for immediate reuse. After calling stop(), you can call display_static_frame() or display_solid_color() again without needing to re-initialize.
cleanup()
Cleanup resources and stop output.
Stops video output (if running) and releases all device resources. After cleanup(), the device must be re-initialized with initialize() before it can be used again. This method automatically calls stop() internally, so there is no need to call stop() first.
Context Manager Support:
with BlackmagicOutput() as output:
output.initialize()
# ... use output ...
# Automatic cleanup() called on exitThe context manager automatically calls cleanup() when exiting, so explicit cleanup is not needed when using the with statement.
create_test_pattern(width, height, pattern='gradient', grad_start=0.0, grad_end=1.0) -> np.ndarray
Create test patterns for display testing and calibration.
width: Frame width in pixelsheight: Frame height in pixelspattern: Pattern type -'gradient','bars', or'checkerboard'grad_start: Float starting value for gradient pattern (default: 0.0, use <0.0 for sub-black)grad_end: Float ending value for gradient pattern (default: 1.0, use >1.0 for super-white)- Returns: R'G'B' array (H×W×3), dtype float32
Direct C++ API for more fine-grained control.
get_device_list() -> List[str]
Get list of available DeckLink devices.
initialize(device_index=0) -> bool
Initialize the specified DeckLink device.
get_supported_display_modes() -> List[DisplayModeInfo]
Get list of supported display modes for the initialized device.
- Returns: List of DisplayModeInfo objects with display_mode, name, width, height, framerate
is_pixel_format_supported(display_mode, pixel_format) -> bool
Check if a pixel format is supported for a given display mode.
get_video_settings(display_mode) -> VideoSettings
Get video settings object for a display mode.
set_hdr_metadata(colorimetry: Gamut, eotf: Eotf)
Set HDR metadata with default values. Must be called before setup_output().
set_hdr_metadata_custom(colorimetry: Gamut, eotf: Eotf, custom: HdrMetadataCustom)
Set HDR metadata with custom values. Must be called before setup_output().
clear_hdr_metadata()
Clear HDR metadata and reset to SDR. Call before setup_output() if you want to ensure no HDR metadata is present.
setup_output(settings: VideoSettings) -> bool
Setup output with detailed settings.
set_frame_data(data: np.ndarray) -> bool
Set frame data from NumPy array (must be in correct format).
display_frame() -> bool
Display the current frame synchronously. Call this after set_frame_data() to update the display.
get_current_output_info() -> OutputInfo
Get information about the current output configuration.
- Returns: OutputInfo struct with display_mode_name, pixel_format_name, width, height, framerate, rgb444_mode_enabled
stop_output() -> bool
Stop video output.
- Returns: True if successful
Stops displaying frames but keeps the device initialized and ready for immediate reuse. After calling stop_output(), you can call setup_output() and display_frame() again without needing to re-initialize.
cleanup()
Cleanup all resources.
Stops video output (if running) and releases all device resources. After cleanup(), the device must be re-initialized with initialize() before it can be used again. This method automatically calls stop_output() internally, so there is no need to call stop_output() first.
VideoSettings
class VideoSettings:
mode: DisplayMode # Video mode (resolution / framerate)
format: PixelFormat # Pixel format
width: int # Frame width in pixels
height: int # Frame height in pixels
framerate: float # Frame rate (e.g., 25.0, 29.97, 60.0)
colorimetry: Gamut # Y'CbCr matrix (Rec601 / Rec709 / Rec2020)
eotf: Eotf # Transfer function (SDR / PQ / HLG)Note: What the Blackmagic SDK refers to as the "color space" (BMDColorspace) is in fact the matrix used for R'G'B' to Y'CbCr conversion, not the gamut of the image data. For example, ARRI Wide Gamut data would typically be converted using a Rec.709 matrix.
HdrMetadataCustom
class HdrMetadataCustom:
# Display primaries (xy chromaticity coordinates)
display_primaries_red_x: float
display_primaries_red_y: float
display_primaries_green_x: float
display_primaries_green_y: float
display_primaries_blue_x: float
display_primaries_blue_y: float
white_point_x: float
white_point_y: float
# Luminance values (nits)
max_mastering_luminance: float
min_mastering_luminance: float
max_content_light_level: float
max_frame_average_light_level: floatOutputInfo
class OutputInfo:
display_mode: DisplayMode # Current display mode
pixel_format: PixelFormat # Current pixel format
width: int # Frame width in pixels
height: int # Frame height in pixels
framerate: float # Frame rate (e.g., 25.0, 29.97, 60.0)
rgb444_mode_enabled: bool # Whether RGB 4:4:4 mode is enabled
display_mode_name: str # Human-readable display mode name
pixel_format_name: str # Human-readable pixel format nameDisplayModeInfo
class DisplayModeInfo:
display_mode: DisplayMode # Display mode enum value
name: str # Human-readable mode name
width: int # Frame width in pixels
height: int # Frame height in pixels
framerate: float # Frame rate (e.g., 25.0, 29.97, 60.0)rgb_to_bgra(rgb_array, width, height) -> np.ndarray
Convert RGB to BGRA format.
rgb_array: NumPy array (H×W×3), dtype uint8- 8-bit data is always treated as full range, but 8-bit Y'CbCr output will always be narrow range
- Returns: BGRA array (H×W×4), dtype uint8
rgb_uint16_to_yuv10(rgb_array, width, height, matrix=Matrix.Rec709, input_narrow_range=False, output_narrow_range=True) -> np.ndarray
Convert R'G'B' uint16 to 10-bit Y'CbCr v210 format.
rgb_array: NumPy array (H×W×3), dtype uint16 (0-65535 range)matrix: R'G'B' to Y'CbCr conversion matrix (Matrix.Rec601, Matrix.Rec709 or Matrix.Rec2020). Default: Matrix.Rec709input_narrow_range: Whether to interpret thergb_arrayas narrow range. Default: Falseoutput_narrow_range: Whether to encode the Y'CbCr as narrow range. Default: True- Returns: Packed v210 array
rgb_float_to_yuv10(rgb_array, width, height, matrix=Matrix.Rec709, output_narrow_range=True) -> np.ndarray
Convert R'G'B' float to 10-bit Y'CbCr v210 format.
rgb_array: NumPy array (H×W×3), dtype float32 (0.0-1.0 range)matrix: R'G'B' to Y'CbCr conversion matrix (Matrix.Rec601, Matrix.Rec709 or Matrix.Rec2020). Default: Matrix.Rec709output_narrow_range: Whether to encode the Y'CbCr as narrow range. Default: True- Returns: Packed v210 array
rgb_uint16_to_rgb10(rgb_array, width, height, input_narrow_range=True, output_narrow_range=True) -> np.ndarray
Convert R'G'B' uint16 to 10-bit R'G'B' (bmdFormat10BitRGBXLE) format.
rgb_array: NumPy array (H×W×3), dtype uint16 (0-65535 range)input_narrow_range: Whether to interpret thergb_arrayas narrow range. Default: Trueoutput_narrow_range: Whether to output narrow range. Default: True- Returns: Packed 10-bit R'G'B' array
rgb_float_to_rgb10(rgb_array, width, height, output_narrow_range=True) -> np.ndarray
Convert R'G'B' float to 10-bit R'G'B' (bmdFormat10BitRGBXLE) format.
rgb_array: NumPy array (H×W×3), dtype float32 (0.0-1.0 range)output_narrow_range: Whether to output narrow range. Default: True- Returns: Packed 10-bit R'G'B' array
rgb_uint16_to_rgb12(rgb_array, width, height, input_narrow_range=False, output_narrow_range=False) -> np.ndarray
Convert R'G'B' uint16 to 12-bit R'G'B' (bmdFormat12BitRGBLE) format.
rgb_array: NumPy array (H×W×3), dtype uint16 (0-65535 range)input_narrow_range: Whether to interpret thergb_arrayas narrow range. Default: Falseoutput_narrow_range: Whether to output narrow range. Default: False- Returns: Packed 12-bit R'G'B' array
rgb_float_to_rgb12(rgb_array, width, height, output_narrow_range=False) -> np.ndarray
Convert R'G'B' float to 12-bit R'G'B' (bmdFormat12BitRGBLE) format.
rgb_array: NumPy array (H×W×3), dtype float32 (0.0-1.0 range)output_narrow_range: Whether to output narrow range. Default: False- Returns: Packed 12-bit R'G'B' array
DisplayMode
The library supports all display modes available on your DeckLink device. Display mode settings (resolution, framerate) are queried dynamically from the hardware. Common examples include:
HD1080p25: 1920×1080 @ 25fpsHD1080p30: 1920×1080 @ 30fpsHD1080p50: 1920×1080 @ 50fpsHD1080p60: 1920×1080 @ 60fpsHD720p50: 1280×720 @ 50fpsHD720p60: 1280×720 @ 60fps
Additional modes are available including SD (NTSC, PAL), 2K, 4K, 8K, and PC display modes. The complete list of DisplayMode values can be found in src/blackmagic_output/blackmagic_output.py.
Querying Available Display Modes:
You can query which display modes are supported by your specific DeckLink device using get_supported_display_modes():
from blackmagic_output import BlackmagicOutput
with BlackmagicOutput() as output:
output.initialize()
# Get all supported display modes
modes = output.get_supported_display_modes()
print(f"Device supports {len(modes)} display modes:\n")
for mode in modes:
print(f"{mode['name']}: {mode['width']}x{mode['height']} @ {mode['framerate']:.2f} fps")To determine which pixel formats are supported for a specific display mode, use is_pixel_format_supported():
from blackmagic_output import BlackmagicOutput, DisplayMode, PixelFormat
with BlackmagicOutput() as output:
output.initialize()
# Test pixel format support for a specific mode
print("Pixel formats supported for HD1080p25:")
test_formats = [PixelFormat.YUV10, PixelFormat.RGB10, PixelFormat.RGB12]
for fmt in test_formats:
supported = output.is_pixel_format_supported(DisplayMode.HD1080p25, fmt)
status = "✓" if supported else "✗"
print(f"{status} {fmt.name}")PixelFormat
BGRA: 8-bit BGRA (automatically used for uint8 data)- Note: Over SDI, BGRA data is output as narrow range 8-bit Y'CbCr 4:2:2, not RGB. The BGRA name refers to the input buffer format, not the SDI wire format.
YUV10: 10-bit Y'CbCr 4:2:2 (v210) - default for uint16 / float data- Defaults to narrow range: Y: 64-940, UV: 64-960. Supports full range Y'CbCr (0-1023, as per Rec. ITU-T H.273) if
output_narrow_rangeis False in the high level API
- Defaults to narrow range: Y: 64-940, UV: 64-960. Supports full range Y'CbCr (0-1023, as per Rec. ITU-T H.273) if
RGB10: 10-bit R'G'B' (bmdFormat10BitRGBXLE) - native R'G'B' output without Y'CbCr conversion- uint16 input: Configurable interpretation via
input_narrow_rangeparameter - float input: Always interpreted as full range (0.0-1.0)
- Output range configurable via
output_narrow_rangeparameter - Defaults:
input_narrow_range=True, output_narrow_range=True
- uint16 input: Configurable interpretation via
RGB12: 12-bit R'G'B' (bmdFormat12BitRGBLE) - native R'G'B' output with 12-bit precision- uint16 input: Configurable interpretation via
input_narrow_rangeparameter - float input: Always interpreted as full range (0.0-1.0)
- Output range configurable via
output_narrow_rangeparameter - Defaults:
input_narrow_range=False, output_narrow_range=False
- uint16 input: Configurable interpretation via
Important: While this library supports both narrow and full range output encoding via the output_narrow_range parameter, the Blackmagic DeckLink SDK (v14.1) does not provide APIs to control the full range flag in the VPID, as per SMPTE ST 425-1 (byte 4, bit 7):
-
YUV10: The library can encode full range Y'CbCr (0-1023) with
output_narrow_range=False, but cannot set the full range flag in the VPID. Downstream devices may well assume narrow range. -
RGB10: The convention is that 10-bit RGB is narrow range, as described in the Blackmagic SDK, so using
output_narrow_range=Falsemay cause downstream devices to misinterpret the signal. -
RGB12: The convention is that 12-bit RGB is full range, as described in the Blackmagic SDK, so using
output_narrow_range=Truemay cause downstream devices to misinterpret the signal.
The output_narrow_range parameter controls the actual encoded values in the output stream, not metadata signaling. Use it when you know the downstream device will correctly interpret the range, or when the receiving device allows manual range configuration.
Matrix (High-level API)
Rec709: ITU-R BT.709 R'G'B' to Y'CbCr conversion matrix (standard HD)Rec2020: ITU-R BT.2020 R'G'B' to Y'CbCr conversion matrix (wide color gamut for HDR)
Gamut (Low-level API, same values as Matrix)
Rec709: ITU-R BT.709 colorimetry (standard HD)Rec2020: ITU-R BT.2020 colorimetry (wide color gamut for HDR)
Eotf
SDR: Standard Dynamic Range (BT.1886 transfer function)PQ: Perceptual Quantizer (SMPTE ST 2084, HDR10)HLG: Hybrid Log-Gamma (HDR broadcast standard)
from blackmagic_output import BlackmagicOutput, DisplayMode, create_test_pattern
# Create color bars test pattern
frame = create_test_pattern(1920, 1080, 'bars')
with BlackmagicOutput() as output:
output.initialize() # Optional - auto-initializes on first display if omitted
output.display_static_frame(frame, DisplayMode.HD1080p25)
input("Press Enter to stop...")import numpy as np
import time
from blackmagic_output import BlackmagicOutput, DisplayMode
with BlackmagicOutput() as output:
# Start with black frame
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
output.display_static_frame(frame, DisplayMode.HD1080p25)
# Animate
for i in range(100):
# Create moving pattern
frame.fill(0.0)
offset = i * 10
frame[:, offset:offset+100] = [1.0, 1.0, 1.0] # White bar
output.update_frame(frame)
time.sleep(1 / 25) # Limit update rate (actual rate will be lower due to processing overhead)import imageio.v3 as iio
import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode
# Load image (preserves bit depth for 16-bit TIFFs, etc.)
# Note: Use TIFF for reliable 16-bit support (PNGs may convert to 8-bit)
frame = iio.imread('your_image.tif')
# Resize if needed
if frame.shape[0] != 1080 or frame.shape[1] != 1920:
from PIL import Image
img = Image.fromarray(frame)
img = img.resize((1920, 1080), Image.Resampling.LANCZOS)
frame = np.array(img)
# Remove alpha channel if present
if frame.shape[2] == 4:
frame = frame[:, :, :3]
# Display image (format auto-detected from dtype)
with BlackmagicOutput() as output:
output.display_static_frame(frame, DisplayMode.HD1080p25)
input("Press Enter to stop...")import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode
# Create float R'G'B' image (0.0-1.0 range)
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# Example: gradient in float space
for y in range(1080):
for x in range(1920):
frame[y, x] = [
x / 1920, # Red gradient
y / 1080, # Green gradient
0.5 # Blue constant
]
# Output as 10-bit Y'CbCr (automatically selected for float data)
with BlackmagicOutput() as output:
output.display_static_frame(frame, DisplayMode.HD1080p25)
input("Press Enter to stop...")import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode
# Create uint16 R'G'B' image (0-65535 range)
# Useful for 10-bit / 12-bit / 16-bit image processing pipelines
frame = np.zeros((1080, 1920, 3), dtype=np.uint16)
# Full range gradient
for x in range(1920):
frame[:, x, 0] = int(x / 1920 * 65535) # Red gradient
# Output as 10-bit Y'CbCr (automatically selected for uint16 data)
with BlackmagicOutput() as output:
output.display_static_frame(frame, DisplayMode.HD1080p25)
input("Press Enter to stop...")import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode, PixelFormat
# Create uint16 R'G'B' image (0-65535 range)
frame = np.zeros((1080, 1920, 3), dtype=np.uint16)
# Full range gradient
for x in range(1920):
frame[:, x, 0] = int(x / 1920 * 65535) # Red gradient
# Output as 10-bit R'G'B' (bit-shifted from 16-bit to 10-bit)
with BlackmagicOutput() as output:
output.display_static_frame(frame, DisplayMode.HD1080p25, PixelFormat.RGB10)
input("Press Enter to stop...")import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode, PixelFormat
# Create float R'G'B' image (0.0-1.0 range)
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# Gradient
for x in range(1920):
frame[:, x, 0] = x / 1920 # Red gradient
# Output as 10-bit R'G'B' with narrow range (0.0-1.0 maps to 64-940)
with BlackmagicOutput() as output:
output.display_static_frame(
frame,
DisplayMode.HD1080p25,
PixelFormat.RGB10,
output_narrow_range=True # Default: narrow range
)
input("Press Enter to stop...")import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode, PixelFormat
# Create float R'G'B' image (0.0-1.0 range)
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# Gradient
for x in range(1920):
frame[:, x, 0] = x / 1920 # Red gradient
# Output as 10-bit R'G'B' with full range (0.0-1.0 maps to 0-1023)
with BlackmagicOutput() as output:
output.display_static_frame(
frame,
DisplayMode.HD1080p25,
PixelFormat.RGB10,
output_narrow_range=False # Full range
)
input("Press Enter to stop...")For simple applications or quick testing, 8-bit RGB data can be used directly without conversion to float or uint16. Note that 8-bit data is always treated as full range RGB input and output as narrow range 8-bit Y'CbCr 4:2:2 over SDI.
import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode
# Create 8-bit R'G'B' image (0-255 range, full range)
frame = np.zeros((1080, 1920, 3), dtype=np.uint8)
# Simple gradient
for x in range(1920):
frame[:, x, 0] = int(255 * x / 1920) # Red gradient
# Output as 8-bit (BGRA format automatically selected for uint8 data)
with BlackmagicOutput() as output:
output.display_static_frame(frame, DisplayMode.HD1080p25)
input("Press Enter to stop...")import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode, Matrix, Eotf
# Create HDR content in normalised float (0.0-1.0 range)
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# Example: HDR gradient with extended range
for y in range(1080):
for x in range(1920):
frame[y, x] = [
x / 1920, # Red gradient
y / 1080, # Green gradient
0.5 # Blue constant
]
# Configure for HLG HDR output using the simplified API
with BlackmagicOutput() as output:
# Single call with matrix and HDR metadata
# YUV10 automatically selected for float data
output.display_static_frame(
frame,
DisplayMode.HD1080p25,
matrix=Matrix.Rec2020, # Use Rec.2020 matrix
hdr_metadata={'eotf': Eotf.HLG} # HLG with default metadata
)
input("Press Enter to stop...")Alternative: Low-level API for more control
import numpy as np
import decklink_output as dl
# Create HDR content
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# ... fill frame ...
# Configure for HLG HDR output using low-level API
output = dl.DeckLinkOutput()
output.initialize()
# Set HDR metadata BEFORE setup_output()
output.set_hdr_metadata(dl.Gamut.Rec2020, dl.Eotf.HLG)
# Setup output
settings = output.get_video_settings(dl.DisplayMode.HD1080p25)
settings.format = dl.PixelFormat.YUV10
output.setup_output(settings)
# Convert R'G'B' to Y'CbCr using Rec.2020 matrix
yuv_data = dl.rgb_float_to_yuv10(frame, 1920, 1080, dl.Matrix.Rec2020)
output.set_frame_data(yuv_data)
# Display the frame
output.display_frame()
input("Press Enter to stop...")
output.stop_output()
output.cleanup()import numpy as np
from blackmagic_output import BlackmagicOutput, DisplayMode, Matrix, Eotf
# Create HDR10 content with PQ transfer function applied
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# Fill with PQ-encoded HDR content
# A library such as Colour Science for Python (https://www.colour-science.org/) is needed for PQ encoding
# frame = colour.eotf(linear_rgb_data, 'ST 2084')
# Configure for HDR10 (PQ) output using the simplified API
with BlackmagicOutput() as output:
# Single call with Rec.2020 matrix and PQ metadata
# YUV10 automatically selected for float data
output.display_static_frame(
frame,
DisplayMode.HD1080p25,
matrix=Matrix.Rec2020,
hdr_metadata={'eotf': Eotf.PQ}
)
input("Press Enter to stop...")Alternative: Low-level API
import numpy as np
import decklink_output as dl
# Create HDR10 content
frame = np.zeros((1080, 1920, 3), dtype=np.float32)
# ... fill frame ...
# Configure for HDR10 (PQ) output
output = dl.DeckLinkOutput()
output.initialize()
# IMPORTANT: Set HDR metadata BEFORE setup_output()
# This embeds HDR metadata (including Rec.2020 primaries, EOTF, mastering display info) in frames
output.set_hdr_metadata(dl.Gamut.Rec2020, dl.Eotf.PQ)
# Setup output settings
settings = output.get_video_settings(dl.DisplayMode.HD1080p25)
settings.format = dl.PixelFormat.YUV10
output.setup_output(settings)
# Convert to Y'CbCr with Rec.2020 matrix
yuv_data = dl.rgb_float_to_yuv10(frame, 1920, 1080, dl.Gamut.Rec2020)
output.set_frame_data(yuv_data)
# Display the frame
output.display_frame()
input("Press Enter to stop...")
output.stop_output()
output.cleanup()The display_solid_color() method supports displaying color patches smaller than full screen, useful for testing, calibration, and creating custom test patterns.
import time
from blackmagic_output import BlackmagicOutput, DisplayMode
with BlackmagicOutput() as output:
# Full screen white (default behavior)
output.display_solid_color((1.0, 1.0, 1.0), DisplayMode.HD1080p25)
time.sleep(2)
# Centered 50% white patch on black background
output.display_solid_color(
(1.0, 1.0, 1.0),
DisplayMode.HD1080p25,
patch=(0.5, 0.5, 0.5, 0.5) # (center_x, center_y, width, height)
)
time.sleep(2)
# Small centered white patch (10% size) on gray background
output.display_solid_color(
(1.0, 1.0, 1.0),
DisplayMode.HD1080p25,
patch=(0.5, 0.5, 0.1, 0.1),
background_color=(0.5, 0.5, 0.5)
)
time.sleep(2)
# Red patch in top-left quadrant on blue background
output.display_solid_color(
(1.0, 0.0, 0.0),
DisplayMode.HD1080p25,
patch=(0.25, 0.25, 0.3, 0.3),
background_color=(0.0, 0.0, 1.0)
)
time.sleep(2)
# Horizontal bar across center (full width, half height)
# Using integer 10-bit values with narrow range
output.display_solid_color(
(940, 940, 64),
DisplayMode.HD1080p25,
patch=(0.5, 0.5, 1.0, 0.5),
background_color=(400, 400, 400),
input_narrow_range=True
)
time.sleep(2)Patch coordinates:
- All values are normalized (0.0-1.0) for resolution independence
center_x, center_y: Position of patch center (0.0 = left/top, 1.0 = right/bottom)width, height: Patch dimensions as fraction of screen (1.0 = full width/height)- Example:
(0.5, 0.5, 0.25, 0.25)= centered patch, 25% of screen size
Background color:
- Uses same format as foreground
color(integers 0-1023 or floats 0.0-1.0) - Defaults to black if not specified
- For integer colors with
input_narrow_range=True, black defaults to 64 instead of 0
HDR metadata is embedded into each video frame using the DeckLink SDK's IDeckLinkVideoFrameMetadataExtensions interface. When you call set_hdr_metadata(), the library automatically wraps each output frame with the specified metadata.
- Display Primaries: Automatically set to match the matrix parameter (unless explicitly specified via custom metadata)
- Matrix.Rec709 → Rec.709 primaries (x,y): R(0.64, 0.33), G(0.30, 0.60), B(0.15, 0.06)
- Matrix.Rec2020 → Rec.2020 primaries (x,y): R(0.708, 0.292), G(0.170, 0.797), B(0.131, 0.046)
- White Point: D65 (0.3127, 0.3290) for all matrices (unless explicitly specified)
- EOTF: Electro-Optical Transfer Function (SDR / Rec.709, PQ / SMPTE ST 2084, or HLG)
- Mastering Display Info: Default values for max / min luminance
- Content Light Levels: Max content light level and max frame average
When using Matrix.Rec709:
Display Primaries: Rec.709 (ITU-R BT.709)
Red: (0.64, 0.33)
Green: (0.30, 0.60)
Blue: (0.15, 0.06)
White Point: D65 (0.3127, 0.3290)
When using Matrix.Rec2020:
Display Primaries: Rec.2020 (ITU-R BT.2020)
Red: (0.708, 0.292)
Green: (0.170, 0.797)
Blue: (0.131, 0.046)
White Point: D65 (0.3127, 0.3290)
Luminance values:
Max Mastering Luminance: 1000 nits
Min Mastering Luminance: 0.0001 nits
Max Content Light Level: 1000 nits
Max Frame Average Light Level: 50 nitsHigh-level API:
from blackmagic_output import BlackmagicOutput, DisplayMode, PixelFormat, Matrix, Eotf
import decklink_output as dl
# Create custom metadata
custom = dl.HdrMetadataCustom()
custom.display_primaries_red_x = 0.708
custom.display_primaries_red_y = 0.292
# ... set other values ...
custom.max_mastering_luminance = 1000.0
custom.min_mastering_luminance = 0.0001
# Use in simplified API
with BlackmagicOutput() as output:
output.initialize()
output.display_static_frame(
frame,
DisplayMode.HD1080p25,
pixel_format=PixelFormat.YUV10,
matrix=Matrix.Rec2020,
hdr_metadata={'eotf': Eotf.PQ, 'custom': custom}
)
input("Press Enter to stop...")Low-level API:
For precise control over HDR metadata with the low-level API, use set_hdr_metadata_custom():
import decklink_output as dl
# Create custom metadata for specific mastering display
custom = dl.HdrMetadataCustom()
# Display primaries (chromaticity coordinates)
custom.display_primaries_red_x = 0.708
custom.display_primaries_red_y = 0.292
custom.display_primaries_green_x = 0.170
custom.display_primaries_green_y = 0.797
custom.display_primaries_blue_x = 0.131
custom.display_primaries_blue_y = 0.046
custom.white_point_x = 0.3127
custom.white_point_y = 0.3290
# Mastering display luminance
custom.max_mastering_luminance = 4000.0 # 4000 nits peak (e.g., for HDR10+ content)
custom.min_mastering_luminance = 0.0005 # 0.0005 nits black level
# Content light levels
custom.max_content_light_level = 2000.0 # 2000 nits max content (MaxCLL)
custom.max_frame_average_light_level = 400.0 # 400 nits average (MaxFALL)
output = dl.DeckLinkOutput()
output.initialize()
output.set_hdr_metadata_custom(dl.Gamut.Rec2020, dl.Eotf.PQ, custom)All 14 SMPTE ST 2086 / CEA-861.3 HDR static metadata fields are supported:
Display Primaries (xy chromaticity coordinates):
display_primaries_red_x,display_primaries_red_ydisplay_primaries_green_x,display_primaries_green_ydisplay_primaries_blue_x,display_primaries_blue_ywhite_point_x,white_point_y
Mastering Display Luminance:
max_mastering_luminance(nits) - Peak luminance of mastering displaymin_mastering_luminance(nits) - Minimum luminance of mastering display
Content Light Levels:
max_content_light_level(nits) - Maximum luminance of any pixel (MaxCLL)max_frame_average_light_level(nits) - Maximum average luminance of any frame (MaxFALL)
- Simplified API: With
display_static_frame(), HDR metadata and matrix are set in a single call - Low-level API call order: When using the low-level API,
set_hdr_metadata()must be called beforesetup_output() - Frame-level metadata: Metadata is embedded in every video frame, not set globally
- Matrix consistency: When using the simplified API, the same
matrixparameter is used for both metadata and R'G'B' →Y'CbCr conversion. With the low-level API, ensure consistency betweenset_hdr_metadata()and conversion functions. - Transfer function: The library only sets the metadata - you must apply the actual transfer function (PQ / HLG curve) to your RGB data before conversion
- All 14 metadata fields supported: The library implements all SMPTE ST 2086 / CEA-861.3 HDR metadata fields including display primaries, white point, mastering display luminance, and content light levels
- Matrix / Resolution restrictions:
- Rec.601 is only supported for SD display modes (NTSC, PAL, etc.) and is the only matrix supported for SD
- Rec.709 and Rec.2020 are only supported for HD and higher resolutions (720p, 1080p, 2K, 4K, 8K, etc.)
"DeckLink output module not found"
- Build and install the C++ extension:
pip install -e . - Check that pybind11 is installed:
pip install pybind11
"Could not create DeckLink iterator"
- Install Blackmagic Desktop Video software
- Ensure DeckLink device is connected and recognized by the system
- Check device drivers are properly installed
"Could not find DeckLink device"
- Verify device is connected and powered
- Check device appears in Blackmagic software (Media Express, etc.)
- Try different device index:
output.initialize(device_index=1)
Build errors about missing headers
- The SDK headers are included in the repository under
_vendor/decklink_sdk/ - If you need to use a different SDK version, update the paths in
CMakeLists.txt - On Linux, ensure headers are accessible to the build system
Permission errors (Linux)
- Add user to appropriate groups:
sudo usermod -a -G video $USER - Log out and back in for group changes to take effect
HDR output not displaying correctly
- Simplified API: Specify both
matrixandhdr_metadataindisplay_static_frame()- they're automatically set correctly - Low-level API: Call
set_hdr_metadata()BEFOREsetup_output()- metadata is embedded in each frame - Ensure matrix consistency: same value in both metadata and R'G'B' →Y'CbCr conversion
Run the example script to test your installation:
python example_usage.pyThis will show available devices and let you test various output modes.
The pixel_reader tool captures and analyzes video input from a DeckLink device, displaying pixel values and metadata. This is useful for verifying output from the library by looping a DeckLink output back to its own input.
Build:
cd tools
makeUsage:
./pixel_reader [device_index]See tools/README.md for more detail.
The tool displays:
- Pixel format and color space (RGB 4:4:4, YCbCr 4:2:2, etc.)
- Resolution and frame rate
- Metadata: EOTF (SDR / PQ / HLG), matrix (Rec.601 / Rec.709 / Rec.2020)
- Pixel values at selected coordinates in native format (code values)
Use this tool to verify that matrix and EOTF metadata are being set correctly by the output library.
- Requires Visual Studio Build Tools or Visual Studio with C++ support
- DeckLink SDK typically installs to Program Files
- Requires Xcode Command Line Tools
- May need to codesign the built extension for some versions
- Requires build-essential package
- May need to configure udev rules for device access
- Some distributions require additional video group membership
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
This repository includes header files from the Blackmagic DeckLink SDK v14.1. These header files are redistributable under the terms of the Blackmagic DeckLink SDK End User License Agreement (Section 0.1), which specifically exempts the Include folder headers from the more restrictive licensing terms that apply to other parts of the SDK.
Important notes about the SDK headers:
- The header files in
_vendor/decklink_sdk/{Mac,Win,Linux}/include/directories are from the Blackmagic DeckLink SDK - These headers are required only for building the library from source
- Runtime usage requires the Blackmagic Desktop Video software to be installed separately
- The SDK headers are provided under Blackmagic Design's EULA - see
_vendor/Blackmagic Design EULA.pdffor full terms - Download the complete SDK and Desktop Video software from: https://www.blackmagicdesign.com/developer
The Blackmagic DeckLink SDK is © Blackmagic Design Pty. Ltd. All rights reserved.
- Check the Issues page for known problems
- Review Blackmagic's official DeckLink SDK documentation
- Ensure your DeckLink device is supported by the SDK version
- Blackmagic Design for the DeckLink SDK
- pybind11 project for the C++/Python bindings
- Contributors and testers
- Special thanks to Zach Lewis and Gino Bollaert