Compare commits
No commits in common. "main" and "feat/recorder-codec-bitrate" have entirely different histories.
main
...
feat/recor
172 changed files with 1787 additions and 21626 deletions
39
.env.example
39
.env.example
|
|
@ -22,21 +22,9 @@ SESSION_SECRET=changeme
|
||||||
# MAM API Configuration
|
# MAM API Configuration
|
||||||
MAM_API_URL=http://mam-api:3000
|
MAM_API_URL=http://mam-api:3000
|
||||||
|
|
||||||
# Node Agent Authentication
|
|
||||||
# Bearer token for node-agent to authenticate with mam-api /driver/* endpoints.
|
|
||||||
# Generate with: openssl rand -hex 32
|
|
||||||
NODE_AGENT_TOKEN=changeme
|
|
||||||
|
|
||||||
# Auth — default to ON in production. Setting to 'false' is a dev-only escape
|
# Auth — default to ON in production. Setting to 'false' is a dev-only escape
|
||||||
# hatch that disables all auth checks and attaches a synthetic 'dev' user to
|
# hatch that disables all auth checks and attaches a synthetic 'dev' user to
|
||||||
# every request. Never run with AUTH_ENABLED=false on a network you don't control.
|
# every request. Never run with AUTH_ENABLED=false on a network you don't control.
|
||||||
#
|
|
||||||
# RBAC v2 note: with AUTH_ENABLED=true, per-project access is enforced. Service
|
|
||||||
# API tokens (capture sidecar, Premiere panel, integrations) must belong to a
|
|
||||||
# user with the access they need — an 'admin' user (full access), or a user with
|
|
||||||
# the right project grants. A non-admin service token with no grants will get
|
|
||||||
# 403 on asset registration (ingest) and streaming. In dev mode the synthetic
|
|
||||||
# user is admin, so this only matters once auth is on.
|
|
||||||
AUTH_ENABLED=true
|
AUTH_ENABLED=true
|
||||||
|
|
||||||
# CORS allowlist — comma-separated origins that may carry credentials to the API.
|
# CORS allowlist — comma-separated origins that may carry credentials to the API.
|
||||||
|
|
@ -48,30 +36,3 @@ ALLOWED_ORIGINS=
|
||||||
# so secure-cookie + X-Forwarded-Proto behave correctly. ALSO required for accurate
|
# so secure-cookie + X-Forwarded-Proto behave correctly. ALSO required for accurate
|
||||||
# per-IP login rate-limiting (otherwise req.ip is always the nginx IP).
|
# per-IP login rate-limiting (otherwise req.ip is always the nginx IP).
|
||||||
TRUST_PROXY=false
|
TRUST_PROXY=false
|
||||||
|
|
||||||
# Google OAuth (OIDC) sign-in — OPTIONAL. Leave the client id/secret blank to
|
|
||||||
# disable; the "Sign in with Google" button and the /auth/google routes only
|
|
||||||
# activate when all three of CLIENT_ID, CLIENT_SECRET, and REDIRECT_URL are set.
|
|
||||||
# Create an OAuth 2.0 Client (type: Web application) in Google Cloud Console and
|
|
||||||
# add OAUTH_REDIRECT_URL to its authorized redirect URIs.
|
|
||||||
GOOGLE_CLIENT_ID=
|
|
||||||
GOOGLE_CLIENT_SECRET=
|
|
||||||
# Must exactly match a redirect URI on the OAuth client, e.g.
|
|
||||||
# https://dragonflight.live/api/v1/auth/google/callback
|
|
||||||
OAUTH_REDIRECT_URL=
|
|
||||||
# Restrict sign-in to one Google Workspace domain (recommended). First login from
|
|
||||||
# an allowed-domain account auto-provisions a NEW 'viewer' account (matched only
|
|
||||||
# by Google's stable subject id, never by email — so a Google login can never
|
|
||||||
# seize a pre-existing local account). An admin then grants project access.
|
|
||||||
# Leave blank to allow any verified Google account to self-provision (NOT advised).
|
|
||||||
GOOGLE_ALLOWED_DOMAIN=
|
|
||||||
# Note: if a Google-linked account also has TOTP enabled, sign-in still requires
|
|
||||||
# the authenticator code (Google is treated as the first factor). Accounts without
|
|
||||||
# TOTP complete sign-in in one Google step.
|
|
||||||
|
|
||||||
# Playout / Master Control (MCR)
|
|
||||||
# Image tag the mam-api spawns when a channel starts. Build with:
|
|
||||||
# docker compose --profile build-only build playout
|
|
||||||
PLAYOUT_IMAGE=wild-dragon-playout:latest
|
|
||||||
# Base AMCP port — each channel binds to BASE + channel_id (in CasparCG terms).
|
|
||||||
PLAYOUT_AMCP_BASE_PORT=5250
|
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -27,8 +27,8 @@ services/editor/**/node_modules
|
||||||
services/editor/**/dist
|
services/editor/**/dist
|
||||||
services/editor/.pnpm-store
|
services/editor/.pnpm-store
|
||||||
|
|
||||||
# Blackmagic DeckLink SDK headers are now committed (private/internal repo) under services/capture/sdk/.
|
# Blackmagic DeckLink SDK + runtime libs (operator-supplied; see services/capture/build-with-decklink.sh)
|
||||||
# Runtime .so libs (libDeckLinkAPI.so) come from the DesktopVideo driver install and are not committed.
|
services/capture/sdk/
|
||||||
services/capture/lib/
|
services/capture/lib/
|
||||||
|
|
||||||
# Editor backups
|
# Editor backups
|
||||||
|
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
# Playout / Master Control — Implementation Work Log
|
|
||||||
|
|
||||||
**Branch:** `feat/playout-mcr` (off `main`)
|
|
||||||
**Started:** 2026-05-30
|
|
||||||
**Status:** Code complete, awaiting runtime validation
|
|
||||||
|
|
||||||
Tracks the build of the playout (MCR) subsystem against the design at
|
|
||||||
`docs/superpowers/specs/2026-05-30-playout-mcr-design.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Commit sequence
|
|
||||||
|
|
||||||
| # | Commit | Scope |
|
|
||||||
|---|--------|-------|
|
|
||||||
| 1 | `docs(playout)` | Design spec, §7 questions answered |
|
|
||||||
| 2 | `feat(mam-api): migration 029` | Six tables, failover columns, audio_normalized flag |
|
|
||||||
| 3 | `feat(worker): playout-stage` | S3 → /media + EBU R128 loudnorm + index.js wiring |
|
|
||||||
| 4 | `feat(playout): sidecar` | CasparCG image + AMCP shim, HLS preview consumer, fps-aware frame math |
|
|
||||||
| 5 | `feat(mam-api): /playout control plane + auto-failover` | Routes + scheduler health tick + restartChannel helper |
|
|
||||||
| 6 | `feat(web-ui): MCR page` | screens-playout, styles, app/shell/index.html wiring |
|
|
||||||
| 7 | `build(playout): compose wiring + .env knobs` | /media volume, queue addition, build-only service |
|
|
||||||
| 8 | `docs(playout): work log` | This file |
|
|
||||||
|
|
||||||
## Resolved §7 decisions (2026-05-30)
|
|
||||||
|
|
||||||
- **Audio loudness:** pre-normalize at stage time. ffmpeg `loudnorm` two-pass
|
|
||||||
(I=-23 LUFS, TP=-1 dBTP, LRA=11), linear mode preserves dynamics. Output
|
|
||||||
AAC 192k @ 48 kHz, video stream copied. Per-item `audio_normalized` flag
|
|
||||||
so re-stages of the same asset skip the pass.
|
|
||||||
- **Frame rate:** `1080p5994` default (was `1080i5994`). Per-channel
|
|
||||||
override allowed via `video_format`. `fpsFor(videoFormat)` helper in
|
|
||||||
the sidecar drives SEEK / LENGTH / transition-frames math.
|
|
||||||
- **Preview latency:** HLS v1. CasparCG runs a second FFMPEG consumer
|
|
||||||
alongside the primary output, writing `/media/live/<channel_id>/index.m3u8`
|
|
||||||
(~600 kbps, 2s segments, 6-window list). Web UI plays via the existing
|
|
||||||
HLS plumbing.
|
|
||||||
- **Failover:** auto-restart on healthy node for NDI/SRT/RTMP. Alert-only
|
|
||||||
for DeckLink (device-index pinning makes blind re-placement risky).
|
|
||||||
Scheduler tick (PG advisory lock, same lock as recorder schedules) polls
|
|
||||||
sidecar `/status`; ~3 missed checks → `restartChannel(id)` picks the most
|
|
||||||
recently-seen-online other node, bumps `restart_count`, calls `/start`.
|
|
||||||
|
|
||||||
## Architecture notes
|
|
||||||
|
|
||||||
**Sidecar model.** One CasparCG container per channel. Spawned by mam-api
|
|
||||||
via local Docker socket (primary node) or remote node-agent
|
|
||||||
`/sidecar/start`. Tracked in `playout_sidecars` plus `playout_channels.container_id`.
|
|
||||||
Killed on `/stop` or by `restartChannel` during failover.
|
|
||||||
|
|
||||||
**Media flow.**
|
|
||||||
```
|
|
||||||
S3 master/proxy → playout-stage worker → /media/playout/<assetId>.<ext>
|
|
||||||
(loudnormed, AAC@-23 LUFS)
|
|
||||||
↓
|
|
||||||
CasparCG channel #1
|
|
||||||
↓
|
|
||||||
primary consumer HLS consumer
|
|
||||||
(DeckLink/NDI/ ↓
|
|
||||||
SRT/RTMP) /media/live/<ch_id>/*.m3u8
|
|
||||||
```
|
|
||||||
|
|
||||||
**Port contention.** `assertDeckLinkFree()` blocks starting a SDI channel
|
|
||||||
when a recorder or another channel on the same node+device_index is active.
|
|
||||||
|
|
||||||
**Failover scope.** NDI/SRT/RTMP have no hardware tie, so any healthy
|
|
||||||
cluster_node is eligible. DeckLink channels surface an alert in the UI
|
|
||||||
(`status='error'` + `error_message`) and require operator intervention.
|
|
||||||
|
|
||||||
## Testing checklist
|
|
||||||
|
|
||||||
- [ ] Apply migration 029 on dev DB
|
|
||||||
- [ ] Build playout image: `docker compose --profile build-only build playout`
|
|
||||||
- [ ] Build web-ui (`screens-playout` joins the esbuild list automatically)
|
|
||||||
- [ ] Create channel via POST /api/v1/playout/channels (SRT first, no HW)
|
|
||||||
- [ ] Stage 2-3 assets to a playlist, verify loudnorm metadata in stderr
|
|
||||||
- [ ] Start channel → sidecar container appears in `docker ps`
|
|
||||||
- [ ] AMCP smoke: `telnet <host> 5250`, `VERSION`, `INFO`
|
|
||||||
- [ ] Play playlist; verify HLS at /media/live/<id>/index.m3u8
|
|
||||||
- [ ] Skip / pause / resume / stop
|
|
||||||
- [ ] As-run log: GET /api/v1/playout/channels/:id/asrun
|
|
||||||
- [ ] Kill sidecar container → scheduler should restart on another node
|
|
||||||
within ~3 ticks (~45s), restart_count increments
|
|
||||||
- [ ] DeckLink channel kill: status flips to 'error', NO restart attempt
|
|
||||||
- [ ] Try starting a decklink channel on a device_index already held by a
|
|
||||||
recorder → 409
|
|
||||||
- [ ] MCR UI smoke: nav entry visible, page renders, drag-drop adds items,
|
|
||||||
transport buttons hit the API
|
|
||||||
|
|
||||||
## Known gaps (deferred)
|
|
||||||
|
|
||||||
- No WebRTC preview (HLS-only v1 — 4-6s lag, fine for confidence monitor).
|
|
||||||
- No graphics/CG overlay layer in Phase A (templates land in Phase B).
|
|
||||||
- No Phase B scheduler / 24/7 wall-clock channel (schema is in place,
|
|
||||||
scheduler tick is not).
|
|
||||||
- No multi-channel grid view (one channel at a time per page).
|
|
||||||
- No timecode / remaining-duration overlay (would need CasparCG INFO poll).
|
|
||||||
- No audio level meters on the UI.
|
|
||||||
- `restartChannel` updates DB state and triggers `/start`; if the new node
|
|
||||||
also fails repeatedly, there's no exponential backoff yet — bounded only
|
|
||||||
by the manual stop button.
|
|
||||||
|
|
@ -1,325 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# ============================================================================
|
|
||||||
# install-driver.sh <vendor>
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Idempotent HOST installer for capture-card runtime drivers / SDKs.
|
|
||||||
#
|
|
||||||
# Runs ON the cluster node's HOST kernel. The node-agent invokes it inside a
|
|
||||||
# one-shot PRIVILEGED ubuntu container that bind-mounts this repo plus the host
|
|
||||||
# paths needed to affect the host kernel (/lib/modules, /usr/src, /boot, /dev,
|
|
||||||
# and the host apt/dpkg via the mounted root). dkms / modprobe / ldconfig
|
|
||||||
# therefore operate against the running host kernel.
|
|
||||||
#
|
|
||||||
# Reads the proprietary vendor file(s) from sdk/<vendor>/ (in this repo).
|
|
||||||
# NO binaries are committed — if the expected file is missing the script exits
|
|
||||||
# non-zero with a clear message telling the operator what to drop in.
|
|
||||||
#
|
|
||||||
# Vendors (ALLOWLIST — nothing else is accepted):
|
|
||||||
# blackmagic Desktop Video .deb (DKMS kernel module)
|
|
||||||
# aja NTV2 driver source/SDK (built kernel module)
|
|
||||||
# deltacast VideoMaster installer (kernel module)
|
|
||||||
# ndi redistributable runtime libs (user-space only, no module)
|
|
||||||
#
|
|
||||||
# Exit codes:
|
|
||||||
# 0 installed (or already present / up to date)
|
|
||||||
# 2 bad usage / unknown vendor
|
|
||||||
# 3 expected vendor file missing in sdk/<vendor>/
|
|
||||||
# 4 missing kernel headers (cannot build DKMS / module)
|
|
||||||
# 5 build / install / module-load failure
|
|
||||||
#
|
|
||||||
# `bash -n` must pass. set -euo pipefail with `|| true` guarding every probe.
|
|
||||||
# ============================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Resolve our own repo dir (deploy/ -> repo root), regardless of CWD.
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
|
||||||
REPO_DIR="$(cd "$SCRIPT_DIR/.." >/dev/null 2>&1 && pwd)"
|
|
||||||
SDK_ROOT="$REPO_DIR/sdk"
|
|
||||||
|
|
||||||
VENDOR="${1:-}"
|
|
||||||
KVER="$(uname -r 2>/dev/null || echo unknown)"
|
|
||||||
REBOOT_REQUIRED=0
|
|
||||||
|
|
||||||
log() { echo "[install-driver] $*"; }
|
|
||||||
warn() { echo "[install-driver] WARN: $*" >&2; }
|
|
||||||
die() { echo "[install-driver] ERROR: $*" >&2; exit "${2:-5}"; }
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
echo "Usage: install-driver.sh <blackmagic|aja|deltacast|ndi>" >&2
|
|
||||||
exit 2
|
|
||||||
}
|
|
||||||
|
|
||||||
[ -n "$VENDOR" ] || usage
|
|
||||||
case "$VENDOR" in
|
|
||||||
blackmagic|aja|deltacast|ndi) : ;;
|
|
||||||
*) echo "[install-driver] ERROR: unknown vendor '$VENDOR' (allowed: blackmagic aja deltacast ndi)" >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
VENDOR_DIR="$SDK_ROOT/$VENDOR"
|
|
||||||
log "vendor=$VENDOR kernel=$KVER repo=$REPO_DIR"
|
|
||||||
log "reading vendor files from $VENDOR_DIR"
|
|
||||||
[ -d "$VENDOR_DIR" ] || die "vendor dir $VENDOR_DIR does not exist (repo not mounted?)" 3
|
|
||||||
|
|
||||||
# Pick the newest file matching a glob; echo its path or empty.
|
|
||||||
newest_match() {
|
|
||||||
# shellcheck disable=SC2012
|
|
||||||
ls -1t $1 2>/dev/null | head -n1 || true
|
|
||||||
}
|
|
||||||
|
|
||||||
ensure_headers() {
|
|
||||||
if [ -d "/lib/modules/$KVER/build" ] || dpkg -s "linux-headers-$KVER" >/dev/null 2>&1; then
|
|
||||||
log "kernel headers for $KVER present"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
log "installing linux-headers-$KVER ..."
|
|
||||||
apt-get update -y >/dev/null 2>&1 || true
|
|
||||||
if ! apt-get install -y "linux-headers-$KVER" >/dev/null 2>&1; then
|
|
||||||
# Fall back to the generic meta-package; still may not match a custom kernel.
|
|
||||||
apt-get install -y linux-headers-generic >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
if [ -d "/lib/modules/$KVER/build" ] || dpkg -s "linux-headers-$KVER" >/dev/null 2>&1; then
|
|
||||||
log "kernel headers ready"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
die "kernel headers for $KVER unavailable — cannot build module. Install linux-headers-$KVER on the host." 4
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# blackmagic — Desktop Video .deb (DKMS) + modprobe + restart desktopvideo
|
|
||||||
# ===========================================================================
|
|
||||||
install_blackmagic() {
|
|
||||||
# Detect existing state first (idempotent skip).
|
|
||||||
if lsmod 2>/dev/null | grep -q '^blackmagic' && [ -e /dev/blackmagic ]; then
|
|
||||||
log "blackmagic module loaded and /dev/blackmagic present — already installed"
|
|
||||||
if command -v dkms >/dev/null 2>&1; then dkms status 2>/dev/null | grep -i blackmagic || true; fi
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local deb
|
|
||||||
deb="$(newest_match "$VENDOR_DIR/desktopvideo_*_amd64.deb")"
|
|
||||||
[ -n "$deb" ] && [ -f "$deb" ] || die \
|
|
||||||
"no desktopvideo_*_amd64.deb in $VENDOR_DIR — download Desktop Video for Ubuntu 22.04 and drop the .deb there (see sdk/blackmagic/README.md)." 3
|
|
||||||
log "using package: $(basename "$deb")"
|
|
||||||
|
|
||||||
ensure_headers
|
|
||||||
|
|
||||||
log "apt-get install (DKMS build) ..."
|
|
||||||
apt-get update -y >/dev/null 2>&1 || true
|
|
||||||
apt-get install -y "$deb" || die "apt-get install of $(basename "$deb") failed (DKMS build?)" 5
|
|
||||||
|
|
||||||
log "depmod + modprobe blackmagic ..."
|
|
||||||
depmod -a "$KVER" 2>/dev/null || true
|
|
||||||
if ! modprobe blackmagic 2>/dev/null; then
|
|
||||||
warn "modprobe blackmagic failed — a reboot may be required for the DKMS module to bind"
|
|
||||||
REBOOT_REQUIRED=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Restart the DesktopVideoHelper daemon if present.
|
|
||||||
if command -v systemctl >/dev/null 2>&1; then
|
|
||||||
systemctl restart desktopvideo 2>/dev/null \
|
|
||||||
|| systemctl restart DesktopVideoHelper 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if lsmod 2>/dev/null | grep -q '^blackmagic' || [ -e /dev/blackmagic ]; then
|
|
||||||
log "blackmagic installed (module loaded / device present)"
|
|
||||||
else
|
|
||||||
warn "blackmagic installed but module not yet loaded — reboot required"
|
|
||||||
REBOOT_REQUIRED=1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# aja — build ntv2 kernel module from source archive + modprobe
|
|
||||||
# ===========================================================================
|
|
||||||
install_aja() {
|
|
||||||
if lsmod 2>/dev/null | grep -q 'ajantv2'; then
|
|
||||||
log "ajantv2 module already loaded — already installed"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local src
|
|
||||||
src="$(newest_match "$VENDOR_DIR/ntv2sdk*.zip")"
|
|
||||||
[ -n "$src" ] || src="$(newest_match "$VENDOR_DIR/libajantv2*.tar.gz")"
|
|
||||||
[ -n "$src" ] && [ -f "$src" ] || die \
|
|
||||||
"no ntv2sdk*.zip / libajantv2*.tar.gz in $VENDOR_DIR — download the AJA NTV2 Linux SDK and drop it there (see sdk/aja/README.md)." 3
|
|
||||||
log "using source: $(basename "$src")"
|
|
||||||
|
|
||||||
ensure_headers
|
|
||||||
apt-get install -y build-essential unzip >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
local work; work="$(mktemp -d)"
|
|
||||||
log "extracting into $work ..."
|
|
||||||
case "$src" in
|
|
||||||
*.zip) unzip -q -o "$src" -d "$work" || die "failed to unzip $(basename "$src")" 5 ;;
|
|
||||||
*.tar.gz) tar -xzf "$src" -C "$work" || die "failed to untar $(basename "$src")" 5 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Find the linux driver dir (contains a Makefile producing ajantv2.ko).
|
|
||||||
local drvdir
|
|
||||||
drvdir="$(dirname "$(find "$work" -type d -path '*driver/linux' -print -quit 2>/dev/null || true)/." 2>/dev/null)"
|
|
||||||
[ -d "$drvdir" ] || drvdir="$(dirname "$(find "$work" -name 'ajantv2.c' -print -quit 2>/dev/null || true)" 2>/dev/null)"
|
|
||||||
[ -n "$drvdir" ] && [ -d "$drvdir" ] || die "could not locate AJA driver/linux source inside the archive" 5
|
|
||||||
log "building module in $drvdir ..."
|
|
||||||
make -C "$drvdir" >/dev/null 2>&1 || die "AJA module build failed" 5
|
|
||||||
|
|
||||||
local ko
|
|
||||||
ko="$(find "$drvdir" -name 'ajantv2.ko' -print -quit 2>/dev/null || true)"
|
|
||||||
if [ -n "$ko" ]; then
|
|
||||||
install -D -m 0644 "$ko" "/lib/modules/$KVER/extra/ajantv2.ko" 2>/dev/null || true
|
|
||||||
depmod -a "$KVER" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
if ! modprobe ajantv2 2>/dev/null; then
|
|
||||||
# Fall back to the SDK's own load script if shipped.
|
|
||||||
local loader; loader="$(find "$work" -name 'load_ajantv2' -print -quit 2>/dev/null || true)"
|
|
||||||
if [ -n "$loader" ]; then bash "$loader" 2>/dev/null || true; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf "$work" 2>/dev/null || true
|
|
||||||
|
|
||||||
if lsmod 2>/dev/null | grep -q 'ajantv2'; then
|
|
||||||
log "ajantv2 installed and loaded"
|
|
||||||
else
|
|
||||||
warn "ajantv2 built but not loaded — a reboot may be required (old in-tree module wedged?)"
|
|
||||||
REBOOT_REQUIRED=1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# deltacast — VideoMaster installer + module load
|
|
||||||
# ===========================================================================
|
|
||||||
install_deltacast() {
|
|
||||||
if lsmod 2>/dev/null | grep -q 'videomaster' || ls /dev/deltacast* >/dev/null 2>&1; then
|
|
||||||
log "Deltacast module loaded / device present — already installed"
|
|
||||||
install_deltacast_udev_rule
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
local pkg
|
|
||||||
pkg="$(newest_match "$VENDOR_DIR/VideoMaster*.run")"
|
|
||||||
[ -n "$pkg" ] || pkg="$(newest_match "$VENDOR_DIR/VideoMaster*.tar.gz")"
|
|
||||||
[ -n "$pkg" ] && [ -f "$pkg" ] || die \
|
|
||||||
"no VideoMaster*.run / VideoMaster*.tar.gz in $VENDOR_DIR — obtain the Deltacast VideoMaster Linux installer and drop it there (see sdk/deltacast/README.md)." 3
|
|
||||||
log "using installer: $(basename "$pkg")"
|
|
||||||
|
|
||||||
ensure_headers
|
|
||||||
apt-get install -y build-essential dkms >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
local work; work="$(mktemp -d)"
|
|
||||||
case "$pkg" in
|
|
||||||
*.run)
|
|
||||||
log "extracting self-extractor ..."
|
|
||||||
chmod +x "$pkg" 2>/dev/null || true
|
|
||||||
"$pkg" --noexec --target "$work" >/dev/null 2>&1 \
|
|
||||||
|| cp "$pkg" "$work/installer.run"
|
|
||||||
;;
|
|
||||||
*.tar.gz)
|
|
||||||
tar -xzf "$pkg" -C "$work" || die "failed to untar $(basename "$pkg")" 5
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
local installer
|
|
||||||
installer="$(find "$work" -name 'install.sh' -print -quit 2>/dev/null || true)"
|
|
||||||
[ -n "$installer" ] || installer="$(find "$work" -name 'installer.run' -print -quit 2>/dev/null || true)"
|
|
||||||
[ -n "$installer" ] && [ -f "$installer" ] || die "Deltacast installer (install.sh) not found inside the package" 5
|
|
||||||
log "running vendor installer: $(basename "$installer") ..."
|
|
||||||
chmod +x "$installer" 2>/dev/null || true
|
|
||||||
( cd "$(dirname "$installer")" && bash "$installer" ) || die "Deltacast VideoMaster installer failed" 5
|
|
||||||
|
|
||||||
depmod -a "$KVER" 2>/dev/null || true
|
|
||||||
modprobe videomasterhd 2>/dev/null || modprobe videomaster 2>/dev/null || true
|
|
||||||
|
|
||||||
rm -rf "$work" 2>/dev/null || true
|
|
||||||
|
|
||||||
if lsmod 2>/dev/null | grep -q 'videomaster' || ls /dev/deltacast* >/dev/null 2>&1; then
|
|
||||||
log "Deltacast VideoMaster installed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install our own udev rule that creates 8 /dev/deltacast symlinks (ports 0-7)
|
|
||||||
# pointing at the single real device node. Kept separate from the SDK's own
|
|
||||||
# rule so a driver reinstall won't clobber it.
|
|
||||||
install_deltacast_udev_rule
|
|
||||||
|
|
||||||
# First-time VideoMaster installs lay down udev rules + firmware that need a reboot.
|
|
||||||
warn "Deltacast: a REBOOT is recommended after a first-time VideoMaster install (udev + firmware)"
|
|
||||||
REBOOT_REQUIRED=1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Copy the repo's 99-wild-dragon-deltacast.rules into /etc/udev/rules.d/ and
|
|
||||||
# reload. Idempotent. Creates /dev/deltacast0..7 -> /dev/delta-x3700 so the
|
|
||||||
# node-agent advertises all 8 RX channels.
|
|
||||||
install_deltacast_udev_rule() {
|
|
||||||
local rule_src="$REPO_DIR/deploy/udev/99-wild-dragon-deltacast.rules"
|
|
||||||
local rule_dst="/etc/udev/rules.d/99-wild-dragon-deltacast.rules"
|
|
||||||
if [ ! -f "$rule_src" ]; then
|
|
||||||
warn "Deltacast: udev rule $rule_src not found in repo — skipping symlink rule install"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [ -f "$rule_dst" ] && cmp -s "$rule_src" "$rule_dst"; then
|
|
||||||
log "Deltacast: udev rule already up to date at $rule_dst"
|
|
||||||
else
|
|
||||||
log "installing Deltacast udev rule -> $rule_dst"
|
|
||||||
install -D -m 0644 "$rule_src" "$rule_dst" 2>/dev/null \
|
|
||||||
|| { warn "Deltacast: failed to install udev rule (continuing)"; return 0; }
|
|
||||||
udevadm control --reload-rules 2>/dev/null || true
|
|
||||||
udevadm trigger --action=add /dev/delta-x3700 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===========================================================================
|
|
||||||
# ndi — copy redistributable runtime libs to /usr/local/lib + ldconfig
|
|
||||||
# ===========================================================================
|
|
||||||
install_ndi() {
|
|
||||||
local target="/opt/ndi-lib"
|
|
||||||
local found=0
|
|
||||||
# shellcheck disable=SC2231
|
|
||||||
for f in "$VENDOR_DIR"/libndi*.so*; do
|
|
||||||
[ -e "$f" ] || continue
|
|
||||||
found=1
|
|
||||||
break
|
|
||||||
done
|
|
||||||
[ "$found" = 1 ] || die \
|
|
||||||
"no libndi*.so* in $VENDOR_DIR — drop the NDI runtime redistributable libs there (see sdk/ndi/README.md)." 3
|
|
||||||
|
|
||||||
log "copying NDI runtime libs to $target ..."
|
|
||||||
mkdir -p "$target"
|
|
||||||
cp -av "$VENDOR_DIR"/libndi*.so* "$target"/ 2>/dev/null || die "failed copying NDI libs" 5
|
|
||||||
|
|
||||||
# Recreate the libndi.so dev symlink if only versioned libs were shipped.
|
|
||||||
if [ ! -e "$target/libndi.so" ]; then
|
|
||||||
local versioned
|
|
||||||
versioned="$(newest_match "$target/libndi.so.*")"
|
|
||||||
if [ -n "$versioned" ]; then
|
|
||||||
ln -sf "$(basename "$versioned")" "$target/libndi.so" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$target" > /etc/ld.so.conf.d/ndi.conf
|
|
||||||
ldconfig 2>/dev/null || true
|
|
||||||
|
|
||||||
if ldconfig -p 2>/dev/null | grep -q 'libndi'; then
|
|
||||||
log "NDI runtime registered with the dynamic linker"
|
|
||||||
else
|
|
||||||
die "NDI libs copied but ldconfig did not resolve libndi" 5
|
|
||||||
fi
|
|
||||||
log "NDI: no kernel module and no reboot required."
|
|
||||||
log "NDI: restart any process that already loaded an older libndi to pick up the new version."
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
case "$VENDOR" in
|
|
||||||
blackmagic) install_blackmagic ;;
|
|
||||||
aja) install_aja ;;
|
|
||||||
deltacast) install_deltacast ;;
|
|
||||||
ndi) install_ndi ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ "$REBOOT_REQUIRED" = 1 ]; then
|
|
||||||
log "RESULT: $VENDOR install completed — REBOOT REQUIRED"
|
|
||||||
echo "[install-driver] REBOOT_REQUIRED=1"
|
|
||||||
else
|
|
||||||
log "RESULT: $VENDOR install completed — no reboot required"
|
|
||||||
echo "[install-driver] REBOOT_REQUIRED=0"
|
|
||||||
fi
|
|
||||||
exit 0
|
|
||||||
|
|
@ -16,12 +16,11 @@
|
||||||
# Environment variables:
|
# Environment variables:
|
||||||
# MAM_API_URL REQUIRED Primary MAM API base URL
|
# MAM_API_URL REQUIRED Primary MAM API base URL
|
||||||
# NODE_TOKEN API bearer token (required if AUTH_ENABLED=true)
|
# NODE_TOKEN API bearer token (required if AUTH_ENABLED=true)
|
||||||
# NODE_ROLE Role tag reported to the cluster (default: auto-detect)
|
# NODE_ROLE Role tag reported to the cluster (default: worker)
|
||||||
# NODE_IP Override the LAN IP reported back (default: auto-detect)
|
# NODE_IP Override the LAN IP reported back (default: auto-detect)
|
||||||
# AGENT_PORT Host port for the node agent (default: 7436)
|
# AGENT_PORT Host port for the node agent (default: 7436)
|
||||||
# INSTALL_DIR Where to clone/find the repo (default: /opt/wild-dragon)
|
# INSTALL_DIR Where to clone/find the repo (default: /opt/wild-dragon)
|
||||||
# PROFILES Compose profiles, space-sep (default: auto-detect from hardware)
|
# PROFILES Extra compose profiles, space-sep e.g. "worker capture"
|
||||||
# Override only to force, e.g. "worker capture"
|
|
||||||
# BMD_MODEL DeckLink card model name (e.g. "DeckLink Duo 2")
|
# BMD_MODEL DeckLink card model name (e.g. "DeckLink Duo 2")
|
||||||
# REPO_URL Override the Forgejo clone URL
|
# REPO_URL Override the Forgejo clone URL
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -33,16 +32,8 @@ REPO_URL="${REPO_URL:-https://forge.wilddragon.net/zgaetano/wild-dragon.git}"
|
||||||
INSTALL_DIR="${INSTALL_DIR:-/opt/wild-dragon}"
|
INSTALL_DIR="${INSTALL_DIR:-/opt/wild-dragon}"
|
||||||
MAM_API_URL="${MAM_API_URL:-}"
|
MAM_API_URL="${MAM_API_URL:-}"
|
||||||
NODE_TOKEN="${NODE_TOKEN:-}"
|
NODE_TOKEN="${NODE_TOKEN:-}"
|
||||||
# Track whether the caller pinned NODE_ROLE explicitly (manual override) vs.
|
|
||||||
# us defaulting it — so auto-detection only fills in an *unset* role.
|
|
||||||
[[ -n "${NODE_ROLE:-}" ]] && NODE_ROLE_EXPLICIT=1 || NODE_ROLE_EXPLICIT=""
|
|
||||||
NODE_ROLE="${NODE_ROLE:-worker}"
|
NODE_ROLE="${NODE_ROLE:-worker}"
|
||||||
NODE_IP="${NODE_IP:-}"
|
NODE_IP="${NODE_IP:-}"
|
||||||
# NODE_NAME pins this node's cluster identity (the heartbeat key). Default to the
|
|
||||||
# OS hostname, but ALWAYS write it explicitly so cloned VMs that share an
|
|
||||||
# /etc/hostname (e.g. two boxes both named "zampp1") don't collide on the same
|
|
||||||
# cluster_nodes row — which silently hides the capture node's DeckLink devices.
|
|
||||||
NODE_NAME="${NODE_NAME:-$(hostname)}"
|
|
||||||
AGENT_PORT="${AGENT_PORT:-7436}"
|
AGENT_PORT="${AGENT_PORT:-7436}"
|
||||||
PROFILES="${PROFILES:-}"
|
PROFILES="${PROFILES:-}"
|
||||||
BMD_MODEL="${BMD_MODEL:-}"
|
BMD_MODEL="${BMD_MODEL:-}"
|
||||||
|
|
@ -74,37 +65,6 @@ detect_lan_ip() {
|
||||||
echo "$ip"
|
echo "$ip"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Auto-detect hardware ─────────────────────────────────────────────────────
|
|
||||||
# Mirror detect_lan_ip's style: best-effort, guard every probe with `|| true`
|
|
||||||
# so a missing nvidia-smi/lspci never aborts under `set -euo pipefail`. The
|
|
||||||
# node self-describes its hardware here so the operator never has to pick a
|
|
||||||
# role — the right compose profiles are enabled automatically.
|
|
||||||
|
|
||||||
# GPU present? nvidia-smi is the strong signal; fall back to an lspci scan for
|
|
||||||
# NVIDIA or AMD VGA controllers (covers nodes where the driver isn't installed
|
|
||||||
# yet but the card is physically present).
|
|
||||||
detect_gpu() {
|
|
||||||
if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if command -v lspci &>/dev/null; then
|
|
||||||
if lspci 2>/dev/null | grep -iE 'nvidia|vga.*amd' &>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# SDI capture card present? Blackmagic DeckLink or Deltacast, via lspci.
|
|
||||||
detect_sdi() {
|
|
||||||
if command -v lspci &>/dev/null; then
|
|
||||||
if lspci 2>/dev/null | grep -iE 'blackmagic|deltacast' &>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Preflight ────────────────────────────────────────────────────────────────
|
# ── Preflight ────────────────────────────────────────────────────────────────
|
||||||
echo -e "\n${BLD}${CYN}Wild Dragon MAM — Cluster Node Onboarding${NC}\n"
|
echo -e "\n${BLD}${CYN}Wild Dragon MAM — Cluster Node Onboarding${NC}\n"
|
||||||
|
|
||||||
|
|
@ -119,36 +79,6 @@ if [[ -z "$NODE_IP" ]]; then
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Auto-assign compose profiles from detected hardware ──────────────────────
|
|
||||||
# Operator never picks a role: the worker profile always runs, and we add the
|
|
||||||
# gpu / capture profiles only when the matching hardware is present. Explicit
|
|
||||||
# PROFILES / NODE_ROLE env vars are honoured as a manual override escape hatch.
|
|
||||||
HAS_GPU=false; HAS_SDI=false
|
|
||||||
detect_gpu && HAS_GPU=true || true
|
|
||||||
detect_sdi && HAS_SDI=true || true
|
|
||||||
|
|
||||||
DETECTED_DESC="CPU"
|
|
||||||
[[ "$HAS_GPU" == true ]] && DETECTED_DESC="$DETECTED_DESC, GPU"
|
|
||||||
[[ "$HAS_SDI" == true ]] && DETECTED_DESC="$DETECTED_DESC, SDI capture card"
|
|
||||||
|
|
||||||
if [[ -z "$PROFILES" ]]; then
|
|
||||||
AUTO_PROFILES="worker"
|
|
||||||
[[ "$HAS_GPU" == true ]] && AUTO_PROFILES="$AUTO_PROFILES gpu"
|
|
||||||
[[ "$HAS_SDI" == true ]] && AUTO_PROFILES="$AUTO_PROFILES capture"
|
|
||||||
PROFILES="$AUTO_PROFILES"
|
|
||||||
info "Detected: $DETECTED_DESC → profiles: $PROFILES"
|
|
||||||
else
|
|
||||||
info "Detected: $DETECTED_DESC (profiles overridden by env: $PROFILES)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Derive a human-friendly role tag from detected hardware when not pinned.
|
|
||||||
# Capture cards win over GPU (an SDI ingest node is the more specific role).
|
|
||||||
if [[ -z "$NODE_ROLE_EXPLICIT" ]]; then
|
|
||||||
if [[ "$HAS_SDI" == true ]]; then NODE_ROLE="capture"
|
|
||||||
elif [[ "$HAS_GPU" == true ]]; then NODE_ROLE="gpu"
|
|
||||||
else NODE_ROLE="worker"; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
info "Primary API : $MAM_API_URL"
|
info "Primary API : $MAM_API_URL"
|
||||||
info "Role : $NODE_ROLE"
|
info "Role : $NODE_ROLE"
|
||||||
info "Agent port : $AGENT_PORT"
|
info "Agent port : $AGENT_PORT"
|
||||||
|
|
@ -205,7 +135,6 @@ info "Writing $ENV_FILE"
|
||||||
echo "MAM_API_URL=$MAM_API_URL"
|
echo "MAM_API_URL=$MAM_API_URL"
|
||||||
echo "NODE_TOKEN=$NODE_TOKEN"
|
echo "NODE_TOKEN=$NODE_TOKEN"
|
||||||
echo "NODE_ROLE=$NODE_ROLE"
|
echo "NODE_ROLE=$NODE_ROLE"
|
||||||
echo "NODE_NAME=$NODE_NAME"
|
|
||||||
echo "NODE_IP=$NODE_IP"
|
echo "NODE_IP=$NODE_IP"
|
||||||
echo "AGENT_PORT=$AGENT_PORT"
|
echo "AGENT_PORT=$AGENT_PORT"
|
||||||
echo "HEARTBEAT_MS=30000"
|
echo "HEARTBEAT_MS=30000"
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
KERNEL=="delta-x3700", MODE="0666", RUN+="/bin/sh -c 'for i in 0 1 2 3 4 5 6 7; do ln -sf /dev/delta-x3700 /dev/deltacast$i; done'"
|
|
||||||
|
|
@ -47,10 +47,6 @@ services:
|
||||||
MAM_API_URL: ${MAM_API_URL}
|
MAM_API_URL: ${MAM_API_URL}
|
||||||
NODE_TOKEN: ${NODE_TOKEN:-}
|
NODE_TOKEN: ${NODE_TOKEN:-}
|
||||||
NODE_ROLE: ${NODE_ROLE:-worker}
|
NODE_ROLE: ${NODE_ROLE:-worker}
|
||||||
# NODE_NAME pins the cluster identity (heartbeat key). Set it per-node so
|
|
||||||
# cloned VMs that share /etc/hostname don't collide on the same
|
|
||||||
# cluster_nodes row. Falls back to the OS hostname when unset.
|
|
||||||
NODE_NAME: ${NODE_NAME:-}
|
|
||||||
NODE_IP: ${NODE_IP:-}
|
NODE_IP: ${NODE_IP:-}
|
||||||
AGENT_PORT: ${AGENT_PORT:-7436}
|
AGENT_PORT: ${AGENT_PORT:-7436}
|
||||||
HEARTBEAT_MS: ${HEARTBEAT_MS:-30000}
|
HEARTBEAT_MS: ${HEARTBEAT_MS:-30000}
|
||||||
|
|
@ -59,25 +55,10 @@ services:
|
||||||
BMD_MODEL: ${BMD_MODEL:-}
|
BMD_MODEL: ${BMD_MODEL:-}
|
||||||
BMD_DEVICE_PREFIX: ${BMD_DEVICE_PREFIX:-dv}
|
BMD_DEVICE_PREFIX: ${BMD_DEVICE_PREFIX:-dv}
|
||||||
LIVE_DIR: ${LIVE_DIR:-/mnt/NVME/MAM/wild-dragon-live}
|
LIVE_DIR: ${LIVE_DIR:-/mnt/NVME/MAM/wild-dragon-live}
|
||||||
# REPO_DIR: host path to the checked-out repo. The agent passes this to the
|
|
||||||
# one-shot driver-install container so install-driver.sh can read
|
|
||||||
# sdk/<vendor>/ and run deploy/install-driver.sh. Must match the host path
|
|
||||||
# bind-mounted below (onboard-node.sh clones to /opt/wild-dragon).
|
|
||||||
REPO_DIR: ${REPO_DIR:-/opt/wild-dragon}
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /dev:/dev:ro
|
- /dev:/dev:ro
|
||||||
- /mnt/NVME/MAM/wild-dragon-live:/mnt/NVME/MAM/wild-dragon-live:ro
|
- /mnt/NVME/MAM/wild-dragon-live:/mnt/NVME/MAM/wild-dragon-live:ro
|
||||||
# Capture-driver deployment ("Capture Drivers / SDKs" in the Cluster admin
|
|
||||||
# screen): the agent itself does NOT run dkms/modprobe — it spawns a
|
|
||||||
# separate privileged ubuntu container that bind-mounts these host paths.
|
|
||||||
# The agent only needs to *see* the repo path so it can pass it through as
|
|
||||||
# a bind to that container; no extra privileges are granted to the agent.
|
|
||||||
# /opt/wild-dragon → repo (sdk/<vendor>/ + deploy/install-driver.sh)
|
|
||||||
# The install container additionally mounts /lib/modules,/usr/src,/boot,
|
|
||||||
# /dev and /opt from the host (handled in the agent, not here) so DKMS /
|
|
||||||
# modprobe / ldconfig affect the host kernel.
|
|
||||||
- ${REPO_DIR:-/opt/wild-dragon}:${REPO_DIR:-/opt/wild-dragon}:ro
|
|
||||||
devices:
|
devices:
|
||||||
- /dev/blackmagic:/dev/blackmagic
|
- /dev/blackmagic:/dev/blackmagic
|
||||||
|
|
||||||
|
|
@ -122,7 +103,6 @@ services:
|
||||||
# worker-l4: HEAVY tier (proxy/conform/trim) on the L4 (NVENC). Talks to
|
# worker-l4: HEAVY tier (proxy/conform/trim) on the L4 (NVENC). Talks to
|
||||||
# zampp1's Redis/Postgres/S3 over the LAN (.200). No promotion scanner here.
|
# zampp1's Redis/Postgres/S3 over the LAN (.200). No promotion scanner here.
|
||||||
worker-l4:
|
worker-l4:
|
||||||
profiles: [gpu]
|
|
||||||
build:
|
build:
|
||||||
context: ./services/worker
|
context: ./services/worker
|
||||||
dockerfile: Dockerfile.gpu
|
dockerfile: Dockerfile.gpu
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ services:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- /mnt/NVME/MAM/wild-dragon-live:/live
|
- /mnt/NVME/MAM/wild-dragon-live:/live
|
||||||
- /mnt/NVME/MAM/wild-dragon-growing:/growing
|
- /mnt/NVME/MAM/wild-dragon-growing:/growing
|
||||||
- /mnt/NVME/MAM/wild-dragon-media:/media
|
|
||||||
- /mnt/NVME/MAM/sdk:/sdk
|
- /mnt/NVME/MAM/sdk:/sdk
|
||||||
- /dev/shm:/dev/shm
|
- /dev/shm:/dev/shm
|
||||||
- /run/dbus:/run/dbus
|
- /run/dbus:/run/dbus
|
||||||
|
|
@ -61,14 +60,7 @@ services:
|
||||||
DOCKER_NETWORK: wild-dragon_wild-dragon
|
DOCKER_NETWORK: wild-dragon_wild-dragon
|
||||||
NODE_IP: ${NODE_IP}
|
NODE_IP: ${NODE_IP}
|
||||||
NODE_HOSTNAME: ${NODE_HOSTNAME:-}
|
NODE_HOSTNAME: ${NODE_HOSTNAME:-}
|
||||||
# Bearer mam-api forwards to a node-agent when installing capture drivers
|
|
||||||
# ("Capture Drivers / SDKs" panel). Set to the same value as the agents'
|
|
||||||
# NODE_TOKEN. If empty, agents with an empty NODE_TOKEN accept the call
|
|
||||||
# (dev); agents with a token will reject it (401).
|
|
||||||
NODE_AGENT_TOKEN: ${NODE_AGENT_TOKEN:-}
|
|
||||||
CAPTURE_TOKEN: ${CAPTURE_TOKEN}
|
CAPTURE_TOKEN: ${CAPTURE_TOKEN}
|
||||||
PLAYOUT_IMAGE: ${PLAYOUT_IMAGE:-wild-dragon-playout:latest}
|
|
||||||
PLAYOUT_AMCP_BASE_PORT: ${PLAYOUT_AMCP_BASE_PORT:-5250}
|
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
reservations:
|
reservations:
|
||||||
|
|
@ -112,11 +104,6 @@ services:
|
||||||
dockerfile: Dockerfile.gpu
|
dockerfile: Dockerfile.gpu
|
||||||
image: wild-dragon-worker-gpu:latest
|
image: wild-dragon-worker-gpu:latest
|
||||||
runtime: nvidia
|
runtime: nvidia
|
||||||
# Privileged so the promotion scanner can mount the growing-files CIFS share
|
|
||||||
# at /growing (same Approach A as the capture sidecar). Without the share
|
|
||||||
# mounted the scanner watches an empty local dir and never promotes growing
|
|
||||||
# captures to S3.
|
|
||||||
privileged: true
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- queue
|
- queue
|
||||||
- db
|
- db
|
||||||
|
|
@ -129,22 +116,14 @@ services:
|
||||||
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||||
S3_REGION: ${S3_REGION:-us-east-1}
|
S3_REGION: ${S3_REGION:-us-east-1}
|
||||||
GROWING_PATH: /growing
|
GROWING_PATH: /growing
|
||||||
# Includes `import` (YouTube importer): the import queue had no consumer
|
WORKER_QUEUES: proxy,conform,trim
|
||||||
# after the capability-routing split, so import jobs sat unprocessed and
|
|
||||||
# assets stayed `ingesting` forever. import is concurrency-1 + network-
|
|
||||||
# bound, so one consumer (this heavy/primary worker) is sufficient.
|
|
||||||
WORKER_QUEUES: proxy,conform,trim,import,playout-stage
|
|
||||||
RUN_PROMOTION: "true"
|
RUN_PROMOTION: "true"
|
||||||
PROXY_CONCURRENCY: "2"
|
PROXY_CONCURRENCY: "2"
|
||||||
PLAYOUT_MEDIA_DIR: /media
|
|
||||||
NVIDIA_VISIBLE_DEVICES: GPU-79afca3e-2ab2-a6ea-1c44-706c1f0a26d6
|
NVIDIA_VISIBLE_DEVICES: GPU-79afca3e-2ab2-a6ea-1c44-706c1f0a26d6
|
||||||
WORKER_LABEL: "zampp1 / Tesla P4"
|
WORKER_LABEL: "zampp1 / Tesla P4"
|
||||||
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility
|
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility
|
||||||
volumes:
|
volumes:
|
||||||
# NOTE: /growing is NOT a host bind anymore — the promotion scanner mounts
|
- /mnt/NVME/MAM/wild-dragon-growing:/growing
|
||||||
# the CIFS landing-zone share there itself (a bind would shadow it). The
|
|
||||||
# mount needs rshared propagation so the in-container CIFS mount is visible.
|
|
||||||
- /mnt/NVME/MAM/wild-dragon-media:/media
|
|
||||||
networks:
|
networks:
|
||||||
- wild-dragon
|
- wild-dragon
|
||||||
|
|
||||||
|
|
@ -193,22 +172,12 @@ services:
|
||||||
- "${PORT_WEB_UI:-7434}:80"
|
- "${PORT_WEB_UI:-7434}:80"
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/NVME/MAM/wild-dragon-live:/live
|
- /mnt/NVME/MAM/wild-dragon-live:/live
|
||||||
- /mnt/NVME/MAM/wild-dragon-media:/media:ro
|
|
||||||
- /dev/shm:/dev/shm
|
- /dev/shm:/dev/shm
|
||||||
- /run/dbus:/run/dbus
|
- /run/dbus:/run/dbus
|
||||||
- /run/systemd:/run/systemd
|
- /run/systemd:/run/systemd
|
||||||
networks:
|
networks:
|
||||||
- wild-dragon
|
- wild-dragon
|
||||||
|
|
||||||
# Build-only: the CasparCG sidecar image. mam-api spawns these on-demand per
|
|
||||||
# channel (one container per playout channel), so this service is never up'd —
|
|
||||||
# it exists so `docker compose build playout` produces the image the API tags
|
|
||||||
# via PLAYOUT_IMAGE. Profile excludes it from default `up`.
|
|
||||||
playout:
|
|
||||||
profiles: ["build-only"]
|
|
||||||
build: ./services/playout
|
|
||||||
image: wild-dragon-playout:latest
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
|
|
||||||
|
|
@ -1,834 +0,0 @@
|
||||||
# Deltacast SDI Capture — Implementation Plan
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Wire up Deltacast VideoMaster SDI cards in the capture service using a C bridge binary that streams raw video to FFmpeg via pipe, with embedded audio via a named FIFO.
|
|
||||||
|
|
||||||
**Architecture:** A `deltacast-capture` C binary opens the VideoMaster board, waits for signal lock, emits a JSON format line to stderr, then streams raw UYVY video frames to stdout and 2-channel PCM audio to a named FIFO. `capture-manager.js` reads the JSON, spawns FFmpeg with `-f rawvideo -i pipe:0` for video and `-f s16le -i <fifo>` for audio, and pipes bridge stdout into FFmpeg stdin. Two concurrent SDK streams share the same board handle — `VHD_SDI_STPROC_DISJOINED_VIDEO` for video and `VHD_SDI_STPROC_DISJOINED_ANC` for audio.
|
|
||||||
|
|
||||||
**Tech Stack:** Deltacast VideoMaster C SDK 6.34.1 (`libvideomasterhd.so`, `libvideomasterhd_audio.so`), C17, CMake, Node.js ES modules, Docker multi-stage build.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Map
|
|
||||||
|
|
||||||
| Action | Path | Responsibility |
|
|
||||||
|---|---|---|
|
|
||||||
| Create | `services/capture/deltacast-bridge/CMakeLists.txt` | Build config for the bridge binary |
|
|
||||||
| Create | `services/capture/deltacast-bridge/main.c` | Bridge: board open, signal detect, video stream, audio thread |
|
|
||||||
| Modify | `services/capture/Dockerfile` | SDK extraction stage, bridge build stage, runtime .so install |
|
|
||||||
| Modify | `services/capture/src/capture-manager.js` | `readFirstStderrLine` helper, deltacast `_buildInputArgs`, bridge lifecycle in `start()`/`stop()` |
|
|
||||||
| Modify | `services/capture/src/routes/capture.js` | Accept `deltacast` as a valid `source_type` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 1: Bridge CMakeLists.txt
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `services/capture/deltacast-bridge/CMakeLists.txt`
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create the CMakeLists.txt**
|
|
||||||
|
|
||||||
```cmake
|
|
||||||
cmake_minimum_required(VERSION 3.16)
|
|
||||||
project(deltacast-bridge C)
|
|
||||||
set(CMAKE_C_STANDARD 17)
|
|
||||||
|
|
||||||
set(SDK_ROOT "/sdk" CACHE PATH "Path to extracted VideoMaster SDK")
|
|
||||||
|
|
||||||
add_executable(deltacast-capture main.c)
|
|
||||||
|
|
||||||
target_include_directories(deltacast-capture PRIVATE
|
|
||||||
${SDK_ROOT}/include/videomaster
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_directories(deltacast-capture PRIVATE
|
|
||||||
${SDK_ROOT}/lib
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(deltacast-capture PRIVATE
|
|
||||||
videomasterhd
|
|
||||||
videomasterhd_audio
|
|
||||||
pthread
|
|
||||||
)
|
|
||||||
|
|
||||||
# Embed the SDK RPATH so the binary finds the .so at runtime
|
|
||||||
set_target_properties(deltacast-capture PROPERTIES
|
|
||||||
INSTALL_RPATH "/usr/local/lib/deltacast"
|
|
||||||
BUILD_WITH_INSTALL_RPATH TRUE
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/deltacast-bridge/CMakeLists.txt
|
|
||||||
git commit -m "build(capture): add CMakeLists for deltacast-capture bridge binary"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 2: Bridge main.c
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `services/capture/deltacast-bridge/main.c`
|
|
||||||
|
|
||||||
The binary: parses CLI args, opens the board, waits for signal lock, emits one JSON line to stderr, then spawns an audio thread writing to a FIFO and runs a video capture loop writing raw UYVY frames to stdout.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Create the bridge source file**
|
|
||||||
|
|
||||||
```c
|
|
||||||
/* services/capture/deltacast-bridge/main.c
|
|
||||||
*
|
|
||||||
* Deltacast VideoMaster SDI capture bridge.
|
|
||||||
* Writes raw UYVY video to stdout and stereo PCM to a named FIFO.
|
|
||||||
* Emits one JSON line to stderr on signal lock before streaming starts.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* deltacast-capture --device <N> --port <N> --audio-pipe <path>
|
|
||||||
* [--signal-timeout <sec>]
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdatomic.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "VideoMasterHD_Core.h"
|
|
||||||
#include "VideoMasterHD_Sdi.h"
|
|
||||||
#include "VideoMasterHD_Sdi_Audio.h"
|
|
||||||
|
|
||||||
/* ── Globals ─────────────────────────────────────────────────────────── */
|
|
||||||
static atomic_int g_stop = 0;
|
|
||||||
|
|
||||||
static void on_signal(int s) { (void)s; atomic_store(&g_stop, 1); }
|
|
||||||
|
|
||||||
/* ── Stream type by port index ───────────────────────────────────────── */
|
|
||||||
static ULONG rx_streamtype(unsigned port) {
|
|
||||||
switch (port) {
|
|
||||||
case 0: return VHD_ST_RX0;
|
|
||||||
case 1: return VHD_ST_RX1;
|
|
||||||
case 2: return VHD_ST_RX2;
|
|
||||||
case 3: return VHD_ST_RX3;
|
|
||||||
default: return VHD_ST_RX0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Loopback board property by port index ───────────────────────────── */
|
|
||||||
static ULONG loopback_prop(unsigned port) {
|
|
||||||
switch (port) {
|
|
||||||
case 0: return VHD_CORE_BP_PASSIVE_LOOPBACK_0;
|
|
||||||
case 1: return VHD_CORE_BP_PASSIVE_LOOPBACK_1;
|
|
||||||
case 2: return VHD_CORE_BP_PASSIVE_LOOPBACK_2;
|
|
||||||
case 3: return VHD_CORE_BP_PASSIVE_LOOPBACK_3;
|
|
||||||
default: return VHD_CORE_BP_PASSIVE_LOOPBACK_0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Video standard → width/height/fps/interlaced ───────────────────── */
|
|
||||||
typedef struct { int width, height, fps_num, fps_den; int interlaced; } VideoInfo;
|
|
||||||
|
|
||||||
static VideoInfo video_info(VHD_VIDEOSTANDARD std, VHD_CLOCKDIVISOR div) {
|
|
||||||
int ntsc = (div == VHD_CLOCKDIV_1001);
|
|
||||||
switch (std) {
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_25Hz: return (VideoInfo){1920,1080,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_30Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_24Hz: return (VideoInfo){1920,1080,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_50Hz: return (VideoInfo){1920,1080,50,1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_60Hz: return (VideoInfo){1920,1080,ntsc?60000:60,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080psf_24Hz: return (VideoInfo){1920,1080,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080psf_25Hz: return (VideoInfo){1920,1080,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080psf_30Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080i_50Hz: return (VideoInfo){1920,1080,25,1,1};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080i_60Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,1};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_50Hz: return (VideoInfo){1280,720,50,1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_60Hz: return (VideoInfo){1280,720,ntsc?60000:60,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_25Hz: return (VideoInfo){1280,720,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_30Hz: return (VideoInfo){1280,720,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_24Hz: return (VideoInfo){1280,720,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_24Hz: return (VideoInfo){3840,2160,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_25Hz: return (VideoInfo){3840,2160,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_30Hz: return (VideoInfo){3840,2160,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_50Hz: return (VideoInfo){3840,2160,50,1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_60Hz: return (VideoInfo){3840,2160,ntsc?60000:60,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S259M_NTSC_480: return (VideoInfo){720,480,ntsc?30000:30,ntsc?1001:1,1};
|
|
||||||
default: return (VideoInfo){1920,1080,25,1,0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Audio thread ────────────────────────────────────────────────────── */
|
|
||||||
typedef struct {
|
|
||||||
HANDLE board;
|
|
||||||
unsigned port;
|
|
||||||
ULONG video_std;
|
|
||||||
ULONG clock_div;
|
|
||||||
const char *fifo_path;
|
|
||||||
} AudioArgs;
|
|
||||||
|
|
||||||
static void *audio_thread(void *arg) {
|
|
||||||
AudioArgs *a = (AudioArgs *)arg;
|
|
||||||
|
|
||||||
HANDLE stream = NULL;
|
|
||||||
ULONG r = VHD_OpenStreamHandle(a->board, rx_streamtype(a->port),
|
|
||||||
VHD_SDI_STPROC_DISJOINED_ANC,
|
|
||||||
NULL, &stream, NULL);
|
|
||||||
if (r != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "[audio] VHD_OpenStreamHandle failed: %lu\n", r);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
VHD_SetStreamProperty(stream, VHD_SDI_SP_VIDEO_STANDARD, a->video_std);
|
|
||||||
VHD_SetStreamProperty(stream, VHD_SDI_SP_CLOCK_SYSTEM, a->clock_div);
|
|
||||||
VHD_SetStreamProperty(stream, VHD_CORE_SP_TRANSFER_SCHEME, VHD_TRANSFER_SLAVED);
|
|
||||||
|
|
||||||
/* Stereo pair, 16-bit, 48kHz on group 0 channel 0 */
|
|
||||||
ULONG max_samples = VHD_GetNbSamples((VHD_VIDEOSTANDARD)a->video_std,
|
|
||||||
(VHD_CLOCKDIVISOR)a->clock_div,
|
|
||||||
VHD_ASR_48000, 0);
|
|
||||||
ULONG block_size = VHD_GetBlockSize(VHD_AF_16, VHD_AM_STEREO);
|
|
||||||
ULONG buf_sz = (max_samples + 4) * block_size; /* +4 for 29.97 variation */
|
|
||||||
unsigned char *buf = calloc(1, buf_sz);
|
|
||||||
if (!buf) { VHD_CloseStreamHandle(stream); return NULL; }
|
|
||||||
|
|
||||||
VHD_AUDIOINFO ai;
|
|
||||||
memset(&ai, 0, sizeof(ai));
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].Mode = VHD_AM_STEREO;
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].BufferFormat = VHD_AF_16;
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].pData = buf;
|
|
||||||
|
|
||||||
if (VHD_StartStream(stream) != VHDERR_NOERROR) {
|
|
||||||
free(buf); VHD_CloseStreamHandle(stream); return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Open FIFO for writing — blocks until FFmpeg opens the read end */
|
|
||||||
int fd = open(a->fifo_path, O_WRONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
fprintf(stderr, "[audio] open FIFO failed: %s\n", strerror(errno));
|
|
||||||
VHD_StopStream(stream); VHD_CloseStreamHandle(stream); free(buf);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE slot = NULL;
|
|
||||||
while (!atomic_load(&g_stop)) {
|
|
||||||
r = VHD_LockSlotHandle(stream, &slot);
|
|
||||||
if (r == VHDERR_NOERROR) {
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].DataSize = buf_sz;
|
|
||||||
if (VHD_SlotExtractAudio(slot, &ai) == VHDERR_NOERROR) {
|
|
||||||
ULONG sz = ai.pAudioGroups[0].pAudioChannels[0].DataSize;
|
|
||||||
if (sz > 0) write(fd, buf, sz);
|
|
||||||
}
|
|
||||||
VHD_UnlockSlotHandle(slot);
|
|
||||||
} else if (r != VHDERR_TIMEOUT) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
VHD_StopStream(stream);
|
|
||||||
VHD_CloseStreamHandle(stream);
|
|
||||||
free(buf);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Main ────────────────────────────────────────────────────────────── */
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
unsigned device_id = 0;
|
|
||||||
unsigned port_id = 0;
|
|
||||||
int sig_timeout = 30;
|
|
||||||
const char *audio_pipe = NULL;
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
if (!strcmp(argv[i], "--device") && i+1 < argc) device_id = (unsigned)atoi(argv[++i]);
|
|
||||||
else if (!strcmp(argv[i], "--port") && i+1 < argc) port_id = (unsigned)atoi(argv[++i]);
|
|
||||||
else if (!strcmp(argv[i], "--audio-pipe") && i+1 < argc) audio_pipe = argv[++i];
|
|
||||||
else if (!strcmp(argv[i], "--signal-timeout") && i+1 < argc) sig_timeout = atoi(argv[++i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
signal(SIGINT, on_signal);
|
|
||||||
signal(SIGTERM, on_signal);
|
|
||||||
|
|
||||||
/* ── Init API ─────────────────────────────────────────────────── */
|
|
||||||
ULONG dll_ver, nb_boards;
|
|
||||||
if (VHD_GetApiInfo(&dll_ver, &nb_boards) != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "{\"error\":\"VHD_GetApiInfo failed\"}\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (device_id >= nb_boards) {
|
|
||||||
fprintf(stderr, "{\"error\":\"board %u not found (%lu detected)\"}\n", device_id, nb_boards);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Open board ───────────────────────────────────────────────── */
|
|
||||||
HANDLE board = NULL;
|
|
||||||
if (VHD_OpenBoardHandle(device_id, &board, NULL, 0) != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "{\"error\":\"VHD_OpenBoardHandle failed for board %u\"}\n", device_id);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disable passive (relay) loopback so RX is live */
|
|
||||||
VHD_SetBoardProperty(board, loopback_prop(port_id), FALSE);
|
|
||||||
|
|
||||||
/* ── Wait for signal lock ──────────────────────────────────────── */
|
|
||||||
ULONG video_std = (ULONG)NB_VHD_VIDEOSTANDARDS;
|
|
||||||
struct timespec deadline;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &deadline);
|
|
||||||
deadline.tv_sec += sig_timeout;
|
|
||||||
|
|
||||||
while (!atomic_load(&g_stop)) {
|
|
||||||
struct timespec now;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
if (now.tv_sec > deadline.tv_sec ||
|
|
||||||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) break;
|
|
||||||
|
|
||||||
VHD_GetChannelProperty(board, VHD_RX_CHANNEL, port_id,
|
|
||||||
VHD_SDI_CP_VIDEO_STANDARD, &video_std);
|
|
||||||
if (video_std != (ULONG)NB_VHD_VIDEOSTANDARDS) break;
|
|
||||||
|
|
||||||
struct timespec ts = {0, 200000000L}; /* 200ms */
|
|
||||||
nanosleep(&ts, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (atomic_load(&g_stop) || video_std == (ULONG)NB_VHD_VIDEOSTANDARDS) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"{\"error\":\"no signal on board %u port %u within %ds\"}\n",
|
|
||||||
device_id, port_id, sig_timeout);
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ULONG clock_div = VHD_CLOCKDIV_1;
|
|
||||||
VHD_GetChannelProperty(board, VHD_RX_CHANNEL, port_id,
|
|
||||||
VHD_SDI_CP_CLOCK_DIVISOR, &clock_div);
|
|
||||||
|
|
||||||
VideoInfo vi = video_info((VHD_VIDEOSTANDARD)video_std,
|
|
||||||
(VHD_CLOCKDIVISOR)clock_div);
|
|
||||||
|
|
||||||
/* ── Emit format JSON to stderr (one line, flushed) ─────────────── */
|
|
||||||
fprintf(stderr,
|
|
||||||
"{\"width\":%d,\"height\":%d,\"fps_num\":%d,\"fps_den\":%d,"
|
|
||||||
"\"interlaced\":%s,\"pix_fmt\":\"uyvy422\","
|
|
||||||
"\"audio_channels\":2,\"audio_rate\":48000,"
|
|
||||||
"\"device\":%u,\"port\":%u}\n",
|
|
||||||
vi.width, vi.height, vi.fps_num, vi.fps_den,
|
|
||||||
vi.interlaced ? "true" : "false",
|
|
||||||
device_id, port_id);
|
|
||||||
fflush(stderr);
|
|
||||||
|
|
||||||
/* ── Open video stream ───────────────────────────────────────────── */
|
|
||||||
HANDLE video_stream = NULL;
|
|
||||||
if (VHD_OpenStreamHandle(board, rx_streamtype(port_id),
|
|
||||||
VHD_SDI_STPROC_DISJOINED_VIDEO,
|
|
||||||
NULL, &video_stream, NULL) != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "{\"error\":\"VHD_OpenStreamHandle (video) failed\"}\n");
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_SDI_SP_VIDEO_STANDARD, video_std);
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_SDI_SP_CLOCK_SYSTEM, clock_div);
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_CORE_SP_TRANSFER_SCHEME, VHD_TRANSFER_SLAVED);
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_CORE_SP_SLOTS_QUEUE_DEPTH, 8);
|
|
||||||
|
|
||||||
/* ── Launch audio thread (FIFO open blocks until FFmpeg connects) ── */
|
|
||||||
pthread_t audio_tid = 0;
|
|
||||||
AudioArgs audio_args = { board, port_id, video_std, clock_div, audio_pipe };
|
|
||||||
if (audio_pipe) {
|
|
||||||
pthread_create(&audio_tid, NULL, audio_thread, &audio_args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Start video stream ──────────────────────────────────────────── */
|
|
||||||
if (VHD_StartStream(video_stream) != VHDERR_NOERROR) {
|
|
||||||
atomic_store(&g_stop, 1);
|
|
||||||
if (audio_tid) pthread_join(audio_tid, NULL);
|
|
||||||
VHD_CloseStreamHandle(video_stream);
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Video capture loop ──────────────────────────────────────────── */
|
|
||||||
HANDLE slot = NULL;
|
|
||||||
while (!atomic_load(&g_stop)) {
|
|
||||||
ULONG r = VHD_LockSlotHandle(video_stream, &slot);
|
|
||||||
if (r == VHDERR_NOERROR) {
|
|
||||||
BYTE *buf = NULL;
|
|
||||||
ULONG sz = 0;
|
|
||||||
if (VHD_GetSlotBuffer(slot, VHD_SDI_BT_VIDEO, &buf, &sz) == VHDERR_NOERROR) {
|
|
||||||
ULONG written = 0;
|
|
||||||
while (written < sz) {
|
|
||||||
ssize_t n = write(STDOUT_FILENO, buf + written, sz - written);
|
|
||||||
if (n <= 0) { atomic_store(&g_stop, 1); break; }
|
|
||||||
written += (ULONG)n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VHD_UnlockSlotHandle(slot);
|
|
||||||
} else if (r != VHDERR_TIMEOUT) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Cleanup ─────────────────────────────────────────────────────── */
|
|
||||||
VHD_StopStream(video_stream);
|
|
||||||
VHD_CloseStreamHandle(video_stream);
|
|
||||||
if (audio_tid) pthread_join(audio_tid, NULL);
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/deltacast-bridge/main.c
|
|
||||||
git commit -m "feat(capture): add deltacast-capture bridge binary source"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 3: Dockerfile — SDK extraction + bridge build + runtime
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `services/capture/Dockerfile`
|
|
||||||
|
|
||||||
The existing Dockerfile has three logical sections: FFmpeg build, runtime. We add two new stages before FFmpeg and patch the runtime stage.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Read the current Dockerfile**
|
|
||||||
|
|
||||||
Read `services/capture/Dockerfile` and verify it starts with `FROM debian:bookworm AS ffmpeg-builder`.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Prepend two new stages and patch runtime**
|
|
||||||
|
|
||||||
The full new Dockerfile:
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# ── Stage 0: Extract Deltacast VideoMaster SDK ───────────────────────────
|
|
||||||
FROM debian:bookworm AS sdk-extractor
|
|
||||||
COPY videomaster-linux.x64-6.34.1-dev.tar.gz /tmp/
|
|
||||||
RUN mkdir -p /sdk && tar -xzf /tmp/videomaster-linux.x64-6.34.1-dev.tar.gz -C /sdk
|
|
||||||
|
|
||||||
# ── Stage 1: Build deltacast-capture bridge binary ───────────────────────
|
|
||||||
FROM debian:bookworm AS bridge-builder
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
build-essential cmake ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
COPY --from=sdk-extractor /sdk /sdk
|
|
||||||
COPY deltacast-bridge/ /bridge/
|
|
||||||
RUN cmake -S /bridge -B /bridge/build \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DSDK_ROOT=/sdk \
|
|
||||||
&& cmake --build /bridge/build -j$(nproc)
|
|
||||||
|
|
||||||
# ── Stage 2: Build FFmpeg with DeckLink + NVENC (HEVC/H264) support ──────
|
|
||||||
# (unchanged — keep original content here)
|
|
||||||
FROM debian:bookworm AS ffmpeg-builder
|
|
||||||
# ... (rest of the existing ffmpeg-builder stage unchanged) ...
|
|
||||||
|
|
||||||
# ── Stage 3: Runtime image ───────────────────────────────────────────────
|
|
||||||
FROM node:20-bookworm
|
|
||||||
|
|
||||||
# Runtime deps for compiled ffmpeg libs (unchanged)
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
libx264-164 libx265-199 libvpx7 libopus0 libmp3lame0 \
|
|
||||||
libsrt1.5-openssl libzmq5 libstdc++6 libc++1 libc++abi1 \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Copy compiled ffmpeg/ffprobe (unchanged)
|
|
||||||
COPY --from=ffmpeg-builder /usr/local/bin/ffmpeg /usr/local/bin/ffmpeg
|
|
||||||
COPY --from=ffmpeg-builder /usr/local/bin/ffprobe /usr/local/bin/ffprobe
|
|
||||||
COPY --from=ffmpeg-builder /usr/local/lib/ /usr/local/lib/
|
|
||||||
|
|
||||||
# DeckLink runtime .so (unchanged)
|
|
||||||
COPY lib/libDeckLinkAPI.so /usr/lib/libDeckLinkAPI.so
|
|
||||||
COPY lib/libDeckLinkPreviewAPI.so /usr/lib/libDeckLinkPreviewAPI.so
|
|
||||||
|
|
||||||
# Deltacast bridge binary + SDK runtime libs
|
|
||||||
COPY --from=bridge-builder /bridge/build/deltacast-capture /usr/local/bin/deltacast-capture
|
|
||||||
COPY --from=sdk-extractor /sdk/lib/libvideomasterhd.so.6.34.1 /usr/local/lib/deltacast/
|
|
||||||
COPY --from=sdk-extractor /sdk/lib/libvideomasterhd_audio.so.6.34.1 /usr/local/lib/deltacast/
|
|
||||||
RUN ln -sf libvideomasterhd.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd.so.6 \
|
|
||||||
&& ln -sf libvideomasterhd.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd.so \
|
|
||||||
&& ln -sf libvideomasterhd_audio.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd_audio.so.6 \
|
|
||||||
&& ln -sf libvideomasterhd_audio.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd_audio.so \
|
|
||||||
&& ldconfig /usr/local/lib/deltacast \
|
|
||||||
&& ldconfig
|
|
||||||
|
|
||||||
RUN mkdir -p /live /growing
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install --omit=dev
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
EXPOSE 3001
|
|
||||||
CMD ["node", "src/index.js"]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Implementation note:** Edit the existing Dockerfile. Prepend the two new FROM stages (sdk-extractor, bridge-builder) before the existing `FROM debian:bookworm AS ffmpeg-builder` line. Then in the final runtime stage, add the Deltacast `COPY` and `RUN` lines after the DeckLink `.so` lines (before the `RUN mkdir -p /live /growing` line).
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/Dockerfile
|
|
||||||
git commit -m "build(capture): add Deltacast SDK extraction and bridge build stages to Dockerfile"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 4: capture-manager.js — `readFirstStderrLine` helper
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `services/capture/src/capture-manager.js` (add helper near top, after imports)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Add the helper function after the existing imports (after line 6 `import { v4 as uuidv4 } from 'uuid';`)**
|
|
||||||
|
|
||||||
```js
|
|
||||||
/**
|
|
||||||
* Reads the first line from a spawned process's stderr stream.
|
|
||||||
* Resolves with the parsed JSON object when the first '\n' arrives.
|
|
||||||
* Rejects if the process exits with a non-zero code before emitting a line,
|
|
||||||
* or if timeoutMs elapses.
|
|
||||||
*/
|
|
||||||
function readFirstStderrLine(proc, timeoutMs = 35_000) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let buf = '';
|
|
||||||
let settled = false;
|
|
||||||
const settle = (fn) => { if (settled) return; settled = true; fn(); };
|
|
||||||
|
|
||||||
const timer = setTimeout(() => {
|
|
||||||
settle(() => reject(new Error(`deltacast-capture: timed out waiting for format JSON after ${timeoutMs}ms`)));
|
|
||||||
}, timeoutMs);
|
|
||||||
|
|
||||||
proc.stderr.setEncoding('utf8');
|
|
||||||
proc.stderr.on('data', (chunk) => {
|
|
||||||
buf += chunk;
|
|
||||||
const nl = buf.indexOf('\n');
|
|
||||||
if (nl === -1) return;
|
|
||||||
const line = buf.slice(0, nl).trim();
|
|
||||||
clearTimeout(timer);
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(line);
|
|
||||||
if (parsed.error) {
|
|
||||||
settle(() => reject(new Error(`deltacast-capture: ${parsed.error}`)));
|
|
||||||
} else {
|
|
||||||
settle(() => resolve(parsed));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
settle(() => reject(new Error(`deltacast-capture: invalid JSON on stderr: ${line}`)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
proc.on('exit', (code) => {
|
|
||||||
clearTimeout(timer);
|
|
||||||
settle(() => reject(new Error(`deltacast-capture: exited with code ${code} before emitting format JSON`)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/src/capture-manager.js
|
|
||||||
git commit -m "feat(capture): add readFirstStderrLine helper for deltacast bridge handshake"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 5: capture-manager.js — Deltacast `_buildInputArgs`
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `services/capture/src/capture-manager.js` — replace the `deltacast` branch of `_buildInputArgs` (currently lines 160–191)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Replace the existing deltacast branch**
|
|
||||||
|
|
||||||
Find the block starting with `// Deltacast SDI via VideoMaster SDK FFmpeg plugin.` and ending at the closing `}` of the `if (sourceType === 'deltacast')` block. Replace the entire `if (sourceType === 'deltacast') { ... }` block with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (sourceType === 'deltacast') {
|
|
||||||
const idx = (typeof device === 'number' || /^\d+$/.test(String(device)))
|
|
||||||
? parseInt(device, 10) : 0;
|
|
||||||
const audioFifo = `/tmp/dc-audio-${this._sessionIdForBridge}`;
|
|
||||||
|
|
||||||
// Create the audio FIFO before spawning the bridge.
|
|
||||||
const { execSync: _exec } = await import('child_process');
|
|
||||||
try { _exec(`mkfifo ${audioFifo}`); } catch (_) { /* may already exist */ }
|
|
||||||
|
|
||||||
const bridge = spawn('deltacast-capture', [
|
|
||||||
'--device', String(idx),
|
|
||||||
'--port', String(idx),
|
|
||||||
'--audio-pipe', audioFifo,
|
|
||||||
'--signal-timeout', '30',
|
|
||||||
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
||||||
|
|
||||||
// Log bridge stderr after the first line (non-JSON diagnostic output)
|
|
||||||
let firstLineDone = false;
|
|
||||||
bridge.stderr.on('data', (d) => {
|
|
||||||
if (firstLineDone) console.error(`[deltacast-bridge] ${d}`);
|
|
||||||
else if (d.toString().includes('\n')) firstLineDone = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const fmt = await readFirstStderrLine(bridge, 35_000);
|
|
||||||
// fmt: { width, height, fps_num, fps_den, interlaced, pix_fmt,
|
|
||||||
// audio_channels, audio_rate, device, port }
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputArgs: [
|
|
||||||
'-f', 'rawvideo',
|
|
||||||
'-pix_fmt', fmt.pix_fmt,
|
|
||||||
'-video_size', `${fmt.width}x${fmt.height}`,
|
|
||||||
'-framerate', `${fmt.fps_num}/${fmt.fps_den}`,
|
|
||||||
'-i', 'pipe:0',
|
|
||||||
'-f', 's16le',
|
|
||||||
'-ar', String(fmt.audio_rate),
|
|
||||||
'-ac', String(fmt.audio_channels),
|
|
||||||
'-i', audioFifo,
|
|
||||||
],
|
|
||||||
isNetwork: false,
|
|
||||||
bridgeProcess: bridge,
|
|
||||||
audioFifo,
|
|
||||||
interlaced: !!fmt.interlaced,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/src/capture-manager.js
|
|
||||||
git commit -m "feat(capture): replace deltacast _buildInputArgs stub with real bridge spawn"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 6: capture-manager.js — `start()` bridge lifecycle + `stop()` cleanup
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `services/capture/src/capture-manager.js`
|
|
||||||
|
|
||||||
Four changes to `start()` and one to `stop()`.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Store session ID before `_buildInputArgs` call**
|
|
||||||
|
|
||||||
In `start()`, before the `const { inputArgs, isNetwork } = await this._buildInputArgs(...)` call (currently around line 307), add:
|
|
||||||
|
|
||||||
```js
|
|
||||||
this._sessionIdForBridge = sessionId;
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Store bridge state after `_buildInputArgs` returns**
|
|
||||||
|
|
||||||
After `const { inputArgs, isNetwork } = await this._buildInputArgs(...)`, change the destructuring to also capture `bridgeProcess` and `audioFifo`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const { inputArgs, isNetwork, bridgeProcess = null, audioFifo = null, interlaced = false } = await this._buildInputArgs({
|
|
||||||
sourceType, device, sourceUrl, listen, listenPort, streamKey,
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Pipe bridge stdout into FFmpeg stdin for deltacast**
|
|
||||||
|
|
||||||
After `const hiresProcess = spawn('ffmpeg', hiresArgs, { stdio: hiresStdio });`, add:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// For deltacast, the bridge writes raw video to its stdout.
|
|
||||||
// Pipe it into FFmpeg's stdin so FFmpeg reads -i pipe:0.
|
|
||||||
if (bridgeProcess) {
|
|
||||||
bridgeProcess.stdout.pipe(hiresProcess.stdin);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Add bridge to `processes` map and `audioFifo` to `currentSession`**
|
|
||||||
|
|
||||||
Change the existing `const processes = { hires: hiresProcess };` line to:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const processes = { hires: hiresProcess };
|
|
||||||
if (bridgeProcess) processes.bridge = bridgeProcess;
|
|
||||||
```
|
|
||||||
|
|
||||||
And in the `this.state.currentSession = { ... }` object (near the end of `start()`), add:
|
|
||||||
|
|
||||||
```js
|
|
||||||
audioFifo,
|
|
||||||
```
|
|
||||||
|
|
||||||
to the object literal (alongside `sourceType`, `device`, etc.).
|
|
||||||
|
|
||||||
- [ ] **Step 5: Fix deinterlace filter to include deltacast interlaced signals**
|
|
||||||
|
|
||||||
Find the line (currently ~321):
|
|
||||||
```js
|
|
||||||
const sdiFilterArgs = (sourceType === 'sdi') ? ['-vf', 'yadif=mode=1:deint=1'] : [];
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```js
|
|
||||||
const isInterlacedSource = sourceType === 'sdi' || (sourceType === 'deltacast' && interlaced);
|
|
||||||
const sdiFilterArgs = isInterlacedSource ? ['-vf', 'yadif=mode=1:deint=1'] : [];
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 6: Include deltacast in the HLS split-output branch**
|
|
||||||
|
|
||||||
Find the line (currently ~334):
|
|
||||||
```js
|
|
||||||
if (sourceType === 'sdi' && this._assetIdForHls) {
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace with:
|
|
||||||
```js
|
|
||||||
if ((sourceType === 'sdi' || sourceType === 'deltacast') && this._assetIdForHls) {
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 7: Kill bridge in `stop()` and clean up FIFO**
|
|
||||||
|
|
||||||
In the `stop()` method, find the existing kill block:
|
|
||||||
```js
|
|
||||||
if (processes.hires) processes.hires.kill('SIGINT');
|
|
||||||
if (processes.proxy) processes.proxy.kill('SIGINT');
|
|
||||||
if (processes.hls) { try { processes.hls.kill('SIGINT'); } catch (_) {} }
|
|
||||||
```
|
|
||||||
|
|
||||||
Add a bridge kill and FIFO cleanup:
|
|
||||||
```js
|
|
||||||
if (processes.hires) processes.hires.kill('SIGINT');
|
|
||||||
if (processes.proxy) processes.proxy.kill('SIGINT');
|
|
||||||
if (processes.hls) { try { processes.hls.kill('SIGINT'); } catch (_) {} }
|
|
||||||
if (processes.bridge) { try { processes.bridge.kill('SIGINT'); } catch (_) {} }
|
|
||||||
```
|
|
||||||
|
|
||||||
Then after the existing `await Promise.all(uploadPromises);` block (around line 462), add FIFO cleanup:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (currentSession.audioFifo) {
|
|
||||||
try { (await import('node:fs')).unlinkSync(currentSession.audioFifo); } catch (_) {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 8: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/src/capture-manager.js
|
|
||||||
git commit -m "feat(capture): wire bridge process lifecycle into start/stop for deltacast"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 7: routes/capture.js — Accept `deltacast` source_type
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `services/capture/src/routes/capture.js` (line 329)
|
|
||||||
|
|
||||||
- [ ] **Step 1: Find the source_type validation block in `/start` handler (around line 318)**
|
|
||||||
|
|
||||||
Current code:
|
|
||||||
```js
|
|
||||||
} else {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: `Unknown source_type: ${source_type}. Must be sdi, srt, or rtmp`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This `else` branch fires when source_type isn't `sdi`, `srt`, or `rtmp`. Add `deltacast` to the accepted list.
|
|
||||||
|
|
||||||
- [ ] **Step 2: Add deltacast validation before the else block**
|
|
||||||
|
|
||||||
After the `} else if (source_type === 'srt' || source_type === 'rtmp') {` block, add:
|
|
||||||
|
|
||||||
```js
|
|
||||||
} else if (source_type === 'deltacast') {
|
|
||||||
if (device === undefined || device === null) {
|
|
||||||
return res.status(400).json({ error: 'deltacast source requires: device (board/port index)' });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: `Unknown source_type: ${source_type}. Must be sdi, srt, rtmp, or deltacast`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add services/capture/src/routes/capture.js
|
|
||||||
git commit -m "feat(capture): accept deltacast as valid source_type in /start handler"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task 8: Smoke test — verify the build and Node.js changes
|
|
||||||
|
|
||||||
**Files:** None created.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Verify the bridge compiles on the capture host (or in Docker)**
|
|
||||||
|
|
||||||
On the Deltacast machine (once it is available), run:
|
|
||||||
```bash
|
|
||||||
cd services/capture
|
|
||||||
tar -xzf ../../videomaster-linux.x64-6.34.1-dev.tar.gz -C /tmp/sdk
|
|
||||||
cmake -S deltacast-bridge -B /tmp/bridge-build -DSDK_ROOT=/tmp/sdk -DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build /tmp/bridge-build -j$(nproc)
|
|
||||||
ls -lh /tmp/bridge-build/deltacast-capture
|
|
||||||
```
|
|
||||||
Expected: binary present, size ~50–200KB.
|
|
||||||
|
|
||||||
Until the hardware machine is available, verify the CMakeLists.txt syntax is correct by running the configure step only:
|
|
||||||
```bash
|
|
||||||
cmake -S services/capture/deltacast-bridge -B /tmp/bridge-test \
|
|
||||||
-DSDK_ROOT=C:/Users/zacga/Nextcloud/Claude/Projects/Dragonflight \
|
|
||||||
--check-system-vars 2>&1 | head -20
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Verify capture-manager.js has no syntax errors**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd services/capture
|
|
||||||
node --input-type=module < src/capture-manager.js 2>&1 | head -5
|
|
||||||
```
|
|
||||||
Expected: no output (file imports fine) or a module-not-found error for uuid (acceptable — the file is correct).
|
|
||||||
|
|
||||||
- [ ] **Step 3: Verify routes/capture.js has no syntax errors**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node --input-type=module < services/capture/src/routes/capture.js 2>&1 | head -5
|
|
||||||
```
|
|
||||||
Expected: no output or dependency error only.
|
|
||||||
|
|
||||||
- [ ] **Step 4: Confirm deltacast recorder creation is rejected correctly without device param**
|
|
||||||
|
|
||||||
Start the capture service locally (if possible) and POST:
|
|
||||||
```bash
|
|
||||||
curl -s -X POST http://localhost:3001/capture/start \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"project_id":"test","clip_name":"test","source_type":"deltacast"}' | jq .
|
|
||||||
```
|
|
||||||
Expected response:
|
|
||||||
```json
|
|
||||||
{"error":"deltacast source requires: device (board/port index)"}
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 5: Final commit if any fixups were needed**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add -A
|
|
||||||
git commit -m "fix(capture): deltacast smoke-test fixups"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Hardware Validation Checklist (run on the Deltacast machine)
|
|
||||||
|
|
||||||
After the hardware machine is available:
|
|
||||||
|
|
||||||
1. Build the Docker image: `docker compose build capture`
|
|
||||||
2. Create a recorder with `source_type=deltacast`, `device=0`
|
|
||||||
3. Confirm capture container logs show the JSON format line within 5s of feed going live
|
|
||||||
4. Confirm recorder status shows `signal: "receiving"`
|
|
||||||
5. Record a 30s clip → verify asset created, proxy + HLS generated
|
|
||||||
6. Test stop mid-record → file finalized correctly
|
|
||||||
7. Test no-signal path → recorder stays idle, no asset created
|
|
||||||
8. Test container restart mid-record → existing asset finalized via `/finalize` endpoint
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
# HLS VOD Playback for Browser
|
|
||||||
|
|
||||||
Date: 2026-05-29 | Status: design → implementation
|
|
||||||
Authors: Zac + Claude
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Replace the browser playback path for **recorded (VOD) assets** with HLS, retiring
|
|
||||||
the MP4 range-stitching workaround. The MP4 proxy is **kept** (supplements, not
|
|
||||||
replaces) because the Premiere UXP panel and conform pipeline consume it.
|
|
||||||
|
|
||||||
## Background — current state
|
|
||||||
|
|
||||||
- `GET /assets/:id/stream` returns `{ url: /api/v1/assets/:id/video, type: 'mp4' }`
|
|
||||||
for ready assets.
|
|
||||||
- `GET /assets/:id/video` streams `proxies/<id>.mp4` through Node with the
|
|
||||||
**RustFS range-stitching hack** (`stitchedS3Stream`): RustFS mis-serves ranged
|
|
||||||
GETs whose start offset is past ~5.8 MB, so the endpoint streams from byte 0 and
|
|
||||||
drops bytes. Works, but wastes bandwidth/CPU per seek and is fragile.
|
|
||||||
- **Live** assets already use HLS (`type: 'hls'`, `/live/<id>/index.m3u8`), and
|
|
||||||
`hls.js` is already loaded and wired in `screens-asset.jsx` for `type === 'hls'`.
|
|
||||||
- The proxy worker (`services/worker/src/workers/proxy.js`) produces a single
|
|
||||||
H.264/AAC/yuv420p MP4 — already HLS-compatible.
|
|
||||||
|
|
||||||
## Decisions
|
|
||||||
|
|
||||||
- **Supplement, not replace.** Keep `proxies/<id>.mp4`; add an HLS rendition.
|
|
||||||
- **Generate in the proxy worker** via fast remux (`-c copy`) — no re-encode.
|
|
||||||
- **Serve segments through mam-api** as whole-file GETs (no Range) — sidesteps the
|
|
||||||
RustFS range bug entirely and reuses session auth.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### 1. Generation (worker/proxy.js)
|
|
||||||
After uploading `proxies/<id>.mp4`, remux it to HLS into a temp dir:
|
|
||||||
```
|
|
||||||
ffmpeg -i <proxy.mp4> -c copy -f hls \
|
|
||||||
-hls_time 6 -hls_playlist_type vod -hls_flags independent_segments \
|
|
||||||
-hls_segment_filename <tmp>/seg_%03d.ts <tmp>/index.m3u8
|
|
||||||
```
|
|
||||||
Upload every file in the temp dir to `hls/<assetId>/` (playlist + `.ts`). Set
|
|
||||||
`assets.hls_s3_key = 'hls/<assetId>/index.m3u8'`. Remux is seconds; failure is
|
|
||||||
non-fatal (MP4 path still works as fallback).
|
|
||||||
|
|
||||||
### 2. Storage / schema
|
|
||||||
Migration adds `assets.hls_s3_key TEXT` (nullable). Presence = HLS available.
|
|
||||||
Segment objects live under `hls/<assetId>/seg_NNN.ts`; playlist references
|
|
||||||
**relative** segment names so the serving endpoint is path-agnostic.
|
|
||||||
|
|
||||||
### 3. Serving (mam-api)
|
|
||||||
New `GET /assets/:id/hls/:file` (file = `index.m3u8` or `seg_NNN.ts`):
|
|
||||||
- Validate `:file` against `^(index\.m3u8|seg_\d+\.ts)$` (no traversal).
|
|
||||||
- Whole-object GET of `hls/<id>/<file>` from S3 — **no Range handling**.
|
|
||||||
- Content-Type: `application/vnd.apple.mpegurl` (m3u8) / `video/mp2t` (ts).
|
|
||||||
- `Cache-Control: private, max-age=3600` for segments; `no-cache` for the playlist.
|
|
||||||
- Covered by the existing `requireAuth` gate; `hls.js` carries the same-origin
|
|
||||||
session cookie (same mechanism the live HLS path already relies on).
|
|
||||||
|
|
||||||
### 4. Stream selection (mam-api `/stream`)
|
|
||||||
For non-live assets: if `hls_s3_key` is set →
|
|
||||||
`{ url: '/api/v1/assets/:id/hls/index.m3u8', type: 'hls' }`. Else fall back to the
|
|
||||||
existing MP4 `/video` response. Live unchanged.
|
|
||||||
|
|
||||||
### 5. Backfill (existing assets)
|
|
||||||
Add an `hls` BullMQ job + `POST /assets/:id/reprocess?type=hls`: downloads the
|
|
||||||
existing `proxy_s3_key`, remuxes to HLS, uploads, sets `hls_s3_key`. No re-encode.
|
|
||||||
|
|
||||||
### 6. Frontend
|
|
||||||
No change required — `screens-asset.jsx` already plays `type: 'hls'` via `hls.js`.
|
|
||||||
Verify `hls.js` xhr carries credentials (same-origin cookie) for the proxied
|
|
||||||
segments; add `xhrSetup` withCredentials only if needed.
|
|
||||||
|
|
||||||
## Out of scope
|
|
||||||
- Multi-bitrate/ABR ladders (single rendition for now).
|
|
||||||
- Replacing the MP4 proxy or the `/video` endpoint (kept as fallback + for panel).
|
|
||||||
- Live-asset playback changes (already HLS).
|
|
||||||
|
|
||||||
## Test plan
|
|
||||||
1. Upload/capture an asset → proxy job produces MP4 **and** `hls/<id>/index.m3u8`.
|
|
||||||
2. `/stream` returns `type: 'hls'`; `/assets/:id/hls/index.m3u8` → 200 m3u8;
|
|
||||||
`/assets/:id/hls/seg_000.ts` → 200 `video/mp2t`, whole-file (no 206/Range).
|
|
||||||
3. Browser: asset plays + seeks via hls.js (no range-stitching path hit).
|
|
||||||
4. `reprocess?type=hls` backfills an older asset; it then plays via HLS.
|
|
||||||
5. MP4 proxy + `/hires` download still work (panel workflow intact).
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
# Wild Dragon MAM — Playout / Master Control (MCR)
|
|
||||||
|
|
||||||
**Date:** 2026-05-30 (revised 2026-05-30 — §7 closed)
|
|
||||||
**Status:** APPROVED — implementation in progress (code drafted but uncommitted; see WORK_LOG_PLAYOUT.md)
|
|
||||||
**Author:** Zac + Claude
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resolved Decisions
|
|
||||||
|
|
||||||
| Question | Decision |
|
|
||||||
|----------|----------|
|
|
||||||
| Playout engine | **CasparCG Server** (orchestrated via AMCP), not ffmpeg-native |
|
|
||||||
| Channel count | **Multi-channel from start** — N independent channels, placed across cluster nodes by capability (mirrors recorders) |
|
|
||||||
| Scheduling model | **Phased** — Phase A: on-demand playlist player; Phase B: 24/7 continuous channel |
|
|
||||||
| Output targets | SDI (DeckLink), NDI, SRT, RTMP — all via CasparCG consumers |
|
|
||||||
| Media source | Assets live in **S3**; must be staged to a CasparCG-local media volume before play (see §4) |
|
|
||||||
| CasparCG packaging | **Build our own image** (like `capture/build-with-decklink.sh`) — GL context via GPU passthrough or Xvfb; NDI + DeckLink SDKs fetched at build time (not redistributable) |
|
|
||||||
| Master codec playability | Zac confirms current masters **play fine in CasparCG** — no transcode-for-playout step; staging is a plain S3→/media copy. Validate on hardware but do not gate on it |
|
|
||||||
| Management UI | **Single Dragonflight `playout.html` GUI** drives everything via AMCP; operator never touches CasparCG directly |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Playout adds **master-control-room** capability to Dragonflight: take library assets, arrange them on a timeline / playlist, and play them out continuously to a broadcast output — SDI via DeckLink, or stream via SRT / RTMP / NDI. Drag-and-drop scheduling, a program/preview monitor, and as-run logging.
|
|
||||||
|
|
||||||
This is the **mirror image** of the existing capture path. Capture is `input → ffmpeg encode → S3`. Playout is `S3 asset → engine → output`. We reuse three things wholesale:
|
|
||||||
|
|
||||||
1. **Cluster node + capability model** — nodes already advertise DeckLink/Deltacast/GPU in `cluster_nodes.capabilities`; channels are placed on nodes that have a free output port, exactly as recorders claim input ports.
|
|
||||||
2. **Sidecar orchestration** — mam-api spawns containers via the local Docker socket or the remote `node-agent /sidecar/start`. A CasparCG channel is just a different sidecar image.
|
|
||||||
3. **Scheduler tick + PG advisory lock** — `src/scheduler.js` already runs a single-leader tick over a schedule table. Phase B's wall-clock channel reuses this pattern.
|
|
||||||
|
|
||||||
### Why CasparCG over ffmpeg-native
|
|
||||||
|
|
||||||
The capture stack proves we can drive ffmpeg + DeckLink. But playout's hard part is **gapless, frame-accurate, clean transitions between clips** — every clip boundary in an ffmpeg-per-clip model is a black flash unless we engineer a concat-feeder. CasparCG solves this natively: a channel is a persistent output with a playlist, hard/mix/wipe transitions, layered graphics/logo (CG), and DeckLink/NDI/SRT/RTMP consumers built in. We orchestrate it over **AMCP** (its TCP control protocol) instead of reinventing a feeder. Trade: a new dependency + container image, and media must be on a CasparCG-visible disk (§4).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Data Model
|
|
||||||
|
|
||||||
New migration `029-playout.sql`. Five tables.
|
|
||||||
|
|
||||||
### 1.1 `playout_channels`
|
|
||||||
A logical output. One channel → one engine instance → one output target.
|
|
||||||
|
|
||||||
```
|
|
||||||
id uuid pk
|
|
||||||
name text -- "Channel 1", "Pop-up SDI"
|
|
||||||
node_id uuid -> cluster_nodes(id) -- where the engine runs (null = primary)
|
|
||||||
output_type text -- 'decklink' | 'ndi' | 'srt' | 'rtmp'
|
|
||||||
output_config jsonb -- { device_index } | { ndi_name } | { url, latency } | { url, key }
|
|
||||||
video_format text -- '1080i5994' | '1080p5994' | '720p5994' ...
|
|
||||||
status text -- 'stopped' | 'starting' | 'running' | 'error'
|
|
||||||
container_id text -- running CasparCG sidecar
|
|
||||||
project_id uuid -> projects(id) -- RBAC scoping (nullable = admin-only)
|
|
||||||
created_at / updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
`output_type` + `output_config` map straight to a CasparCG consumer:
|
|
||||||
- `decklink` → `ADD <ch> DECKLINK <device> ...`
|
|
||||||
- `ndi` → `ADD <ch> NDI ...`
|
|
||||||
- `srt`/`rtmp` → `ADD <ch> FFMPEG <url> -f mpegts ...` (CasparCG 2.3+ ffmpeg consumer)
|
|
||||||
|
|
||||||
### 1.2 `playout_playlists`
|
|
||||||
An ordered list of items bound to a channel. Phase A's primary object.
|
|
||||||
|
|
||||||
```
|
|
||||||
id, channel_id -> playout_channels(id)
|
|
||||||
name, loop boolean, created_at / updated_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.3 `playout_items`
|
|
||||||
One entry on a playlist OR one entry on the 24/7 timeline.
|
|
||||||
|
|
||||||
```
|
|
||||||
id
|
|
||||||
playlist_id uuid -> playout_playlists(id) -- Phase A
|
|
||||||
asset_id uuid -> assets(id)
|
|
||||||
sort_order int -- position in playlist (Phase A)
|
|
||||||
scheduled_at timestamptz -- wall-clock start (Phase B, null in A)
|
|
||||||
in_point numeric -- seconds, trim head (reuse subclip in/out from editor)
|
|
||||||
out_point numeric -- seconds, trim tail
|
|
||||||
transition text -- 'cut' | 'mix' | 'wipe'
|
|
||||||
transition_ms int
|
|
||||||
graphics jsonb -- optional CG/template overlay (Phase B+)
|
|
||||||
media_status text -- 'pending' | 'staging' | 'ready' | 'error' (see §4)
|
|
||||||
media_path text -- resolved path inside the CasparCG media volume
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.4 `playout_schedule` (Phase B)
|
|
||||||
Day-ahead, wall-clock-bound timeline rows. Same shape as `playout_items` but `scheduled_at` is authoritative and the scheduler tick (§5) drives transitions. Phase A can ignore this table.
|
|
||||||
|
|
||||||
### 1.5 `playout_as_run`
|
|
||||||
Append-only log: what actually played, when, for how long. Compliance / billing.
|
|
||||||
|
|
||||||
```
|
|
||||||
id, channel_id, asset_id, item_id
|
|
||||||
started_at, ended_at, duration_s, result -- 'played' | 'skipped' | 'error'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Services & Components
|
|
||||||
|
|
||||||
### 2.1 New sidecar: `services/playout/` (CasparCG wrapper)
|
|
||||||
A thin container: **CasparCG Server** + a small Node control shim exposing HTTP, the same way `capture` wraps ffmpeg.
|
|
||||||
|
|
||||||
- Base image: official/community `casparcg/server` (Linux build with DeckLink + NDI + FFmpeg producers/consumers).
|
|
||||||
- Node shim (`src/index.js`): opens an AMCP TCP socket to local CasparCG, exposes:
|
|
||||||
- `POST /channel/start` → `ADD <ch> <consumer>` for the channel's output target
|
|
||||||
- `POST /play` → `PLAY <ch>-<layer> <media> [transition]`
|
|
||||||
- `POST /loadbg` + `/play` → preview/cue then take (preview monitor)
|
|
||||||
- `POST /stop`, `GET /status` → `INFO <ch>` (current clip, position, fps)
|
|
||||||
- playlist load → translate `playout_items` rows into a sequence of AMCP `LOADBG`/`PLAY` calls, advancing on `OnTransition` / end-of-clip events.
|
|
||||||
- Mirrors capture's status-polling contract so the UI monitor reuses existing plumbing.
|
|
||||||
|
|
||||||
### 2.2 mam-api: `src/routes/playout.js`
|
|
||||||
CRUD + control, RBAC-scoped via the existing `assertProjectAccess` helper (channels carry `project_id`).
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /playout/channels list (project-filtered)
|
|
||||||
POST /playout/channels create (edit on project)
|
|
||||||
POST /playout/channels/:id/start|stop spawn/kill CasparCG sidecar
|
|
||||||
GET /playout/channels/:id/status proxy engine INFO
|
|
||||||
POST /playout/channels/:id/play|pause|skip transport control
|
|
||||||
GET/POST/PUT/DELETE /playout/playlists... playlist + item CRUD, reorder
|
|
||||||
POST /playout/items/:id/stage kick S3→media-volume staging (§4)
|
|
||||||
GET /playout/channels/:id/asrun as-run log
|
|
||||||
```
|
|
||||||
|
|
||||||
Channel start/stop reuses `resolveNodeTarget()` + the Docker-socket / `node-agent /sidecar/start` split already in `recorders.js`. **Refactor opportunity:** lift that sidecar-spawn logic out of `recorders.js` into `src/orchestration/sidecar.js` so both recorders and playout share it (keep this small — only what both need).
|
|
||||||
|
|
||||||
### 2.3 web-ui: `playout.html` + `public/playout.jsx`
|
|
||||||
New MCR page. Layout:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─ PREVIEW ───────────┬─ PROGRAM (on air) ──────┐
|
|
||||||
│ [cued clip] │ [live output] ● ON AIR │
|
|
||||||
│ TC / duration │ TC / remaining │
|
|
||||||
│ [CUE] [TAKE] │ [PLAY][PAUSE][SKIP][STOP]│
|
|
||||||
├─ MEDIA BIN ─────────┴──────────────────────────┤
|
|
||||||
│ (draggable asset list, reuse asset browser) │
|
|
||||||
├─ PLAYLIST / TIMELINE ──────────────────────────┤
|
|
||||||
│ ▸ clip A ──▸ clip B ──▸ clip C (drag-drop) │ Phase A: ordered list
|
|
||||||
│ └ 24h time grid w/ now-bar │ Phase B: time-of-day grid
|
|
||||||
└────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
- Drag-drop: reuse whatever the NLE editor timeline uses (check `editor.jsx`); assets drag from the bin into the playlist/grid.
|
|
||||||
- API via existing `ZAMPP_API.fetch` wrapper.
|
|
||||||
- Program monitor: HLS preview of the output — CasparCG can emit a second low-bitrate FFmpeg consumer to HLS, reusing the `/live/<id>` HLS plumbing capture already uses.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Channel placement & ports
|
|
||||||
|
|
||||||
A DeckLink port is exclusive — same constraint capture already handles. A node's DeckLink port can be an **input (recorder)** or an **output (playout channel)**, never both at once. So:
|
|
||||||
|
|
||||||
- Extend the capability/port-claim check: when starting a channel on `output_type=decklink`, verify the target node has that device index free (no active recorder, no active channel).
|
|
||||||
- NDI / SRT / RTMP outputs have no hardware contention → can stack many per node (GPU/CPU-bound only).
|
|
||||||
- Surface a unified "device map" (extend the existing cluster DeckLink-status endpoint) showing each port's role: idle / recording / playing-out.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Media staging (the S3 ⇄ CasparCG gap)
|
|
||||||
|
|
||||||
**The crux.** Assets live in S3 (`original_s3_key` / `proxy_s3_key`). CasparCG plays from a **local media folder**. Options:
|
|
||||||
|
|
||||||
- **A — Pre-stage to a shared media volume (recommended).** Before a clip can go on air, download/symlink it from S3 to a CasparCG-visible volume (`/media`), set `playout_items.media_status='ready'` + `media_path`. A new BullMQ `playout-stage` job (reuses the worker pattern) does the pull. UI shows per-item readiness; TAKE is blocked until `ready`. Mirrors the growing-file SMB share already mounted for capture.
|
|
||||||
- **B — Stream from S3 via presigned URL.** CasparCG FFmpeg producer plays an HTTPS presigned URL directly. Zero staging, but seeking/trim and gapless transitions over network are fragile for broadcast. Acceptable as a fallback for SRT/RTMP, risky for SDI.
|
|
||||||
|
|
||||||
**Decision:** Phase A uses **A** (stage proxies for preview, masters for air) with **B** available as a per-channel "low-latency / no-stage" toggle. Zac confirms the current masters play fine in CasparCG, so staging is a **plain S3→/media copy — no transcode-for-playout step**. (Validate on hardware during implementation, but the model does not assume a transcode stage.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Scheduling
|
|
||||||
|
|
||||||
### Phase A — playlist player
|
|
||||||
No wall clock. Operator builds a `playout_playlists` row, drags items in, hits PLAY. The playout sidecar walks `playout_items` by `sort_order`, cueing the next clip during the current one (`LOADBG`) and taking it at end-of-clip with the configured transition. `loop` repeats. As-run logged per item.
|
|
||||||
|
|
||||||
### Phase B — 24/7 continuous channel
|
|
||||||
Wall-clock timeline in `playout_schedule`. Reuse `src/scheduler.js`:
|
|
||||||
- Add a second tick (or extend the existing one) under the **same PG advisory lock pattern** — exactly-one-leader, so a multi-replica deploy doesn't double-fire.
|
|
||||||
- Tick responsibilities: stage upcoming items (look-ahead window), verify the on-air item matches the schedule, **fill gaps** (loop a filler/slate asset when the timeline has a hole — a channel must never go black), roll the day forward.
|
|
||||||
- As-run becomes the compliance record.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Phasing / Milestones
|
|
||||||
|
|
||||||
**Phase A — Playlist playout MVP**
|
|
||||||
1. Migration `029-playout.sql` (channels, playlists, items, as-run).
|
|
||||||
2. `services/playout/` sidecar: CasparCG image + AMCP control shim, single output target (start with **SRT or NDI** — no hardware needed for dev; DeckLink behind hardware check).
|
|
||||||
3. mam-api `routes/playout.js` — channel + playlist CRUD, start/stop, transport, RBAC.
|
|
||||||
4. `playout-stage` BullMQ job (S3 → /media).
|
|
||||||
5. web-ui `playout.html` — bin + drag-drop ordered playlist + program/preview monitors + transport.
|
|
||||||
6. DeckLink output on real hardware; port-contention check vs recorders.
|
|
||||||
|
|
||||||
**Phase B — 24/7 continuous channel**
|
|
||||||
7. `playout_schedule` + time-of-day grid UI.
|
|
||||||
8. Scheduler tick (advisory-locked) — look-ahead staging, gap-fill/slate, day-roll.
|
|
||||||
9. As-run reporting view.
|
|
||||||
10. Graphics/CG overlay (logo bug, lower-thirds) via CasparCG templates.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Open Questions (for review)
|
|
||||||
|
|
||||||
**Resolved (2026-05-30):**
|
|
||||||
- ~~CasparCG packaging~~ → **build our own image.** Fetch DeckLink + NDI SDKs at build time (not redistributable — same as capture's DeckLink build). GL context for the mixer comes from GPU passthrough on a real node, or **Xvfb** (virtual framebuffer) where there's no display — community images run `--privileged` + X11 socket. Pin the NDI SDK version to what the server expects (`.so` version mismatch is the common docker failure).
|
|
||||||
- ~~Master codec playability~~ → Zac confirms masters **play fine**; no transcode-for-playout. Staging = plain S3→/media copy.
|
|
||||||
- ~~Management GUI~~ → **single Dragonflight `playout.html`** drives everything via AMCP; operator never touches CasparCG.
|
|
||||||
- ~~Audio loudness~~ → **pre-normalize at stage time** (Zac, 2026-05-30). `playout-stage` job runs ffmpeg `loudnorm` (EBU R128, target −23 LUFS, true-peak −1 dBTP) once, on the S3→/media copy. Output is the cached version CasparCG plays. Staging is no longer a pure copy — staging cost ≈ realtime CPU per clip on first stage; results are reusable across channels. Override (`media_status='ready'` + raw copy) available for clips already mastered to spec.
|
|
||||||
- ~~Frame rate~~ → **`1080p5994`** default for new channels (Zac, 2026-05-30). Progressive 1080 @ 59.94 fps. Per-channel override allowed (`video_format` column). Streaming-friendly (SRT/RTMP/NDI) and current SDI gear accepts it; matches capture's 59.94 cadence.
|
|
||||||
- ~~Preview latency~~ → **HLS v1** (Zac, 2026-05-30). Reuse capture's `/live/<id>` HLS plumbing. CasparCG emits a second low-bitrate FFmpeg consumer to HLS. ~4–6s lag, fine for confidence monitor. Operator desk gets a real downstream monitor off the SDI/NDI output anyway. Revisit WebRTC if MCR operators complain.
|
|
||||||
- ~~Failover~~ → **auto-restart on healthy node** (Zac, 2026-05-30). Scheduler tick (§5) monitors `playout_sidecars` health (AMCP ping + container alive); on N missed checks marks the channel `error`, re-places it on another capability-matching node with a free output port, resumes the playlist from the next item after the as-run-logged on-air item. Gap = black/slate for ~5–30 s during respawn (operator sees a flag in the UI). **DeckLink channels are not auto-failed-over in v1** — device-index pinning makes the destination port non-trivial; v1 alerts and lets the operator move the channel. NDI/SRT/RTMP channels (no hardware contention) failover automatically. Tracked via `restart_count` + `last_restart_at` on `playout_channels`.
|
|
||||||
|
|
||||||
**Still open:**
|
|
||||||
- (none — all §7 questions resolved 2026-05-30)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Reused building blocks (already in the repo)
|
|
||||||
|
|
||||||
| Need | Existing piece |
|
|
||||||
|------|----------------|
|
|
||||||
| Spawn engine container local/remote | `recorders.js` Docker-socket + `node-agent /sidecar/start` |
|
|
||||||
| Node capability / port model | `cluster_nodes.capabilities`, cluster DeckLink-status endpoint |
|
|
||||||
| Single-leader scheduled transitions | `src/scheduler.js` + PG advisory lock |
|
|
||||||
| Background media jobs | BullMQ worker (`services/worker`) |
|
|
||||||
| RBAC scoping | `src/auth/authz.js` `assertProjectAccess` (channel/project_id) |
|
|
||||||
| HLS preview plumbing | capture's `/live/<id>` HLS output |
|
|
||||||
| Subclip in/out points | NLE editor in/out marking |
|
|
||||||
| API wrapper / SPA shell | `ZAMPP_API.fetch`, esbuild JSX pages |
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
# Storage Settings Warning + Growing-Files SMB Auth + Per-Recorder Growing Mode
|
|
||||||
**Date:** 2026-05-31
|
|
||||||
**Branch:** `feat/playout-mcr` (Forgejo: WildDragonLLC/dragonflight)
|
|
||||||
**Status:** Approved, ready for implementation plan
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Three related refinements to the Settings page and growing-files capture pipeline:
|
|
||||||
|
|
||||||
1. **Storage warning header** — a prominent set-once warning at the top of the Storage settings section.
|
|
||||||
2. **Growing-files SMB credentials + system CIFS mount** — store an SMB username/password and have the capture stack mount the growing share itself (Approach A).
|
|
||||||
3. **Per-recorder growing mode** — remove the global "capture writes to local SMB share first" toggle; make growing-files mode a per-recorder setting.
|
|
||||||
|
|
||||||
All changes live on the `growing-files` / storage-settings path. No playout changes (handled separately).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Background (current state)
|
|
||||||
|
|
||||||
- **Settings storage:** single key/value `settings(key TEXT PRIMARY KEY, value TEXT, updated_at)` table (migration 006). Secrets like `s3_secret_key` are stored but `GET /settings/s3` returns only `s3_secret_key_exists` (never the value).
|
|
||||||
- **Growing settings UI:** `GrowingSettingsCard` in `services/web-ui/public/screens-admin.jsx` (rendered by `StorageSection` alongside `MountHealthStrip` and `S3SettingsCard`). Current keys: `growing_enabled`, `growing_path`, `growing_smb_url`, `growing_promote_after_seconds`.
|
|
||||||
- **Settings API:** `services/mam-api/src/routes/settings.js` — `GET/PUT /settings/growing` over `GROWING_KEYS`.
|
|
||||||
- **Global enable today:** the `growing_enabled` setting (checkbox labelled "Capture writes to the local SMB share first; Premier can edit while it's still growing"). `recorders.js` reads this global key at recorder-start and passes `GROWING_ENABLED=true/false` to the capture container.
|
|
||||||
- **Capture write path:** `services/capture/src/capture-manager.js` reads `process.env.GROWING_ENABLED` and `GROWING_PATH` (default `/growing`). When on, it writes the master to `/growing/{projectId}/{clipName}.{ext}` instead of streaming to S3; the promotion worker uploads to S3 after stop.
|
|
||||||
- **Current mount model:** `/growing` is a pre-mounted host bind-mount; the app never authenticates to SMB.
|
|
||||||
- **Per-recorder column already exists:** migration 014 added `recorders.growing_enabled BOOLEAN DEFAULT NULL` ("NULL = use global"), but recorder-start logic ignores it and the new-recorder modal does not expose it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Part 1 — Storage warning header
|
|
||||||
|
|
||||||
Add a danger-styled banner at the **top of `StorageSection()`**, above `MountHealthStrip`.
|
|
||||||
|
|
||||||
- Visual: full-width banner, danger token styling (`--danger` border + subtle danger background), alert icon, bold uppercase text.
|
|
||||||
- Exact copy:
|
|
||||||
> **⚠ WARNING — THESE SETTINGS ARE MEANT TO BE SET ONCE AT INITIAL DEPLOYMENT. CHANGING STORAGE PATHS MID-CYCLE CAN CAUSE DATABASE CORRUPTION AND FILE LOSS. PLEASE USE WITH CAUTION.**
|
|
||||||
- Pure presentational; no backend, no dismiss state (always visible).
|
|
||||||
|
|
||||||
**Files:** `services/web-ui/public/screens-admin.jsx` (and a small style rule in the appropriate CSS file if needed).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Part 2 — Growing-files SMB credentials + system CIFS mount (Approach A)
|
|
||||||
|
|
||||||
The growing share is **shared infrastructure**, so the SMB connection config is global.
|
|
||||||
|
|
||||||
### New settings keys
|
|
||||||
| Key | Purpose | Notes |
|
|
||||||
|-----|---------|-------|
|
|
||||||
| `growing_smb_mount` | CIFS source for the system mount, e.g. `//10.0.0.25/mam-growing` | Distinct from `growing_smb_url` |
|
|
||||||
| `growing_smb_username` | SMB user | Returned in GET (not secret) |
|
|
||||||
| `growing_smb_password` | SMB password | **Write-only** — never returned |
|
|
||||||
| `growing_smb_vers` *(optional)* | CIFS protocol version, default `3.0` | Avoids mount negotiation failures |
|
|
||||||
|
|
||||||
`growing_smb_url` (the `smb://…` string) is retained unchanged as the **editor-facing** display value (Premiere connect string).
|
|
||||||
|
|
||||||
### Settings API (`settings.js`)
|
|
||||||
- Extend `GROWING_KEYS` with the new keys (except the password is handled specially).
|
|
||||||
- `GET /settings/growing`: return `growing_smb_mount`, `growing_smb_username`, `growing_smb_vers`, and `growing_smb_password_exists: boolean` — **never** the password value. (Mirror the existing `s3_secret_key_exists` pattern.)
|
|
||||||
- `PUT /settings/growing`: upsert each provided key. For `growing_smb_password`, only write it when a non-empty value is provided (an empty/omitted field leaves the stored password unchanged). Provide a way to clear it (explicit empty sentinel or a "clear" affordance) — see Resolved decisions below.
|
|
||||||
|
|
||||||
### Settings UI (`GrowingSettingsCard`)
|
|
||||||
Add three fields to the card:
|
|
||||||
- **SMB mount (CIFS):** text input bound to `growing_smb_mount`, placeholder `//10.0.0.25/mam-growing`.
|
|
||||||
- **SMB username:** text input bound to `growing_smb_username`.
|
|
||||||
- **SMB password:** masked password input. Shows a "saved" indicator when `growing_smb_password_exists` is true; typing a new value replaces it; leaving it blank keeps the existing one. `autoComplete="new-password"`.
|
|
||||||
|
|
||||||
### Capture image (`services/capture/Dockerfile`)
|
|
||||||
Add `cifs-utils` to the installed packages so `mount -t cifs` is available inside the capture container.
|
|
||||||
|
|
||||||
### Capture-manager (`capture-manager.js`)
|
|
||||||
On capture start, when growing mode is active **and** `GROWING_SMB_MOUNT` is set:
|
|
||||||
1. Write a root-only credentials file (e.g. `/run/smb-creds`, mode `0600`) containing:
|
|
||||||
```
|
|
||||||
username=<GROWING_SMB_USERNAME>
|
|
||||||
password=<GROWING_SMB_PASSWORD>
|
|
||||||
```
|
|
||||||
(Credentials go in the file, **not** the mount command line, to avoid `ps`/log exposure.)
|
|
||||||
2. `mkdir -p $GROWING_PATH` then `mount -t cifs $GROWING_SMB_MOUNT $GROWING_PATH -o credentials=/run/smb-creds,uid=0,gid=0,file_mode=0664,dir_mode=0775,vers=$GROWING_SMB_VERS`.
|
|
||||||
3. If the mount succeeds → proceed writing the master to `$GROWING_PATH/...` (existing behaviour).
|
|
||||||
4. If the mount **fails** → log the error and fall back to S3 streaming (`growingPath = null`), so a recording is never lost.
|
|
||||||
5. On capture stop/cleanup, unmount `$GROWING_PATH` (best-effort; ignore "not mounted").
|
|
||||||
|
|
||||||
Mount isolation: each recorder runs in its **own** capture container, so each container mounts CIFS at its own private `/growing` — no cross-recorder mount conflicts, no ref-counting needed.
|
|
||||||
|
|
||||||
### Recorder start (`recorders.js`)
|
|
||||||
- Pass new env to the spawned capture container: `GROWING_SMB_MOUNT`, `GROWING_SMB_USERNAME`, `GROWING_SMB_PASSWORD`, `GROWING_SMB_VERS` (read from the `settings` table at start).
|
|
||||||
- The dynamically-spawned capture container must get `/growing` as an **empty mountpoint** (not a host bind-mount) so the in-container CIFS mount lands cleanly. Confirm/adjust the container spec accordingly. The container is already privileged (required for `mount`).
|
|
||||||
|
|
||||||
### Security notes
|
|
||||||
- The password is stored plaintext in the `settings` table, identical to the existing `s3_secret_key` handling — acceptable within this app's current secret model.
|
|
||||||
- The password reaches the capture container as an env var (visible via `docker inspect`), same as S3 keys already are. The credentials **file** (not the command line) is used for the actual mount.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Part 3 — Per-recorder growing mode (remove the global toggle)
|
|
||||||
|
|
||||||
### Remove global enable
|
|
||||||
- Delete the global "Capture writes to the local SMB share first…" checkbox (`growing_enabled` key) from `GrowingSettingsCard`. The card no longer carries a global on/off — it is **infrastructure-only**: container mount path, SMB URL (editor), SMB mount + credentials, promote threshold.
|
|
||||||
- The `growing_enabled` settings *key* is retired from the UI. (It may remain in the table harmlessly; recorder-start no longer reads it.)
|
|
||||||
|
|
||||||
### Per-recorder semantics
|
|
||||||
- Reuse the existing `recorders.growing_enabled BOOLEAN` column. New semantics (no global to defer to): `TRUE` = this recorder uses growing-files mode; `NULL`/`FALSE` = off.
|
|
||||||
- `recorders.js` recorder-start: read the **recorder's own** `growing_enabled` (defaulting `NULL`→off) and set `GROWING_ENABLED` for the capture container from that, instead of the global setting.
|
|
||||||
- Add `growing_enabled` to `RECORDER_FIELDS` so create/update accept it.
|
|
||||||
|
|
||||||
### UI
|
|
||||||
- **New-recorder modal** (`modal-new-recorder.jsx`): add a "Growing-files mode" toggle that sets `growing_enabled` on the created recorder (default off).
|
|
||||||
- **Recorder edit** (wherever recorders are edited): same toggle.
|
|
||||||
- Helper text on the toggle notes that growing-files requires the SMB share to be configured in Settings → Storage.
|
|
||||||
|
|
||||||
### Fallback
|
|
||||||
If a recorder has `growing_enabled = true` but `growing_smb_mount` is not configured globally, capture logs a warning and falls back to S3 streaming (same fallback path as a failed mount). Recording is never blocked.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files changed
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
|------|--------|
|
|
||||||
| `services/web-ui/public/screens-admin.jsx` | Storage warning banner; SMB mount/username/password fields in `GrowingSettingsCard`; remove global growing-enable checkbox |
|
|
||||||
| `services/web-ui/public/modal-new-recorder.jsx` | Per-recorder "Growing-files mode" toggle |
|
|
||||||
| `services/mam-api/src/routes/settings.js` | New growing SMB keys; write-only password (`growing_smb_password_exists`) |
|
|
||||||
| `services/mam-api/src/routes/recorders.js` | Read per-recorder `growing_enabled`; pass SMB env to capture; `RECORDER_FIELDS` += `growing_enabled`; empty `/growing` mountpoint |
|
|
||||||
| `services/capture/Dockerfile` | Add `cifs-utils` |
|
|
||||||
| `services/capture/src/capture-manager.js` | CIFS mount-on-start (creds file), unmount-on-stop, fallback to S3 on failure |
|
|
||||||
| CSS (storage warning / fields) | Minor styles if needed |
|
|
||||||
|
|
||||||
No DB migration required (the `recorders.growing_enabled` column already exists; new settings are key/value rows).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Resolved decisions
|
|
||||||
|
|
||||||
- **Clearing the SMB password:** `PUT /settings/growing` treats a field value of the literal sentinel `""` with an explicit `growing_smb_password_clear: true` flag as "remove the stored password"; a blank field with no clear flag leaves it unchanged. (Keeps the common "don't retype the password on every save" UX while still allowing removal.)
|
|
||||||
- **CIFS version:** default `growing_smb_vers = 3.0`; overridable via settings to support older NAS targets.
|
|
||||||
- **Recorders already recording when the toggle changes:** the per-recorder `growing_enabled` is read at **start** only; changing it mid-recording has no effect on the active session (consistent with how all recorder encode settings already behave).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Out of scope (deferred)
|
|
||||||
|
|
||||||
- Encrypting secrets at rest (the app's existing model stores `s3_secret_key` in plaintext; SMB password follows the same model).
|
|
||||||
- A global "growing-files master kill switch" (removed by design — control is now per-recorder).
|
|
||||||
- Exposing `growing_retention_days` in the UI (seeded in DB, still unsurfaced; unrelated to this work).
|
|
||||||
- Playout HLS preview fix (handled by a separate parallel effort).
|
|
||||||
|
|
@ -1,231 +0,0 @@
|
||||||
# Deltacast SDI Capture — Design Spec
|
|
||||||
**Date:** 2026-06-01
|
|
||||||
**Status:** Approved
|
|
||||||
**Approach:** Bridge binary (Option B2)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem
|
|
||||||
|
|
||||||
Dragonflight supports SDI ingest via Blackmagic DeckLink. Deltacast VideoMaster cards are a second hardware target. The VideoMaster SDK (v6.34.1) ships C++ headers and shared libraries but no FFmpeg demuxer plugin — there is no mainline FFmpeg `-f deltacast` input device. The `capture-manager.js` stub exists but falls back to a lavfi test card on all deployments.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Approach
|
|
||||||
|
|
||||||
Write a small C++ bridge binary (`deltacast-capture`) using the VideoMaster C++ Wrapper SDK. The bridge:
|
|
||||||
1. Detects signal format on startup, writes one JSON line to stderr
|
|
||||||
2. Streams raw YUV video frames to stdout
|
|
||||||
3. Streams raw PCM audio to a named FIFO
|
|
||||||
|
|
||||||
`capture-manager.js` reads the JSON handshake, then spawns FFmpeg with `-f rawvideo -i pipe:0` (video from bridge stdout) and `-f s16le -i <fifo>` (audio from FIFO). The existing HEVC NVENC / ProRes encode pipeline is unchanged.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ capture container │
|
|
||||||
│ │
|
|
||||||
│ capture-manager.js │
|
|
||||||
│ │ │
|
|
||||||
│ ├─ spawn deltacast-capture --device 0 --port 0 │
|
|
||||||
│ │ --audio-pipe /tmp/dc-audio-{sessionId} │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ ├─ stderr: JSON format line (one-time handshake) │
|
|
||||||
│ │ ├─ stdout: raw YUV frames (continuous) │
|
|
||||||
│ │ └─ FIFO: raw PCM audio (continuous) │
|
|
||||||
│ │ │
|
|
||||||
│ └─ spawn ffmpeg │
|
|
||||||
│ -f rawvideo -pix_fmt uyvy422 -s WxH -r FPS/1 │
|
|
||||||
│ -i pipe:0 ← piped from bridge stdout │
|
|
||||||
│ -f s16le -ar 48000 -ac <N> │
|
|
||||||
│ -i /tmp/dc-audio-{sessionId} │
|
|
||||||
│ <hevc_nvenc / prores / h264 encode args> │
|
|
||||||
│ <S3 pipe or growing-file output> │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### New files
|
|
||||||
- `services/capture/deltacast-bridge/CMakeLists.txt`
|
|
||||||
- `services/capture/deltacast-bridge/main.cpp`
|
|
||||||
|
|
||||||
### Modified files
|
|
||||||
- `services/capture/src/capture-manager.js` — `_buildInputArgs()` deltacast branch; `start()` and `stop()` bridge lifecycle
|
|
||||||
- `services/capture/Dockerfile` — SDK extraction stage, bridge build stage, runtime `.so` install
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## The `deltacast-capture` Binary
|
|
||||||
|
|
||||||
### CLI
|
|
||||||
```
|
|
||||||
deltacast-capture
|
|
||||||
--device <N> Board index (0-based)
|
|
||||||
--port <N> RX port index (0-based)
|
|
||||||
--audio-pipe <path> Named FIFO path for PCM audio output
|
|
||||||
[--signal-timeout <sec=30>]
|
|
||||||
[--audio-groups <N=2>] Number of SDI audio groups (2 groups = 8 channels)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Startup sequence
|
|
||||||
1. `Board::open(device, loopback_restore_cb)`
|
|
||||||
2. Disable loopback on `port`
|
|
||||||
3. `board.sdi().open_stream(rx_streamtype(port), VHD_SDI_STPROC_DISJOINED_VIDEO)`
|
|
||||||
4. Poll `wait_for_input()` up to `--signal-timeout` seconds
|
|
||||||
5. On timeout → write `{"error":"no signal","device":N,"port":N}` to stderr, exit 1
|
|
||||||
6. Detect `video_standard`, `clock_divisor`, `interface` → map to width/height/fps/pix_fmt/interlaced
|
|
||||||
7. Write one JSON line to stderr (flushed):
|
|
||||||
```json
|
|
||||||
{"width":1920,"height":1080,"fps_num":25,"fps_den":1,"pix_fmt":"uyvy422","interlaced":false,"audio_channels":8,"audio_rate":48000,"device":0,"port":0}
|
|
||||||
```
|
|
||||||
8. Set queue depth = 8, `rx_stream.start()`
|
|
||||||
9. Capture loop: `pop_slot()` → write video buffer to stdout → extract audio → write PCM to FIFO (background thread)
|
|
||||||
10. SIGTERM/SIGINT → set stop flag → flush, close FIFO, close stream/board, exit 0
|
|
||||||
|
|
||||||
### Pixel format
|
|
||||||
Default: `uyvy422` (4:2:2 8-bit, `VHD_SDI_BUFTYPE_VIDEO`). 10-bit (`v210`) is a future follow-up via `--pix-fmt v210`.
|
|
||||||
|
|
||||||
### Audio
|
|
||||||
`sdi_slot.audio().extract(num_groups)` returns `std::vector<VHD_AUDIOGROUP>`. Samples are written to the FIFO as interleaved s16le PCM at 48000 Hz in a background thread so the video loop never blocks on audio consumers. Default `--audio-groups 2` yields 8 channels (standard embedded SDI stereo pairs 1–4).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## `capture-manager.js` Changes
|
|
||||||
|
|
||||||
### `_buildInputArgs()` — deltacast branch
|
|
||||||
|
|
||||||
Replace the existing lavfi-fallback stub with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
if (sourceType === 'deltacast') {
|
|
||||||
const idx = parseInt(device, 10) || 0;
|
|
||||||
const audioFifo = `/tmp/dc-audio-${sessionId}`;
|
|
||||||
await execAsync(`mkfifo ${audioFifo}`);
|
|
||||||
|
|
||||||
const bridge = spawn('deltacast-capture', [
|
|
||||||
'--device', String(idx),
|
|
||||||
'--port', String(idx), // port == board index for single-port-per-recorder model
|
|
||||||
'--audio-pipe', audioFifo,
|
|
||||||
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
||||||
|
|
||||||
const fmt = await readFirstStderrLine(bridge, 35_000); // 35s timeout
|
|
||||||
// fmt: { width, height, fps_num, fps_den, pix_fmt, interlaced, audio_channels, audio_rate }
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputArgs: [
|
|
||||||
'-f', 'rawvideo',
|
|
||||||
'-pix_fmt', fmt.pix_fmt,
|
|
||||||
'-video_size', `${fmt.width}x${fmt.height}`,
|
|
||||||
'-framerate', `${fmt.fps_num}/${fmt.fps_den}`,
|
|
||||||
'-i', 'pipe:0',
|
|
||||||
'-f', 's16le',
|
|
||||||
'-ar', String(fmt.audio_rate),
|
|
||||||
'-ac', String(fmt.audio_channels),
|
|
||||||
'-i', audioFifo,
|
|
||||||
],
|
|
||||||
isNetwork: false,
|
|
||||||
bridgeProcess: bridge,
|
|
||||||
audioFifo,
|
|
||||||
interlaced: fmt.interlaced,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`readFirstStderrLine(proc, timeoutMs)` is a small helper that returns a parsed JSON object from the first line emitted on `proc.stderr`, or throws on timeout or non-zero exit.
|
|
||||||
|
|
||||||
### `start()` changes
|
|
||||||
- After `_buildInputArgs()` returns, store `bridgeProcess` and `audioFifo` on `this.state`
|
|
||||||
- Spawn FFmpeg with `stdio: ['pipe', ...]` for stdin
|
|
||||||
- `bridgeProcess.stdout.pipe(hiresProcess.stdin)`
|
|
||||||
- Deinterlace: if `interlaced === true`, add `-vf yadif=mode=1:deint=1` (already present for `sourceType === 'sdi'`; extend that check to include `deltacast`)
|
|
||||||
|
|
||||||
### `stop()` changes
|
|
||||||
- `if (processes.bridge) processes.bridge.kill('SIGINT')`
|
|
||||||
- After process cleanup: `if (this.state.audioFifo) { try { fs.unlinkSync(this.state.audioFifo); } catch (_) {} }`
|
|
||||||
|
|
||||||
### HLS preview
|
|
||||||
The existing `filter_complex split` SDI preview path works unchanged — the bridge→pipe is just a different `-i` source. Extend the `sourceType === 'sdi'` guard to `['sdi', 'deltacast'].includes(sourceType)`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dockerfile Changes
|
|
||||||
|
|
||||||
```dockerfile
|
|
||||||
# ── Stage 0: Extract VideoMaster SDK ─────────────────────────────────────
|
|
||||||
FROM debian:bookworm AS sdk-extractor
|
|
||||||
COPY videomaster-linux.x64-6.34.1-dev.tar.gz /tmp/
|
|
||||||
RUN mkdir -p /sdk && tar -xzf /tmp/videomaster-linux.x64-6.34.1-dev.tar.gz -C /sdk
|
|
||||||
|
|
||||||
# ── Stage 1: Build deltacast-capture bridge ───────────────────────────────
|
|
||||||
FROM debian:bookworm AS bridge-builder
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
build-essential cmake ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
COPY --from=sdk-extractor /sdk /sdk
|
|
||||||
COPY deltacast-bridge/ /bridge/
|
|
||||||
RUN cmake -S /bridge -B /bridge/build \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DSDK_ROOT=/sdk \
|
|
||||||
&& cmake --build /bridge/build -j$(nproc)
|
|
||||||
|
|
||||||
# ── Stage 2: Build FFmpeg (unchanged) ─────────────────────────────────────
|
|
||||||
FROM debian:bookworm AS ffmpeg-builder
|
|
||||||
# ... existing content, no changes ...
|
|
||||||
|
|
||||||
# ── Stage 3: Runtime ──────────────────────────────────────────────────────
|
|
||||||
FROM node:20-bookworm
|
|
||||||
# ... existing runtime deps ...
|
|
||||||
COPY --from=bridge-builder /bridge/build/deltacast-capture /usr/local/bin/deltacast-capture
|
|
||||||
COPY --from=sdk-extractor /sdk/lib/ /usr/local/lib/deltacast/
|
|
||||||
RUN ldconfig /usr/local/lib/deltacast && ldconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
SDK `.so` files total ~4MB. The bridge binary adds ~200KB.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Scenario | Bridge behavior | `capture-manager.js` response |
|
|
||||||
|---|---|---|
|
|
||||||
| No signal within timeout | Exit 1, `{"error":"no signal"}` on stderr | Throws — recorder stays idle, no asset created |
|
|
||||||
| Invalid board/port | Exit 1, `{"error":"board N not found"}` | Same as above |
|
|
||||||
| Bridge crash mid-capture | stdout closes → FFmpeg stdin EOF → FFmpeg exits cleanly | Existing stop handler fires; asset finalized with frames received so far |
|
|
||||||
| Audio FIFO open stall | Bridge blocks on FIFO write-open until FFmpeg opens read-end | Guarded by 10s watchdog on bridge spawn; if FFmpeg fails to start, bridge is SIGKILL'd |
|
|
||||||
| FIFO leftover on container crash | Stale file in `/tmp/` | Next `start()` uses a new `sessionId`-based path; harmless |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Without hardware (dev mode)
|
|
||||||
The lavfi fallback is **removed** from the deltacast branch — a missing `deltacast-capture` binary will throw at spawn time (clear error). Developers run the existing test card by using `sourceType = 'sdi'` with a DeckLink card or `sourceType = 'srt'` with a test stream.
|
|
||||||
|
|
||||||
The bridge binary can be tested standalone:
|
|
||||||
```bash
|
|
||||||
mkfifo /tmp/test-audio
|
|
||||||
deltacast-capture --device 0 --port 0 --audio-pipe /tmp/test-audio &
|
|
||||||
# watch stderr for JSON line, then:
|
|
||||||
cat /tmp/test-audio | ffprobe -f s16le -ar 48000 -ac 8 -i -
|
|
||||||
```
|
|
||||||
|
|
||||||
### With hardware (post-implementation)
|
|
||||||
1. Create recorder: `source_type=deltacast`, `device=0`, `port=0`
|
|
||||||
2. Verify JSON handshake in capture container logs within signal timeout
|
|
||||||
3. Verify `signal=receiving` in recorder status
|
|
||||||
4. Record 30s clip → asset created, proxy + HLS generated
|
|
||||||
5. Test stop mid-record → file finalized correctly
|
|
||||||
6. Test no-signal → recorder stays idle, no asset created
|
|
||||||
7. Test container restart mid-record → asset finalized on restart via existing `finalize` endpoint
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
|
|
||||||
- 10-bit (`v210`) pixel format — follow-up
|
|
||||||
- `--audio-groups` UI control — follow-up
|
|
||||||
- GPU extension SDK (`gpuextension-linux.x64-2.2.0-dev.zip`) — covers GPU-accelerated colorspace conversion on the card; not needed for basic capture
|
|
||||||
- IP virtual card SDK (`ipvirtualcard`) — separate feature
|
|
||||||
- Promoting bridge to a native FFmpeg `libavdevice` input device — future v2
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
# Capture-card SDK / driver file store
|
|
||||||
|
|
||||||
This directory holds the **proprietary, non-redistributable** vendor SDKs and
|
|
||||||
drivers used to enable SDI / NDI capture cards on cluster nodes.
|
|
||||||
|
|
||||||
> **INTERNAL ONLY.** These files are licensed by their respective vendors and
|
|
||||||
> must **not** be published, redistributed, or committed to any public mirror.
|
|
||||||
> This repository is private. Do not change that.
|
|
||||||
|
|
||||||
## Why these live in the repo
|
|
||||||
|
|
||||||
The cluster admin screen lets an operator install/update capture-card drivers on
|
|
||||||
a node from the web UI (no SSH). The node-agent spawns a one-shot privileged
|
|
||||||
container that bind-mounts this repository and runs
|
|
||||||
[`deploy/install-driver.sh <vendor>`](../deploy/install-driver.sh), which reads
|
|
||||||
the vendor files from `sdk/<vendor>/`. Because the install must work offline on
|
|
||||||
an isolated broadcast LAN, the binaries ship in-repo rather than being fetched at
|
|
||||||
install time.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
|
||||||
sdk/
|
|
||||||
README.md ← this file
|
|
||||||
blackmagic/ ← Blackmagic Desktop Video (DeckLink) .deb
|
|
||||||
aja/ ← AJA ntv2 driver source / installer
|
|
||||||
deltacast/ ← Deltacast VideoMaster installer
|
|
||||||
ndi/ ← NDI redistributable runtime libs
|
|
||||||
```
|
|
||||||
|
|
||||||
Each vendor directory has its own `README.md` listing **exactly** which files an
|
|
||||||
admin must drop in. A `.gitkeep` keeps the empty directory committed.
|
|
||||||
|
|
||||||
## Important
|
|
||||||
|
|
||||||
- **No binaries are committed by default.** The directory structure + READMEs
|
|
||||||
are the deliverable. An admin downloads the proprietary files from the vendor
|
|
||||||
(per their licence) and drops them in the matching `sdk/<vendor>/` directory.
|
|
||||||
- The install script **fails gracefully** with a clear message if the expected
|
|
||||||
file is absent — it never fabricates or downloads binaries.
|
|
||||||
- Target host OS for all install paths is **Ubuntu 22.04 LTS (jammy), x86_64**,
|
|
||||||
matching the cluster worker nodes.
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# AJA NTV2 driver
|
|
||||||
|
|
||||||
Drop the **AJA NTV2** driver source/SDK archive for Linux into this directory.
|
|
||||||
|
|
||||||
## Required file
|
|
||||||
|
|
||||||
| File | Notes |
|
|
||||||
|------|-------|
|
|
||||||
| `ntv2sdk*linux*.zip` **or** `libajantv2*.tar.gz` | The NTV2 SDK / open-source `libajantv2` source tree containing `driver/linux/` with the kernel-module `Makefile` and `load_ajantv2` / `unload_ajantv2` scripts. |
|
|
||||||
|
|
||||||
Example names: `ntv2sdklinux_17.0.1.zip`, `libajantv2-17.0.1.tar.gz`
|
|
||||||
|
|
||||||
The installer reads the **newest** matching archive.
|
|
||||||
|
|
||||||
## Where to get it
|
|
||||||
|
|
||||||
AJA → Support → Software & firmware → *NTV2 SDK* (Linux), or the public
|
|
||||||
`aja-video/libajantv2` source release. Download the Linux SDK zip / source
|
|
||||||
tarball and copy it here unmodified.
|
|
||||||
|
|
||||||
## What the install script does
|
|
||||||
|
|
||||||
1. Ensures `linux-headers-$(uname -r)`, `build-essential` are present.
|
|
||||||
2. Extracts the archive into a scratch build dir.
|
|
||||||
3. Builds the `ajantv2` kernel module from `driver/linux` (`make`).
|
|
||||||
4. Installs the module under `/lib/modules/$(uname -r)/extra`, runs `depmod`,
|
|
||||||
`modprobe ajantv2` (falls back to the SDK's `load_ajantv2` script).
|
|
||||||
5. Verifies the `ajantv2` module is loaded.
|
|
||||||
|
|
||||||
A **reboot is not normally required**; the module loads immediately after build.
|
|
||||||
The script reports if a reboot is needed (e.g. an old in-tree module is wedged).
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# Blackmagic Desktop Video (DeckLink) driver
|
|
||||||
|
|
||||||
Drop the **Blackmagic Desktop Video** Debian package for **Ubuntu 22.04 (x86_64)**
|
|
||||||
into this directory.
|
|
||||||
|
|
||||||
## Required file
|
|
||||||
|
|
||||||
| File | Notes |
|
|
||||||
|------|-------|
|
|
||||||
| `desktopvideo_*_amd64.deb` | The `desktopvideo` package from the Desktop Video installer archive. Provides the `blackmagic` kernel module (built via DKMS) and the `DesktopVideoHelper` daemon. |
|
|
||||||
|
|
||||||
Example name: `desktopvideo_14.4.1a4_amd64.deb`
|
|
||||||
|
|
||||||
The installer reads the **newest** matching `desktopvideo_*_amd64.deb` if more
|
|
||||||
than one is present.
|
|
||||||
|
|
||||||
## Where to get it
|
|
||||||
|
|
||||||
Blackmagic Design → Support → *Desktop Video* (Linux). Download the
|
|
||||||
"Desktop Video x.y.z Linux" tarball, extract it, and copy the
|
|
||||||
`deb/<arch>/desktopvideo_*_amd64.deb` file here.
|
|
||||||
|
|
||||||
> Optional: `desktopvideo-gui_*_amd64.deb` is **not** required for headless
|
|
||||||
> capture and is not installed.
|
|
||||||
|
|
||||||
## What the install script does
|
|
||||||
|
|
||||||
1. Ensures `linux-headers-$(uname -r)` is present (needed for the DKMS build).
|
|
||||||
2. `apt-get install -y ./desktopvideo_*_amd64.deb` (pulls DKMS deps).
|
|
||||||
3. Triggers the DKMS build, `depmod`, `modprobe blackmagic`.
|
|
||||||
4. Restarts the `DesktopVideoHelper` daemon.
|
|
||||||
5. Verifies `/dev/blackmagic` appears.
|
|
||||||
|
|
||||||
A **reboot is usually not required** but a DKMS rebuild against a freshly
|
|
||||||
installed kernel may need one — the script reports this.
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# Deltacast VideoMaster driver / SDK
|
|
||||||
|
|
||||||
Drop the **Deltacast VideoMaster** Linux installer into this directory.
|
|
||||||
|
|
||||||
## Required file
|
|
||||||
|
|
||||||
| File | Notes |
|
|
||||||
|------|-------|
|
|
||||||
| `VideoMaster*.run` **or** `VideoMaster*linux*.tar.gz` | The VideoMaster SDK + driver installer for Linux. Contains the `videomasterhd` kernel module sources and the `install.sh` driver installer. |
|
|
||||||
|
|
||||||
Example names: `VideoMaster-6.25.0.run`, `VideoMaster_6_25_Linux.tar.gz`
|
|
||||||
|
|
||||||
The installer reads the **newest** matching file.
|
|
||||||
|
|
||||||
## Where to get it
|
|
||||||
|
|
||||||
Deltacast → Products → *SDK* (<https://www.deltacast.tv/products/sdk>). Request
|
|
||||||
the VideoMaster Linux package (licence-gated) and copy the `.run` self-extractor
|
|
||||||
or the `.tar.gz` here unmodified.
|
|
||||||
|
|
||||||
## What the install script does
|
|
||||||
|
|
||||||
1. Ensures `linux-headers-$(uname -r)`, `build-essential`, `dkms` are present.
|
|
||||||
2. Runs the vendor installer:
|
|
||||||
- `.run` → executed with `--noexec --target <dir>` then its `install.sh`,
|
|
||||||
- `.tar.gz` → extracted, then its bundled `install.sh` is run.
|
|
||||||
3. Loads the Deltacast module (`modprobe videomasterhd` / vendor load script).
|
|
||||||
4. Verifies a `/dev/deltacast*` device node appears.
|
|
||||||
|
|
||||||
A **reboot may be required** after a first-time VideoMaster install (udev rules
|
|
||||||
+ firmware). The script reports this explicitly.
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
# NDI redistributable runtime
|
|
||||||
|
|
||||||
Drop the **NDI runtime redistributable** shared libraries into this directory.
|
|
||||||
NDI has **no kernel module** — it is purely user-space shared libraries, so this
|
|
||||||
is the lowest-risk install (no DKMS, no reboot).
|
|
||||||
|
|
||||||
## Required files
|
|
||||||
|
|
||||||
| File | Notes |
|
|
||||||
|------|-------|
|
|
||||||
| `libndi.so.*` | The versioned NDI runtime shared object, e.g. `libndi.so.6`. **Required.** |
|
|
||||||
| `libndi.so` *(optional)* | Dev symlink. The installer recreates it if absent. |
|
|
||||||
|
|
||||||
You may instead drop the whole **NDI SDK / Advanced SDK** `lib/x86_64-linux-gnu/`
|
|
||||||
directory contents here; the installer copies every `libndi*.so*` it finds.
|
|
||||||
|
|
||||||
Example name: `libndi.so.6.1.1`
|
|
||||||
|
|
||||||
## Where to get it
|
|
||||||
|
|
||||||
NDI → Tools / SDK download (NDI 6 SDK or NDI Advanced SDK for Linux). The
|
|
||||||
runtime libs live under `lib/x86_64-linux-gnu/` in the SDK. Per the NDI licence
|
|
||||||
the runtime is redistributable **within your own product** only — keep it in this
|
|
||||||
private repo, do not publish it.
|
|
||||||
|
|
||||||
## What the install script does
|
|
||||||
|
|
||||||
1. Copies every `libndi*.so*` from here into `/opt/ndi-lib`.
|
|
||||||
2. Writes `/etc/ld.so.conf.d/ndi.conf` pointing at `/opt/ndi-lib` and runs
|
|
||||||
`ldconfig`.
|
|
||||||
3. Recreates the `libndi.so` → `libndi.so.<N>` dev symlink if missing.
|
|
||||||
4. Verifies `ldconfig -p | grep libndi` resolves.
|
|
||||||
|
|
||||||
**No reboot required.** Running processes that already loaded an old `libndi`
|
|
||||||
must be restarted to pick up the new version — the script notes this.
|
|
||||||
|
|
@ -1,21 +1,4 @@
|
||||||
# ── Stage 0: Extract Deltacast VideoMaster SDK ───────────────────────────
|
# ── Stage 1: Build FFmpeg with DeckLink + NVENC (HEVC/H264) support ─────────
|
||||||
FROM debian:bookworm AS sdk-extractor
|
|
||||||
COPY videomaster-linux.x64-6.34.1-dev.tar.gz /tmp/
|
|
||||||
RUN mkdir -p /sdk && tar -xzf /tmp/videomaster-linux.x64-6.34.1-dev.tar.gz -C /sdk
|
|
||||||
|
|
||||||
# ── Stage 1: Build deltacast-capture bridge binary ───────────────────────
|
|
||||||
FROM debian:bookworm AS bridge-builder
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
build-essential cmake ca-certificates \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
COPY --from=sdk-extractor /sdk /sdk
|
|
||||||
COPY deltacast-bridge/ /bridge/
|
|
||||||
RUN cmake -S /bridge -B /bridge/build \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DSDK_ROOT=/sdk \
|
|
||||||
&& cmake --build /bridge/build -j$(nproc)
|
|
||||||
|
|
||||||
# ── Stage 2: Build FFmpeg with DeckLink + NVENC (HEVC/H264) support ─────────
|
|
||||||
# All-Intra HEVC NVENC is the master codec for growing-file ingest (see
|
# All-Intra HEVC NVENC is the master codec for growing-file ingest (see
|
||||||
# docs/design/2026-05-29-all-intra-hevc-ingest.md). This stage gets the
|
# docs/design/2026-05-29-all-intra-hevc-ingest.md). This stage gets the
|
||||||
# nv-codec-headers (header-only, no driver / no full CUDA toolkit needed)
|
# nv-codec-headers (header-only, no driver / no full CUDA toolkit needed)
|
||||||
|
|
@ -81,46 +64,13 @@ RUN ./configure \
|
||||||
RUN /usr/local/bin/ffmpeg -hide_banner -encoders 2>&1 | grep -E 'nvenc' \
|
RUN /usr/local/bin/ffmpeg -hide_banner -encoders 2>&1 | grep -E 'nvenc' \
|
||||||
|| (echo 'FATAL: nvenc encoders missing from ffmpeg build' && exit 1)
|
|| (echo 'FATAL: nvenc encoders missing from ffmpeg build' && exit 1)
|
||||||
|
|
||||||
# ── Stage 1b: Build bmx (raw2bmx / bmxtranswrap) from source ─────────────────
|
|
||||||
# bmx (bmxlib + libMXF + libMXF++) is the reference GROWING OP1a MXF writer. It
|
|
||||||
# writes a fresh IndexTableSegment (with an updated IndexDuration) into a new
|
|
||||||
# body partition at a periodic interval, so the recorded duration is readable —
|
|
||||||
# and INCREASES — from the header+index alone while the file is still being
|
|
||||||
# written (no footer needed). This is what makes the master a TRUE Premiere
|
|
||||||
# growing file. ffmpeg's MXF muxer cannot do this (its real duration/index lands
|
|
||||||
# only in the footer at av_write_trailer, so duration probes N/A until close).
|
|
||||||
#
|
|
||||||
# Debian/Ubuntu have no `bmxlib-tools` package (verified absent in bookworm), so
|
|
||||||
# we build from the BBC source. liburiparser/uuid/lzma/zlib/expat are the build
|
|
||||||
# deps; the runtime needs only libexpat1 + liburiparser1 + libuuid1 (added in
|
|
||||||
# the runtime stage below). Pinned to the bbc/bmx default branch (v1.6.x).
|
|
||||||
FROM debian:bookworm AS bmx-builder
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
||||||
build-essential cmake git ca-certificates pkg-config \
|
|
||||||
liburiparser-dev uuid-dev liblzma-dev zlib1g-dev libexpat1-dev \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
# Pin to a release tag so the produced soname (libMXF.so.1.6 etc.) stays stable
|
|
||||||
# for the COPY in the runtime stage. v1.6 is the BBC bmx series verified here.
|
|
||||||
RUN git clone --recursive --branch v1.6 https://github.com/bbc/bmx.git /bmx \
|
|
||||||
|| git clone --recursive https://github.com/bbc/bmx.git /bmx
|
|
||||||
WORKDIR /bmx/build
|
|
||||||
RUN cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local .. \
|
|
||||||
&& make -j"$(nproc)" && make install && ldconfig
|
|
||||||
# Sanity-check: raw2bmx must run, otherwise the growing-MXF pipeline is broken.
|
|
||||||
RUN /usr/local/bin/raw2bmx -h >/dev/null 2>&1 && echo 'raw2bmx OK'
|
|
||||||
|
|
||||||
# ── Stage 2: Runtime image ───────────────────────────────────────────────────
|
# ── Stage 2: Runtime image ───────────────────────────────────────────────────
|
||||||
FROM node:20-bookworm
|
FROM node:20-bookworm
|
||||||
|
|
||||||
# Runtime deps for compiled ffmpeg libs.
|
# Runtime deps for compiled ffmpeg libs
|
||||||
# cifs-utils provides mount.cifs so growing-files capture can mount the SMB
|
|
||||||
# landing-zone share inside the (privileged) container at start (Approach A).
|
|
||||||
# util-linux supplies mount/umount/mountpoint.
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
libx264-164 libx265-199 libvpx7 libopus0 libmp3lame0 \
|
libx264-164 libx265-199 libvpx7 libopus0 libmp3lame0 \
|
||||||
libsrt1.5-openssl libzmq5 libstdc++6 libc++1 libc++abi1 \
|
libsrt1.5-openssl libzmq5 libstdc++6 libc++1 libc++abi1 \
|
||||||
cifs-utils util-linux \
|
|
||||||
libexpat1 liburiparser1 libuuid1 \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy compiled ffmpeg/ffprobe
|
# Copy compiled ffmpeg/ffprobe
|
||||||
|
|
@ -131,34 +81,7 @@ COPY --from=ffmpeg-builder /usr/local/lib/ /usr/local/lib/
|
||||||
# DeckLink runtime .so
|
# DeckLink runtime .so
|
||||||
COPY lib/libDeckLinkAPI.so /usr/lib/libDeckLinkAPI.so
|
COPY lib/libDeckLinkAPI.so /usr/lib/libDeckLinkAPI.so
|
||||||
COPY lib/libDeckLinkPreviewAPI.so /usr/lib/libDeckLinkPreviewAPI.so
|
COPY lib/libDeckLinkPreviewAPI.so /usr/lib/libDeckLinkPreviewAPI.so
|
||||||
|
RUN ldconfig
|
||||||
# bmx (raw2bmx / bmxtranswrap / mxf2raw) — the growing OP1a MXF writer used for
|
|
||||||
# the edit-while-record master. Copy the built binaries + shared libs; runtime
|
|
||||||
# deps (libexpat1/liburiparser1/libuuid1) were installed above.
|
|
||||||
COPY --from=bmx-builder /usr/local/bin/raw2bmx /usr/local/bin/raw2bmx
|
|
||||||
COPY --from=bmx-builder /usr/local/bin/bmxtranswrap /usr/local/bin/bmxtranswrap
|
|
||||||
COPY --from=bmx-builder /usr/local/bin/mxf2raw /usr/local/bin/mxf2raw
|
|
||||||
COPY --from=bmx-builder /usr/local/lib/libMXF.so.1.6 /usr/local/lib/
|
|
||||||
COPY --from=bmx-builder /usr/local/lib/libMXF++.so.1.6 /usr/local/lib/
|
|
||||||
COPY --from=bmx-builder /usr/local/lib/libbmx.so.1.6 /usr/local/lib/
|
|
||||||
RUN cd /usr/local/lib \
|
|
||||||
&& ln -sf libMXF.so.1.6 libMXF.so.1 && ln -sf libMXF.so.1 libMXF.so \
|
|
||||||
&& ln -sf libMXF++.so.1.6 libMXF++.so.1 && ln -sf libMXF++.so.1 libMXF++.so \
|
|
||||||
&& ln -sf libbmx.so.1.6 libbmx.so.1 && ln -sf libbmx.so.1 libbmx.so \
|
|
||||||
&& ldconfig
|
|
||||||
# Verify raw2bmx resolves its libs and runs in the final image.
|
|
||||||
RUN raw2bmx -h >/dev/null 2>&1 && echo 'raw2bmx runtime OK'
|
|
||||||
|
|
||||||
# Deltacast bridge binary + SDK runtime libs
|
|
||||||
COPY --from=bridge-builder /bridge/build/deltacast-capture /usr/local/bin/deltacast-capture
|
|
||||||
COPY --from=sdk-extractor /sdk/lib/libvideomasterhd.so.6.34.1 /usr/local/lib/deltacast/
|
|
||||||
COPY --from=sdk-extractor /sdk/lib/libvideomasterhd_audio.so.6.34.1 /usr/local/lib/deltacast/
|
|
||||||
RUN ln -sf libvideomasterhd.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd.so.6 \
|
|
||||||
&& ln -sf libvideomasterhd.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd.so \
|
|
||||||
&& ln -sf libvideomasterhd_audio.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd_audio.so.6 \
|
|
||||||
&& ln -sf libvideomasterhd_audio.so.6.34.1 /usr/local/lib/deltacast/libvideomasterhd_audio.so \
|
|
||||||
&& ldconfig /usr/local/lib/deltacast \
|
|
||||||
&& ldconfig
|
|
||||||
|
|
||||||
# Mount points the recorder lifecycle expects to exist.
|
# Mount points the recorder lifecycle expects to exist.
|
||||||
# /live — HLS preview output (bound from host LIVE_DIR by node-agent)
|
# /live — HLS preview output (bound from host LIVE_DIR by node-agent)
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
|
||||||
project(deltacast-bridge C)
|
|
||||||
set(CMAKE_C_STANDARD 17)
|
|
||||||
|
|
||||||
set(SDK_ROOT "/sdk" CACHE PATH "Path to extracted VideoMaster SDK")
|
|
||||||
|
|
||||||
add_executable(deltacast-capture main.c)
|
|
||||||
|
|
||||||
target_include_directories(deltacast-capture PRIVATE
|
|
||||||
${SDK_ROOT}/include/videomaster
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_directories(deltacast-capture PRIVATE
|
|
||||||
${SDK_ROOT}/lib
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(deltacast-capture PRIVATE
|
|
||||||
videomasterhd
|
|
||||||
videomasterhd_audio
|
|
||||||
pthread
|
|
||||||
)
|
|
||||||
|
|
||||||
# Embed the SDK RPATH so the binary finds the .so at runtime
|
|
||||||
set_target_properties(deltacast-capture PROPERTIES
|
|
||||||
INSTALL_RPATH "/usr/local/lib/deltacast"
|
|
||||||
BUILD_WITH_INSTALL_RPATH TRUE
|
|
||||||
)
|
|
||||||
|
|
@ -1,395 +0,0 @@
|
||||||
/* services/capture/deltacast-bridge/main.c
|
|
||||||
*
|
|
||||||
* Deltacast VideoMaster SDI capture bridge.
|
|
||||||
* Writes raw UYVY video to stdout and stereo PCM to a named FIFO.
|
|
||||||
* Emits one JSON line to stderr on signal lock before streaming starts.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* deltacast-capture --device <N> --port <N> --audio-pipe <path>
|
|
||||||
* [--signal-timeout <sec>]
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdatomic.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "VideoMasterHD_Core.h"
|
|
||||||
#include "VideoMasterHD_Sdi.h"
|
|
||||||
#include "VideoMasterHD_Sdi_Audio.h"
|
|
||||||
|
|
||||||
/* ── Globals ─────────────────────────────────────────────────────────── */
|
|
||||||
static atomic_int g_stop = 0;
|
|
||||||
|
|
||||||
static void on_signal(int s) { (void)s; atomic_store(&g_stop, 1); }
|
|
||||||
|
|
||||||
/* ── Stream type by port index ───────────────────────────────────────── */
|
|
||||||
static ULONG rx_streamtype(unsigned port) {
|
|
||||||
switch (port) {
|
|
||||||
case 0: return VHD_ST_RX0;
|
|
||||||
case 1: return VHD_ST_RX1;
|
|
||||||
case 2: return VHD_ST_RX2;
|
|
||||||
case 3: return VHD_ST_RX3;
|
|
||||||
case 4: return VHD_ST_RX4;
|
|
||||||
case 5: return VHD_ST_RX5;
|
|
||||||
case 6: return VHD_ST_RX6;
|
|
||||||
case 7: return VHD_ST_RX7;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "{\"error\":\"port %u not supported (max 7)\"}\n", port);
|
|
||||||
return VHD_ST_RX0; /* caller will fail on signal lock */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Loopback board property by port index ───────────────────────────── */
|
|
||||||
static ULONG loopback_prop(unsigned port) {
|
|
||||||
switch (port) {
|
|
||||||
case 0: return VHD_CORE_BP_PASSIVE_LOOPBACK_0;
|
|
||||||
case 1: return VHD_CORE_BP_PASSIVE_LOOPBACK_1;
|
|
||||||
case 2: return VHD_CORE_BP_PASSIVE_LOOPBACK_2;
|
|
||||||
case 3: return VHD_CORE_BP_PASSIVE_LOOPBACK_3;
|
|
||||||
default: return VHD_CORE_BP_PASSIVE_LOOPBACK_0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Video standard → width/height/fps/interlaced ───────────────────── */
|
|
||||||
typedef struct { int width, height, fps_num, fps_den; int interlaced; } VideoInfo;
|
|
||||||
|
|
||||||
static VideoInfo video_info(VHD_VIDEOSTANDARD std, VHD_CLOCKDIVISOR div) {
|
|
||||||
int ntsc = (div == VHD_CLOCKDIV_1001);
|
|
||||||
switch (std) {
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_25Hz: return (VideoInfo){1920,1080,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_30Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_24Hz: return (VideoInfo){1920,1080,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_50Hz: return (VideoInfo){1920,1080,50,1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080p_60Hz: return (VideoInfo){1920,1080,ntsc?60000:60,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080psf_24Hz: return (VideoInfo){1920,1080,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080psf_25Hz: return (VideoInfo){1920,1080,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080psf_30Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080i_50Hz: return (VideoInfo){1920,1080,25,1,1};
|
|
||||||
case VHD_VIDEOSTD_S274M_1080i_60Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,1};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_50Hz: return (VideoInfo){1280,720,50,1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_60Hz: return (VideoInfo){1280,720,ntsc?60000:60,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_25Hz: return (VideoInfo){1280,720,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_30Hz: return (VideoInfo){1280,720,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S296M_720p_24Hz: return (VideoInfo){1280,720,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_24Hz: return (VideoInfo){3840,2160,ntsc?24000:24,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_25Hz: return (VideoInfo){3840,2160,25,1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_30Hz: return (VideoInfo){3840,2160,ntsc?30000:30,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_50Hz: return (VideoInfo){3840,2160,50,1,0};
|
|
||||||
case VHD_VIDEOSTD_3840x2160p_60Hz: return (VideoInfo){3840,2160,ntsc?60000:60,ntsc?1001:1,0};
|
|
||||||
case VHD_VIDEOSTD_S259M_NTSC_480: return (VideoInfo){720,480,ntsc?30000:30,ntsc?1001:1,1};
|
|
||||||
default: return (VideoInfo){1920,1080,25,1,0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Audio thread ────────────────────────────────────────────
|
|
||||||
*
|
|
||||||
* CRITICAL: ffmpeg opens ALL of its inputs before it starts processing any of
|
|
||||||
* them, and input 1 is this audio FIFO. Opening the read end of a FIFO blocks
|
|
||||||
* until a writer connects, so if this thread fails to open the FIFO writer
|
|
||||||
* ffmpeg hangs forever on input 1 -> no video frames are ever read from
|
|
||||||
* pipe:0 -> 0 fps and an empty HLS preview. Therefore the FIFO writer is
|
|
||||||
* opened UNCONDITIONALLY and FIRST, independent of any VideoMaster audio open,
|
|
||||||
* and the thread then feeds the FIFO a CONTINUOUS, wall-clock-paced s16le
|
|
||||||
* stereo stream (real samples when available, otherwise silence) so ffmpeg's
|
|
||||||
* A/V demux stays alive and video keeps flowing. */
|
|
||||||
typedef struct {
|
|
||||||
HANDLE board;
|
|
||||||
unsigned port;
|
|
||||||
ULONG video_std;
|
|
||||||
ULONG clock_div;
|
|
||||||
int fps_num;
|
|
||||||
int fps_den;
|
|
||||||
const char *fifo_path;
|
|
||||||
} AudioArgs;
|
|
||||||
|
|
||||||
/* Write exactly `len` bytes; returns 0 on success, -1 if writing should stop
|
|
||||||
* (EPIPE when ffmpeg is gone, or any other error). */
|
|
||||||
static int write_all(int fd, const unsigned char *p, size_t len) {
|
|
||||||
size_t off = 0;
|
|
||||||
while (off < len) {
|
|
||||||
ssize_t n = write(fd, p + off, len - off);
|
|
||||||
if (n > 0) { off += (size_t)n; continue; }
|
|
||||||
if (n < 0 && errno == EINTR) continue;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *audio_thread(void *arg) {
|
|
||||||
AudioArgs *a = (AudioArgs *)arg;
|
|
||||||
|
|
||||||
/* 1. Open the FIFO writer FIRST, unconditionally. This is what unblocks
|
|
||||||
* ffmpeg's input 1; we must reach it even if the VHD audio open fails. */
|
|
||||||
int fd = open(a->fifo_path, O_WRONLY);
|
|
||||||
if (fd < 0) {
|
|
||||||
fprintf(stderr, "[audio] open FIFO failed: %s\n", strerror(errno));
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Pacing + silence buffer sized to one video frame of 48kHz stereo
|
|
||||||
* s16le. samples_per_frame = 48000 * fps_den / fps_num (rounded). */
|
|
||||||
const int AUDIO_RATE = 48000;
|
|
||||||
const int CHANNELS = 2;
|
|
||||||
const size_t FRAME_BYTES = (size_t)CHANNELS * 2; /* s16le stereo */
|
|
||||||
int fps_num = a->fps_num > 0 ? a->fps_num : 25;
|
|
||||||
int fps_den = a->fps_den > 0 ? a->fps_den : 1;
|
|
||||||
long samples_per_frame = ((long)AUDIO_RATE * fps_den + fps_num / 2) / fps_num;
|
|
||||||
if (samples_per_frame < 1) samples_per_frame = 1;
|
|
||||||
size_t tick_bytes = (size_t)samples_per_frame * FRAME_BYTES;
|
|
||||||
|
|
||||||
ULONG max_samples = VHD_GetNbSamples((VHD_VIDEOSTANDARD)a->video_std,
|
|
||||||
(VHD_CLOCKDIVISOR)a->clock_div,
|
|
||||||
VHD_ASR_48000, 0);
|
|
||||||
ULONG block_size = VHD_GetBlockSize(VHD_AF_16, VHD_AM_STEREO);
|
|
||||||
size_t vhd_buf_sz = ((size_t)max_samples + 64) * (block_size ? block_size : FRAME_BYTES);
|
|
||||||
size_t buf_sz = vhd_buf_sz > tick_bytes ? vhd_buf_sz : tick_bytes;
|
|
||||||
unsigned char *buf = calloc(1, buf_sz); /* zeroed -> doubles as silence */
|
|
||||||
if (!buf) { close(fd); return NULL; }
|
|
||||||
|
|
||||||
/* 3. Try to open the VideoMaster audio stream (best effort, NON-FATAL). */
|
|
||||||
HANDLE stream = NULL;
|
|
||||||
int have_vhd_audio = 0;
|
|
||||||
VHD_AUDIOINFO ai;
|
|
||||||
memset(&ai, 0, sizeof(ai));
|
|
||||||
|
|
||||||
ULONG r = VHD_OpenStreamHandle(a->board, rx_streamtype(a->port),
|
|
||||||
VHD_SDI_STPROC_DISJOINED_ANC,
|
|
||||||
NULL, &stream, NULL);
|
|
||||||
if (r == VHDERR_NOERROR) {
|
|
||||||
VHD_SetStreamProperty(stream, VHD_SDI_SP_VIDEO_STANDARD, a->video_std);
|
|
||||||
VHD_SetStreamProperty(stream, VHD_SDI_SP_CLOCK_SYSTEM, a->clock_div);
|
|
||||||
VHD_SetStreamProperty(stream, VHD_CORE_SP_TRANSFER_SCHEME, VHD_TRANSFER_SLAVED);
|
|
||||||
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].Mode = VHD_AM_STEREO;
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].BufferFormat = VHD_AF_16;
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].pData = buf;
|
|
||||||
|
|
||||||
if (VHD_StartStream(stream) == VHDERR_NOERROR) {
|
|
||||||
have_vhd_audio = 1;
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "[audio] VHD_StartStream failed - feeding silence\n");
|
|
||||||
VHD_CloseStreamHandle(stream);
|
|
||||||
stream = NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "[audio] VHD_OpenStreamHandle failed (%lu) - feeding silence\n", r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. Continuous, wall-clock-paced feed loop: real audio when available,
|
|
||||||
* otherwise silence, so ffmpeg's input 1 never starves. */
|
|
||||||
struct timespec next;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &next);
|
|
||||||
long frame_ns = (long)(1000000000.0 * (double)fps_den / (double)fps_num);
|
|
||||||
HANDLE slot = NULL;
|
|
||||||
|
|
||||||
while (!atomic_load(&g_stop)) {
|
|
||||||
size_t out_bytes = 0;
|
|
||||||
|
|
||||||
if (have_vhd_audio) {
|
|
||||||
r = VHD_LockSlotHandle(stream, &slot);
|
|
||||||
if (r == VHDERR_NOERROR) {
|
|
||||||
ai.pAudioGroups[0].pAudioChannels[0].DataSize = (ULONG)buf_sz;
|
|
||||||
if (VHD_SlotExtractAudio(slot, &ai) == VHDERR_NOERROR) {
|
|
||||||
ULONG sz = ai.pAudioGroups[0].pAudioChannels[0].DataSize;
|
|
||||||
if (sz > 0 && (size_t)sz <= buf_sz) out_bytes = (size_t)sz;
|
|
||||||
}
|
|
||||||
VHD_UnlockSlotHandle(slot);
|
|
||||||
} else if (r != VHDERR_TIMEOUT) {
|
|
||||||
fprintf(stderr, "[audio] lock error %lu - degrading to silence\n", r);
|
|
||||||
VHD_StopStream(stream);
|
|
||||||
VHD_CloseStreamHandle(stream);
|
|
||||||
stream = NULL;
|
|
||||||
have_vhd_audio = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out_bytes == 0) {
|
|
||||||
memset(buf, 0, tick_bytes); /* one frame of silence */
|
|
||||||
out_bytes = tick_bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (write_all(fd, buf, out_bytes) < 0) {
|
|
||||||
atomic_store(&g_stop, 1); /* ffmpeg closed the FIFO (EPIPE) */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
next.tv_nsec += frame_ns;
|
|
||||||
while (next.tv_nsec >= 1000000000L) { next.tv_nsec -= 1000000000L; next.tv_sec += 1; }
|
|
||||||
struct timespec now;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
if (next.tv_sec > now.tv_sec ||
|
|
||||||
(next.tv_sec == now.tv_sec && next.tv_nsec > now.tv_nsec)) {
|
|
||||||
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
|
|
||||||
} else {
|
|
||||||
next = now; /* fell behind (real-audio burst) - resync */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fd);
|
|
||||||
if (stream) {
|
|
||||||
VHD_StopStream(stream);
|
|
||||||
VHD_CloseStreamHandle(stream);
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Main ────────────────────────────────────────────────────────────── */
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
unsigned device_id = 0;
|
|
||||||
unsigned port_id = 0;
|
|
||||||
int sig_timeout = 30;
|
|
||||||
const char *audio_pipe = NULL;
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
|
||||||
if (!strcmp(argv[i], "--device") && i+1 < argc) device_id = (unsigned)atoi(argv[++i]);
|
|
||||||
else if (!strcmp(argv[i], "--port") && i+1 < argc) port_id = (unsigned)atoi(argv[++i]);
|
|
||||||
else if (!strcmp(argv[i], "--audio-pipe") && i+1 < argc) audio_pipe = argv[++i];
|
|
||||||
else if (!strcmp(argv[i], "--signal-timeout") && i+1 < argc) sig_timeout = atoi(argv[++i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
signal(SIGINT, on_signal);
|
|
||||||
signal(SIGTERM, on_signal);
|
|
||||||
/* Don't let a dying ffmpeg kill us with SIGPIPE - writes return EPIPE
|
|
||||||
* and the FIFO/stdout write loops handle that by stopping cleanly. */
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
|
||||||
|
|
||||||
/* ── Init API ─────────────────────────────────────────────────── */
|
|
||||||
ULONG dll_ver, nb_boards;
|
|
||||||
if (VHD_GetApiInfo(&dll_ver, &nb_boards) != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "{\"error\":\"VHD_GetApiInfo failed\"}\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (device_id >= nb_boards) {
|
|
||||||
fprintf(stderr, "{\"error\":\"board %u not found (%lu detected)\"}\n", device_id, nb_boards);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Open board ───────────────────────────────────────────────── */
|
|
||||||
HANDLE board = NULL;
|
|
||||||
if (VHD_OpenBoardHandle(device_id, &board, NULL, 0) != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "{\"error\":\"VHD_OpenBoardHandle failed for board %u\"}\n", device_id);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Disable passive (relay) loopback so RX is live.
|
|
||||||
* VHD_CORE_BP_PASSIVE_LOOPBACK_<n> only exists for ports 0-3 in SDK 6.34.1,
|
|
||||||
* and the board reports passive-loopback capability 0, so skipping ports 4-7
|
|
||||||
* is harmless. */
|
|
||||||
if (port_id < 4) {
|
|
||||||
VHD_SetBoardProperty(board, loopback_prop(port_id), FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Wait for signal lock ──────────────────────────────────────── */
|
|
||||||
ULONG video_std = (ULONG)NB_VHD_VIDEOSTANDARDS;
|
|
||||||
struct timespec deadline;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &deadline);
|
|
||||||
deadline.tv_sec += sig_timeout;
|
|
||||||
|
|
||||||
while (!atomic_load(&g_stop)) {
|
|
||||||
struct timespec now;
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
||||||
if (now.tv_sec > deadline.tv_sec ||
|
|
||||||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) break;
|
|
||||||
|
|
||||||
VHD_GetChannelProperty(board, VHD_RX_CHANNEL, port_id,
|
|
||||||
VHD_SDI_CP_VIDEO_STANDARD, &video_std);
|
|
||||||
if (video_std != (ULONG)NB_VHD_VIDEOSTANDARDS) break;
|
|
||||||
|
|
||||||
struct timespec ts = {0, 200000000L}; /* 200ms */
|
|
||||||
nanosleep(&ts, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (atomic_load(&g_stop) || video_std == (ULONG)NB_VHD_VIDEOSTANDARDS) {
|
|
||||||
fprintf(stderr,
|
|
||||||
"{\"error\":\"no signal on board %u port %u within %ds\"}\n",
|
|
||||||
device_id, port_id, sig_timeout);
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
ULONG clock_div = VHD_CLOCKDIV_1;
|
|
||||||
VHD_GetChannelProperty(board, VHD_RX_CHANNEL, port_id,
|
|
||||||
VHD_SDI_CP_CLOCK_DIVISOR, &clock_div);
|
|
||||||
|
|
||||||
VideoInfo vi = video_info((VHD_VIDEOSTANDARD)video_std,
|
|
||||||
(VHD_CLOCKDIVISOR)clock_div);
|
|
||||||
|
|
||||||
/* ── Emit format JSON to stderr (one line, flushed) ─────────────── */
|
|
||||||
fprintf(stderr,
|
|
||||||
"{\"width\":%d,\"height\":%d,\"fps_num\":%d,\"fps_den\":%d,"
|
|
||||||
"\"interlaced\":%s,\"pix_fmt\":\"uyvy422\","
|
|
||||||
"\"audio_channels\":2,\"audio_rate\":48000,"
|
|
||||||
"\"device\":%u,\"port\":%u}\n",
|
|
||||||
vi.width, vi.height, vi.fps_num, vi.fps_den,
|
|
||||||
vi.interlaced ? "true" : "false",
|
|
||||||
device_id, port_id);
|
|
||||||
fflush(stderr);
|
|
||||||
|
|
||||||
/* ── Open video stream ───────────────────────────────────────────── */
|
|
||||||
HANDLE video_stream = NULL;
|
|
||||||
if (VHD_OpenStreamHandle(board, rx_streamtype(port_id),
|
|
||||||
VHD_SDI_STPROC_DISJOINED_VIDEO,
|
|
||||||
NULL, &video_stream, NULL) != VHDERR_NOERROR) {
|
|
||||||
fprintf(stderr, "{\"error\":\"VHD_OpenStreamHandle (video) failed\"}\n");
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_SDI_SP_VIDEO_STANDARD, video_std);
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_SDI_SP_CLOCK_SYSTEM, clock_div);
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_CORE_SP_TRANSFER_SCHEME, VHD_TRANSFER_SLAVED);
|
|
||||||
VHD_SetStreamProperty(video_stream, VHD_CORE_SP_BUFFERQUEUE_DEPTH, 8);
|
|
||||||
|
|
||||||
/* ── Launch audio thread (FIFO open blocks until FFmpeg connects) ── */
|
|
||||||
pthread_t audio_tid = 0;
|
|
||||||
AudioArgs audio_args = { board, port_id, video_std, clock_div,
|
|
||||||
vi.fps_num, vi.fps_den, audio_pipe };
|
|
||||||
if (audio_pipe) {
|
|
||||||
pthread_create(&audio_tid, NULL, audio_thread, &audio_args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Start video stream ──────────────────────────────────────────── */
|
|
||||||
if (VHD_StartStream(video_stream) != VHDERR_NOERROR) {
|
|
||||||
atomic_store(&g_stop, 1);
|
|
||||||
if (audio_tid) pthread_join(audio_tid, NULL);
|
|
||||||
VHD_CloseStreamHandle(video_stream);
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Video capture loop ──────────────────────────────────────────── */
|
|
||||||
HANDLE slot = NULL;
|
|
||||||
while (!atomic_load(&g_stop)) {
|
|
||||||
ULONG r = VHD_LockSlotHandle(video_stream, &slot);
|
|
||||||
if (r == VHDERR_NOERROR) {
|
|
||||||
BYTE *buf = NULL;
|
|
||||||
ULONG sz = 0;
|
|
||||||
if (VHD_GetSlotBuffer(slot, VHD_SDI_BT_VIDEO, &buf, &sz) == VHDERR_NOERROR) {
|
|
||||||
ULONG written = 0;
|
|
||||||
while (written < sz) {
|
|
||||||
ssize_t n = write(STDOUT_FILENO, buf + written, sz - written);
|
|
||||||
if (n <= 0) { atomic_store(&g_stop, 1); break; }
|
|
||||||
written += (ULONG)n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VHD_UnlockSlotHandle(slot);
|
|
||||||
} else if (r != VHDERR_TIMEOUT) {
|
|
||||||
fprintf(stderr, "[video] VHD_LockSlotHandle error %lu — stopping\n", r);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Cleanup ─────────────────────────────────────────────────── */
|
|
||||||
VHD_StopStream(video_stream);
|
|
||||||
VHD_CloseStreamHandle(video_stream);
|
|
||||||
if (audio_tid) pthread_join(audio_tid, NULL);
|
|
||||||
VHD_CloseBoardHandle(board);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,338 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2026 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-
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* -- AUTOMATICALLY GENERATED - DO NOT EDIT ---
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPICONFIGURATION_H
|
|
||||||
#define BMD_DECKLINKAPICONFIGURATION_H
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef BMD_CONST
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define BMD_CONST __declspec(selectany) static const
|
|
||||||
#else
|
|
||||||
#define BMD_CONST static const
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BMD_PUBLIC
|
|
||||||
#define BMD_PUBLIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkConfiguration = /* 5A68FFD4-1C12-4EDE-A6D2-45451D385FC1 */ { 0x5A,0x68,0xFF,0xD4,0x1C,0x12,0x4E,0xDE,0xA6,0xD2,0x45,0x45,0x1D,0x38,0x5F,0xC1 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkEncoderConfiguration = /* 138050E5-C60A-4552-BF3F-0F358049327E */ { 0x13,0x80,0x50,0xE5,0xC6,0x0A,0x45,0x52,0xBF,0x3F,0x0F,0x35,0x80,0x49,0x32,0x7E };
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkConfigurationID - DeckLink Configuration ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkConfigurationID;
|
|
||||||
enum _BMDDeckLinkConfigurationID {
|
|
||||||
|
|
||||||
/* Serial port Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigSwapSerialRxTx = /* 'ssrt' */ 0x73737274,
|
|
||||||
|
|
||||||
/* Video Input/Output Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigHDMI3DPackingFormat = /* '3dpf' */ 0x33647066,
|
|
||||||
bmdDeckLinkConfigBypass = /* 'byps' */ 0x62797073,
|
|
||||||
bmdDeckLinkConfigClockTimingAdjustment = /* 'ctad' */ 0x63746164,
|
|
||||||
bmdDeckLinkConfigAudioMeterType = /* 'aumt' */ 0x61756D74,
|
|
||||||
|
|
||||||
/* Audio Input/Output Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigAnalogAudioConsumerLevels = /* 'aacl' */ 0x6161636C,
|
|
||||||
bmdDeckLinkConfigSwapHDMICh3AndCh4OnInput = /* 'hi34' */ 0x68693334,
|
|
||||||
bmdDeckLinkConfigSwapHDMICh3AndCh4OnOutput = /* 'ho34' */ 0x686F3334,
|
|
||||||
bmdDeckLinkConfigAnalogAudioOutputChannelsMutedByHeadphone = /* 'amhp' */ 0x616D6870,
|
|
||||||
bmdDeckLinkConfigAnalogAudioOutputChannelsMutedBySpeaker = /* 'amsp' */ 0x616D7370,
|
|
||||||
|
|
||||||
/* Video Output Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigFieldFlickerRemoval = /* 'fdfr' */ 0x66646672,
|
|
||||||
bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion = /* 'to59' */ 0x746F3539,
|
|
||||||
bmdDeckLinkConfig444SDIVideoOutput = /* '444o' */ 0x3434346F,
|
|
||||||
bmdDeckLinkConfigBlackVideoOutputDuringCapture = /* 'bvoc' */ 0x62766F63,
|
|
||||||
bmdDeckLinkConfigLowLatencyVideoOutput = /* 'llvo' */ 0x6C6C766F,
|
|
||||||
bmdDeckLinkConfigDownConversionOnAllAnalogOutput = /* 'caao' */ 0x6361616F,
|
|
||||||
bmdDeckLinkConfigSMPTELevelAOutput = /* 'smta' */ 0x736D7461,
|
|
||||||
bmdDeckLinkConfigRec2020Output = /* 'rec2' */ 0x72656332, // Ensure output is Rec.2020 colorspace
|
|
||||||
bmdDeckLinkConfigQuadLinkSDIVideoOutputSquareDivisionSplit = /* 'SDQS' */ 0x53445153,
|
|
||||||
bmdDeckLinkConfigOutput1080pAsPsF = /* 'pfpr' */ 0x70667072,
|
|
||||||
bmdDeckLinkConfigOutputValidateEDIDForDolbyVision = /* 'pred' */ 0x70726564,
|
|
||||||
bmdDeckLinkConfigExtendedDesktop = /* 'exdt' */ 0x65786474,
|
|
||||||
bmdDeckLinkConfigEthernetVideoOutputIP10 = /* 'IP10' */ 0x49503130,
|
|
||||||
|
|
||||||
/* Video Output Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigVideoOutputConnection = /* 'vocn' */ 0x766F636E,
|
|
||||||
bmdDeckLinkConfigVideoOutputConversionMode = /* 'vocm' */ 0x766F636D,
|
|
||||||
bmdDeckLinkConfigVideoOutputConversionColorspaceDestination = /* 'vccd' */ 0x76636364, // Parameter is of type BMDColorspace
|
|
||||||
bmdDeckLinkConfigVideoOutputConversionColorspaceSource = /* 'vccs' */ 0x76636373, // Parameter is of type BMDColorspace
|
|
||||||
bmdDeckLinkConfigAnalogVideoOutputFlags = /* 'avof' */ 0x61766F66,
|
|
||||||
bmdDeckLinkConfigReferenceInputTimingOffset = /* 'glot' */ 0x676C6F74,
|
|
||||||
bmdDeckLinkConfigReferenceOutputMode = /* 'glOm' */ 0x676C4F6D,
|
|
||||||
bmdDeckLinkConfigVideoOutputIdleOperation = /* 'voio' */ 0x766F696F,
|
|
||||||
bmdDeckLinkConfigDefaultVideoOutputMode = /* 'dvom' */ 0x64766F6D,
|
|
||||||
bmdDeckLinkConfigDefaultVideoOutputModeFlags = /* 'dvof' */ 0x64766F66,
|
|
||||||
bmdDeckLinkConfigSDIOutputLinkConfiguration = /* 'solc' */ 0x736F6C63,
|
|
||||||
bmdDeckLinkConfigHDMITimecodePacking = /* 'htpk' */ 0x6874706B,
|
|
||||||
bmdDeckLinkConfigPlaybackGroup = /* 'plgr' */ 0x706C6772,
|
|
||||||
|
|
||||||
/* Video Output Floats */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigVideoOutputComponentLumaGain = /* 'oclg' */ 0x6F636C67,
|
|
||||||
bmdDeckLinkConfigVideoOutputComponentChromaBlueGain = /* 'occb' */ 0x6F636362,
|
|
||||||
bmdDeckLinkConfigVideoOutputComponentChromaRedGain = /* 'occr' */ 0x6F636372,
|
|
||||||
bmdDeckLinkConfigVideoOutputCompositeLumaGain = /* 'oilg' */ 0x6F696C67,
|
|
||||||
bmdDeckLinkConfigVideoOutputCompositeChromaGain = /* 'oicg' */ 0x6F696367,
|
|
||||||
bmdDeckLinkConfigVideoOutputSVideoLumaGain = /* 'oslg' */ 0x6F736C67,
|
|
||||||
bmdDeckLinkConfigVideoOutputSVideoChromaGain = /* 'oscg' */ 0x6F736367,
|
|
||||||
bmdDeckLinkConfigDolbyVisionCMVersion = /* 'dvvr' */ 0x64767672,
|
|
||||||
bmdDeckLinkConfigDolbyVisionMasterMinimumNits = /* 'mnnt' */ 0x6D6E6E74,
|
|
||||||
bmdDeckLinkConfigDolbyVisionMasterMaximumNits = /* 'mxnt' */ 0x6D786E74,
|
|
||||||
|
|
||||||
/* Video Input Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigVideoInputScanning = /* 'visc' */ 0x76697363, // Applicable to H264 Pro Recorder only
|
|
||||||
bmdDeckLinkConfigUseDedicatedLTCInput = /* 'dltc' */ 0x646C7463, // Use timecode from LTC input instead of SDI stream
|
|
||||||
bmdDeckLinkConfigSDIInput3DPayloadOverride = /* '3dds' */ 0x33646473,
|
|
||||||
bmdDeckLinkConfigCapture1080pAsPsF = /* 'cfpr' */ 0x63667072,
|
|
||||||
|
|
||||||
/* Video Input Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigVideoInputConnection = /* 'vicn' */ 0x7669636E,
|
|
||||||
bmdDeckLinkConfigAnalogVideoInputFlags = /* 'avif' */ 0x61766966,
|
|
||||||
bmdDeckLinkConfigVideoInputConversionMode = /* 'vicm' */ 0x7669636D,
|
|
||||||
bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame = /* 'pdif' */ 0x70646966,
|
|
||||||
bmdDeckLinkConfigVANCSourceLine1Mapping = /* 'vsl1' */ 0x76736C31,
|
|
||||||
bmdDeckLinkConfigVANCSourceLine2Mapping = /* 'vsl2' */ 0x76736C32,
|
|
||||||
bmdDeckLinkConfigVANCSourceLine3Mapping = /* 'vsl3' */ 0x76736C33,
|
|
||||||
bmdDeckLinkConfigCapturePassThroughMode = /* 'cptm' */ 0x6370746D,
|
|
||||||
bmdDeckLinkConfigCaptureGroup = /* 'cpgr' */ 0x63706772,
|
|
||||||
bmdDeckLinkConfigHANCInputFilter1 = /* 'hif1' */ 0x68696631,
|
|
||||||
bmdDeckLinkConfigHANCInputFilter2 = /* 'hif2' */ 0x68696632,
|
|
||||||
bmdDeckLinkConfigHANCInputFilter3 = /* 'hif3' */ 0x68696633,
|
|
||||||
bmdDeckLinkConfigHANCInputFilter4 = /* 'hif4' */ 0x68696634,
|
|
||||||
|
|
||||||
/* Video Input Floats */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigVideoInputComponentLumaGain = /* 'iclg' */ 0x69636C67,
|
|
||||||
bmdDeckLinkConfigVideoInputComponentChromaBlueGain = /* 'iccb' */ 0x69636362,
|
|
||||||
bmdDeckLinkConfigVideoInputComponentChromaRedGain = /* 'iccr' */ 0x69636372,
|
|
||||||
bmdDeckLinkConfigVideoInputCompositeLumaGain = /* 'iilg' */ 0x69696C67,
|
|
||||||
bmdDeckLinkConfigVideoInputCompositeChromaGain = /* 'iicg' */ 0x69696367,
|
|
||||||
bmdDeckLinkConfigVideoInputSVideoLumaGain = /* 'islg' */ 0x69736C67,
|
|
||||||
bmdDeckLinkConfigVideoInputSVideoChromaGain = /* 'iscg' */ 0x69736367,
|
|
||||||
|
|
||||||
/* Keying Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigInternalKeyingAncillaryDataSource = /* 'ikas' */ 0x696B6173,
|
|
||||||
|
|
||||||
/* Audio Input Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigMicrophonePhantomPower = /* 'mphp' */ 0x6D706870,
|
|
||||||
|
|
||||||
/* Audio Input Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigAudioInputConnection = /* 'aicn' */ 0x6169636E,
|
|
||||||
|
|
||||||
/* Audio Input Floats */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigAnalogAudioInputScaleChannel1 = /* 'ais1' */ 0x61697331,
|
|
||||||
bmdDeckLinkConfigAnalogAudioInputScaleChannel2 = /* 'ais2' */ 0x61697332,
|
|
||||||
bmdDeckLinkConfigAnalogAudioInputScaleChannel3 = /* 'ais3' */ 0x61697333,
|
|
||||||
bmdDeckLinkConfigAnalogAudioInputScaleChannel4 = /* 'ais4' */ 0x61697334,
|
|
||||||
bmdDeckLinkConfigDigitalAudioInputScale = /* 'dais' */ 0x64616973,
|
|
||||||
bmdDeckLinkConfigMicrophoneInputGain = /* 'micg' */ 0x6D696367,
|
|
||||||
bmdDeckLinkConfigAudioOutputXLRDelayFrames = /* 'xdfr' */ 0x78646672,
|
|
||||||
|
|
||||||
/* Audio Output Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigAudioOutputAESAnalogSwitch = /* 'aoaa' */ 0x616F6161,
|
|
||||||
bmdDeckLinkConfigAudioOutputXLRDelayTime = /* 'xdms' */ 0x78646D73,
|
|
||||||
bmdDeckLinkConfigAudioOutputXLRDelayType = /* 'xdty' */ 0x78647479,
|
|
||||||
|
|
||||||
/* Audio Output Floats */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigAnalogAudioOutputScaleChannel1 = /* 'aos1' */ 0x616F7331,
|
|
||||||
bmdDeckLinkConfigAnalogAudioOutputScaleChannel2 = /* 'aos2' */ 0x616F7332,
|
|
||||||
bmdDeckLinkConfigAnalogAudioOutputScaleChannel3 = /* 'aos3' */ 0x616F7333,
|
|
||||||
bmdDeckLinkConfigAnalogAudioOutputScaleChannel4 = /* 'aos4' */ 0x616F7334,
|
|
||||||
bmdDeckLinkConfigDigitalAudioOutputScale = /* 'daos' */ 0x64616F73,
|
|
||||||
bmdDeckLinkConfigHeadphoneVolume = /* 'hvol' */ 0x68766F6C,
|
|
||||||
bmdDeckLinkConfigSpeakerVolume = /* 'svol' */ 0x73766F6C,
|
|
||||||
|
|
||||||
/* Ethernet Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigEthernetPTPFollowerOnly = /* 'PTPf' */ 0x50545066,
|
|
||||||
bmdDeckLinkConfigEthernetPTPUseUDPEncapsulation = /* 'PTPU' */ 0x50545055,
|
|
||||||
bmdDeckLinkConfigEthernetUseManualNMOSRegistry = /* 'nmrp' */ 0x6E6D7270,
|
|
||||||
|
|
||||||
/* Ethernet Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigEthernetPTPPriority1 = /* 'PTP1' */ 0x50545031,
|
|
||||||
bmdDeckLinkConfigEthernetPTPPriority2 = /* 'PTP2' */ 0x50545032,
|
|
||||||
bmdDeckLinkConfigEthernetPTPDomain = /* 'PTPD' */ 0x50545044,
|
|
||||||
bmdDeckLinkConfigEthernetPTPLogAnnounceInterval = /* 'PTPA' */ 0x50545041,
|
|
||||||
|
|
||||||
/* Ethernet Strings */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigEthernetAudioOutputChannelOrder = /* 'caco' */ 0x6361636F,
|
|
||||||
bmdDeckLinkConfigEthernetNMOSRegistryAddress = /* 'nmre' */ 0x6E6D7265,
|
|
||||||
|
|
||||||
/* Parameterized Ethernet Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigParamEthernetUseDHCP = /* 'DHCP' */ 0x44484350,
|
|
||||||
|
|
||||||
/* Parameterized Ethernet Strings */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigParamEthernetStaticLocalIPAddress = /* 'nsip' */ 0x6E736970,
|
|
||||||
bmdDeckLinkConfigParamEthernetStaticSubnetMask = /* 'nssm' */ 0x6E73736D,
|
|
||||||
bmdDeckLinkConfigParamEthernetStaticGatewayIPAddress = /* 'nsgw' */ 0x6E736777,
|
|
||||||
bmdDeckLinkConfigParamEthernetStaticPrimaryDNS = /* 'nspd' */ 0x6E737064,
|
|
||||||
bmdDeckLinkConfigParamEthernetStaticSecondaryDNS = /* 'nssd' */ 0x6E737364,
|
|
||||||
bmdDeckLinkConfigParamEthernetVideoOutputAddress = /* 'noav' */ 0x6E6F6176,
|
|
||||||
bmdDeckLinkConfigParamEthernetAudioOutputAddress = /* 'noaa' */ 0x6E6F6161,
|
|
||||||
bmdDeckLinkConfigParamEthernetAncillaryOutputAddress = /* 'noaA' */ 0x6E6F6141,
|
|
||||||
|
|
||||||
/* Device Information Strings */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigDeviceInformationLabel = /* 'dila' */ 0x64696C61,
|
|
||||||
bmdDeckLinkConfigDeviceInformationSerialNumber = /* 'disn' */ 0x6469736E,
|
|
||||||
bmdDeckLinkConfigDeviceInformationCompany = /* 'dico' */ 0x6469636F,
|
|
||||||
bmdDeckLinkConfigDeviceInformationPhone = /* 'diph' */ 0x64697068,
|
|
||||||
bmdDeckLinkConfigDeviceInformationEmail = /* 'diem' */ 0x6469656D,
|
|
||||||
bmdDeckLinkConfigDeviceInformationDate = /* 'dida' */ 0x64696461,
|
|
||||||
|
|
||||||
/* Deck Control Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigDeckControlConnection = /* 'dcco' */ 0x6463636F,
|
|
||||||
|
|
||||||
/* UI/UX Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigDisplayLanguage = /* 'lang' */ 0x6C616E67
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkEncoderConfigurationID - DeckLink Encoder Configuration ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkEncoderConfigurationID;
|
|
||||||
enum _BMDDeckLinkEncoderConfigurationID {
|
|
||||||
|
|
||||||
/* Video Encoder Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkEncoderConfigPreferredBitDepth = /* 'epbr' */ 0x65706272,
|
|
||||||
bmdDeckLinkEncoderConfigFrameCodingMode = /* 'efcm' */ 0x6566636D,
|
|
||||||
|
|
||||||
/* HEVC/H.265 Encoder Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkEncoderConfigH265TargetBitrate = /* 'htbr' */ 0x68746272,
|
|
||||||
|
|
||||||
/* DNxHR/DNxHD Compression ID */
|
|
||||||
|
|
||||||
bmdDeckLinkEncoderConfigDNxHRCompressionID = /* 'dcid' */ 0x64636964,
|
|
||||||
|
|
||||||
/* DNxHR/DNxHD Level */
|
|
||||||
|
|
||||||
bmdDeckLinkEncoderConfigDNxHRLevel = /* 'dlev' */ 0x646C6576,
|
|
||||||
|
|
||||||
/* Encoded Sample Decriptions */
|
|
||||||
|
|
||||||
bmdDeckLinkEncoderConfigMPEG4SampleDescription = /* 'stsE' */ 0x73747345, // Full MPEG4 sample description (aka SampleEntry of an 'stsd' atom-box). Useful for MediaFoundation, QuickTime, MKV and more
|
|
||||||
bmdDeckLinkEncoderConfigMPEG4CodecSpecificDesc = /* 'esds' */ 0x65736473 // Sample description extensions only (atom stream, each with size and fourCC header). Useful for AVFoundation, VideoToolbox, MKV and more
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkConfiguration;
|
|
||||||
class IDeckLinkEncoderConfiguration;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkConfiguration - DeckLink Configuration interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkConfiguration : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool* value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t* value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double* value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char* value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char** value) = 0;
|
|
||||||
virtual HRESULT SetFlagWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlagWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* out */ bool* value) = 0;
|
|
||||||
virtual HRESULT SetIntWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetIntWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* out */ int64_t* value) = 0;
|
|
||||||
virtual HRESULT SetFloatWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloatWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* out */ double* value) = 0;
|
|
||||||
virtual HRESULT SetStringWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* in */ const char* value) = 0;
|
|
||||||
virtual HRESULT GetStringWithParam (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ uint64_t param, /* out */ const char** value) = 0;
|
|
||||||
virtual HRESULT WriteConfigurationToPreferences (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkConfiguration () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkEncoderConfiguration - DeckLink Encoder Configuration interface. Obtained from IDeckLinkEncoderInput */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkEncoderConfiguration : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ bool* value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ int64_t* value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ double* value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ const char* value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ const char** value) = 0;
|
|
||||||
virtual HRESULT GetBytes (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ void* buffer /* optional */, /* in, out */ uint32_t* bufferSize) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkEncoderConfiguration () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__cplusplus) */
|
|
||||||
#endif /* defined(BMD_DECKLINKAPICONFIGURATION_H) */
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2017 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPICONFIGURATION_v10_11_H
|
|
||||||
#define BMD_DECKLINKAPICONFIGURATION_v10_11_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPIConfiguration.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkConfiguration_v10_11 = /* EF90380B-4AE5-4346-9077-E288E149F129 */ {0xEF,0x90,0x38,0x0B,0x4A,0xE5,0x43,0x46,0x90,0x77,0xE2,0x88,0xE1,0x49,0xF1,0x29};
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkConfigurationID_v10_11 - DeckLink Configuration ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkConfigurationID_v10_11;
|
|
||||||
enum _BMDDeckLinkConfigurationID_v10_11 {
|
|
||||||
|
|
||||||
/* Video Input/Output Integers */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigDuplexMode_v10_11 = /* 'dupx' */ 0x64757078,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkConfiguration_v10_11;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkConfiguration_v10_11 - DeckLink Configuration interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkConfiguration_v10_11 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char *value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char **value) = 0;
|
|
||||||
virtual HRESULT WriteConfigurationToPreferences (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkConfiguration_v10_11 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPICONFIGURATION_v10_11_H) */
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2014 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPICONFIGURATION_v10_2_H
|
|
||||||
#define BMD_DECKLINKAPICONFIGURATION_v10_2_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPIConfiguration.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkConfiguration_v10_2 = /* C679A35B-610C-4D09-B748-1D0478100FC0 */ {0xC6,0x79,0xA3,0x5B,0x61,0x0C,0x4D,0x09,0xB7,0x48,0x1D,0x04,0x78,0x10,0x0F,0xC0};
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkConfiguration_v10_2;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkConfiguration_v10_2 - DeckLink Configuration interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkConfiguration_v10_2 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char *value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char **value) = 0;
|
|
||||||
virtual HRESULT WriteConfigurationToPreferences (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkConfiguration_v10_2 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPICONFIGURATION_v10_2_H) */
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
/* -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 (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPICONFIGURATION_v10_4_H
|
|
||||||
#define BMD_DECKLINKAPICONFIGURATION_v10_4_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPIConfiguration.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkConfiguration_v10_4 = /* 1E69FCF6-4203-4936-8076-2A9F4CFD50CB */ {0x1E,0x69,0xFC,0xF6,0x42,0x03,0x49,0x36,0x80,0x76,0x2A,0x9F,0x4C,0xFD,0x50,0xCB};
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkConfiguration_v10_4;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkConfiguration_v10_4 - DeckLink Configuration interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkConfiguration_v10_4 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char *value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char **value) = 0;
|
|
||||||
virtual HRESULT WriteConfigurationToPreferences (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkConfiguration_v10_4 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPICONFIGURATION_v10_4_H) */
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
/* -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 (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPICONFIGURATION_v10_5_H
|
|
||||||
#define BMD_DECKLINKAPICONFIGURATION_v10_5_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPIConfiguration.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkEncoderConfiguration_v10_5 = /* 67455668-0848-45DF-8D8E-350A77C9A028 */ {0x67,0x45,0x56,0x68,0x08,0x48,0x45,0xDF,0x8D,0x8E,0x35,0x0A,0x77,0xC9,0xA0,0x28};
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkEncoderConfiguration_v10_5;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkEncoderConfiguration_v10_5 - DeckLink Encoder Configuration interface. Obtained from IDeckLinkEncoderInput */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkEncoderConfiguration_v10_5 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* in */ const char *value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkEncoderConfigurationID cfgID, /* out */ const char **value) = 0;
|
|
||||||
virtual HRESULT GetDecoderConfigurationInfo (/* out */ void *buffer, /* in */ long bufferSize, /* out */ long *returnedSize) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkEncoderConfiguration_v10_5 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPICONFIGURATION_v10_5_H) */
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2017 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPICONFIGURATION_v10_9_H
|
|
||||||
#define BMD_DECKLINKAPICONFIGURATION_v10_9_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPIConfiguration.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkConfiguration_v10_9 = /* CB71734A-FE37-4E8D-8E13-802133A1C3F2 */ {0xCB,0x71,0x73,0x4A,0xFE,0x37,0x4E,0x8D,0x8E,0x13,0x80,0x21,0x33,0xA1,0xC3,0xF2};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkConfiguration_v10_9;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkConfiguration_v10_9 - DeckLink Configuration interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkConfiguration_v10_9 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char *value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char **value) = 0;
|
|
||||||
virtual HRESULT WriteConfigurationToPreferences (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkConfiguration_v10_9 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPICONFIGURATION_v10_9_H) */
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2026 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "DeckLinkAPIConfiguration.h"
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkConfigurationID_v15_3_1 - DeckLink Configuration ID */
|
|
||||||
typedef uint32_t BMDDeckLinkConfigurationID_v15_3_1;
|
|
||||||
enum _BMDDeckLinkConfigurationID_v15_3_1
|
|
||||||
{
|
|
||||||
/* Network Flags */
|
|
||||||
bmdDeckLinkConfigEthernetUseDHCP_v15_3_1 = /* 'DHCP' */ 0x44484350,
|
|
||||||
|
|
||||||
/* Network Strings */
|
|
||||||
bmdDeckLinkConfigEthernetStaticLocalIPAddress_v15_3_1 = /* 'nsip' */ 0x6E736970,
|
|
||||||
bmdDeckLinkConfigEthernetStaticSubnetMask_v15_3_1 = /* 'nssm' */ 0x6E73736D,
|
|
||||||
bmdDeckLinkConfigEthernetStaticGatewayIPAddress_v15_3_1 = /* 'nsgw' */ 0x6E736777,
|
|
||||||
bmdDeckLinkConfigEthernetStaticPrimaryDNS_v15_3_1 = /* 'nspd' */ 0x6E737064,
|
|
||||||
bmdDeckLinkConfigEthernetStaticSecondaryDNS_v15_3_1 = /* 'nssd' */ 0x6E737364,
|
|
||||||
bmdDeckLinkConfigEthernetVideoOutputAddress_v15_3_1 = /* 'noav' */ 0x6E6F6176,
|
|
||||||
bmdDeckLinkConfigEthernetAudioOutputAddress_v15_3_1 = /* 'noaa' */ 0x6E6F6161,
|
|
||||||
bmdDeckLinkConfigEthernetAncillaryOutputAddress_v15_3_1 = /* 'noaA' */ 0x6E6F6141,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkConfiguration_v15_3_1 = /* 912F634B-2D4E-40A4-8AAB-8D80B73F1289 */ {0x91,0x2F,0x63,0x4B,0x2D,0x4E,0x40,0xA4,0x8A,0xAB,0x8D,0x80,0xB7,0x3F,0x12,0x89};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkConfiguration_v15_3_1 - DeckLink Configuration interface */
|
|
||||||
|
|
||||||
class IDeckLinkConfiguration_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char* value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char* *value) = 0;
|
|
||||||
virtual HRESULT WriteConfigurationToPreferences (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkConfiguration_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
@ -1,227 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2026 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-
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* -- AUTOMATICALLY GENERATED - DO NOT EDIT ---
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIDECKCONTROL_H
|
|
||||||
#define BMD_DECKLINKAPIDECKCONTROL_H
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef BMD_CONST
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define BMD_CONST __declspec(selectany) static const
|
|
||||||
#else
|
|
||||||
#define BMD_CONST static const
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BMD_PUBLIC
|
|
||||||
#define BMD_PUBLIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkDeckControlStatusCallback = /* 53436FFB-B434-4906-BADC-AE3060FFE8EF */ { 0x53,0x43,0x6F,0xFB,0xB4,0x34,0x49,0x06,0xBA,0xDC,0xAE,0x30,0x60,0xFF,0xE8,0xEF };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkDeckControl = /* 8E1C3ACE-19C7-4E00-8B92-D80431D958BE */ { 0x8E,0x1C,0x3A,0xCE,0x19,0xC7,0x4E,0x00,0x8B,0x92,0xD8,0x04,0x31,0xD9,0x58,0xBE };
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlMode - DeckControl mode */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlMode;
|
|
||||||
enum _BMDDeckControlMode {
|
|
||||||
bmdDeckControlNotOpened = /* 'ntop' */ 0x6E746F70,
|
|
||||||
bmdDeckControlVTRControlMode = /* 'vtrc' */ 0x76747263,
|
|
||||||
bmdDeckControlExportMode = /* 'expm' */ 0x6578706D,
|
|
||||||
bmdDeckControlCaptureMode = /* 'capm' */ 0x6361706D
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlEvent - DeckControl event */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlEvent;
|
|
||||||
enum _BMDDeckControlEvent {
|
|
||||||
bmdDeckControlAbortedEvent = /* 'abte' */ 0x61627465, // This event is triggered when a capture or edit-to-tape operation is aborted.
|
|
||||||
|
|
||||||
/* Export-To-Tape events */
|
|
||||||
|
|
||||||
bmdDeckControlPrepareForExportEvent = /* 'pfee' */ 0x70666565, // This event is triggered a few frames before reaching the in-point. IDeckLinkInput::StartScheduledPlayback should be called at this point.
|
|
||||||
bmdDeckControlExportCompleteEvent = /* 'exce' */ 0x65786365, // This event is triggered a few frames after reaching the out-point. At this point, it is safe to stop playback. Upon reception of this event the deck's control mode is set back to bmdDeckControlVTRControlMode.
|
|
||||||
|
|
||||||
/* Capture events */
|
|
||||||
|
|
||||||
bmdDeckControlPrepareForCaptureEvent = /* 'pfce' */ 0x70666365, // This event is triggered a few frames before reaching the in-point. The serial timecode attached to IDeckLinkVideoInputFrames is now valid.
|
|
||||||
bmdDeckControlCaptureCompleteEvent = /* 'ccev' */ 0x63636576 // This event is triggered a few frames after reaching the out-point. Upon reception of this event the deck's control mode is set back to bmdDeckControlVTRControlMode.
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlVTRControlState - VTR Control state */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlVTRControlState;
|
|
||||||
enum _BMDDeckControlVTRControlState {
|
|
||||||
bmdDeckControlNotInVTRControlMode = /* 'nvcm' */ 0x6E76636D,
|
|
||||||
bmdDeckControlVTRControlPlaying = /* 'vtrp' */ 0x76747270,
|
|
||||||
bmdDeckControlVTRControlRecording = /* 'vtrr' */ 0x76747272,
|
|
||||||
bmdDeckControlVTRControlStill = /* 'vtra' */ 0x76747261,
|
|
||||||
bmdDeckControlVTRControlShuttleForward = /* 'vtsf' */ 0x76747366,
|
|
||||||
bmdDeckControlVTRControlShuttleReverse = /* 'vtsr' */ 0x76747372,
|
|
||||||
bmdDeckControlVTRControlJogForward = /* 'vtjf' */ 0x76746A66,
|
|
||||||
bmdDeckControlVTRControlJogReverse = /* 'vtjr' */ 0x76746A72,
|
|
||||||
bmdDeckControlVTRControlStopped = /* 'vtro' */ 0x7674726F
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlStatusFlags - Deck Control status flags */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlStatusFlags;
|
|
||||||
enum _BMDDeckControlStatusFlags {
|
|
||||||
bmdDeckControlStatusDeckConnected = 1 << 0,
|
|
||||||
bmdDeckControlStatusRemoteMode = 1 << 1,
|
|
||||||
bmdDeckControlStatusRecordInhibited = 1 << 2,
|
|
||||||
bmdDeckControlStatusCassetteOut = 1 << 3
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlExportModeOpsFlags - Export mode flags */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlExportModeOpsFlags;
|
|
||||||
enum _BMDDeckControlExportModeOpsFlags {
|
|
||||||
bmdDeckControlExportModeInsertVideo = 1 << 0,
|
|
||||||
bmdDeckControlExportModeInsertAudio1 = 1 << 1,
|
|
||||||
bmdDeckControlExportModeInsertAudio2 = 1 << 2,
|
|
||||||
bmdDeckControlExportModeInsertAudio3 = 1 << 3,
|
|
||||||
bmdDeckControlExportModeInsertAudio4 = 1 << 4,
|
|
||||||
bmdDeckControlExportModeInsertAudio5 = 1 << 5,
|
|
||||||
bmdDeckControlExportModeInsertAudio6 = 1 << 6,
|
|
||||||
bmdDeckControlExportModeInsertAudio7 = 1 << 7,
|
|
||||||
bmdDeckControlExportModeInsertAudio8 = 1 << 8,
|
|
||||||
bmdDeckControlExportModeInsertAudio9 = 1 << 9,
|
|
||||||
bmdDeckControlExportModeInsertAudio10 = 1 << 10,
|
|
||||||
bmdDeckControlExportModeInsertAudio11 = 1 << 11,
|
|
||||||
bmdDeckControlExportModeInsertAudio12 = 1 << 12,
|
|
||||||
bmdDeckControlExportModeInsertTimeCode = 1 << 13,
|
|
||||||
bmdDeckControlExportModeInsertAssemble = 1 << 14,
|
|
||||||
bmdDeckControlExportModeInsertPreview = 1 << 15,
|
|
||||||
bmdDeckControlUseManualExport = 1 << 16
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlError - Deck Control error */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlError;
|
|
||||||
enum _BMDDeckControlError {
|
|
||||||
bmdDeckControlNoError = /* 'noer' */ 0x6E6F6572,
|
|
||||||
bmdDeckControlModeError = /* 'moer' */ 0x6D6F6572,
|
|
||||||
bmdDeckControlMissedInPointError = /* 'mier' */ 0x6D696572,
|
|
||||||
bmdDeckControlDeckTimeoutError = /* 'dter' */ 0x64746572,
|
|
||||||
bmdDeckControlCommandFailedError = /* 'cfer' */ 0x63666572,
|
|
||||||
bmdDeckControlDeviceAlreadyOpenedError = /* 'dalo' */ 0x64616C6F,
|
|
||||||
bmdDeckControlFailedToOpenDeviceError = /* 'fder' */ 0x66646572,
|
|
||||||
bmdDeckControlInLocalModeError = /* 'lmer' */ 0x6C6D6572,
|
|
||||||
bmdDeckControlEndOfTapeError = /* 'eter' */ 0x65746572,
|
|
||||||
bmdDeckControlUserAbortError = /* 'uaer' */ 0x75616572,
|
|
||||||
bmdDeckControlNoTapeInDeckError = /* 'nter' */ 0x6E746572,
|
|
||||||
bmdDeckControlNoVideoFromCardError = /* 'nvfc' */ 0x6E766663,
|
|
||||||
bmdDeckControlNoCommunicationError = /* 'ncom' */ 0x6E636F6D,
|
|
||||||
bmdDeckControlBufferTooSmallError = /* 'btsm' */ 0x6274736D,
|
|
||||||
bmdDeckControlBadChecksumError = /* 'chks' */ 0x63686B73,
|
|
||||||
bmdDeckControlUnknownError = /* 'uner' */ 0x756E6572
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkDeckControlStatusCallback;
|
|
||||||
class IDeckLinkDeckControl;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkDeckControlStatusCallback - Deck control state change callback. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkDeckControlStatusCallback : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT TimecodeUpdate (/* in */ BMDTimecodeBCD currentTimecode) = 0;
|
|
||||||
virtual HRESULT VTRControlStateChanged (/* in */ BMDDeckControlVTRControlState newState, /* in */ BMDDeckControlError error) = 0;
|
|
||||||
virtual HRESULT DeckControlEventReceived (/* in */ BMDDeckControlEvent event, /* in */ BMDDeckControlError error) = 0;
|
|
||||||
virtual HRESULT DeckControlStatusChanged (/* in */ BMDDeckControlStatusFlags flags, /* in */ uint32_t mask) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkDeckControlStatusCallback () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkDeckControl - Deck Control main interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkDeckControl : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT Open (/* in */ BMDTimeScale timeScale, /* in */ BMDTimeValue timeValue, /* in */ bool timecodeIsDropFrame, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Close (/* in */ bool standbyOn) = 0;
|
|
||||||
virtual HRESULT GetCurrentState (/* out */ BMDDeckControlMode* mode, /* out */ BMDDeckControlVTRControlState* vtrControlState, /* out */ BMDDeckControlStatusFlags* flags) = 0;
|
|
||||||
virtual HRESULT SetStandby (/* in */ bool standbyOn) = 0;
|
|
||||||
virtual HRESULT SendCommand (/* in */ uint8_t* inBuffer, /* in */ uint32_t inBufferSize, /* out */ uint8_t* outBuffer, /* out */ uint32_t* outDataSize, /* in */ uint32_t outBufferSize, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Play (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Stop (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT TogglePlayStop (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Eject (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT GoToTimecode (/* in */ BMDTimecodeBCD timecode, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT FastForward (/* in */ bool viewTape, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Rewind (/* in */ bool viewTape, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT StepForward (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT StepBack (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Jog (/* in */ double rate, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Shuttle (/* in */ double rate, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT GetTimecodeString (/* out */ const char** currentTimeCode, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT GetTimecode (/* out */ IDeckLinkTimecode** currentTimecode, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT GetTimecodeBCD (/* out */ BMDTimecodeBCD* currentTimecode, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT SetPreroll (/* in */ uint32_t prerollSeconds) = 0;
|
|
||||||
virtual HRESULT GetPreroll (/* out */ uint32_t* prerollSeconds) = 0;
|
|
||||||
virtual HRESULT SetExportOffset (/* in */ int32_t exportOffsetFields) = 0;
|
|
||||||
virtual HRESULT GetExportOffset (/* out */ int32_t* exportOffsetFields) = 0;
|
|
||||||
virtual HRESULT GetManualExportOffset (/* out */ int32_t* deckManualExportOffsetFields) = 0;
|
|
||||||
virtual HRESULT SetCaptureOffset (/* in */ int32_t captureOffsetFields) = 0;
|
|
||||||
virtual HRESULT GetCaptureOffset (/* out */ int32_t* captureOffsetFields) = 0;
|
|
||||||
virtual HRESULT StartExport (/* in */ BMDTimecodeBCD inTimecode, /* in */ BMDTimecodeBCD outTimecode, /* in */ BMDDeckControlExportModeOpsFlags exportModeOps, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT StartCapture (/* in */ bool useVITC, /* in */ BMDTimecodeBCD inTimecode, /* in */ BMDTimecodeBCD outTimecode, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT GetDeviceID (/* out */ uint16_t* deviceId, /* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT Abort (void) = 0;
|
|
||||||
virtual HRESULT CrashRecordStart (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT CrashRecordStop (/* out */ BMDDeckControlError* error) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkDeckControlStatusCallback* callback) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkDeckControl () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__cplusplus) */
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIDECKCONTROL_H) */
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2026 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-
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* -- AUTOMATICALLY GENERATED - DO NOT EDIT ---
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIDISCOVERY_H
|
|
||||||
#define BMD_DECKLINKAPIDISCOVERY_H
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef BMD_CONST
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define BMD_CONST __declspec(selectany) static const
|
|
||||||
#else
|
|
||||||
#define BMD_CONST static const
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BMD_PUBLIC
|
|
||||||
#define BMD_PUBLIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLink = /* C418FBDD-0587-48ED-8FE5-640F0A14AF91 */ { 0xC4,0x18,0xFB,0xDD,0x05,0x87,0x48,0xED,0x8F,0xE5,0x64,0x0F,0x0A,0x14,0xAF,0x91 };
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLink;
|
|
||||||
|
|
||||||
/* Interface IDeckLink - Represents a DeckLink device */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLink : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetModelName (/* out */ const char** modelName) = 0;
|
|
||||||
virtual HRESULT GetDisplayName (/* out */ const char** displayName) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLink () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__cplusplus) */
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIDISCOVERY_H) */
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2009 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
#define kDeckLinkAPI_Name "libDeckLinkAPI.so"
|
|
||||||
#define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so"
|
|
||||||
|
|
||||||
typedef IDeckLinkIterator* (*CreateIteratorFunc)(void);
|
|
||||||
typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGL3ScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkVideoConversion* (*CreateVideoConversionInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkDiscovery* (*CreateDeckLinkDiscoveryInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkVideoFrameAncillaryPackets* (*CreateVideoFrameAncillaryPacketsInstanceFunc)(void);
|
|
||||||
|
|
||||||
static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static bool gLoadedDeckLinkAPI = false;
|
|
||||||
|
|
||||||
static CreateIteratorFunc gCreateIteratorFunc = NULL;
|
|
||||||
static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL;
|
|
||||||
static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL;
|
|
||||||
static CreateOpenGL3ScreenPreviewHelperFunc gCreateOpenGL3PreviewFunc = NULL;
|
|
||||||
static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL;
|
|
||||||
static CreateDeckLinkDiscoveryInstanceFunc gCreateDeckLinkDiscoveryFunc = NULL;
|
|
||||||
static CreateVideoFrameAncillaryPacketsInstanceFunc gCreateVideoFrameAncillaryPacketsFunc = NULL;
|
|
||||||
|
|
||||||
static void InitDeckLinkAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gLoadedDeckLinkAPI = true;
|
|
||||||
|
|
||||||
gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0004");
|
|
||||||
if (!gCreateIteratorFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateAPIInformationFunc = (CreateAPIInformationFunc)dlsym(libraryHandle, "CreateDeckLinkAPIInformationInstance_0001");
|
|
||||||
if (!gCreateAPIInformationFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0003");
|
|
||||||
if (!gCreateVideoConversionFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateDeckLinkDiscoveryFunc = (CreateDeckLinkDiscoveryInstanceFunc)dlsym(libraryHandle, "CreateDeckLinkDiscoveryInstance_0003");
|
|
||||||
if (!gCreateDeckLinkDiscoveryFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoFrameAncillaryPacketsFunc = (CreateVideoFrameAncillaryPacketsInstanceFunc)dlsym(libraryHandle, "CreateVideoFrameAncillaryPacketsInstance_0002");
|
|
||||||
if (!gCreateVideoFrameAncillaryPacketsFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InitDeckLinkPreviewAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0002");
|
|
||||||
if (!gCreateOpenGLPreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateOpenGL3PreviewFunc = (CreateOpenGL3ScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGL3ScreenPreviewHelper_0002");
|
|
||||||
if (!gCreateOpenGL3PreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDeckLinkAPIPresent (void)
|
|
||||||
{
|
|
||||||
// If the DeckLink API dynamic library was successfully loaded, return this knowledge to the caller
|
|
||||||
return gLoadedDeckLinkAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkIterator* CreateDeckLinkIteratorInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateIteratorFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateIteratorFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateAPIInformationFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateAPIInformationFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGLPreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGLPreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGL3ScreenPreviewHelper (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGL3PreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGL3PreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoConversion* CreateVideoConversionInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoConversionFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoConversionFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateDeckLinkDiscoveryFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateDeckLinkDiscoveryFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoFrameAncillaryPacketsFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoFrameAncillaryPacketsFunc();
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2019 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_v10_11.h"
|
|
||||||
|
|
||||||
#define kDeckLinkAPI_Name "libDeckLinkAPI.so"
|
|
||||||
#define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so"
|
|
||||||
|
|
||||||
typedef IDeckLinkIterator* (*CreateIteratorFunc)(void);
|
|
||||||
typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkVideoConversion* (*CreateVideoConversionInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkDiscovery* (*CreateDeckLinkDiscoveryInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkVideoFrameAncillaryPackets* (*CreateVideoFrameAncillaryPacketsInstanceFunc)(void);
|
|
||||||
|
|
||||||
static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static bool gLoadedDeckLinkAPI = false;
|
|
||||||
|
|
||||||
static CreateIteratorFunc gCreateIteratorFunc = NULL;
|
|
||||||
static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL;
|
|
||||||
static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL;
|
|
||||||
static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL;
|
|
||||||
static CreateDeckLinkDiscoveryInstanceFunc gCreateDeckLinkDiscoveryFunc = NULL;
|
|
||||||
static CreateVideoFrameAncillaryPacketsInstanceFunc gCreateVideoFrameAncillaryPacketsFunc = NULL;
|
|
||||||
|
|
||||||
static void InitDeckLinkAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gLoadedDeckLinkAPI = true;
|
|
||||||
|
|
||||||
gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0003");
|
|
||||||
if (!gCreateIteratorFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateAPIInformationFunc = (CreateAPIInformationFunc)dlsym(libraryHandle, "CreateDeckLinkAPIInformationInstance_0001");
|
|
||||||
if (!gCreateAPIInformationFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0001");
|
|
||||||
if (!gCreateVideoConversionFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateDeckLinkDiscoveryFunc = (CreateDeckLinkDiscoveryInstanceFunc)dlsym(libraryHandle, "CreateDeckLinkDiscoveryInstance_0002");
|
|
||||||
if (!gCreateDeckLinkDiscoveryFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoFrameAncillaryPacketsFunc = (CreateVideoFrameAncillaryPacketsInstanceFunc)dlsym(libraryHandle, "CreateVideoFrameAncillaryPacketsInstance_0001");
|
|
||||||
if (!gCreateVideoFrameAncillaryPacketsFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InitDeckLinkPreviewAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0001");
|
|
||||||
if (!gCreateOpenGLPreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDeckLinkAPIPresent_v10_11 (void)
|
|
||||||
{
|
|
||||||
// If the DeckLink API dynamic library was successfully loaded, return this knowledge to the caller
|
|
||||||
return gLoadedDeckLinkAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkIterator* CreateDeckLinkIteratorInstance_v10_11 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateIteratorFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateIteratorFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance_v10_11 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateAPIInformationFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateAPIInformationFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper_v10_11 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGLPreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGLPreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoConversion* CreateVideoConversionInstance_v10_11 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoConversionFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoConversionFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance_v10_11 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateDeckLinkDiscoveryFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateDeckLinkDiscoveryFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance_v10_11 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoFrameAncillaryPacketsFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoFrameAncillaryPacketsFunc();
|
|
||||||
}
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2009 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
#define kDeckLinkAPI_Name "libDeckLinkAPI.so"
|
|
||||||
#define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so"
|
|
||||||
|
|
||||||
typedef IDeckLinkIterator* (*CreateIteratorFunc)(void);
|
|
||||||
typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkVideoConversion* (*CreateVideoConversionInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkDiscovery* (*CreateDeckLinkDiscoveryInstanceFunc)(void);
|
|
||||||
|
|
||||||
static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static bool gLoadedDeckLinkAPI = false;
|
|
||||||
|
|
||||||
static CreateIteratorFunc gCreateIteratorFunc = NULL;
|
|
||||||
static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL;
|
|
||||||
static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL;
|
|
||||||
static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL;
|
|
||||||
static CreateDeckLinkDiscoveryInstanceFunc gCreateDeckLinkDiscoveryFunc = NULL;
|
|
||||||
|
|
||||||
static void InitDeckLinkAPI(void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW | RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gLoadedDeckLinkAPI = true;
|
|
||||||
|
|
||||||
gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0002");
|
|
||||||
if (!gCreateIteratorFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateAPIInformationFunc = (CreateAPIInformationFunc)dlsym(libraryHandle, "CreateDeckLinkAPIInformationInstance_0001");
|
|
||||||
if (!gCreateAPIInformationFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0001");
|
|
||||||
if (!gCreateVideoConversionFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateDeckLinkDiscoveryFunc = (CreateDeckLinkDiscoveryInstanceFunc)dlsym(libraryHandle, "CreateDeckLinkDiscoveryInstance_0001");
|
|
||||||
if (!gCreateDeckLinkDiscoveryFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InitDeckLinkPreviewAPI(void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW | RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0001");
|
|
||||||
if (!gCreateOpenGLPreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDeckLinkAPIPresent(void)
|
|
||||||
{
|
|
||||||
// If the DeckLink API dynamic library was successfully loaded, return this knowledge to the caller
|
|
||||||
return gLoadedDeckLinkAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkIterator* CreateDeckLinkIteratorInstance(void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateIteratorFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateIteratorFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance(void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateAPIInformationFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateAPIInformationFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper(void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGLPreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGLPreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoConversion* CreateVideoConversionInstance(void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoConversionFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoConversionFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance(void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateDeckLinkDiscoveryFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateDeckLinkDiscoveryFunc();
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2009 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_v14_2_1.h"
|
|
||||||
|
|
||||||
#define kDeckLinkAPI_Name "libDeckLinkAPI.so"
|
|
||||||
#define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so"
|
|
||||||
|
|
||||||
typedef IDeckLinkIterator* (*CreateIteratorFunc)(void);
|
|
||||||
typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper_v14_2_1* (*CreateOpenGLScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper_v14_2_1* (*CreateOpenGL3ScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkVideoConversion_v14_2_1* (*CreateVideoConversionInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkDiscovery* (*CreateDeckLinkDiscoveryInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkVideoFrameAncillaryPackets* (*CreateVideoFrameAncillaryPacketsInstanceFunc)(void);
|
|
||||||
|
|
||||||
static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static bool gLoadedDeckLinkAPI = false;
|
|
||||||
|
|
||||||
static CreateIteratorFunc gCreateIteratorFunc = NULL;
|
|
||||||
static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL;
|
|
||||||
static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL;
|
|
||||||
static CreateOpenGL3ScreenPreviewHelperFunc gCreateOpenGL3PreviewFunc = NULL;
|
|
||||||
static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL;
|
|
||||||
static CreateDeckLinkDiscoveryInstanceFunc gCreateDeckLinkDiscoveryFunc = NULL;
|
|
||||||
static CreateVideoFrameAncillaryPacketsInstanceFunc gCreateVideoFrameAncillaryPacketsFunc = NULL;
|
|
||||||
|
|
||||||
static void InitDeckLinkAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gLoadedDeckLinkAPI = true;
|
|
||||||
|
|
||||||
gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0004");
|
|
||||||
if (!gCreateIteratorFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateAPIInformationFunc = (CreateAPIInformationFunc)dlsym(libraryHandle, "CreateDeckLinkAPIInformationInstance_0001");
|
|
||||||
if (!gCreateAPIInformationFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0001");
|
|
||||||
if (!gCreateVideoConversionFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateDeckLinkDiscoveryFunc = (CreateDeckLinkDiscoveryInstanceFunc)dlsym(libraryHandle, "CreateDeckLinkDiscoveryInstance_0003");
|
|
||||||
if (!gCreateDeckLinkDiscoveryFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoFrameAncillaryPacketsFunc = (CreateVideoFrameAncillaryPacketsInstanceFunc)dlsym(libraryHandle, "CreateVideoFrameAncillaryPacketsInstance_0001");
|
|
||||||
if (!gCreateVideoFrameAncillaryPacketsFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InitDeckLinkPreviewAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0001");
|
|
||||||
if (!gCreateOpenGLPreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateOpenGL3PreviewFunc = (CreateOpenGL3ScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGL3ScreenPreviewHelper_0001");
|
|
||||||
if (!gCreateOpenGL3PreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDeckLinkAPIPresent_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
// If the DeckLink API dynamic library was successfully loaded, return this knowledge to the caller
|
|
||||||
return gLoadedDeckLinkAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkIterator* CreateDeckLinkIteratorInstance_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateIteratorFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateIteratorFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateAPIInformationFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateAPIInformationFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper_v14_2_1* CreateOpenGLScreenPreviewHelper_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGLPreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGLPreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper_v14_2_1* CreateOpenGL3ScreenPreviewHelper_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGL3PreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGL3PreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoConversion_v14_2_1* CreateVideoConversionInstance_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoConversionFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoConversionFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateDeckLinkDiscoveryFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateDeckLinkDiscoveryFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance_v14_2_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoFrameAncillaryPacketsFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoFrameAncillaryPacketsFunc();
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2009 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_v15_2.h"
|
|
||||||
|
|
||||||
#define kDeckLinkAPI_Name "libDeckLinkAPI.so"
|
|
||||||
#define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so"
|
|
||||||
|
|
||||||
typedef IDeckLinkIterator* (*CreateIteratorFunc)(void);
|
|
||||||
typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGL3ScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkVideoConversion* (*CreateVideoConversionInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkDiscovery* (*CreateDeckLinkDiscoveryInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkVideoFrameAncillaryPackets_v15_2* (*CreateVideoFrameAncillaryPacketsInstanceFunc)(void);
|
|
||||||
|
|
||||||
static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static bool gLoadedDeckLinkAPI = false;
|
|
||||||
|
|
||||||
static CreateIteratorFunc gCreateIteratorFunc = NULL;
|
|
||||||
static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL;
|
|
||||||
static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL;
|
|
||||||
static CreateOpenGL3ScreenPreviewHelperFunc gCreateOpenGL3PreviewFunc = NULL;
|
|
||||||
static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL;
|
|
||||||
static CreateDeckLinkDiscoveryInstanceFunc gCreateDeckLinkDiscoveryFunc = NULL;
|
|
||||||
static CreateVideoFrameAncillaryPacketsInstanceFunc gCreateVideoFrameAncillaryPacketsFunc = NULL;
|
|
||||||
|
|
||||||
static void InitDeckLinkAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gLoadedDeckLinkAPI = true;
|
|
||||||
|
|
||||||
gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0004");
|
|
||||||
if (!gCreateIteratorFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateAPIInformationFunc = (CreateAPIInformationFunc)dlsym(libraryHandle, "CreateDeckLinkAPIInformationInstance_0001");
|
|
||||||
if (!gCreateAPIInformationFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0002");
|
|
||||||
if (!gCreateVideoConversionFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateDeckLinkDiscoveryFunc = (CreateDeckLinkDiscoveryInstanceFunc)dlsym(libraryHandle, "CreateDeckLinkDiscoveryInstance_0003");
|
|
||||||
if (!gCreateDeckLinkDiscoveryFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoFrameAncillaryPacketsFunc = (CreateVideoFrameAncillaryPacketsInstanceFunc)dlsym(libraryHandle, "CreateVideoFrameAncillaryPacketsInstance_0001");
|
|
||||||
if (!gCreateVideoFrameAncillaryPacketsFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InitDeckLinkPreviewAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0002");
|
|
||||||
if (!gCreateOpenGLPreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateOpenGL3PreviewFunc = (CreateOpenGL3ScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGL3ScreenPreviewHelper_0002");
|
|
||||||
if (!gCreateOpenGL3PreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDeckLinkAPIPresent_v15_2 (void)
|
|
||||||
{
|
|
||||||
// If the DeckLink API dynamic library was successfully loaded, return this knowledge to the caller
|
|
||||||
return gLoadedDeckLinkAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkIterator* CreateDeckLinkIteratorInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateIteratorFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateIteratorFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateAPIInformationFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateAPIInformationFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGLPreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGLPreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGL3ScreenPreviewHelper (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGL3PreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGL3PreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoConversion* CreateVideoConversionInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoConversionFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoConversionFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateDeckLinkDiscoveryFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateDeckLinkDiscoveryFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoFrameAncillaryPackets_v15_2* CreateVideoFrameAncillaryPacketsInstance_v15_2 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoFrameAncillaryPacketsFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoFrameAncillaryPacketsFunc();
|
|
||||||
}
|
|
||||||
|
|
@ -1,188 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2009 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
**/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_v15_3_1.h"
|
|
||||||
|
|
||||||
#define kDeckLinkAPI_Name "libDeckLinkAPI.so"
|
|
||||||
#define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so"
|
|
||||||
|
|
||||||
typedef IDeckLinkIterator* (*CreateIteratorFunc)(void);
|
|
||||||
typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGL3ScreenPreviewHelperFunc)(void);
|
|
||||||
typedef IDeckLinkVideoConversion_v15_3_1* (*CreateVideoConversionInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkDiscovery* (*CreateDeckLinkDiscoveryInstanceFunc)(void);
|
|
||||||
typedef IDeckLinkVideoFrameAncillaryPackets* (*CreateVideoFrameAncillaryPacketsInstanceFunc)(void);
|
|
||||||
|
|
||||||
static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT;
|
|
||||||
|
|
||||||
static bool gLoadedDeckLinkAPI = false;
|
|
||||||
|
|
||||||
static CreateIteratorFunc gCreateIteratorFunc = NULL;
|
|
||||||
static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL;
|
|
||||||
static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL;
|
|
||||||
static CreateOpenGL3ScreenPreviewHelperFunc gCreateOpenGL3PreviewFunc = NULL;
|
|
||||||
static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL;
|
|
||||||
static CreateDeckLinkDiscoveryInstanceFunc gCreateDeckLinkDiscoveryFunc = NULL;
|
|
||||||
static CreateVideoFrameAncillaryPacketsInstanceFunc gCreateVideoFrameAncillaryPacketsFunc = NULL;
|
|
||||||
|
|
||||||
static void InitDeckLinkAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
gLoadedDeckLinkAPI = true;
|
|
||||||
|
|
||||||
gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0004");
|
|
||||||
if (!gCreateIteratorFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateAPIInformationFunc = (CreateAPIInformationFunc)dlsym(libraryHandle, "CreateDeckLinkAPIInformationInstance_0001");
|
|
||||||
if (!gCreateAPIInformationFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0002");
|
|
||||||
if (!gCreateVideoConversionFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateDeckLinkDiscoveryFunc = (CreateDeckLinkDiscoveryInstanceFunc)dlsym(libraryHandle, "CreateDeckLinkDiscoveryInstance_0003");
|
|
||||||
if (!gCreateDeckLinkDiscoveryFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateVideoFrameAncillaryPacketsFunc = (CreateVideoFrameAncillaryPacketsInstanceFunc)dlsym(libraryHandle, "CreateVideoFrameAncillaryPacketsInstance_0001");
|
|
||||||
if (!gCreateVideoFrameAncillaryPacketsFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void InitDeckLinkPreviewAPI (void)
|
|
||||||
{
|
|
||||||
void *libraryHandle;
|
|
||||||
|
|
||||||
libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW|RTLD_GLOBAL);
|
|
||||||
if (!libraryHandle)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0002");
|
|
||||||
if (!gCreateOpenGLPreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
gCreateOpenGL3PreviewFunc = (CreateOpenGL3ScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGL3ScreenPreviewHelper_0002");
|
|
||||||
if (!gCreateOpenGL3PreviewFunc)
|
|
||||||
fprintf(stderr, "%s\n", dlerror());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDeckLinkAPIPresent (void)
|
|
||||||
{
|
|
||||||
// If the DeckLink API dynamic library was successfully loaded, return this knowledge to the caller
|
|
||||||
return gLoadedDeckLinkAPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkIterator* CreateDeckLinkIteratorInstance_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateIteratorFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateIteratorFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateAPIInformationFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateAPIInformationFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGLPreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGLPreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkGLScreenPreviewHelper* CreateOpenGL3ScreenPreviewHelper_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI);
|
|
||||||
|
|
||||||
if (gCreateOpenGL3PreviewFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateOpenGL3PreviewFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoConversion_v15_3_1* CreateVideoConversionInstance_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoConversionFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoConversionFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateDeckLinkDiscoveryFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateDeckLinkDiscoveryFunc();
|
|
||||||
}
|
|
||||||
|
|
||||||
IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance_v15_3_1 (void)
|
|
||||||
{
|
|
||||||
pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI);
|
|
||||||
|
|
||||||
if (gCreateVideoFrameAncillaryPacketsFunc == NULL)
|
|
||||||
return NULL;
|
|
||||||
return gCreateVideoFrameAncillaryPacketsFunc();
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIGLSCREENPREVIEW_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIGLSCREENPREVIEW_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkGLScreenPreviewHelper_v14_2_1 = /* 504E2209-CAC7-4C1A-9FB4-C5BB6274D22F */ { 0x50, 0x4E, 0x22, 0x09, 0xCA, 0xC7, 0x4C, 0x1A, 0x9F, 0xB4, 0xC5, 0xBB, 0x62, 0x74, 0xD2, 0x2F };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkGLScreenPreviewHelper - Created with CoCreateInstance on platforms with native COM support or from CreateOpenGLScreenPreviewHelper/CreateOpenGL3ScreenPreviewHelper on other platforms. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkGLScreenPreviewHelper_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
/* Methods must be called with OpenGL context set */
|
|
||||||
|
|
||||||
virtual HRESULT InitializeGL (void) = 0;
|
|
||||||
virtual HRESULT PaintGL (void) = 0;
|
|
||||||
virtual HRESULT SetFrame (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame) = 0;
|
|
||||||
virtual HRESULT Set3DPreviewFormat (/* in */ BMD3DPreviewFormat previewFormat) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkGLScreenPreviewHelper_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIMEMORYALLOCATOR_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIMEMORYALLOCATOR_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkMemoryAllocator_v14_2_1 = /* B36EB6E7-9D29-4AA8-92EF-843B87A289E8 */ { 0xB3, 0x6E, 0xB6, 0xE7, 0x9D, 0x29, 0x4A, 0xA8, 0x92, 0xEF, 0x84, 0x3B, 0x87, 0xA2, 0x89, 0xE8 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkMemoryAllocator_v14_2_1 - Created with CoCreateInstance. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkMemoryAllocator_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT AllocateBuffer (/* in */ uint32_t bufferSize, /* out */ void** allocatedBuffer) = 0;
|
|
||||||
virtual HRESULT ReleaseBuffer (/* in */ void* buffer) = 0;
|
|
||||||
virtual HRESULT Commit (void) = 0;
|
|
||||||
virtual HRESULT Decommit (void) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIMETALSCREENPREVIEW_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIMETALSCREENPREVIEW_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkMetalScreenPreviewHelper_v14_2_1 = /* 1AB252C5-DACB-4AE8-A58B-5320DE9CE373 */ { 0x1A, 0xB2, 0x52, 0xC5, 0xDA, 0xCB, 0x4A, 0xE8, 0xA5, 0x8B, 0x53, 0x20, 0xDE, 0x9C, 0xE3, 0x73 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkMetalScreenPreviewHelper - Created with CreateMetalScreenPreviewHelper(). */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkMetalScreenPreviewHelper_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT Initialize (/* in */ void* device) = 0;
|
|
||||||
virtual HRESULT Draw (/* in */ void* cmdBuffer, /* in */ void* renderPassDescriptor, /* in */ void* viewport) = 0;
|
|
||||||
virtual HRESULT SetFrame (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame) = 0;
|
|
||||||
virtual HRESULT Set3DPreviewFormat (/* in */ BMD3DPreviewFormat previewFormat) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkMetalScreenPreviewHelper_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2026 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-
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* -- AUTOMATICALLY GENERATED - DO NOT EDIT ---
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIMODES_H
|
|
||||||
#define BMD_DECKLINKAPIMODES_H
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef BMD_CONST
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define BMD_CONST __declspec(selectany) static const
|
|
||||||
#else
|
|
||||||
#define BMD_CONST static const
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BMD_PUBLIC
|
|
||||||
#define BMD_PUBLIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkDisplayModeIterator = /* 9C88499F-F601-4021-B80B-032E4EB41C35 */ { 0x9C,0x88,0x49,0x9F,0xF6,0x01,0x40,0x21,0xB8,0x0B,0x03,0x2E,0x4E,0xB4,0x1C,0x35 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkDisplayMode = /* 3EB2C1AB-0A3D-4523-A3AD-F40D7FB14E78 */ { 0x3E,0xB2,0xC1,0xAB,0x0A,0x3D,0x45,0x23,0xA3,0xAD,0xF4,0x0D,0x7F,0xB1,0x4E,0x78 };
|
|
||||||
|
|
||||||
/* Enum BMDDisplayMode - BMDDisplayMode enumerates the video modes supported. */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDisplayMode;
|
|
||||||
enum _BMDDisplayMode {
|
|
||||||
|
|
||||||
/* SD Modes */
|
|
||||||
|
|
||||||
bmdModeNTSC = /* 'ntsc' */ 0x6E747363,
|
|
||||||
bmdModeNTSC2398 = /* 'nt23' */ 0x6E743233, // 3:2 pulldown
|
|
||||||
bmdModePAL = /* 'pal ' */ 0x70616C20,
|
|
||||||
bmdModeNTSCp = /* 'ntsp' */ 0x6E747370,
|
|
||||||
bmdModePALp = /* 'palp' */ 0x70616C70,
|
|
||||||
|
|
||||||
/* HD 1080 Modes */
|
|
||||||
|
|
||||||
bmdModeHD1080p2398 = /* '23ps' */ 0x32337073,
|
|
||||||
bmdModeHD1080p24 = /* '24ps' */ 0x32347073,
|
|
||||||
bmdModeHD1080p25 = /* 'Hp25' */ 0x48703235,
|
|
||||||
bmdModeHD1080p2997 = /* 'Hp29' */ 0x48703239,
|
|
||||||
bmdModeHD1080p30 = /* 'Hp30' */ 0x48703330,
|
|
||||||
bmdModeHD1080p4795 = /* 'Hp47' */ 0x48703437,
|
|
||||||
bmdModeHD1080p48 = /* 'Hp48' */ 0x48703438,
|
|
||||||
bmdModeHD1080p50 = /* 'Hp50' */ 0x48703530,
|
|
||||||
bmdModeHD1080p5994 = /* 'Hp59' */ 0x48703539,
|
|
||||||
bmdModeHD1080p6000 = /* 'Hp60' */ 0x48703630, // N.B. This _really_ is 60.00 Hz.
|
|
||||||
bmdModeHD1080p9590 = /* 'Hp95' */ 0x48703935,
|
|
||||||
bmdModeHD1080p96 = /* 'Hp96' */ 0x48703936,
|
|
||||||
bmdModeHD1080p100 = /* 'Hp10' */ 0x48703130,
|
|
||||||
bmdModeHD1080p11988 = /* 'Hp11' */ 0x48703131,
|
|
||||||
bmdModeHD1080p120 = /* 'Hp12' */ 0x48703132,
|
|
||||||
bmdModeHD1080i50 = /* 'Hi50' */ 0x48693530,
|
|
||||||
bmdModeHD1080i5994 = /* 'Hi59' */ 0x48693539,
|
|
||||||
bmdModeHD1080i6000 = /* 'Hi60' */ 0x48693630, // N.B. This _really_ is 60.00 Hz.
|
|
||||||
|
|
||||||
/* HD 720 Modes */
|
|
||||||
|
|
||||||
bmdModeHD720p50 = /* 'hp50' */ 0x68703530,
|
|
||||||
bmdModeHD720p5994 = /* 'hp59' */ 0x68703539,
|
|
||||||
bmdModeHD720p60 = /* 'hp60' */ 0x68703630,
|
|
||||||
|
|
||||||
/* 2K Modes */
|
|
||||||
|
|
||||||
bmdMode2k2398 = /* '2k23' */ 0x326B3233,
|
|
||||||
bmdMode2k24 = /* '2k24' */ 0x326B3234,
|
|
||||||
bmdMode2k25 = /* '2k25' */ 0x326B3235,
|
|
||||||
|
|
||||||
/* 2K DCI Modes */
|
|
||||||
|
|
||||||
bmdMode2kDCI2398 = /* '2d23' */ 0x32643233,
|
|
||||||
bmdMode2kDCI24 = /* '2d24' */ 0x32643234,
|
|
||||||
bmdMode2kDCI25 = /* '2d25' */ 0x32643235,
|
|
||||||
bmdMode2kDCI2997 = /* '2d29' */ 0x32643239,
|
|
||||||
bmdMode2kDCI30 = /* '2d30' */ 0x32643330,
|
|
||||||
bmdMode2kDCI4795 = /* '2d47' */ 0x32643437,
|
|
||||||
bmdMode2kDCI48 = /* '2d48' */ 0x32643438,
|
|
||||||
bmdMode2kDCI50 = /* '2d50' */ 0x32643530,
|
|
||||||
bmdMode2kDCI5994 = /* '2d59' */ 0x32643539,
|
|
||||||
bmdMode2kDCI60 = /* '2d60' */ 0x32643630,
|
|
||||||
bmdMode2kDCI9590 = /* '2d95' */ 0x32643935,
|
|
||||||
bmdMode2kDCI96 = /* '2d96' */ 0x32643936,
|
|
||||||
bmdMode2kDCI100 = /* '2d10' */ 0x32643130,
|
|
||||||
bmdMode2kDCI11988 = /* '2d11' */ 0x32643131,
|
|
||||||
bmdMode2kDCI120 = /* '2d12' */ 0x32643132,
|
|
||||||
|
|
||||||
/* 4K UHD Modes */
|
|
||||||
|
|
||||||
bmdMode4K2160p2398 = /* '4k23' */ 0x346B3233,
|
|
||||||
bmdMode4K2160p24 = /* '4k24' */ 0x346B3234,
|
|
||||||
bmdMode4K2160p25 = /* '4k25' */ 0x346B3235,
|
|
||||||
bmdMode4K2160p2997 = /* '4k29' */ 0x346B3239,
|
|
||||||
bmdMode4K2160p30 = /* '4k30' */ 0x346B3330,
|
|
||||||
bmdMode4K2160p4795 = /* '4k47' */ 0x346B3437,
|
|
||||||
bmdMode4K2160p48 = /* '4k48' */ 0x346B3438,
|
|
||||||
bmdMode4K2160p50 = /* '4k50' */ 0x346B3530,
|
|
||||||
bmdMode4K2160p5994 = /* '4k59' */ 0x346B3539,
|
|
||||||
bmdMode4K2160p60 = /* '4k60' */ 0x346B3630,
|
|
||||||
bmdMode4K2160p9590 = /* '4k95' */ 0x346B3935,
|
|
||||||
bmdMode4K2160p96 = /* '4k96' */ 0x346B3936,
|
|
||||||
bmdMode4K2160p100 = /* '4k10' */ 0x346B3130,
|
|
||||||
bmdMode4K2160p11988 = /* '4k11' */ 0x346B3131,
|
|
||||||
bmdMode4K2160p120 = /* '4k12' */ 0x346B3132,
|
|
||||||
|
|
||||||
/* 4K DCI Modes */
|
|
||||||
|
|
||||||
bmdMode4kDCI2398 = /* '4d23' */ 0x34643233,
|
|
||||||
bmdMode4kDCI24 = /* '4d24' */ 0x34643234,
|
|
||||||
bmdMode4kDCI25 = /* '4d25' */ 0x34643235,
|
|
||||||
bmdMode4kDCI2997 = /* '4d29' */ 0x34643239,
|
|
||||||
bmdMode4kDCI30 = /* '4d30' */ 0x34643330,
|
|
||||||
bmdMode4kDCI4795 = /* '4d47' */ 0x34643437,
|
|
||||||
bmdMode4kDCI48 = /* '4d48' */ 0x34643438,
|
|
||||||
bmdMode4kDCI50 = /* '4d50' */ 0x34643530,
|
|
||||||
bmdMode4kDCI5994 = /* '4d59' */ 0x34643539,
|
|
||||||
bmdMode4kDCI60 = /* '4d60' */ 0x34643630,
|
|
||||||
bmdMode4kDCI9590 = /* '4d95' */ 0x34643935,
|
|
||||||
bmdMode4kDCI96 = /* '4d96' */ 0x34643936,
|
|
||||||
bmdMode4kDCI100 = /* '4d10' */ 0x34643130,
|
|
||||||
bmdMode4kDCI11988 = /* '4d11' */ 0x34643131,
|
|
||||||
bmdMode4kDCI120 = /* '4d12' */ 0x34643132,
|
|
||||||
|
|
||||||
/* 8K UHD Modes */
|
|
||||||
|
|
||||||
bmdMode8K4320p2398 = /* '8k23' */ 0x386B3233,
|
|
||||||
bmdMode8K4320p24 = /* '8k24' */ 0x386B3234,
|
|
||||||
bmdMode8K4320p25 = /* '8k25' */ 0x386B3235,
|
|
||||||
bmdMode8K4320p2997 = /* '8k29' */ 0x386B3239,
|
|
||||||
bmdMode8K4320p30 = /* '8k30' */ 0x386B3330,
|
|
||||||
bmdMode8K4320p4795 = /* '8k47' */ 0x386B3437,
|
|
||||||
bmdMode8K4320p48 = /* '8k48' */ 0x386B3438,
|
|
||||||
bmdMode8K4320p50 = /* '8k50' */ 0x386B3530,
|
|
||||||
bmdMode8K4320p5994 = /* '8k59' */ 0x386B3539,
|
|
||||||
bmdMode8K4320p60 = /* '8k60' */ 0x386B3630,
|
|
||||||
|
|
||||||
/* 8K DCI Modes */
|
|
||||||
|
|
||||||
bmdMode8kDCI2398 = /* '8d23' */ 0x38643233,
|
|
||||||
bmdMode8kDCI24 = /* '8d24' */ 0x38643234,
|
|
||||||
bmdMode8kDCI25 = /* '8d25' */ 0x38643235,
|
|
||||||
bmdMode8kDCI2997 = /* '8d29' */ 0x38643239,
|
|
||||||
bmdMode8kDCI30 = /* '8d30' */ 0x38643330,
|
|
||||||
bmdMode8kDCI4795 = /* '8d47' */ 0x38643437,
|
|
||||||
bmdMode8kDCI48 = /* '8d48' */ 0x38643438,
|
|
||||||
bmdMode8kDCI50 = /* '8d50' */ 0x38643530,
|
|
||||||
bmdMode8kDCI5994 = /* '8d59' */ 0x38643539,
|
|
||||||
bmdMode8kDCI60 = /* '8d60' */ 0x38643630,
|
|
||||||
|
|
||||||
/* PC Modes */
|
|
||||||
|
|
||||||
bmdMode640x480p60 = /* 'vga6' */ 0x76676136,
|
|
||||||
bmdMode800x600p60 = /* 'svg6' */ 0x73766736,
|
|
||||||
bmdMode1440x900p50 = /* 'wxg5' */ 0x77786735,
|
|
||||||
bmdMode1440x900p60 = /* 'wxg6' */ 0x77786736,
|
|
||||||
bmdMode1440x1080p50 = /* 'sxg5' */ 0x73786735,
|
|
||||||
bmdMode1440x1080p60 = /* 'sxg6' */ 0x73786736,
|
|
||||||
bmdMode1600x1200p50 = /* 'uxg5' */ 0x75786735,
|
|
||||||
bmdMode1600x1200p60 = /* 'uxg6' */ 0x75786736,
|
|
||||||
bmdMode1920x1200p50 = /* 'wux5' */ 0x77757835,
|
|
||||||
bmdMode1920x1200p60 = /* 'wux6' */ 0x77757836,
|
|
||||||
bmdMode1920x1440p50 = /* '1945' */ 0x31393435,
|
|
||||||
bmdMode1920x1440p60 = /* '1946' */ 0x31393436,
|
|
||||||
bmdMode2560x1440p50 = /* 'wqh5' */ 0x77716835,
|
|
||||||
bmdMode2560x1440p60 = /* 'wqh6' */ 0x77716836,
|
|
||||||
bmdMode2560x1600p50 = /* 'wqx5' */ 0x77717835,
|
|
||||||
bmdMode2560x1600p60 = /* 'wqx6' */ 0x77717836,
|
|
||||||
bmdModeUnknown = /* 'iunk' */ 0x69756E6B
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDFieldDominance - BMDFieldDominance enumerates settings applicable to video fields. */
|
|
||||||
|
|
||||||
typedef uint32_t BMDFieldDominance;
|
|
||||||
enum _BMDFieldDominance {
|
|
||||||
bmdUnknownFieldDominance = 0,
|
|
||||||
bmdLowerFieldFirst = /* 'lowr' */ 0x6C6F7772,
|
|
||||||
bmdUpperFieldFirst = /* 'uppr' */ 0x75707072,
|
|
||||||
bmdProgressiveFrame = /* 'prog' */ 0x70726F67,
|
|
||||||
bmdProgressiveSegmentedFrame = /* 'psf ' */ 0x70736620
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDPixelFormat - Video pixel formats supported for output/input */
|
|
||||||
|
|
||||||
typedef uint32_t BMDPixelFormat;
|
|
||||||
enum _BMDPixelFormat {
|
|
||||||
bmdFormatUnspecified = 0,
|
|
||||||
bmdFormat8BitYUV = /* '2vuy' */ 0x32767579,
|
|
||||||
bmdFormat10BitYUV = /* 'v210' */ 0x76323130,
|
|
||||||
bmdFormat10BitYUVA = /* 'Ay10' */ 0x41793130, // Big-endian YUVA 10 bit per component with SMPTE video levels (64-940) for YUV but full range alpha
|
|
||||||
bmdFormat8BitARGB = 32,
|
|
||||||
bmdFormat8BitBGRA = /* 'BGRA' */ 0x42475241,
|
|
||||||
bmdFormat10BitRGB = /* 'r210' */ 0x72323130, // Big-endian RGB 10-bit per component with SMPTE video levels (64-940). Packed as 2:10:10:10
|
|
||||||
bmdFormat12BitRGB = /* 'R12B' */ 0x52313242, // Big-endian RGB 12-bit per component with full range (0-4095). Packed as 12-bit per component
|
|
||||||
bmdFormat12BitRGBLE = /* 'R12L' */ 0x5231324C, // Little-endian RGB 12-bit per component with full range (0-4095). Packed as 12-bit per component
|
|
||||||
bmdFormat10BitRGBXLE = /* 'R10l' */ 0x5231306C, // Little-endian 10-bit RGB with SMPTE video levels (64-940)
|
|
||||||
bmdFormat10BitRGBX = /* 'R10b' */ 0x52313062, // Big-endian 10-bit RGB with SMPTE video levels (64-940)
|
|
||||||
|
|
||||||
/* Formats supported only by devices that can be queried for an IDeckLinkEncoderInput */
|
|
||||||
|
|
||||||
bmdFormatH265 = /* 'hev1' */ 0x68657631,
|
|
||||||
bmdFormatDNxHR = /* 'AVdh' */ 0x41566468
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDisplayModeFlags - Flags to describe the characteristics of an IDeckLinkDisplayMode. */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDisplayModeFlags;
|
|
||||||
enum _BMDDisplayModeFlags {
|
|
||||||
bmdDisplayModeSupports3D = 1 << 0,
|
|
||||||
bmdDisplayModeColorspaceRec601 = 1 << 1,
|
|
||||||
bmdDisplayModeColorspaceRec709 = 1 << 2,
|
|
||||||
bmdDisplayModeColorspaceRec2020 = 1 << 3
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkDisplayModeIterator;
|
|
||||||
class IDeckLinkDisplayMode;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkDisplayModeIterator - Enumerates over supported input/output display modes. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkDisplayModeIterator : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT Next (/* out */ IDeckLinkDisplayMode** deckLinkDisplayMode) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkDisplayModeIterator () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkDisplayMode - Represents a display mode */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkDisplayMode : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetName (/* out */ const char** name) = 0;
|
|
||||||
virtual BMDDisplayMode GetDisplayMode (void) = 0;
|
|
||||||
virtual long GetWidth (void) = 0;
|
|
||||||
virtual long GetHeight (void) = 0;
|
|
||||||
virtual HRESULT GetFrameRate (/* out */ BMDTimeValue* frameDuration, /* out */ BMDTimeScale* timeScale) = 0;
|
|
||||||
virtual BMDFieldDominance GetFieldDominance (void) = 0;
|
|
||||||
virtual BMDDisplayModeFlags GetFlags (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkDisplayMode () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__cplusplus) */
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIMODES_H) */
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPISCREENPREVIEWCALLBACK_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPISCREENPREVIEWCALLBACK_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkScreenPreviewCallback_v14_2_1 = /* B1D3F49A-85FE-4C5D-95C8-0B5D5DCCD438 */ { 0xB1, 0xD3, 0xF4, 0x9A, 0x85, 0xFE, 0x4C, 0x5D, 0x95, 0xC8, 0x0B, 0x5D, 0x5D, 0xCC, 0xD4, 0x38 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkScreenPreviewCallback_v14_2_1 - Screen preview callback */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkScreenPreviewCallback_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DrawFrame (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkScreenPreviewCallback_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPISCREENPREVIEWCALLBACK_v14_2_1_H) */
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2026 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-
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* -- AUTOMATICALLY GENERATED - DO NOT EDIT ---
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPITYPES_H
|
|
||||||
#define BMD_DECKLINKAPITYPES_H
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef BMD_CONST
|
|
||||||
#if defined(_MSC_VER)
|
|
||||||
#define BMD_CONST __declspec(selectany) static const
|
|
||||||
#else
|
|
||||||
#define BMD_CONST static const
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef BMD_PUBLIC
|
|
||||||
#define BMD_PUBLIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
typedef int64_t BMDTimeValue;
|
|
||||||
typedef int64_t BMDTimeScale;
|
|
||||||
typedef uint32_t BMDTimecodeBCD;
|
|
||||||
typedef uint32_t BMDTimecodeUserBits;
|
|
||||||
typedef int64_t BMDIPFlowID;
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkTimecode = /* BC6CFBD3-8317-4325-AC1C-1216391E9340 */ { 0xBC,0x6C,0xFB,0xD3,0x83,0x17,0x43,0x25,0xAC,0x1C,0x12,0x16,0x39,0x1E,0x93,0x40 };
|
|
||||||
|
|
||||||
/* Enum BMDTimecodeFlags - Timecode flags */
|
|
||||||
|
|
||||||
typedef uint32_t BMDTimecodeFlags;
|
|
||||||
enum _BMDTimecodeFlags {
|
|
||||||
bmdTimecodeFlagDefault = 0,
|
|
||||||
bmdTimecodeIsDropFrame = 1 << 0,
|
|
||||||
bmdTimecodeFieldMark = 1 << 1,
|
|
||||||
bmdTimecodeColorFrame = 1 << 2,
|
|
||||||
bmdTimecodeEmbedRecordingTrigger = 1 << 3, // On SDI recording trigger utilises a user-bit.
|
|
||||||
bmdTimecodeRecordingTriggered = 1 << 4
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDVideoConnection - Video connection types */
|
|
||||||
|
|
||||||
typedef uint32_t BMDVideoConnection;
|
|
||||||
enum _BMDVideoConnection {
|
|
||||||
bmdVideoConnectionUnspecified = 0,
|
|
||||||
bmdVideoConnectionSDI = 1 << 0,
|
|
||||||
bmdVideoConnectionHDMI = 1 << 1,
|
|
||||||
bmdVideoConnectionOpticalSDI = 1 << 2,
|
|
||||||
bmdVideoConnectionComponent = 1 << 3,
|
|
||||||
bmdVideoConnectionComposite = 1 << 4,
|
|
||||||
bmdVideoConnectionSVideo = 1 << 5,
|
|
||||||
bmdVideoConnectionEthernet = 1 << 6,
|
|
||||||
bmdVideoConnectionOpticalEthernet = 1 << 7,
|
|
||||||
bmdVideoConnectionInternal = 1 << 8
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDAudioConnection - Audio connection types */
|
|
||||||
|
|
||||||
typedef uint32_t BMDAudioConnection;
|
|
||||||
enum _BMDAudioConnection {
|
|
||||||
bmdAudioConnectionEmbedded = 1 << 0,
|
|
||||||
bmdAudioConnectionAESEBU = 1 << 1,
|
|
||||||
bmdAudioConnectionAnalog = 1 << 2,
|
|
||||||
bmdAudioConnectionAnalogXLR = 1 << 3,
|
|
||||||
bmdAudioConnectionAnalogRCA = 1 << 4,
|
|
||||||
bmdAudioConnectionMicrophone = 1 << 5,
|
|
||||||
bmdAudioConnectionHeadphones = 1 << 6
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckControlConnection - Deck control connections */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckControlConnection;
|
|
||||||
enum _BMDDeckControlConnection {
|
|
||||||
bmdDeckControlConnectionRS422Remote1 = 1 << 0,
|
|
||||||
bmdDeckControlConnectionRS422Remote2 = 1 << 1
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
// Forward Declarations
|
|
||||||
|
|
||||||
class IDeckLinkTimecode;
|
|
||||||
|
|
||||||
/* Interface IDeckLinkTimecode - Used for video frame timecode representation. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkTimecode : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual BMDTimecodeBCD GetBCD (void) = 0;
|
|
||||||
virtual HRESULT GetComponents (/* out */ uint8_t* hours, /* out */ uint8_t* minutes, /* out */ uint8_t* seconds, /* out */ uint8_t* frames) = 0;
|
|
||||||
virtual HRESULT GetString (/* out */ const char** timecode) = 0;
|
|
||||||
virtual BMDTimecodeFlags GetFlags (void) = 0;
|
|
||||||
virtual HRESULT GetTimecodeUserBits (/* out */ BMDTimecodeUserBits* userBits) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkTimecode () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif /* defined(__cplusplus) */
|
|
||||||
#endif /* defined(BMD_DECKLINKAPITYPES_H) */
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
* ** Copyright (c) 2014 Blackmagic Design
|
|
||||||
* **
|
|
||||||
* ** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
* ** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
* ** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
* ** accordance with:
|
|
||||||
* **
|
|
||||||
* ** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
* ** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
* ** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
* **
|
|
||||||
* ** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
* ** as notified by that third party,
|
|
||||||
* **
|
|
||||||
* ** and all subject to the following:
|
|
||||||
* **
|
|
||||||
* ** (3) 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.
|
|
||||||
* **
|
|
||||||
* ** (4) 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.
|
|
||||||
* **
|
|
||||||
* ** A copy of the Software is available free of charge at
|
|
||||||
* ** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
* **
|
|
||||||
* ** -LICENSE-END-
|
|
||||||
* */
|
|
||||||
|
|
||||||
/* DeckLinkAPIVersion.h */
|
|
||||||
|
|
||||||
#ifndef __DeckLink_API_Version_h__
|
|
||||||
#define __DeckLink_API_Version_h__
|
|
||||||
|
|
||||||
#define BLACKMAGIC_DECKLINK_API_VERSION 0x10000000
|
|
||||||
#define BLACKMAGIC_DECKLINK_API_VERSION_STRING "16.0"
|
|
||||||
|
|
||||||
#endif // __DeckLink_API_Version_h__
|
|
||||||
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOCONVERSION_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOCONVERSION_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoConversion_v14_2_1 = /* 3BBCB8A2-DA2C-42D9-B5D8-88083644E99A */ { 0x3B, 0xBC, 0xB8, 0xA2, 0xDA, 0x2C, 0x42, 0xD9, 0xB5, 0xD8, 0x88, 0x08, 0x36, 0x44, 0xE9, 0x9A };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoConversion - Created with CoCreateInstance. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoConversion_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT ConvertFrame (/* in */ IDeckLinkVideoFrame_v14_2_1* srcFrame, /* in */ IDeckLinkVideoFrame_v14_2_1* dstFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoConversion_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2017 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOENCODERINPUT_v10_11_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOENCODERINPUT_v10_11_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPI_v10_11.h"
|
|
||||||
#include "DeckLinkAPIMemoryAllocator_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkEncoderInput_v10_11 = /* 270587DA-6B7D-42E7-A1F0-6D853F581185 */ {0x27,0x05,0x87,0xDA,0x6B,0x7D,0x42,0xE7,0xA1,0xF0,0x6D,0x85,0x3F,0x58,0x11,0x85};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkEncoderInput_v10_11 - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class IDeckLinkEncoderInput_v10_11 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags, /* out */ BMDDisplayModeSupport_v10_11 *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailablePacketsCount (/* out */ uint32_t *availablePacketsCount) = 0;
|
|
||||||
virtual HRESULT SetMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1 *theAllocator) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioFormat audioFormat, /* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t *availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkEncoderInputCallback *theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkEncoderInput_v10_11 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOENCODERINPUT_v10_11_H) */
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOFRAME3DEXTENSIONS_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOFRAME3DEXTENSIONS_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoFrame3DExtensions_v14_2_1 = /* DA0F7E4A-EDC7-48A8-9CDD-2DB51C729CD7 */ { 0xDA, 0x0F, 0x7E, 0x4A, 0xED, 0xC7, 0x48, 0xA8, 0x9C, 0xDD, 0x2D, 0xB5, 0x1C, 0x72, 0x9C, 0xD7 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoFrame3DExtensions - Optional interface implemented on IDeckLinkVideoFrame to support 3D frames */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoFrame3DExtensions_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual BMDVideo3DPackingFormat Get3DPackingFormat (void) = 0;
|
|
||||||
virtual HRESULT GetFrameForRightEye (/* out */ IDeckLinkVideoFrame_v14_2_1** rightEyeFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoFrame3DExtensions_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOFRAME3DEXTENSIONS_v14_2_1_H) */
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOFRAME_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOFRAME_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoFrame_v14_2_1 = /* 3F716FE0-F023-4111-BE5D-EF4414C05B17 */ { 0x3F, 0x71, 0x6F, 0xE0, 0xF0, 0x23, 0x41, 0x11, 0xBE, 0x5D, 0xEF, 0x44, 0x14, 0xC0, 0x5B, 0x17 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoFrame - Interface to encapsulate a video frame; can be caller-implemented. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoFrame_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual long GetWidth (void) = 0;
|
|
||||||
virtual long GetHeight (void) = 0;
|
|
||||||
virtual long GetRowBytes (void) = 0;
|
|
||||||
virtual BMDPixelFormat GetPixelFormat (void) = 0;
|
|
||||||
virtual BMDFrameFlags GetFlags (void) = 0;
|
|
||||||
virtual HRESULT GetBytes (/* out */ void** buffer) = 0;
|
|
||||||
virtual HRESULT GetTimecode (/* in */ BMDTimecodeFormat format, /* out */ IDeckLinkTimecode** timecode) = 0;
|
|
||||||
virtual HRESULT GetAncillaryData (/* out */ IDeckLinkVideoFrameAncillary** ancillary) = 0; // Use of IDeckLinkVideoFrameAncillaryPackets is preferred
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoFrame_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOFRAME_v14_2_1_H) */
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2017 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOINPUT_v10_11_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOINPUT_v10_11_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPI_v10_11.h"
|
|
||||||
#include "DeckLinkAPIMemoryAllocator_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoInput_v11_5_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInput_v10_11 = /* AF22762B-DFAC-4846-AA79-FA8883560995 */ {0xAF,0x22,0x76,0x2B,0xDF,0xAC,0x48,0x46,0xAA,0x79,0xFA,0x88,0x83,0x56,0x09,0x95};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInput_v10_11 - DeckLink input interface. */
|
|
||||||
|
|
||||||
class IDeckLinkInput_v10_11 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags, /* out */ BMDDisplayModeSupport_v10_11 *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1 *previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t *availableFrameCount) = 0;
|
|
||||||
virtual HRESULT SetVideoInputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1 *theAllocator) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t *availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback_v11_5_1 *theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInput_v10_11 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOINPUT_v10_11_H) */
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2019 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOINPUT_v11_4_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOINPUT_v11_4_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIMemoryAllocator_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoInput_v11_5_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInput_v11_4 = /* 2A88CF76-F494-4216-A7EF-DC74EEB83882 */ { 0x2A,0x88,0xCF,0x76,0xF4,0x94,0x42,0x16,0xA7,0xEF,0xDC,0x74,0xEE,0xB8,0x38,0x82 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInput_v11_4 - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkInput_v11_4 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of 0 is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDSupportedVideoModeFlags flags, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t* availableFrameCount) = 0;
|
|
||||||
virtual HRESULT SetVideoInputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1* theAllocator) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t* availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback_v11_5_1* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInput_v11_4 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOINPUT_v11_4_H) */
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2020 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOINPUT_v11_5_1_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOINPUT_v11_5_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoInput_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInputCallback_v11_5_1 = /* DD04E5EC-7415-42AB-AE4A-E80C4DFC044A */ { 0xDD, 0x04, 0xE5, 0xEC, 0x74, 0x15, 0x42, 0xAB, 0xAE, 0x4A, 0xE8, 0x0C, 0x4D, 0xFC, 0x04, 0x4A };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInput_v11_5_1 = /* 9434C6E4-B15D-4B1C-979E-661E3DDCB4B9 */ { 0x94, 0x34, 0xC6, 0xE4, 0xB1, 0x5D, 0x4B, 0x1C, 0x97, 0x9E, 0x66, 0x1E, 0x3D, 0xDC, 0xB4, 0xB9 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInputCallback_v11_5_1 - Frame arrival callback. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkInputCallback_v11_5_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT VideoInputFormatChanged (/* in */ BMDVideoInputFormatChangedEvents notificationEvents, /* in */ IDeckLinkDisplayMode* newDisplayMode, /* in */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0;
|
|
||||||
virtual HRESULT VideoInputFrameArrived (/* in */ IDeckLinkVideoInputFrame_v14_2_1* videoFrame, /* in */ IDeckLinkAudioInputPacket* audioPacket) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInputCallback_v11_5_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInput_v11_5_1 - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkInput_v11_5_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of bmdVideoConnectionUnspecified is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDVideoInputConversionMode conversionMode, /* in */ BMDSupportedVideoModeFlags flags, /* out */ BMDDisplayMode* actualMode, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t* availableFrameCount) = 0;
|
|
||||||
virtual HRESULT SetVideoInputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1* theAllocator) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t* availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback_v11_5_1* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInput_v11_5_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOINPUT_v11_5_1_H) */
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOINPUT_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOINPUT_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIMemoryAllocator_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIScreenPreviewCallback_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoInputFrame_v14_2_1 = /* 05CFE374-537C-4094-9A57-680525118F44 */ { 0x05, 0xCF, 0xE3, 0x74, 0x53, 0x7C, 0x40, 0x94, 0x9A, 0x57, 0x68, 0x05, 0x25, 0x11, 0x8F, 0x44 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInputCallback_v14_2_1 = /* C6FCE4C9-C4E4-4047-82FB-5D238232A902 */ { 0xC6, 0xFC, 0xE4, 0xC9, 0xC4, 0xE4, 0x40, 0x47, 0x82, 0xFB, 0x5D, 0x23, 0x82, 0x32, 0xA9, 0x02 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInput_v14_2_1 = /* C21CDB6E-F414-46E4-A636-80A566E0ED37 */ { 0xC2, 0x1C, 0xDB, 0x6E, 0xF4, 0x14, 0x46, 0xE4, 0xA6, 0x36, 0x80, 0xA5, 0x66, 0xE0, 0xED, 0x37 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoInputFrame - Provided by the IDeckLinkVideoInput frame arrival callback. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoInputFrame_v14_2_1 : public IDeckLinkVideoFrame_v14_2_1
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetStreamTime (/* out */ BMDTimeValue* frameTime, /* out */ BMDTimeValue* frameDuration, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT GetHardwareReferenceTimestamp (/* in */ BMDTimeScale timeScale, /* out */ BMDTimeValue* frameTime, /* out */ BMDTimeValue* frameDuration) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoInputFrame_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInputCallback_v14_2_1 - Frame arrival callback. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkInputCallback_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT VideoInputFormatChanged (/* in */ BMDVideoInputFormatChangedEvents notificationEvents, /* in */ IDeckLinkDisplayMode* newDisplayMode, /* in */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0;
|
|
||||||
virtual HRESULT VideoInputFrameArrived (/* in */ IDeckLinkVideoInputFrame_v14_2_1* videoFrame, /* in */ IDeckLinkAudioInputPacket* audioPacket) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInputCallback_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInput - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkInput_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of bmdVideoConnectionUnspecified is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDVideoInputConversionMode conversionMode, /* in */ BMDSupportedVideoModeFlags flags, /* out */ BMDDisplayMode* actualMode, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t* availableFrameCount) = 0;
|
|
||||||
virtual HRESULT SetVideoInputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1* theAllocator) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t* availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback_v14_2_1* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInput_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOINPUT_v14_2_1_H) */
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2025 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_v15_3_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkInput_v15_3_1 = /* 4095DB82-E294-4B8C-AAA8-3B9E80C49336 */ { 0x40,0x95,0xDB,0x82,0xE2,0x94,0x4B,0x8C,0xAA,0xA8,0x3B,0x9E,0x80,0xC4,0x93,0x36 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkInput_v15_3_1 - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkInput_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of bmdVideoConnectionUnspecified is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDVideoInputConversionMode conversionMode, /* in */ BMDSupportedVideoModeFlags flags, /* out */ BMDDisplayMode* actualMode, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT EnableVideoInputWithAllocatorProvider (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags, /* in */ IDeckLinkVideoBufferAllocatorProvider_v15_3_1* allocatorProvider) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t* availableFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t* availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkInput_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2017 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOOUTPUT_v10_11_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOOUTPUT_v10_11_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPI_v10_11.h"
|
|
||||||
#include "DeckLinkAPIVideoInput_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoOutput_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkOutput_v10_11 = /* CC5C8A6E-3F2F-4B3A-87EA-FD78AF300564 */ {0xCC,0x5C,0x8A,0x6E,0x3F,0x2F,0x4B,0x3A,0x87,0xEA,0xFD,0x78,0xAF,0x30,0x05,0x64};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkOutput_v10_11 - DeckLink output interface. */
|
|
||||||
|
|
||||||
class IDeckLinkOutput_v10_11 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoOutputFlags flags, /* out */ BMDDisplayModeSupport_v10_11 *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1 *previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoOutput (/* in */ BMDDisplayMode displayMode, /* in */ BMDVideoOutputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoOutput (void) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT SetVideoOutputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1 *theAllocator) = 0;
|
|
||||||
virtual HRESULT CreateVideoFrame (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* out */ IDeckLinkMutableVideoFrame_v14_2_1 **outFrame) = 0;
|
|
||||||
virtual HRESULT CreateAncillaryData (/* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoFrameAncillary **outBuffer) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT DisplayVideoFrameSync (/* in */ IDeckLinkVideoFrame_v14_2_1 *theFrame) = 0;
|
|
||||||
virtual HRESULT ScheduleVideoFrame (/* in */ IDeckLinkVideoFrame_v14_2_1 *theFrame, /* in */ BMDTimeValue displayTime, /* in */ BMDTimeValue displayDuration, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT SetScheduledFrameCompletionCallback (/* in */ IDeckLinkVideoOutputCallback_v14_2_1 *theCallback) = 0;
|
|
||||||
virtual HRESULT GetBufferedVideoFrameCount (/* out */ uint32_t *bufferedFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Audio Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioOutput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount, /* in */ BMDAudioOutputStreamType streamType) = 0;
|
|
||||||
virtual HRESULT DisableAudioOutput (void) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT WriteAudioSamplesSync (/* in */ void *buffer, /* in */ uint32_t sampleFrameCount, /* out */ uint32_t *sampleFramesWritten) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT BeginAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT EndAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT ScheduleAudioSamples (/* in */ void *buffer, /* in */ uint32_t sampleFrameCount, /* in */ BMDTimeValue streamTime, /* in */ BMDTimeScale timeScale, /* out */ uint32_t *sampleFramesWritten) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT GetBufferedAudioSampleFrameCount (/* out */ uint32_t *bufferedSampleFrameCount) = 0;
|
|
||||||
virtual HRESULT FlushBufferedAudioSamples (void) = 0;
|
|
||||||
|
|
||||||
virtual HRESULT SetAudioCallback (/* in */ IDeckLinkAudioOutputCallback *theCallback) = 0;
|
|
||||||
|
|
||||||
/* Output Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartScheduledPlayback (/* in */ BMDTimeValue playbackStartTime, /* in */ BMDTimeScale timeScale, /* in */ double playbackSpeed) = 0;
|
|
||||||
virtual HRESULT StopScheduledPlayback (/* in */ BMDTimeValue stopPlaybackAtTime, /* out */ BMDTimeValue *actualStopTime, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT IsScheduledPlaybackRunning (/* out */ bool *active) = 0;
|
|
||||||
virtual HRESULT GetScheduledStreamTime (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *streamTime, /* out */ double *playbackSpeed) = 0;
|
|
||||||
virtual HRESULT GetReferenceStatus (/* out */ BMDReferenceStatus *referenceStatus) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0;
|
|
||||||
virtual HRESULT GetFrameCompletionReferenceTimestamp (/* in */ IDeckLinkVideoFrame_v14_2_1 *theFrame, /* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *frameCompletionTimestamp) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkOutput_v10_11 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOOUTPUT_v10_11_H) */
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2019 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOOUTPUT_v11_4_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOOUTPUT_v11_4_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoOutput_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkOutput_v11_4 = /* 065A0F6C-C508-4D0D-B919-F5EB0EBFC96B */ { 0x06,0x5A,0x0F,0x6C,0xC5,0x08,0x4D,0x0D,0xB9,0x19,0xF5,0xEB,0x0E,0xBF,0xC9,0x6B };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkOutput_v11_4 - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkOutput_v11_4 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of 0 is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDSupportedVideoModeFlags flags, /* out */ BMDDisplayMode* actualMode, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoOutput (/* in */ BMDDisplayMode displayMode, /* in */ BMDVideoOutputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoOutput (void) = 0;
|
|
||||||
virtual HRESULT SetVideoOutputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1* theAllocator) = 0;
|
|
||||||
virtual HRESULT CreateVideoFrame (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* out */ IDeckLinkMutableVideoFrame_v14_2_1** outFrame) = 0;
|
|
||||||
virtual HRESULT CreateAncillaryData (/* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoFrameAncillary** outBuffer) = 0; // Use of IDeckLinkVideoFrameAncillaryPackets is preferred
|
|
||||||
virtual HRESULT DisplayVideoFrameSync (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame) = 0;
|
|
||||||
virtual HRESULT ScheduleVideoFrame (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame, /* in */ BMDTimeValue displayTime, /* in */ BMDTimeValue displayDuration, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT SetScheduledFrameCompletionCallback (/* in */ IDeckLinkVideoOutputCallback_v14_2_1* theCallback) = 0;
|
|
||||||
virtual HRESULT GetBufferedVideoFrameCount (/* out */ uint32_t* bufferedFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Audio Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioOutput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount, /* in */ BMDAudioOutputStreamType streamType) = 0;
|
|
||||||
virtual HRESULT DisableAudioOutput (void) = 0;
|
|
||||||
virtual HRESULT WriteAudioSamplesSync (/* in */ void* buffer, /* in */ uint32_t sampleFrameCount, /* out */ uint32_t* sampleFramesWritten) = 0;
|
|
||||||
virtual HRESULT BeginAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT EndAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT ScheduleAudioSamples (/* in */ void* buffer, /* in */ uint32_t sampleFrameCount, /* in */ BMDTimeValue streamTime, /* in */ BMDTimeScale timeScale, /* out */ uint32_t* sampleFramesWritten) = 0;
|
|
||||||
virtual HRESULT GetBufferedAudioSampleFrameCount (/* out */ uint32_t* bufferedSampleFrameCount) = 0;
|
|
||||||
virtual HRESULT FlushBufferedAudioSamples (void) = 0;
|
|
||||||
virtual HRESULT SetAudioCallback (/* in */ IDeckLinkAudioOutputCallback* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Output Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartScheduledPlayback (/* in */ BMDTimeValue playbackStartTime, /* in */ BMDTimeScale timeScale, /* in */ double playbackSpeed) = 0;
|
|
||||||
virtual HRESULT StopScheduledPlayback (/* in */ BMDTimeValue stopPlaybackAtTime, /* out */ BMDTimeValue* actualStopTime, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT IsScheduledPlaybackRunning (/* out */ bool* active) = 0;
|
|
||||||
virtual HRESULT GetScheduledStreamTime (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* streamTime, /* out */ double* playbackSpeed) = 0;
|
|
||||||
virtual HRESULT GetReferenceStatus (/* out */ BMDReferenceStatus* referenceStatus) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
virtual HRESULT GetFrameCompletionReferenceTimestamp (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame, /* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* frameCompletionTimestamp) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkOutput_v11_4 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPIVIDEOOUTPUT_v11_4_H) */
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2022 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPIVIDEOOUTPUT_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPIVIDEOOUTPUT_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIMemoryAllocator_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIScreenPreviewCallback_v14_2_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkMutableVideoFrame_v14_2_1 = /* 69E2639F-40DA-4E19-B6F2-20ACE815C390 */ { 0x69, 0xE2, 0x63, 0x9F, 0x40, 0xDA, 0x4E, 0x19, 0xB6, 0xF2, 0x20, 0xAC, 0xE8, 0x15, 0xC3, 0x90 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoOutputCallback_v14_2_1 = /* 20AA5225-1958-47CB-820B-80A8D521A6EE */ { 0x20, 0xAA, 0x52, 0x25, 0x19, 0x58, 0x47, 0xCB, 0x82, 0x0B, 0x80, 0xA8, 0xD5, 0x21, 0xA6, 0xEE };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkOutput_v14_2_1 = /* BE2D9020-461E-442F-84B7-E949CB953B9D */ { 0xBE, 0x2D, 0x90, 0x20, 0x46, 0x1E, 0x44, 0x2F, 0x84, 0xB7, 0xE9, 0x49, 0xCB, 0x95, 0x3B, 0x9D };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkMutableVideoFrame - Created by IDeckLinkOutput::CreateVideoFrame. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkMutableVideoFrame_v14_2_1 : public IDeckLinkVideoFrame_v14_2_1
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT SetFlags (/* in */ BMDFrameFlags newFlags) = 0;
|
|
||||||
virtual HRESULT SetTimecode (/* in */ BMDTimecodeFormat format, /* in */ IDeckLinkTimecode* timecode) = 0;
|
|
||||||
virtual HRESULT SetTimecodeFromComponents (/* in */ BMDTimecodeFormat format, /* in */ uint8_t hours, /* in */ uint8_t minutes, /* in */ uint8_t seconds, /* in */ uint8_t frames, /* in */ BMDTimecodeFlags flags) = 0;
|
|
||||||
virtual HRESULT SetAncillaryData (/* in */ IDeckLinkVideoFrameAncillary* ancillary) = 0;
|
|
||||||
virtual HRESULT SetTimecodeUserBits (/* in */ BMDTimecodeFormat format, /* in */ BMDTimecodeUserBits userBits) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkMutableVideoFrame_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoOutputCallback - Frame completion callback. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoOutputCallback_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT ScheduledFrameCompleted (/* in */ IDeckLinkVideoFrame_v14_2_1* completedFrame, /* in */ BMDOutputFrameCompletionResult result) = 0;
|
|
||||||
virtual HRESULT ScheduledPlaybackHasStopped (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoOutputCallback_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkOutput - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkOutput_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of bmdVideoConnectionUnspecified is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDVideoOutputConversionMode conversionMode, /* in */ BMDSupportedVideoModeFlags flags, /* out */ BMDDisplayMode* actualMode, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback_v14_2_1* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoOutput (/* in */ BMDDisplayMode displayMode, /* in */ BMDVideoOutputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoOutput (void) = 0;
|
|
||||||
virtual HRESULT SetVideoOutputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1* theAllocator) = 0;
|
|
||||||
virtual HRESULT CreateVideoFrame (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* out */ IDeckLinkMutableVideoFrame_v14_2_1** outFrame) = 0;
|
|
||||||
virtual HRESULT CreateAncillaryData (/* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoFrameAncillary** outBuffer) = 0; // Use of IDeckLinkVideoFrameAncillaryPackets is preferred
|
|
||||||
virtual HRESULT DisplayVideoFrameSync (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame) = 0;
|
|
||||||
virtual HRESULT ScheduleVideoFrame (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame, /* in */ BMDTimeValue displayTime, /* in */ BMDTimeValue displayDuration, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT SetScheduledFrameCompletionCallback (/* in */ IDeckLinkVideoOutputCallback_v14_2_1* theCallback) = 0;
|
|
||||||
virtual HRESULT GetBufferedVideoFrameCount (/* out */ uint32_t* bufferedFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Audio Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioOutput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount, /* in */ BMDAudioOutputStreamType streamType) = 0;
|
|
||||||
virtual HRESULT DisableAudioOutput (void) = 0;
|
|
||||||
virtual HRESULT WriteAudioSamplesSync (/* in */ void* buffer, /* in */ uint32_t sampleFrameCount, /* out */ uint32_t* sampleFramesWritten) = 0;
|
|
||||||
virtual HRESULT BeginAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT EndAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT ScheduleAudioSamples (/* in */ void* buffer, /* in */ uint32_t sampleFrameCount, /* in */ BMDTimeValue streamTime, /* in */ BMDTimeScale timeScale, /* out */ uint32_t* sampleFramesWritten) = 0;
|
|
||||||
virtual HRESULT GetBufferedAudioSampleFrameCount (/* out */ uint32_t* bufferedSampleFrameCount) = 0;
|
|
||||||
virtual HRESULT FlushBufferedAudioSamples (void) = 0;
|
|
||||||
virtual HRESULT SetAudioCallback (/* in */ IDeckLinkAudioOutputCallback* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Output Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartScheduledPlayback (/* in */ BMDTimeValue playbackStartTime, /* in */ BMDTimeScale timeScale, /* in */ double playbackSpeed) = 0;
|
|
||||||
virtual HRESULT StopScheduledPlayback (/* in */ BMDTimeValue stopPlaybackAtTime, /* out */ BMDTimeValue* actualStopTime, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT IsScheduledPlaybackRunning (/* out */ bool* active) = 0;
|
|
||||||
virtual HRESULT GetScheduledStreamTime (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* streamTime, /* out */ double* playbackSpeed) = 0;
|
|
||||||
virtual HRESULT GetReferenceStatus (/* out */ BMDReferenceStatus* referenceStatus) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
virtual HRESULT GetFrameCompletionReferenceTimestamp (/* in */ IDeckLinkVideoFrame_v14_2_1* theFrame, /* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* frameCompletionTimestamp) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkOutput_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2025 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "DeckLinkAPI_v15_3_1.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkOutput_v15_3_1 = /* 1A8077F1-9FE2-4533-8147-2294305E253F */ { 0x1A,0x80,0x77,0xF1,0x9F,0xE2,0x45,0x33,0x81,0x47,0x22,0x94,0x30,0x5E,0x25,0x3F };
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
/* Interface IDeckLinkOutput - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkOutput_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of bmdVideoConnectionUnspecified is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedPixelFormat, /* in */ BMDVideoOutputConversionMode conversionMode, /* in */ BMDSupportedVideoModeFlags flags, /* out */ BMDDisplayMode* actualMode, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback* previewCallback) = 0;
|
|
||||||
|
|
||||||
/* Video Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoOutput (/* in */ BMDDisplayMode displayMode, /* in */ BMDVideoOutputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoOutput (void) = 0;
|
|
||||||
virtual HRESULT CreateVideoFrame (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* out */ IDeckLinkMutableVideoFrame** outFrame) = 0;
|
|
||||||
virtual HRESULT CreateVideoFrameWithBuffer (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* in */ IDeckLinkVideoBuffer_v15_3_1* buffer, /* out */ IDeckLinkMutableVideoFrame** outFrame) = 0;
|
|
||||||
virtual HRESULT RowBytesForPixelFormat (/* in */ BMDPixelFormat pixelFormat, /* in */ int32_t width, /* out */ int32_t* rowBytes) = 0;
|
|
||||||
virtual HRESULT CreateAncillaryData (/* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoFrameAncillary** outBuffer) = 0; // Use of IDeckLinkVideoFrameAncillaryPackets is preferred
|
|
||||||
virtual HRESULT DisplayVideoFrameSync (/* in */ IDeckLinkVideoFrame* theFrame) = 0;
|
|
||||||
virtual HRESULT ScheduleVideoFrame (/* in */ IDeckLinkVideoFrame* theFrame, /* in */ BMDTimeValue displayTime, /* in */ BMDTimeValue displayDuration, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT SetScheduledFrameCompletionCallback (/* in */ IDeckLinkVideoOutputCallback* theCallback) = 0;
|
|
||||||
virtual HRESULT GetBufferedVideoFrameCount (/* out */ uint32_t* bufferedFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Audio Output */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioOutput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount, /* in */ BMDAudioOutputStreamType streamType) = 0;
|
|
||||||
virtual HRESULT DisableAudioOutput (void) = 0;
|
|
||||||
virtual HRESULT WriteAudioSamplesSync (/* in */ void* buffer, /* in */ uint32_t sampleFrameCount, /* out */ uint32_t* sampleFramesWritten) = 0;
|
|
||||||
virtual HRESULT BeginAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT EndAudioPreroll (void) = 0;
|
|
||||||
virtual HRESULT ScheduleAudioSamples (/* in */ void* buffer, /* in */ uint32_t sampleFrameCount, /* in */ BMDTimeValue streamTime, /* in */ BMDTimeScale timeScale, /* out */ uint32_t* sampleFramesWritten) = 0;
|
|
||||||
virtual HRESULT GetBufferedAudioSampleFrameCount (/* out */ uint32_t* bufferedSampleFrameCount) = 0;
|
|
||||||
virtual HRESULT FlushBufferedAudioSamples (void) = 0;
|
|
||||||
virtual HRESULT SetAudioCallback (/* in */ IDeckLinkAudioOutputCallback* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Output Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartScheduledPlayback (/* in */ BMDTimeValue playbackStartTime, /* in */ BMDTimeScale timeScale, /* in */ double playbackSpeed) = 0;
|
|
||||||
virtual HRESULT StopScheduledPlayback (/* in */ BMDTimeValue stopPlaybackAtTime, /* out */ BMDTimeValue* actualStopTime, /* in */ BMDTimeScale timeScale) = 0;
|
|
||||||
virtual HRESULT IsScheduledPlaybackRunning (/* out */ bool* active) = 0;
|
|
||||||
virtual HRESULT GetScheduledStreamTime (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* streamTime, /* out */ double* playbackSpeed) = 0;
|
|
||||||
virtual HRESULT GetReferenceStatus (/* out */ BMDReferenceStatus* referenceStatus) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
virtual HRESULT GetFrameCompletionReferenceTimestamp (/* in */ IDeckLinkVideoFrame* theFrame, /* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* frameCompletionTimestamp) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkOutput_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // defined(__cplusplus)
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2018 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v10_11_H
|
|
||||||
#define BMD_DECKLINKAPI_v10_11_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkAttributes_v10_11 = /* ABC11843-D966-44CB-96E2-A1CB5D3135C4 */ {0xAB,0xC1,0x18,0x43,0xD9,0x66,0x44,0xCB,0x96,0xE2,0xA1,0xCB,0x5D,0x31,0x35,0xC4};
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkNotification_v10_11 = /* 0A1FB207-E215-441B-9B19-6FA1575946C5 */ {0x0A,0x1F,0xB2,0x07,0xE2,0x15,0x44,0x1B,0x9B,0x19,0x6F,0xA1,0x57,0x59,0x46,0xC5};
|
|
||||||
|
|
||||||
/* Enum BMDDisplayModeSupport_v10_11 - Output mode supported flags */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDisplayModeSupport_v10_11;
|
|
||||||
enum _BMDDisplayModeSupport_v10_11 {
|
|
||||||
bmdDisplayModeNotSupported_v10_11 = 0,
|
|
||||||
bmdDisplayModeSupported_v10_11,
|
|
||||||
bmdDisplayModeSupportedWithConversion_v10_11
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDuplexMode_v10_11 - Duplex for configurable ports */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDuplexMode_v10_11;
|
|
||||||
enum _BMDDuplexMode_v10_11 {
|
|
||||||
bmdDuplexModeFull_v10_11 = /* 'fdup' */ 0x66647570,
|
|
||||||
bmdDuplexModeHalf_v10_11 = /* 'hdup' */ 0x68647570
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkAttributeID_v10_11 - DeckLink Attribute ID */
|
|
||||||
|
|
||||||
enum _BMDDeckLinkAttributeID_v10_11 {
|
|
||||||
|
|
||||||
/* Flags */
|
|
||||||
|
|
||||||
BMDDeckLinkSupportsDuplexModeConfiguration_v10_11 = 'dupx',
|
|
||||||
BMDDeckLinkSupportsHDKeying_v10_11 = 'keyh',
|
|
||||||
|
|
||||||
/* Integers */
|
|
||||||
|
|
||||||
BMDDeckLinkPairedDevicePersistentID_v10_11 = 'ppid',
|
|
||||||
BMDDeckLinkSupportsFullDuplex_v10_11 = 'fdup',
|
|
||||||
};
|
|
||||||
|
|
||||||
enum _BMDDeckLinkStatusID_v10_11 {
|
|
||||||
bmdDeckLinkStatusDuplexMode_v10_11 = 'dupx',
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef uint32_t BMDDuplexStatus_v10_11;
|
|
||||||
enum _BMDDuplexStatus_v10_11 {
|
|
||||||
bmdDuplexFullDuplex_v10_11 = 'fdup',
|
|
||||||
bmdDuplexHalfDuplex_v10_11 = 'hdup',
|
|
||||||
bmdDuplexSimplex_v10_11 = 'splx',
|
|
||||||
bmdDuplexInactive_v10_11 = 'inac',
|
|
||||||
};
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
/* Interface IDeckLinkAttributes_v10_11 - DeckLink Attribute interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkAttributes_v10_11 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ bool *value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ const char **value) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkAttributes_v10_11 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkNotification_v10_11 - DeckLink Notification interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkNotification_v10_11 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT Subscribe (/* in */ BMDNotifications topic, /* in */ IDeckLinkNotificationCallback *theCallback) = 0;
|
|
||||||
virtual HRESULT Unsubscribe (/* in */ BMDNotifications topic, /* in */ IDeckLinkNotificationCallback *theCallback) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
BMD_PUBLIC IDeckLinkIterator* CreateDeckLinkIteratorInstance_v10_11 (void);
|
|
||||||
BMD_PUBLIC IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance_v10_11 (void);
|
|
||||||
BMD_PUBLIC IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance_v10_11 (void);
|
|
||||||
BMD_PUBLIC IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper_v10_11 (void);
|
|
||||||
BMD_PUBLIC IDeckLinkVideoConversion* CreateVideoConversionInstance_v10_11 (void);
|
|
||||||
BMD_PUBLIC IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance_v10_11 (void); // For use when creating a custom IDeckLinkVideoFrame without wrapping IDeckLinkOutput::CreateVideoFrame
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // defined(__cplusplus)
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v10_11_H) */
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2014 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v10_2_H
|
|
||||||
#define BMD_DECKLINKAPI_v10_2_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkConfigurationID - DeckLink Configuration ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkConfigurationID_v10_2;
|
|
||||||
enum _BMDDeckLinkConfigurationID_v10_2 {
|
|
||||||
/* Video output flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfig3GBpsVideoOutput_v10_2 = '3gbs',
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDAudioConnection_v10_2 - Audio connection types */
|
|
||||||
|
|
||||||
typedef uint32_t BMDAudioConnection_v10_2;
|
|
||||||
enum _BMDAudioConnection_v10_2 {
|
|
||||||
bmdAudioConnectionEmbedded_v10_2 = /* 'embd' */ 0x656D6264,
|
|
||||||
bmdAudioConnectionAESEBU_v10_2 = /* 'aes ' */ 0x61657320,
|
|
||||||
bmdAudioConnectionAnalog_v10_2 = /* 'anlg' */ 0x616E6C67,
|
|
||||||
bmdAudioConnectionAnalogXLR_v10_2 = /* 'axlr' */ 0x61786C72,
|
|
||||||
bmdAudioConnectionAnalogRCA_v10_2 = /* 'arca' */ 0x61726361
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v10_2_H) */
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
/* -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 (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v10_4_H
|
|
||||||
#define BMD_DECKLINKAPI_v10_4_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkConfigurationID - DeckLink Configuration ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkConfigurationID_v10_4;
|
|
||||||
enum _BMDDeckLinkConfigurationID_v10_4 {
|
|
||||||
|
|
||||||
/* Video output flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfigSingleLinkVideoOutput_v10_4 = /* 'sglo' */ 0x73676C6F,
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v10_4_H) */
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
/* -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 (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v10_5_H
|
|
||||||
#define BMD_DECKLINKAPI_v10_5_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkAttributeID - DeckLink Attribute ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkAttributeID_v10_5;
|
|
||||||
enum _BMDDeckLinkAttributeID_v10_5 {
|
|
||||||
|
|
||||||
/* Integers */
|
|
||||||
|
|
||||||
BMDDeckLinkDeviceBusyState_v10_5 = /* 'dbst' */ 0x64627374,
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v10_5_H) */
|
|
||||||
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2016 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v10_6_H
|
|
||||||
#define BMD_DECKLINKAPI_v10_6_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkAttributeID - DeckLink Attribute ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkAttributeID_c10_6;
|
|
||||||
enum _BMDDeckLinkAttributeID_v10_6 {
|
|
||||||
|
|
||||||
/* Flags */
|
|
||||||
|
|
||||||
BMDDeckLinkSupportsDesktopDisplay_v10_6 = /* 'extd' */ 0x65787464,
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef uint32_t BMDIdleVideoOutputOperation_v10_6;
|
|
||||||
enum _BMDIdleVideoOutputOperation_v10_6 {
|
|
||||||
bmdIdleVideoOutputDesktop_v10_6 = /* 'desk' */ 0x6465736B
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v10_6_H) */
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2017 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v10_9_H
|
|
||||||
#define BMD_DECKLINKAPI_v10_9_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Type Declarations
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkAttributeID - DeckLink Attribute ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkConfigurationID_v10_9;
|
|
||||||
enum _BMDDeckLinkConfigurationID_v10_9 {
|
|
||||||
|
|
||||||
/* Flags */
|
|
||||||
|
|
||||||
bmdDeckLinkConfig1080pNotPsF_v10_9 = 'fpro',
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v10_9_H) */
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2020 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v11_5_H
|
|
||||||
#define BMD_DECKLINKAPI_v11_5_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoFrameMetadataExtensions_v11_5 = /* D5973DC9-6432-46D0-8F0B-2496F8A1238F */ {0xD5,0x97,0x3D,0xC9,0x64,0x32,0x46,0xD0,0x8F,0x0B,0x24,0x96,0xF8,0xA1,0x23,0x8F};
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkFrameMetadataID - DeckLink Frame Metadata ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkFrameMetadataID_v11_5;
|
|
||||||
enum _BMDDeckLinkFrameMetadataID_v11_5 {
|
|
||||||
bmdDeckLinkFrameMetadataCintelFilmType_v11_5 = /* 'cfty' */ 0x63667479, // Current film type
|
|
||||||
bmdDeckLinkFrameMetadataCintelFilmGauge_v11_5 = /* 'cfga' */ 0x63666761, // Current film gauge
|
|
||||||
bmdDeckLinkFrameMetadataCintelKeykodeLow_v11_5 = /* 'ckkl' */ 0x636B6B6C, // Raw keykode value - low 64 bits
|
|
||||||
bmdDeckLinkFrameMetadataCintelKeykodeHigh_v11_5 = /* 'ckkh' */ 0x636B6B68, // Raw keykode value - high 64 bits
|
|
||||||
bmdDeckLinkFrameMetadataCintelTile1Size_v11_5 = /* 'ct1s' */ 0x63743173, // Size in bytes of compressed raw tile 1
|
|
||||||
bmdDeckLinkFrameMetadataCintelTile2Size_v11_5 = /* 'ct2s' */ 0x63743273, // Size in bytes of compressed raw tile 2
|
|
||||||
bmdDeckLinkFrameMetadataCintelTile3Size_v11_5 = /* 'ct3s' */ 0x63743373, // Size in bytes of compressed raw tile 3
|
|
||||||
bmdDeckLinkFrameMetadataCintelTile4Size_v11_5 = /* 'ct4s' */ 0x63743473, // Size in bytes of compressed raw tile 4
|
|
||||||
bmdDeckLinkFrameMetadataCintelImageWidth_v11_5 = /* 'IWPx' */ 0x49575078, // Width in pixels of image
|
|
||||||
bmdDeckLinkFrameMetadataCintelImageHeight_v11_5 = /* 'IHPx' */ 0x49485078, // Height in pixels of image
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingRedInRed_v11_5 = /* 'mrir' */ 0x6D726972, // Red in red linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingGreenInRed_v11_5 = /* 'mgir' */ 0x6D676972, // Green in red linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingBlueInRed_v11_5 = /* 'mbir' */ 0x6D626972, // Blue in red linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingRedInGreen_v11_5 = /* 'mrig' */ 0x6D726967, // Red in green linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingGreenInGreen_v11_5 = /* 'mgig' */ 0x6D676967, // Green in green linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingBlueInGreen_v11_5 = /* 'mbig' */ 0x6D626967, // Blue in green linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingRedInBlue_v11_5 = /* 'mrib' */ 0x6D726962, // Red in blue linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingGreenInBlue_v11_5 = /* 'mgib' */ 0x6D676962, // Green in blue linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLinearMaskingBlueInBlue_v11_5 = /* 'mbib' */ 0x6D626962, // Blue in blue linear masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingRedInRed_v11_5 = /* 'mlrr' */ 0x6D6C7272, // Red in red log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingGreenInRed_v11_5 = /* 'mlgr' */ 0x6D6C6772, // Green in red log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingBlueInRed_v11_5 = /* 'mlbr' */ 0x6D6C6272, // Blue in red log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingRedInGreen_v11_5 = /* 'mlrg' */ 0x6D6C7267, // Red in green log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingGreenInGreen_v11_5 = /* 'mlgg' */ 0x6D6C6767, // Green in green log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingBlueInGreen_v11_5 = /* 'mlbg' */ 0x6D6C6267, // Blue in green log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingRedInBlue_v11_5 = /* 'mlrb' */ 0x6D6C7262, // Red in blue log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingGreenInBlue_v11_5 = /* 'mlgb' */ 0x6D6C6762, // Green in blue log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelLogMaskingBlueInBlue_v11_5 = /* 'mlbb' */ 0x6D6C6262, // Blue in blue log masking parameter
|
|
||||||
bmdDeckLinkFrameMetadataCintelFilmFrameRate_v11_5 = /* 'cffr' */ 0x63666672, // Film frame rate
|
|
||||||
bmdDeckLinkFrameMetadataCintelOffsetToApplyHorizontal_v11_5 = /* 'otah' */ 0x6F746168, // Horizontal offset (pixels) to be applied to image
|
|
||||||
bmdDeckLinkFrameMetadataCintelOffsetToApplyVertical_v11_5 = /* 'otav' */ 0x6F746176, // Vertical offset (pixels) to be applied to image
|
|
||||||
bmdDeckLinkFrameMetadataCintelGainRed_v11_5 = /* 'LfRd' */ 0x4C665264, // Red gain parameter to apply after log
|
|
||||||
bmdDeckLinkFrameMetadataCintelGainGreen_v11_5 = /* 'LfGr' */ 0x4C664772, // Green gain parameter to apply after log
|
|
||||||
bmdDeckLinkFrameMetadataCintelGainBlue_v11_5 = /* 'LfBl' */ 0x4C66426C, // Blue gain parameter to apply after log
|
|
||||||
bmdDeckLinkFrameMetadataCintelLiftRed_v11_5 = /* 'GnRd' */ 0x476E5264, // Red lift parameter to apply after log and gain
|
|
||||||
bmdDeckLinkFrameMetadataCintelLiftGreen_v11_5 = /* 'GnGr' */ 0x476E4772, // Green lift parameter to apply after log and gain
|
|
||||||
bmdDeckLinkFrameMetadataCintelLiftBlue_v11_5 = /* 'GnBl' */ 0x476E426C, // Blue lift parameter to apply after log and gain
|
|
||||||
bmdDeckLinkFrameMetadataCintelHDRGainRed_v11_5 = /* 'HGRd' */ 0x48475264, // Red gain parameter to apply to linear data for HDR Combination
|
|
||||||
bmdDeckLinkFrameMetadataCintelHDRGainGreen_v11_5 = /* 'HGGr' */ 0x48474772, // Green gain parameter to apply to linear data for HDR Combination
|
|
||||||
bmdDeckLinkFrameMetadataCintelHDRGainBlue_v11_5 = /* 'HGBl' */ 0x4847426C, // Blue gain parameter to apply to linear data for HDR Combination
|
|
||||||
bmdDeckLinkFrameMetadataCintel16mmCropRequired_v11_5 = /* 'c16c' */ 0x63313663, // The image should be cropped to 16mm size
|
|
||||||
bmdDeckLinkFrameMetadataCintelInversionRequired_v11_5 = /* 'cinv' */ 0x63696E76, // The image should be colour inverted
|
|
||||||
bmdDeckLinkFrameMetadataCintelFlipRequired_v11_5 = /* 'cflr' */ 0x63666C72, // The image should be flipped horizontally
|
|
||||||
bmdDeckLinkFrameMetadataCintelFocusAssistEnabled_v11_5 = /* 'cfae' */ 0x63666165, // Focus Assist is currently enabled
|
|
||||||
bmdDeckLinkFrameMetadataCintelKeykodeIsInterpolated_v11_5 = /* 'kkii' */ 0x6B6B6969 // The keykode for this frame is interpolated from nearby keykodes
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoFrameMetadataExtensions - Optional interface implemented on IDeckLinkVideoFrame to support frame metadata such as HDMI HDR information */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoFrameMetadataExtensions_v11_5 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkFrameMetadataID_v11_5 metadataID, /* out */ int64_t *value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkFrameMetadataID_v11_5 metadataID, /* out */ double *value) = 0;
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkFrameMetadataID_v11_5 metadataID, /* out */ bool* value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkFrameMetadataID_v11_5 metadataID, /* out */ const char **value) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoFrameMetadataExtensions_v11_5 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v11_5_H) */
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2020 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v11_5_1_H
|
|
||||||
#define BMD_DECKLINKAPI_v11_5_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkStatusID - DeckLink Status ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkStatusID_v11_5_1;
|
|
||||||
enum _BMDDeckLinkStatusID_v11_5_1 {
|
|
||||||
|
|
||||||
/* Video output flags */
|
|
||||||
|
|
||||||
bmdDeckLinkStatusDetectedVideoInputFlags_v11_5_1 = /* 'dvif' */ 0x64766966,
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v11_5_1_H) */
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2018 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v14_2_1_H
|
|
||||||
#define BMD_DECKLINKAPI_v14_2_1_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
#include "DeckLinkAPIVideoConversion_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIGLScreenPreview_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIMetalScreenPreview_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIScreenPreviewCallback_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoOutput_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoInput_v14_2_1.h"
|
|
||||||
#include "DeckLinkAPIVideoFrame3DExtensions_v14_2_1.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkEncoderInput_v14_2_1 = /* F222551D-13DF-4FD8-B587-9D4F19EC12C9 */ { 0xF2,0x22,0x55,0x1D,0x13,0xDF,0x4F,0xD8,0xB5,0x87,0x9D,0x4F,0x19,0xEC,0x12,0xC9 };
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
/* Interface IDeckLinkEncoderInput - Created by QueryInterface from IDeckLink. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkEncoderInput_v14_2_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT DoesSupportVideoMode (/* in */ BMDVideoConnection connection /* If a value of bmdVideoConnectionUnspecified is specified, the caller does not care about the connection */, /* in */ BMDDisplayMode requestedMode, /* in */ BMDPixelFormat requestedCodec, /* in */ uint32_t requestedCodecProfile, /* in */ BMDSupportedVideoModeFlags flags, /* out */ bool* supported) = 0;
|
|
||||||
virtual HRESULT GetDisplayMode (/* in */ BMDDisplayMode displayMode, /* out */ IDeckLinkDisplayMode** resultDisplayMode) = 0;
|
|
||||||
virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator** iterator) = 0;
|
|
||||||
|
|
||||||
/* Video Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0;
|
|
||||||
virtual HRESULT DisableVideoInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailablePacketsCount (/* out */ uint32_t* availablePacketsCount) = 0;
|
|
||||||
virtual HRESULT SetMemoryAllocator (/* in */ IDeckLinkMemoryAllocator_v14_2_1* theAllocator) = 0;
|
|
||||||
|
|
||||||
/* Audio Input */
|
|
||||||
|
|
||||||
virtual HRESULT EnableAudioInput (/* in */ BMDAudioFormat audioFormat, /* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0;
|
|
||||||
virtual HRESULT DisableAudioInput (void) = 0;
|
|
||||||
virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t* availableSampleFrameCount) = 0;
|
|
||||||
|
|
||||||
/* Input Control */
|
|
||||||
|
|
||||||
virtual HRESULT StartStreams (void) = 0;
|
|
||||||
virtual HRESULT StopStreams (void) = 0;
|
|
||||||
virtual HRESULT PauseStreams (void) = 0;
|
|
||||||
virtual HRESULT FlushStreams (void) = 0;
|
|
||||||
virtual HRESULT SetCallback (/* in */ IDeckLinkEncoderInputCallback* theCallback) = 0;
|
|
||||||
|
|
||||||
/* Hardware Timing */
|
|
||||||
|
|
||||||
virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue* hardwareTime, /* out */ BMDTimeValue* timeInFrame, /* out */ BMDTimeValue* ticksPerFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkEncoderInput_v14_2_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
BMD_PUBLIC IDeckLinkIterator* CreateDeckLinkIteratorInstance_v14_2_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance_v14_2_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance_v14_2_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkGLScreenPreviewHelper_v14_2_1* CreateOpenGLScreenPreviewHelper_v14_2_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkGLScreenPreviewHelper_v14_2_1* CreateOpenGL3ScreenPreviewHelper_v14_2_1(void); // Requires OpenGL 3.2 support and provides improved performance and color handling
|
|
||||||
BMD_PUBLIC IDeckLinkVideoConversion_v14_2_1* CreateVideoConversionInstance_v14_2_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance_v14_2_1(void); // For use when creating a custom IDeckLinkVideoFrame without wrapping IDeckLinkOutput::CreateVideoFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // defined(__cplusplus)
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_v14_2_1_H) */
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2025 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef BMD_DECKLINKAPI_v15_2_H
|
|
||||||
#define BMD_DECKLINKAPI_v15_2_H
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkAncillaryPacket_v15_2 = /* CC5BBF7E-029C-4D3B-9158-6000EF5E3670 */ { 0xCC,0x5B,0xBF,0x7E,0x02,0x9C,0x4D,0x3B,0x91,0x58,0x60,0x00,0xEF,0x5E,0x36,0x70 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkAncillaryPacketIterator_v15_2 = /* 3FC8994B-88FB-4C17-968F-9AAB69D964A7 */ { 0x3F,0xC8,0x99,0x4B,0x88,0xFB,0x4C,0x17,0x96,0x8F,0x9A,0xAB,0x69,0xD9,0x64,0xA7 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoFrameAncillaryPackets_v15_2 = /* 6C186C0F-459E-41D8-AEE2-4812D81AEE68 */ { 0x6C,0x18,0x6C,0x0F,0x45,0x9E,0x41,0xD8,0xAE,0xE2,0x48,0x12,0xD8,0x1A,0xEE,0x68 };
|
|
||||||
|
|
||||||
/* Interface IDeckLinkAncillaryPacket - On output, user needs to implement this interface */
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkAncillaryPacket_v15_2 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetBytes (/* in */ BMDAncillaryPacketFormat format /* For output, only one format need be offered */, /* out */ const void** data /* Optional */, /* out */ uint32_t* size /* Optional */) = 0;
|
|
||||||
virtual uint8_t GetDID (void) = 0;
|
|
||||||
virtual uint8_t GetSDID (void) = 0;
|
|
||||||
virtual uint32_t GetLineNumber (void) = 0; // On output, zero is auto
|
|
||||||
virtual uint8_t GetDataStreamIndex (void) = 0; // Usually zero. Can only be 1 if non-SD and the first data stream is completely full
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkAncillaryPacket_v15_2 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkAncillaryPacketIterator - Enumerates ancillary packets */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkAncillaryPacketIterator_v15_2 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT Next (/* out */ IDeckLinkAncillaryPacket_v15_2** packet) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkAncillaryPacketIterator_v15_2 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoFrameAncillaryPackets - Obtained through QueryInterface on an IDeckLinkVideoFrame object. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoFrameAncillaryPackets_v15_2 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetPacketIterator (/* out */ IDeckLinkAncillaryPacketIterator_v15_2** iterator) = 0;
|
|
||||||
virtual HRESULT GetFirstPacketByID (/* in */ uint8_t DID, /* in */ uint8_t SDID, /* out */ IDeckLinkAncillaryPacket_v15_2** packet) = 0;
|
|
||||||
virtual HRESULT AttachPacket (/* in */ IDeckLinkAncillaryPacket_v15_2* packet) = 0; // Implement IDeckLinkAncillaryPacket to output your own
|
|
||||||
virtual HRESULT DetachPacket (/* in */ IDeckLinkAncillaryPacket_v15_2* packet) = 0;
|
|
||||||
virtual HRESULT DetachAllPackets (void) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoFrameAncillaryPackets_v15_2 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* defined(__cplusplus) */
|
|
||||||
#endif /* defined(BMD_DECKLINKAPI_H) */
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2025 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit (“EULA”) available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "DeckLinkAPI.h"
|
|
||||||
|
|
||||||
// Interface ID Declarations
|
|
||||||
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkStatus_v15_3_1 = /* 5F558200-4028-49BC-BEAC-DB3FA4A96E46 */ { 0x5F,0x55,0x82,0x00,0x40,0x28,0x49,0xBC,0xBE,0xAC,0xDB,0x3F,0xA4,0xA9,0x6E,0x46 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoBuffer_v15_3_1 = /* CCB4B64A-5C86-4E02-B778-885D352709FE */ { 0xCC,0xB4,0xB6,0x4A,0x5C,0x86,0x4E,0x02,0xB7,0x78,0x88,0x5D,0x35,0x27,0x09,0xFE };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoBufferAllocator_v15_3_1 = /* 3481A4DF-2B11-4E55-AC61-836B87985E9A */ { 0x34,0x81,0xA4,0xDF,0x2B,0x11,0x4E,0x55,0xAC,0x61,0x83,0x6B,0x87,0x98,0x5E,0x9A };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoBufferAllocatorProvider_v15_3_1 = /* 08B80403-BFF2-49D0-B448-8C908B9E9FC9 */ { 0x08,0xB8,0x04,0x03,0xBF,0xF2,0x49,0xD0,0xB4,0x48,0x8C,0x90,0x8B,0x9E,0x9F,0xC9 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkVideoConversion_v15_3_1 = /* A48755D9-8BD5-4727-A1E9-069FDEDBA6E9 */ { 0xA4,0x87,0x55,0xD9,0x8B,0xD5,0x47,0x27,0xA1,0xE9,0x06,0x9F,0xDE,0xDB,0xA6,0xE9 };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkProfileAttributes_v15_3_1 = /* 17D4BF8E-4911-473A-80A0-731CF6FF345B */ { 0x17,0xD4,0xBF,0x8E,0x49,0x11,0x47,0x3A,0x80,0xA0,0x73,0x1C,0xF6,0xFF,0x34,0x5B };
|
|
||||||
BMD_CONST REFIID IID_IDeckLinkNotification_v15_3_1 = /* B85DF4C8-BDF5-47C1-8064-28162EBDD4EB */ { 0xB8,0x5D,0xF4,0xC8,0xBD,0xF5,0x47,0xC1,0x80,0x64,0x28,0x16,0x2E,0xBD,0xD4,0xEB };
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkStatusID_v15_3_1 - DeckLink Status ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkStatusID_v15_3_1;
|
|
||||||
enum _BMDDeckLinkStatusID_v15_3_1
|
|
||||||
{
|
|
||||||
/* Integers */
|
|
||||||
bmdDeckLinkStatusDeviceTemperature_v15_3_1 = /* 'dtmp' */ 0x64746D70,
|
|
||||||
|
|
||||||
bmdDeckLinkStatusEthernetLink_v15_3_1 = /* 'sels' */ 0x73656C73,
|
|
||||||
bmdDeckLinkStatusEthernetLinkMbps_v15_3_1 = /* 'sesp' */ 0x73657370,
|
|
||||||
|
|
||||||
/* Strings */
|
|
||||||
bmdDeckLinkStatusEthernetLocalIPAddress_v15_3_1 = /* 'seip' */ 0x73656970,
|
|
||||||
bmdDeckLinkStatusEthernetSubnetMask_v15_3_1 = /* 'sesm' */ 0x7365736D,
|
|
||||||
bmdDeckLinkStatusEthernetGatewayIPAddress_v15_3_1 = /* 'segw' */ 0x73656777,
|
|
||||||
bmdDeckLinkStatusEthernetPrimaryDNS_v15_3_1 = /* 'sepd' */ 0x73657064,
|
|
||||||
bmdDeckLinkStatusEthernetSecondaryDNS_v15_3_1 = /* 'sesd' */ 0x73657364,
|
|
||||||
bmdDeckLinkStatusEthernetVideoOutputAddress_v15_3_1 = /* 'soav' */ 0x736F6176,
|
|
||||||
bmdDeckLinkStatusEthernetAudioOutputAddress_v15_3_1 = /* 'soaa' */ 0x736F6161,
|
|
||||||
bmdDeckLinkStatusEthernetAncillaryOutputAddress_v15_3_1 = /* 'soaA' */ 0x736F6141,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Enum BMDDeckLinkAttributeID - DeckLink Attribute ID */
|
|
||||||
|
|
||||||
typedef uint32_t BMDDeckLinkAttributeID_v15_3_1;
|
|
||||||
enum _BMDDeckLinkAttributeID_v15_3_1
|
|
||||||
{
|
|
||||||
/* Strings */
|
|
||||||
BMDDeckLinkEthernetMACAddress_v15_3_1 = /* 'eMAC' */ 0x654D4143,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkStatus_v15_3_1 - DeckLink Status interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkStatus_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkStatusID statusID, /* out */ bool* value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkStatusID statusID, /* out */ int64_t* value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkStatusID statusID, /* out */ double* value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkStatusID statusID, /* out */ const char** value) = 0;
|
|
||||||
virtual HRESULT GetBytes (/* in */ BMDDeckLinkStatusID statusID, /* out */ void* buffer, /* in, out */ uint32_t* bufferSize) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkStatus_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoBuffer_v15_3_1 - Interface to encapsulate a video frame buffer; can be caller-implemented. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoBuffer_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetBytes (/* out */ void** buffer) = 0;
|
|
||||||
virtual HRESULT StartAccess (/* in */ BMDBufferAccessFlags flags) = 0;
|
|
||||||
virtual HRESULT EndAccess (/* in */ BMDBufferAccessFlags flags) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoBuffer_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoBufferAllocator_v15_3_1 - Buffer allocator for video. */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoBufferAllocator_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT AllocateVideoBuffer (/* out */ IDeckLinkVideoBuffer_v15_3_1** allocatedBuffer) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoBufferAllocator_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoBufferAllocatorProvider_v15_3_1 - Allows EnableVideoInputWithAllocatorProvider to obtain allocators */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoBufferAllocatorProvider_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetVideoBufferAllocator (/* in */ uint32_t bufferSize, /* in */ uint32_t width, /* in */ uint32_t height, /* in */ uint32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoBufferAllocator_v15_3_1** allocator) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoBufferAllocatorProvider_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkVideoConversion_v15_3_1 */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkVideoConversion_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT ConvertFrame (/* in */ IDeckLinkVideoFrame* srcFrame, /* in */ IDeckLinkVideoFrame* dstFrame) = 0;
|
|
||||||
virtual HRESULT ConvertNewFrame (/* in */ IDeckLinkVideoFrame* srcFrame, /* in */ BMDPixelFormat dstPixelFormat, /* in */ BMDColorspace dstColorspace, /* in */ IDeckLinkVideoBuffer_v15_3_1* dstBuffer, /* out */ IDeckLinkVideoFrame** dstFrame) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkVideoConversion_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkProfileAttributes_v15_3_1 - Created by QueryInterface from an IDeckLinkProfile, or from IDeckLink. When queried from IDeckLink, interrogates the active profile */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkProfileAttributes_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT GetFlag (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ bool* value) = 0;
|
|
||||||
virtual HRESULT GetInt (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ int64_t* value) = 0;
|
|
||||||
virtual HRESULT GetFloat (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ double* value) = 0;
|
|
||||||
virtual HRESULT GetString (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ const char** value) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkProfileAttributes_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Interface IDeckLinkNotification_v15_3_1 - DeckLink Notification interface */
|
|
||||||
|
|
||||||
class BMD_PUBLIC IDeckLinkNotification_v15_3_1 : public IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT Subscribe (/* in */ BMDNotifications topic, /* in */ IDeckLinkNotificationCallback *theCallback) = 0;
|
|
||||||
virtual HRESULT Unsubscribe (/* in */ BMDNotifications topic, /* in */ IDeckLinkNotificationCallback *theCallback) = 0;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual ~IDeckLinkNotification_v15_3_1 () {} // call Release method to drop reference count
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Functions */
|
|
||||||
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
BMD_PUBLIC IDeckLinkIterator* CreateDeckLinkIteratorInstance_v15_3_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkDiscovery* CreateDeckLinkDiscoveryInstance_v15_3_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance_v15_3_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper_v15_3_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkGLScreenPreviewHelper* CreateOpenGL3ScreenPreviewHelper_v15_3_1(void); // Requires OpenGL 3.2 support and provides improved performance and color handling
|
|
||||||
BMD_PUBLIC IDeckLinkVideoConversion_v15_3_1* CreateVideoConversionInstance_v15_3_1(void);
|
|
||||||
BMD_PUBLIC IDeckLinkVideoFrameAncillaryPackets* CreateVideoFrameAncillaryPacketsInstance_v15_3_1(void); // For use when creating a custom IDeckLinkVideoFrame without wrapping IDeckLinkOutput::CreateVideoFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // defined(__cplusplus)
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
/* -LICENSE-START-
|
|
||||||
** Copyright (c) 2009 Blackmagic Design
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person or organization
|
|
||||||
** obtaining a copy of the software and accompanying documentation (the
|
|
||||||
** "Software") to use, reproduce, display, distribute, sub-license, 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, in
|
|
||||||
** accordance with:
|
|
||||||
**
|
|
||||||
** (1) if the Software is obtained from Blackmagic Design, the End User License
|
|
||||||
** Agreement for the Software Development Kit ("EULA") available at
|
|
||||||
** https://www.blackmagicdesign.com/EULA/DeckLinkSDK; or
|
|
||||||
**
|
|
||||||
** (2) if the Software is obtained from any third party, such licensing terms
|
|
||||||
** as notified by that third party,
|
|
||||||
**
|
|
||||||
** and all subject to the following:
|
|
||||||
**
|
|
||||||
** (3) 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.
|
|
||||||
**
|
|
||||||
** (4) 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.
|
|
||||||
**
|
|
||||||
** A copy of the Software is available free of charge at
|
|
||||||
** https://www.blackmagicdesign.com/desktopvideo_sdk under the EULA.
|
|
||||||
**
|
|
||||||
** -LICENSE-END-
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __LINUX_COM_H_
|
|
||||||
#define __LINUX_COM_H_
|
|
||||||
|
|
||||||
struct REFIID
|
|
||||||
{
|
|
||||||
unsigned char byte0;
|
|
||||||
unsigned char byte1;
|
|
||||||
unsigned char byte2;
|
|
||||||
unsigned char byte3;
|
|
||||||
unsigned char byte4;
|
|
||||||
unsigned char byte5;
|
|
||||||
unsigned char byte6;
|
|
||||||
unsigned char byte7;
|
|
||||||
unsigned char byte8;
|
|
||||||
unsigned char byte9;
|
|
||||||
unsigned char byte10;
|
|
||||||
unsigned char byte11;
|
|
||||||
unsigned char byte12;
|
|
||||||
unsigned char byte13;
|
|
||||||
unsigned char byte14;
|
|
||||||
unsigned char byte15;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef REFIID CFUUIDBytes;
|
|
||||||
#define CFUUIDGetUUIDBytes(x) x
|
|
||||||
|
|
||||||
typedef int HRESULT;
|
|
||||||
typedef unsigned long ULONG;
|
|
||||||
typedef void *LPVOID;
|
|
||||||
|
|
||||||
#define SUCCEEDED(Status) ((HRESULT)(Status) >= 0)
|
|
||||||
#define FAILED(Status) ((HRESULT)(Status)<0)
|
|
||||||
|
|
||||||
#define IS_ERROR(Status) ((unsigned long)(Status) >> 31 == SEVERITY_ERROR)
|
|
||||||
#define HRESULT_CODE(hr) ((hr) & 0xFFFF)
|
|
||||||
#define HRESULT_FACILITY(hr) (((hr) >> 16) & 0x1fff)
|
|
||||||
#define HRESULT_SEVERITY(hr) (((hr) >> 31) & 0x1)
|
|
||||||
#define SEVERITY_SUCCESS 0
|
|
||||||
#define SEVERITY_ERROR 1
|
|
||||||
|
|
||||||
#define MAKE_HRESULT(sev,fac,code) ((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) )
|
|
||||||
|
|
||||||
#define S_OK ((HRESULT)0x00000000L)
|
|
||||||
#define S_FALSE ((HRESULT)0x00000001L)
|
|
||||||
#define E_UNEXPECTED ((HRESULT)0x8000FFFFL)
|
|
||||||
#define E_NOTIMPL ((HRESULT)0x80000001L)
|
|
||||||
#define E_OUTOFMEMORY ((HRESULT)0x80000002L)
|
|
||||||
#define E_INVALIDARG ((HRESULT)0x80000003L)
|
|
||||||
#define E_NOINTERFACE ((HRESULT)0x80000004L)
|
|
||||||
#define E_POINTER ((HRESULT)0x80000005L)
|
|
||||||
#define E_HANDLE ((HRESULT)0x80000006L)
|
|
||||||
#define E_ABORT ((HRESULT)0x80000007L)
|
|
||||||
#define E_FAIL ((HRESULT)0x80000008L)
|
|
||||||
#define E_ACCESSDENIED ((HRESULT)0x80000009L)
|
|
||||||
|
|
||||||
#define STDMETHODCALLTYPE
|
|
||||||
|
|
||||||
#define IID_IUnknown (REFIID){0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}
|
|
||||||
#define IUnknownUUID IID_IUnknown
|
|
||||||
|
|
||||||
#ifndef BMD_PUBLIC
|
|
||||||
#define BMD_PUBLIC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
class BMD_PUBLIC IUnknown
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) = 0;
|
|
||||||
virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;
|
|
||||||
virtual ULONG STDMETHODCALLTYPE Release(void) = 0;
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -135,15 +135,6 @@ async function gracefulShutdown(signal) {
|
||||||
console.error('[shutdown] failed to flag empty asset:', e.message);
|
console.error('[shutdown] failed to flag empty asset:', e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (completed.growingPath) {
|
|
||||||
// Growing-files recorder: the master lives on the SMB share as a .ts,
|
|
||||||
// NOT in S3 yet. The promotion worker (which watches the same share)
|
|
||||||
// uploads it to S3 and enqueues the proxy from the real, finalized key.
|
|
||||||
// We must NOT call /finalize here: that sets original_s3_key to a key
|
|
||||||
// that doesn't exist yet and enqueues a proxy that instantly fails with
|
|
||||||
// "unable to open the file on disk." Leave the asset 'live' for the
|
|
||||||
// promotion worker to flip to 'ready'.
|
|
||||||
console.log(`[shutdown] growing capture finalized on share (${completed.growingPath}); leaving promotion worker to upload + proxy`);
|
|
||||||
} else if (liveAssetId) {
|
} else if (liveAssetId) {
|
||||||
// Finalise the pre-created live asset by id (avoids POST / 409 collision).
|
// Finalise the pre-created live asset by id (avoids POST / 409 collision).
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -325,13 +325,9 @@ router.post('/start', async (req, res) => {
|
||||||
error: `${source_type.toUpperCase()} caller mode requires: source_url`,
|
error: `${source_type.toUpperCase()} caller mode requires: source_url`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (source_type === 'deltacast') {
|
|
||||||
if (device === undefined || device === null) {
|
|
||||||
return res.status(400).json({ error: 'deltacast source requires: device (board/port index)' });
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: `Unknown source_type: ${source_type}. Must be sdi, srt, rtmp, or deltacast`,
|
error: `Unknown source_type: ${source_type}. Must be sdi, srt, or rtmp`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@
|
||||||
"bullmq": "^5.5.0",
|
"bullmq": "^5.5.0",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5"
|
||||||
"qrcode": "^1.5.4",
|
|
||||||
"google-auth-library": "^9.14.0"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0"
|
"node": ">=22.0.0"
|
||||||
|
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
// Per-project authorization — the single source of truth for "can this user
|
|
||||||
// touch this project?". v1 auth answers "are you logged in?"; this answers
|
|
||||||
// "which projects, and at what level?".
|
|
||||||
//
|
|
||||||
// Model (locked with Zac):
|
|
||||||
// - role 'admin' → global bypass; every project at 'edit'.
|
|
||||||
// - role 'editor'/'viewer' → scoped to projects granted to them directly
|
|
||||||
// (project_access subject_type='user') or via a
|
|
||||||
// group they belong to (subject_type='group').
|
|
||||||
// - grant level 'view' → read-only; 'edit' → read-write.
|
|
||||||
//
|
|
||||||
// A user's effective level on a project is the MAX of every matching grant
|
|
||||||
// (direct + each group). 'edit' outranks 'view'.
|
|
||||||
//
|
|
||||||
// All functions take an optional `db` (defaults to the shared pool) so tests
|
|
||||||
// can inject an isolated test pool.
|
|
||||||
|
|
||||||
import defaultPool from '../db/pool.js';
|
|
||||||
|
|
||||||
const LEVEL_RANK = { view: 1, edit: 2 };
|
|
||||||
|
|
||||||
export function isAdmin(user) {
|
|
||||||
return user?.role === 'admin';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the higher of two levels (either may be null/undefined).
|
|
||||||
function maxLevel(a, b) {
|
|
||||||
const ra = LEVEL_RANK[a] || 0;
|
|
||||||
const rb = LEVEL_RANK[b] || 0;
|
|
||||||
if (ra === 0 && rb === 0) return null;
|
|
||||||
return ra >= rb ? a : b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve every project the user can see, with their effective level.
|
|
||||||
// admin → { all: true, ids: null, levelByProject: null }
|
|
||||||
// else → { all: false, ids: Set<projectId>, levelByProject: Map<projectId, 'view'|'edit'> }
|
|
||||||
export async function accessibleProjectIds(user, db = defaultPool) {
|
|
||||||
if (isAdmin(user)) return { all: true, ids: null, levelByProject: null };
|
|
||||||
|
|
||||||
const levelByProject = new Map();
|
|
||||||
if (!user?.id) return { all: false, ids: new Set(), levelByProject };
|
|
||||||
|
|
||||||
const { rows } = await db.query(
|
|
||||||
`SELECT pa.project_id, pa.level
|
|
||||||
FROM project_access pa
|
|
||||||
WHERE (pa.subject_type = 'user' AND pa.subject_id = $1)
|
|
||||||
OR (pa.subject_type = 'group' AND pa.subject_id IN (
|
|
||||||
SELECT group_id FROM user_groups WHERE user_id = $1
|
|
||||||
))`,
|
|
||||||
[user.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const r of rows) {
|
|
||||||
levelByProject.set(r.project_id, maxLevel(levelByProject.get(r.project_id), r.level));
|
|
||||||
}
|
|
||||||
return { all: false, ids: new Set(levelByProject.keys()), levelByProject };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Effective level on a single project: 'edit' | 'view' | null.
|
|
||||||
export async function projectLevel(user, projectId, db = defaultPool) {
|
|
||||||
if (isAdmin(user)) return 'edit';
|
|
||||||
if (!user?.id || !projectId) return null;
|
|
||||||
|
|
||||||
const { rows } = await db.query(
|
|
||||||
`SELECT pa.level
|
|
||||||
FROM project_access pa
|
|
||||||
WHERE pa.project_id = $1
|
|
||||||
AND ( (pa.subject_type = 'user' AND pa.subject_id = $2)
|
|
||||||
OR (pa.subject_type = 'group' AND pa.subject_id IN (
|
|
||||||
SELECT group_id FROM user_groups WHERE user_id = $2
|
|
||||||
)) )`,
|
|
||||||
[projectId, user.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
let level = null;
|
|
||||||
for (const r of rows) level = maxLevel(level, r.level);
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Throw a 403-shaped error (caught by errorHandler) unless the user has at
|
|
||||||
// least `need` access on the project. `need` ∈ 'view' | 'edit'.
|
|
||||||
export async function assertProjectAccess(user, projectId, need = 'view', db = defaultPool) {
|
|
||||||
if (isAdmin(user)) return;
|
|
||||||
const have = await projectLevel(user, projectId, db);
|
|
||||||
if (!have || (LEVEL_RANK[have] || 0) < (LEVEL_RANK[need] || 0)) {
|
|
||||||
const err = new Error('forbidden');
|
|
||||||
err.status = 403;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
// Google OAuth (OIDC) sign-in helpers.
|
|
||||||
//
|
|
||||||
// Entirely config-gated: if GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET /
|
|
||||||
// OAUTH_REDIRECT_URL aren't set, isConfigured() is false and the routes 404, so
|
|
||||||
// a deployment without Google SSO behaves exactly as before. google-auth-library
|
|
||||||
// is imported lazily so the dependency is only required when the feature is on.
|
|
||||||
//
|
|
||||||
// Flow: /auth/google redirects to Google's consent screen with a signed `state`;
|
|
||||||
// /auth/google/callback exchanges the code, verifies the ID token, enforces the
|
|
||||||
// allowed Workspace domain, and auto-provisions a viewer account on first login.
|
|
||||||
|
|
||||||
const SCOPES = ['openid', 'email', 'profile'];
|
|
||||||
|
|
||||||
export function isConfigured() {
|
|
||||||
return !!(process.env.GOOGLE_CLIENT_ID
|
|
||||||
&& process.env.GOOGLE_CLIENT_SECRET
|
|
||||||
&& process.env.OAUTH_REDIRECT_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function allowedDomain() {
|
|
||||||
return (process.env.GOOGLE_ALLOWED_DOMAIN || '').trim().toLowerCase() || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lazily build an OAuth2 client (throws a clear error if the dep is missing).
|
|
||||||
async function makeClient() {
|
|
||||||
let OAuth2Client;
|
|
||||||
try {
|
|
||||||
({ OAuth2Client } = await import('google-auth-library'));
|
|
||||||
} catch {
|
|
||||||
const err = new Error('google-auth-library is not installed');
|
|
||||||
err.status = 500;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return new OAuth2Client({
|
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
||||||
redirectUri: process.env.OAUTH_REDIRECT_URL,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL of Google's consent screen. `state` is an opaque anti-CSRF token we also
|
|
||||||
// stash in the session and re-check on callback.
|
|
||||||
export async function buildAuthUrl(state) {
|
|
||||||
const client = await makeClient();
|
|
||||||
return client.generateAuthUrl({
|
|
||||||
access_type: 'online',
|
|
||||||
scope: SCOPES,
|
|
||||||
state,
|
|
||||||
prompt: 'select_account',
|
|
||||||
// If a Workspace domain is configured, hint Google to scope the picker to it.
|
|
||||||
...(allowedDomain() ? { hd: allowedDomain() } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exchange the authorization code and verify the returned ID token. Returns the
|
|
||||||
// verified { sub, email, name, hd } payload. Throws { status } on any failure.
|
|
||||||
export async function exchangeAndVerify(code) {
|
|
||||||
const client = await makeClient();
|
|
||||||
const { tokens } = await client.getToken(code);
|
|
||||||
if (!tokens.id_token) {
|
|
||||||
const err = new Error('no id_token from Google'); err.status = 401; throw err;
|
|
||||||
}
|
|
||||||
const ticket = await client.verifyIdToken({
|
|
||||||
idToken: tokens.id_token,
|
|
||||||
audience: process.env.GOOGLE_CLIENT_ID,
|
|
||||||
});
|
|
||||||
const p = ticket.getPayload();
|
|
||||||
if (!p || !p.sub) {
|
|
||||||
const err = new Error('invalid id_token'); err.status = 401; throw err;
|
|
||||||
}
|
|
||||||
// Require an explicitly verified email — a missing/undefined claim is NOT
|
|
||||||
// treated as verified, since the email drives account linking/provisioning.
|
|
||||||
if (!p.email || p.email_verified !== true) {
|
|
||||||
const err = new Error('email not verified'); err.status = 403; throw err;
|
|
||||||
}
|
|
||||||
const domain = allowedDomain();
|
|
||||||
if (domain) {
|
|
||||||
// ONLY trust Google's `hd` (hosted-domain) claim — it's present iff the
|
|
||||||
// account is a member of a Google Workspace domain that Google itself
|
|
||||||
// has verified. The email-suffix fallback we used to allow let any
|
|
||||||
// non-Workspace account with a spoof-friendly email through; if a
|
|
||||||
// GOOGLE_ALLOWED_DOMAIN is set, the operator means "only this Workspace,"
|
|
||||||
// and consumer accounts (no hd) must be rejected.
|
|
||||||
const hd = (p.hd || '').toLowerCase();
|
|
||||||
if (hd !== domain) {
|
|
||||||
const err = new Error('domain not allowed'); err.status = 403; throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { sub: p.sub, email: p.email, name: p.name || p.email, hd: p.hd || null };
|
|
||||||
}
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
// Short-lived MFA tickets bridging the two login steps.
|
|
||||||
//
|
|
||||||
// When a user with TOTP enabled passes password auth, we don't create a session
|
|
||||||
// yet — we hand back an opaque ticket. The second request (code or recovery
|
|
||||||
// code) redeems the ticket to finish login. Tickets are single-use and expire
|
|
||||||
// fast so a stolen ticket is near-useless.
|
|
||||||
//
|
|
||||||
// Tickets are bound to the issuing request's IP and User-Agent (hashed). A
|
|
||||||
// stolen ticket replayed from a different origin redeems to null. This is
|
|
||||||
// defense in depth against ticket exfiltration via a logged proxy, browser
|
|
||||||
// extension, or shoulder-surf; it does not stop an attacker who is on the same
|
|
||||||
// IP and UA.
|
|
||||||
//
|
|
||||||
// In-memory + single-instance, matching the existing login rate-limiter
|
|
||||||
// (auth/rate-limit.js). Documented limitation: in a multi-instance deployment
|
|
||||||
// the second step must hit the same node. Acceptable for Dragonflight's
|
|
||||||
// one-mam-api-per-node shape; revisit if that changes.
|
|
||||||
import { randomBytes, createHash } from 'node:crypto';
|
|
||||||
|
|
||||||
const TTL_MS = 5 * 60 * 1000; // 5 minutes to enter a code
|
|
||||||
const tickets = new Map(); // id -> { userId, ipHash, uaHash, expiresAt }
|
|
||||||
|
|
||||||
function sweep() {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [id, t] of tickets) if (t.expiresAt <= now) tickets.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashBinding(value) {
|
|
||||||
return createHash('sha256').update(String(value || '')).digest('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function issueTicket(userId, { ip, userAgent } = {}) {
|
|
||||||
sweep();
|
|
||||||
const id = randomBytes(32).toString('hex');
|
|
||||||
tickets.set(id, {
|
|
||||||
userId,
|
|
||||||
ipHash: hashBinding(ip),
|
|
||||||
uaHash: hashBinding(userAgent),
|
|
||||||
expiresAt: Date.now() + TTL_MS,
|
|
||||||
});
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redeem (and consume) a ticket. Returns the userId, or null if missing,
|
|
||||||
// expired, or the binding doesn't match the redeeming request.
|
|
||||||
export function redeemTicket(id, { ip, userAgent } = {}) {
|
|
||||||
if (!id) return null;
|
|
||||||
const t = tickets.get(id);
|
|
||||||
if (!t) return null;
|
|
||||||
tickets.delete(id); // single-use — burn even on binding mismatch so a
|
|
||||||
// wrong-binding probe can't be retried.
|
|
||||||
if (t.expiresAt <= Date.now()) return null;
|
|
||||||
// If a caller doesn't supply bindings (e.g. tests), accept — the issue side
|
|
||||||
// controls whether bindings get recorded.
|
|
||||||
if (ip !== undefined && t.ipHash !== hashBinding(ip)) return null;
|
|
||||||
if (userAgent !== undefined && t.uaHash !== hashBinding(userAgent)) return null;
|
|
||||||
return t.userId;
|
|
||||||
}
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
// TOTP (RFC 6238) implemented on node:crypto — no runtime dependency.
|
|
||||||
//
|
|
||||||
// Why hand-rolled: the algorithm is small and stable, and avoiding a dep keeps
|
|
||||||
// the auth core auditable. Verified against the RFC 6238 Appendix B test vectors
|
|
||||||
// in test/auth/totp.test.js.
|
|
||||||
//
|
|
||||||
// Defaults match every mainstream authenticator app (Google Authenticator,
|
|
||||||
// Authy, 1Password): SHA-1, 6 digits, 30-second step.
|
|
||||||
|
|
||||||
import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
||||||
|
|
||||||
const DIGITS = 6;
|
|
||||||
const STEP_SECONDS = 30;
|
|
||||||
const RFC4648_B32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
||||||
|
|
||||||
// ── base32 (RFC 4648, no padding) ──────────────────────────────────────────
|
|
||||||
export function base32Encode(buf) {
|
|
||||||
let bits = 0, value = 0, out = '';
|
|
||||||
for (const byte of buf) {
|
|
||||||
value = (value << 8) | byte;
|
|
||||||
bits += 8;
|
|
||||||
while (bits >= 5) {
|
|
||||||
out += RFC4648_B32[(value >>> (bits - 5)) & 31];
|
|
||||||
bits -= 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bits > 0) out += RFC4648_B32[(value << (5 - bits)) & 31];
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function base32Decode(str) {
|
|
||||||
const clean = str.replace(/=+$/,'').toUpperCase().replace(/\s+/g, '');
|
|
||||||
let bits = 0, value = 0;
|
|
||||||
const out = [];
|
|
||||||
for (const ch of clean) {
|
|
||||||
const idx = RFC4648_B32.indexOf(ch);
|
|
||||||
if (idx === -1) continue; // skip stray chars
|
|
||||||
value = (value << 5) | idx;
|
|
||||||
bits += 5;
|
|
||||||
if (bits >= 8) {
|
|
||||||
out.push((value >>> (bits - 8)) & 0xff);
|
|
||||||
bits -= 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Buffer.from(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a new base32 secret (20 random bytes = 160 bits, the RFC-recommended
|
|
||||||
// SHA-1 key length).
|
|
||||||
export function generateSecret() {
|
|
||||||
return base32Encode(randomBytes(20));
|
|
||||||
}
|
|
||||||
|
|
||||||
// HOTP for a specific counter (RFC 4226).
|
|
||||||
function hotp(secretBuf, counter) {
|
|
||||||
const buf = Buffer.alloc(8);
|
|
||||||
// 64-bit big-endian counter.
|
|
||||||
buf.writeUInt32BE(Math.floor(counter / 0x100000000), 0);
|
|
||||||
buf.writeUInt32BE(counter >>> 0, 4);
|
|
||||||
const hmac = createHmac('sha1', secretBuf).update(buf).digest();
|
|
||||||
const offset = hmac[hmac.length - 1] & 0x0f;
|
|
||||||
const code = ((hmac[offset] & 0x7f) << 24)
|
|
||||||
| ((hmac[offset + 1] & 0xff) << 16)
|
|
||||||
| ((hmac[offset + 2] & 0xff) << 8)
|
|
||||||
| (hmac[offset + 3] & 0xff);
|
|
||||||
return String(code % (10 ** DIGITS)).padStart(DIGITS, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
// The TOTP code for a given time (defaults to now).
|
|
||||||
export function generateToken(base32Secret, atMs = Date.now()) {
|
|
||||||
const counter = Math.floor(atMs / 1000 / STEP_SECONDS);
|
|
||||||
return hotp(base32Decode(base32Secret), counter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify a user-supplied code, allowing ±`window` steps of clock drift
|
|
||||||
// (default ±1 = 90s total tolerance). Constant-time compare per candidate.
|
|
||||||
//
|
|
||||||
// Returns the matched counter on success (so callers can persist it for
|
|
||||||
// replay protection — RFC 6238 §5.2), or null on failure. Boolean truthiness
|
|
||||||
// still works for the common case (`if (verifyToken(...))`).
|
|
||||||
export function verifyToken(base32Secret, token, atMs = Date.now(), window = 1) {
|
|
||||||
if (!base32Secret || !token) return null;
|
|
||||||
const cleaned = String(token).replace(/\s+/g, '');
|
|
||||||
if (!/^\d{6}$/.test(cleaned)) return null;
|
|
||||||
const secretBuf = base32Decode(base32Secret);
|
|
||||||
const counter = Math.floor(atMs / 1000 / STEP_SECONDS);
|
|
||||||
const want = Buffer.from(cleaned);
|
|
||||||
for (let w = -window; w <= window; w++) {
|
|
||||||
const candidate = Buffer.from(hotp(secretBuf, counter + w));
|
|
||||||
if (candidate.length === want.length && timingSafeEqual(candidate, want)) return counter + w;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The otpauth:// URI an authenticator app scans. label/issuer show in the app.
|
|
||||||
export function otpauthURI(base32Secret, accountName, issuer = 'Dragonflight') {
|
|
||||||
const label = encodeURIComponent(`${issuer}:${accountName}`);
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
secret: base32Secret,
|
|
||||||
issuer,
|
|
||||||
algorithm: 'SHA1',
|
|
||||||
digits: String(DIGITS),
|
|
||||||
period: String(STEP_SECONDS),
|
|
||||||
});
|
|
||||||
return `otpauth://totp/${label}?${params.toString()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate N human-friendly one-time recovery codes (raw form). Caller hashes
|
|
||||||
// them before storage and shows the raw set to the user exactly once.
|
|
||||||
export function generateRecoveryCodes(n = 10) {
|
|
||||||
const codes = [];
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
// 10 hex chars in two dash-separated groups, e.g. "a1b2c-3d4e5".
|
|
||||||
const hex = randomBytes(5).toString('hex');
|
|
||||||
codes.push(hex.slice(0, 5) + '-' + hex.slice(5));
|
|
||||||
}
|
|
||||||
return codes;
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
-- HLS VOD playback: per-asset HLS rendition (fMP4) generated by the proxy
|
|
||||||
-- worker alongside the MP4 proxy. Presence of hls_s3_key means an HLS
|
|
||||||
-- playlist exists at hls/<asset_id>/playlist.m3u8 and /assets/:id/stream
|
|
||||||
-- should prefer it (type: 'hls') over the MP4 range-stitched /video path.
|
|
||||||
ALTER TABLE assets ADD COLUMN IF NOT EXISTS hls_s3_key TEXT;
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
-- Migration 026 — per-project access grants (RBAC v2).
|
|
||||||
--
|
|
||||||
-- v1 auth is flat: any logged-in user can do everything. This adds per-project
|
|
||||||
-- scoping. A grant targets either a user or a group (polymorphic subject) and
|
|
||||||
-- carries a level: 'view' (read-only) or 'edit' (read-write). Admins bypass all
|
|
||||||
-- of this in code (authz.js) and need no rows here.
|
|
||||||
--
|
|
||||||
-- subject_id is intentionally NOT a foreign key — it points at either users.id
|
|
||||||
-- or groups.id depending on subject_type. Rows are cleaned up when the project
|
|
||||||
-- is deleted (FK cascade). A deleted user/group leaves an orphan row that
|
|
||||||
-- resolves to nobody (harmless); a later sweep can prune them if desired.
|
|
||||||
|
|
||||||
DO $$ BEGIN
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'access_level') THEN
|
|
||||||
CREATE TYPE access_level AS ENUM ('view', 'edit');
|
|
||||||
END IF;
|
|
||||||
END $$;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS project_access (
|
|
||||||
project_id UUID NOT NULL REFERENCES projects ON DELETE CASCADE,
|
|
||||||
subject_type TEXT NOT NULL CHECK (subject_type IN ('user', 'group')),
|
|
||||||
subject_id UUID NOT NULL,
|
|
||||||
level access_level NOT NULL DEFAULT 'view',
|
|
||||||
granted_by UUID REFERENCES users ON DELETE SET NULL,
|
|
||||||
granted_at TIMESTAMPTZ DEFAULT NOW(),
|
|
||||||
PRIMARY KEY (project_id, subject_type, subject_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_project_access_subject
|
|
||||||
ON project_access (subject_type, subject_id);
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
-- Migration 027 — TOTP two-factor auth.
|
|
||||||
--
|
|
||||||
-- totp_secret holds the base32 shared secret once enrollment is confirmed
|
|
||||||
-- (NULL while disabled or mid-enrollment). totp_enabled flips true only after
|
|
||||||
-- the user verifies their first code, so a half-finished enrollment never locks
|
|
||||||
-- anyone out. Recovery codes are one-time bcrypt-hashed fallbacks; used_at marks
|
|
||||||
-- a code as spent.
|
|
||||||
|
|
||||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS totp_secret TEXT;
|
|
||||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS totp_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS user_recovery_codes (
|
|
||||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
||||||
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
|
|
||||||
code_hash TEXT NOT NULL,
|
|
||||||
used_at TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_user_recovery_codes_user ON user_recovery_codes(user_id);
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
-- Migration 028 — Google OAuth (OIDC) sign-in.
|
|
||||||
--
|
|
||||||
-- google_sub is Google's stable subject identifier — the join key for a linked
|
|
||||||
-- or auto-provisioned account (unique, but NULL for password-only users).
|
|
||||||
-- email is captured for display + domain checks. password_hash becomes nullable
|
|
||||||
-- so an OAuth-only account can exist without a local password; such an account
|
|
||||||
-- simply can't use the password login path until an admin sets one.
|
|
||||||
|
|
||||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_sub TEXT;
|
|
||||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS email TEXT;
|
|
||||||
ALTER TABLE users ALTER COLUMN password_hash DROP NOT NULL;
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_google_sub ON users(google_sub) WHERE google_sub IS NOT NULL;
|
|
||||||
|
|
@ -1,165 +0,0 @@
|
||||||
-- Migration 029 — Playout / Master Control (MCR).
|
|
||||||
--
|
|
||||||
-- Adds a broadcast playout subsystem: take library assets, arrange them on a
|
|
||||||
-- playlist (Phase A) or a wall-clock timeline (Phase B), and play them out
|
|
||||||
-- continuously to SDI (DeckLink) / NDI / SRT / RTMP via a CasparCG sidecar.
|
|
||||||
--
|
|
||||||
-- This is the mirror of the capture path (input -> ffmpeg -> S3). A channel is
|
|
||||||
-- placed on a cluster node by capability the same way recorders claim input
|
|
||||||
-- ports; the engine container is spawned via the same Docker-socket /
|
|
||||||
-- node-agent orchestration. See docs/superpowers/specs/2026-05-30-playout-mcr-design.md.
|
|
||||||
--
|
|
||||||
-- Tables:
|
|
||||||
-- playout_channels — a logical output (one channel -> one CasparCG instance -> one target)
|
|
||||||
-- playout_playlists — an ordered list of items bound to a channel (Phase A)
|
|
||||||
-- playout_items — one clip on a playlist OR one row on the timeline
|
|
||||||
-- playout_sidecars — running CasparCG sidecar registry (one per channel; health-checked)
|
|
||||||
-- playout_schedule — wall-clock day-ahead rows (Phase B; unused in A)
|
|
||||||
-- playout_as_run — append-only log of what actually played (compliance)
|
|
||||||
|
|
||||||
-- ── Channels ───────────────────────────────────────────────────────────────
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_channels (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
node_id UUID REFERENCES cluster_nodes(id) ON DELETE SET NULL,
|
|
||||||
output_type TEXT NOT NULL DEFAULT 'srt',
|
|
||||||
-- output_config is consumer-shape-specific:
|
|
||||||
-- decklink: { "device_index": 1 }
|
|
||||||
-- ndi: { "ndi_name": "DRAGONFLIGHT CH1" }
|
|
||||||
-- srt: { "url": "srt://host:9000", "latency": 200 }
|
|
||||||
-- rtmp: { "url": "rtmp://host/live", "key": "streamkey" }
|
|
||||||
output_config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
||||||
-- 1080p59.94 is the house standard (matches capture cadence, streaming-friendly,
|
|
||||||
-- accepted by current SDI gear). Per-channel override allowed.
|
|
||||||
video_format TEXT NOT NULL DEFAULT '1080p5994',
|
|
||||||
status TEXT NOT NULL DEFAULT 'stopped',
|
|
||||||
container_id TEXT,
|
|
||||||
-- For remote channels the node-agent reports the reachable host:port of the
|
|
||||||
-- sidecar HTTP shim; stored here so the API can proxy transport calls.
|
|
||||||
container_meta JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
||||||
error_message TEXT,
|
|
||||||
-- Failover bookkeeping. Scheduler tick health-checks the sidecar; on N missed
|
|
||||||
-- checks the channel is re-placed on a healthy node (auto for ndi/srt/rtmp,
|
|
||||||
-- alert-only for decklink — device-index pinning makes re-placement non-trivial).
|
|
||||||
restart_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
last_restart_at TIMESTAMPTZ,
|
|
||||||
last_heartbeat_at TIMESTAMPTZ,
|
|
||||||
-- RBAC scoping: a NULL project_id resolves to admin-only (authz.js), the same
|
|
||||||
-- convention recorders use for unassigned resources.
|
|
||||||
project_id UUID REFERENCES projects(id) ON DELETE SET NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
CHECK (output_type IN ('decklink','ndi','srt','rtmp')),
|
|
||||||
CHECK (status IN ('stopped','starting','running','error'))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_channels_node ON playout_channels (node_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_channels_project ON playout_channels (project_id);
|
|
||||||
|
|
||||||
-- ── Playlists ──────────────────────────────────────────────────────────────
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_playlists (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
channel_id UUID NOT NULL REFERENCES playout_channels(id) ON DELETE CASCADE,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
loop BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_playlists_channel ON playout_playlists (channel_id);
|
|
||||||
|
|
||||||
-- ── Items ──────────────────────────────────────────────────────────────────
|
|
||||||
-- One entry on a playlist (Phase A, ordered by sort_order) OR one entry on the
|
|
||||||
-- timeline (Phase B, ordered by scheduled_at). in/out points reuse the editor's
|
|
||||||
-- subclip trim model (seconds). media_status tracks the S3 -> /media staging
|
|
||||||
-- (see playout-stage worker job); a clip cannot go on air until 'ready'.
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_items (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
playlist_id UUID REFERENCES playout_playlists(id) ON DELETE CASCADE,
|
|
||||||
asset_id UUID NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
|
|
||||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
||||||
scheduled_at TIMESTAMPTZ,
|
|
||||||
in_point NUMERIC,
|
|
||||||
out_point NUMERIC,
|
|
||||||
transition TEXT NOT NULL DEFAULT 'cut',
|
|
||||||
transition_ms INTEGER NOT NULL DEFAULT 0,
|
|
||||||
graphics JSONB,
|
|
||||||
media_status TEXT NOT NULL DEFAULT 'pending',
|
|
||||||
media_path TEXT,
|
|
||||||
-- Set when playout-stage has run loudnorm (EBU R128, -23 LUFS / -1 dBTP) on
|
|
||||||
-- the staged file. Re-stages skip the loudnorm pass when true.
|
|
||||||
audio_normalized BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
CHECK (transition IN ('cut','mix','wipe')),
|
|
||||||
CHECK (media_status IN ('pending','staging','ready','error'))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_items_playlist ON playout_items (playlist_id, sort_order);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_items_asset ON playout_items (asset_id);
|
|
||||||
|
|
||||||
-- ── Sidecars ───────────────────────────────────────────────────────────────
|
|
||||||
-- Running CasparCG container registry, one row per running channel. The
|
|
||||||
-- scheduler tick (src/scheduler.js) pings each sidecar's /status endpoint and
|
|
||||||
-- updates last_heartbeat_at; missed checks trigger the failover path in
|
|
||||||
-- routes/playout.js.
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_sidecars (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
channel_id UUID NOT NULL REFERENCES playout_channels(id) ON DELETE CASCADE,
|
|
||||||
node_id UUID REFERENCES cluster_nodes(id) ON DELETE SET NULL,
|
|
||||||
container_id TEXT NOT NULL,
|
|
||||||
sidecar_url TEXT, -- http://host:port for the shim
|
|
||||||
amcp_port INTEGER, -- in-container AMCP port (default 5250)
|
|
||||||
status TEXT NOT NULL DEFAULT 'running',
|
|
||||||
last_heartbeat_at TIMESTAMPTZ,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
CHECK (status IN ('starting','running','error','stopped'))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_playout_sidecars_channel ON playout_sidecars (channel_id)
|
|
||||||
WHERE status IN ('starting','running');
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_sidecars_status ON playout_sidecars (status);
|
|
||||||
|
|
||||||
-- ── Schedule (Phase B) ─────────────────────────────────────────────────────
|
|
||||||
-- Wall-clock day-ahead timeline. The scheduler tick (src/scheduler.js, under
|
|
||||||
-- the existing PG advisory lock) drives transitions and gap-fill. Unused by the
|
|
||||||
-- Phase A playlist player but created now so the schema is stable.
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_schedule (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
channel_id UUID NOT NULL REFERENCES playout_channels(id) ON DELETE CASCADE,
|
|
||||||
asset_id UUID REFERENCES assets(id) ON DELETE SET NULL,
|
|
||||||
scheduled_at TIMESTAMPTZ NOT NULL,
|
|
||||||
in_point NUMERIC,
|
|
||||||
out_point NUMERIC,
|
|
||||||
transition TEXT NOT NULL DEFAULT 'cut',
|
|
||||||
transition_ms INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_filler BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
status TEXT NOT NULL DEFAULT 'scheduled',
|
|
||||||
media_status TEXT NOT NULL DEFAULT 'pending',
|
|
||||||
media_path TEXT,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
CHECK (transition IN ('cut','mix','wipe')),
|
|
||||||
CHECK (status IN ('scheduled','playing','played','skipped','error')),
|
|
||||||
CHECK (media_status IN ('pending','staging','ready','error'))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_schedule_channel_time ON playout_schedule (channel_id, scheduled_at);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_schedule_status ON playout_schedule (status, scheduled_at);
|
|
||||||
|
|
||||||
-- ── As-run log ─────────────────────────────────────────────────────────────
|
|
||||||
-- Append-only record of what actually went to air. Never updated after insert.
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_as_run (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
channel_id UUID NOT NULL REFERENCES playout_channels(id) ON DELETE CASCADE,
|
|
||||||
asset_id UUID REFERENCES assets(id) ON DELETE SET NULL,
|
|
||||||
item_id UUID,
|
|
||||||
clip_name TEXT,
|
|
||||||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
ended_at TIMESTAMPTZ,
|
|
||||||
duration_s NUMERIC,
|
|
||||||
result TEXT NOT NULL DEFAULT 'played',
|
|
||||||
CHECK (result IN ('played','skipped','error'))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_as_run_channel ON playout_as_run (channel_id, started_at DESC);
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
-- Migration 030 — TOTP replay protection.
|
|
||||||
--
|
|
||||||
-- RFC 6238 §5.2 hardening: track the last counter value we accepted for each
|
|
||||||
-- user and reject codes at counters ≤ the last one. Without this, the same
|
|
||||||
-- 6-digit code can be submitted N times within its 30s step. Low impact in
|
|
||||||
-- practice (the code is only valid for ~90s with ±1 drift) but standard.
|
|
||||||
|
|
||||||
ALTER TABLE users
|
|
||||||
ADD COLUMN IF NOT EXISTS totp_last_counter BIGINT NOT NULL DEFAULT 0;
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
-- Migration 031 — Add last_seen_at to cluster_nodes
|
|
||||||
--
|
|
||||||
-- Playout failover (routes/playout.js restartChannel) queries cluster_nodes.last_seen_at
|
|
||||||
-- to find healthy nodes for channel re-placement. Column was missing from original
|
|
||||||
-- cluster schema; heartbeat endpoint updates it via /cluster/heartbeat.
|
|
||||||
|
|
||||||
ALTER TABLE cluster_nodes ADD COLUMN IF NOT EXISTS last_seen_at TIMESTAMPTZ;
|
|
||||||
|
|
||||||
-- Backfill existing nodes to NOW() so they're immediately eligible for failover
|
|
||||||
UPDATE cluster_nodes SET last_seen_at = NOW() WHERE last_seen_at IS NULL;
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
-- Migration 032: Per-recorder GPU affinity (Issue #167)
|
|
||||||
-- Adds a nullable GPU UUID to the recorders table so each recorder can be
|
|
||||||
-- pinned to a specific GPU on its node. The value is passed through to the
|
|
||||||
-- node-agent sidecar-start payload and becomes NVIDIA_VISIBLE_DEVICES for the
|
|
||||||
-- capture container. NULL = legacy behavior (NVIDIA_VISIBLE_DEVICES=all, i.e.
|
|
||||||
-- every GPU visible). Accepts an nvidia-smi GPU UUID (e.g. "GPU-xxxx") or a
|
|
||||||
-- numeric index string.
|
|
||||||
|
|
||||||
ALTER TABLE recorders
|
|
||||||
ADD COLUMN IF NOT EXISTS gpu_uuid TEXT DEFAULT NULL;
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
-- Migration 033 — SCTE-35 ad-break markers for playout.
|
|
||||||
--
|
|
||||||
-- Adds the missing SCTE-35 splice feature to the playout (MCR) subsystem. An
|
|
||||||
-- operator can either schedule an ad break on a channel's timeline (relative to
|
|
||||||
-- the active playlist position, or at a wall-clock time) or fire one immediately
|
|
||||||
-- ("splice now"). Each break is recorded here and, when fired, also written to
|
|
||||||
-- the append-only as-run log so it shows in the compliance record alongside the
|
|
||||||
-- clips that aired.
|
|
||||||
--
|
|
||||||
-- type:
|
|
||||||
-- splice_insert — a scheduled break (out → return), duration_s seconds long
|
|
||||||
-- immediate — fire-now splice (operator pressed "Trigger ad break now")
|
|
||||||
-- splice_out — open-ended avail out (provider break start)
|
|
||||||
-- splice_in — return-to-network (provider break end)
|
|
||||||
--
|
|
||||||
-- status: pending → fired (when the engine acts on it) → done (when the break
|
|
||||||
-- window has elapsed). cancelled is set if the operator removes a pending break.
|
|
||||||
--
|
|
||||||
-- The engine (services/playout) acts on a break by logging the cue, marking the
|
|
||||||
-- as-run row, and — where the output path supports it — injecting a real
|
|
||||||
-- SCTE-35 cue (see playout-manager.triggerScte for the injection point/TODO).
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS playout_scte_breaks (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
channel_id UUID NOT NULL REFERENCES playout_channels(id) ON DELETE CASCADE,
|
|
||||||
-- Position on the active playlist this break should fire after (0-based item
|
|
||||||
-- index). NULL for immediate/wall-clock breaks.
|
|
||||||
playlist_pos INTEGER,
|
|
||||||
-- Wall-clock fire time for scheduled breaks. NULL for immediate breaks.
|
|
||||||
scheduled_at TIMESTAMPTZ,
|
|
||||||
duration_s INTEGER NOT NULL DEFAULT 30,
|
|
||||||
-- SCTE-35 event id (the splice_event_id carried in the cue). Auto-assigned.
|
|
||||||
event_id INTEGER NOT NULL DEFAULT 1,
|
|
||||||
type TEXT NOT NULL DEFAULT 'splice_insert',
|
|
||||||
status TEXT NOT NULL DEFAULT 'pending',
|
|
||||||
fired_at TIMESTAMPTZ,
|
|
||||||
created_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
||||||
CHECK (type IN ('splice_insert','immediate','splice_out','splice_in')),
|
|
||||||
CHECK (status IN ('pending','fired','done','cancelled'))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_scte_channel ON playout_scte_breaks (channel_id, created_at DESC);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_playout_scte_status ON playout_scte_breaks (status);
|
|
||||||
|
|
||||||
-- As-run gains a 'scte' result so fired breaks land in the compliance log next to
|
|
||||||
-- the clips. The original migration constrained result to played/skipped/error;
|
|
||||||
-- widen it.
|
|
||||||
ALTER TABLE playout_as_run DROP CONSTRAINT IF EXISTS playout_as_run_result_check;
|
|
||||||
ALTER TABLE playout_as_run ADD CONSTRAINT playout_as_run_result_check
|
|
||||||
CHECK (result IN ('played','skipped','error','scte'));
|
|
||||||
|
|
@ -1,16 +1,4 @@
|
||||||
import { Pool, types } from 'pg';
|
import { Pool } from 'pg';
|
||||||
|
|
||||||
// node-postgres returns BIGINT (int8, OID 20) as a *string* by default, because
|
|
||||||
// a 64-bit integer can exceed JS Number.MAX_SAFE_INTEGER. Our int8 columns
|
|
||||||
// (duration_ms, file_size, …) are always well within 2^53, so a string here is
|
|
||||||
// pure footgun: it breaks any consumer that does arithmetic or comparison on the
|
|
||||||
// value (e.g. `duration_ms + x` silently string-concatenates, sorts go
|
|
||||||
// lexicographic, `!ms`/`Math.round` edge cases). Parse int8 to a real Number so
|
|
||||||
// the API always emits numeric duration_ms/file_size in its JSON.
|
|
||||||
//
|
|
||||||
// 20 = int8/bigint OID. Values above Number.MAX_SAFE_INTEGER would lose
|
|
||||||
// precision, but no column in this schema ever reaches that range.
|
|
||||||
types.setTypeParser(20, (val) => (val === null ? null : parseInt(val, 10)));
|
|
||||||
|
|
||||||
// Prefer DATABASE_URL (set in docker-compose) over individual DB_* vars
|
// Prefer DATABASE_URL (set in docker-compose) over individual DB_* vars
|
||||||
const pool = process.env.DATABASE_URL
|
const pool = process.env.DATABASE_URL
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import os from 'node:os';
|
||||||
import { exec } from 'node:child_process';
|
import { exec } from 'node:child_process';
|
||||||
import pool from './db/pool.js';
|
import pool from './db/pool.js';
|
||||||
import { errorHandler } from './middleware/errors.js';
|
import { errorHandler } from './middleware/errors.js';
|
||||||
import { requireAuth, requireUiHeader, requireAdmin } from './middleware/auth.js';
|
import { requireAuth, requireUiHeader } from './middleware/auth.js';
|
||||||
import { loadS3ConfigFromDb } from './s3/client.js';
|
import { loadS3ConfigFromDb } from './s3/client.js';
|
||||||
|
|
||||||
import authRouter from './routes/auth.js';
|
import authRouter from './routes/auth.js';
|
||||||
|
|
@ -22,7 +22,6 @@ import jobsRouter from './routes/jobs.js';
|
||||||
import captureRouter from './routes/capture.js';
|
import captureRouter from './routes/capture.js';
|
||||||
import uploadRouter from './routes/upload.js';
|
import uploadRouter from './routes/upload.js';
|
||||||
import recordersRouter from './routes/recorders.js';
|
import recordersRouter from './routes/recorders.js';
|
||||||
import playoutRouter from './routes/playout.js';
|
|
||||||
import settingsRouter from './routes/settings.js';
|
import settingsRouter from './routes/settings.js';
|
||||||
import amppRouter from './routes/ampp.js';
|
import amppRouter from './routes/ampp.js';
|
||||||
import groupsRouter from './routes/groups.js';
|
import groupsRouter from './routes/groups.js';
|
||||||
|
|
@ -41,12 +40,18 @@ import { startCleanupLoop } from './tasks/cleanupTempSegments.js';
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// ── Middleware ────────────────────────────────────────────────────────────────
|
||||||
|
// Tightened CORS — once cookies carry authority, `origin: true` would let
|
||||||
|
// any site forge requests with the cookie. Drive the allowlist from env.
|
||||||
const allowedOrigins = (process.env.ALLOWED_ORIGINS || '')
|
const allowedOrigins = (process.env.ALLOWED_ORIGINS || '')
|
||||||
.split(',').map(s => s.trim()).filter(Boolean);
|
.split(',').map(s => s.trim()).filter(Boolean);
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: (origin, cb) => {
|
origin: (origin, cb) => {
|
||||||
|
// No Origin header (same-origin or curl) — allow.
|
||||||
if (!origin) return cb(null, true);
|
if (!origin) return cb(null, true);
|
||||||
if (allowedOrigins.length === 0 || allowedOrigins.includes(origin)) return cb(null, true);
|
if (allowedOrigins.length === 0 || allowedOrigins.includes(origin)) return cb(null, true);
|
||||||
|
// Reject cleanly: omit the Allow-Origin header so the browser surfaces
|
||||||
|
// a real CORS error instead of a 500 from a thrown Error in the callback.
|
||||||
console.warn('[cors] rejected origin:', origin);
|
console.warn('[cors] rejected origin:', origin);
|
||||||
return cb(null, false);
|
return cb(null, false);
|
||||||
},
|
},
|
||||||
|
|
@ -54,8 +59,14 @@ app.use(cors({
|
||||||
}));
|
}));
|
||||||
app.use(express.json({ limit: '50mb' }));
|
app.use(express.json({ limit: '50mb' }));
|
||||||
|
|
||||||
|
// Trust the reverse proxy only when explicitly told to (production HTTPS).
|
||||||
if (process.env.TRUST_PROXY === 'true') app.set('trust proxy', 1);
|
if (process.env.TRUST_PROXY === 'true') app.set('trust proxy', 1);
|
||||||
|
|
||||||
|
// HSTS — once a browser has seen this header over HTTPS for dragonflight.live,
|
||||||
|
// it auto-upgrades every future http:// request to https:// before hitting the
|
||||||
|
// wire. Cookies are Secure-only (below) and the CORS allowlist rejects HTTP,
|
||||||
|
// so without HSTS a user who lands on http:// silently can't log in.
|
||||||
|
// Only emit on actual HTTPS responses; req.secure honors trust proxy + X-Forwarded-Proto.
|
||||||
if (process.env.AUTH_ENABLED === 'true') {
|
if (process.env.AUTH_ENABLED === 'true') {
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (req.secure) res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
if (req.secure) res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||||
|
|
@ -63,13 +74,17 @@ if (process.env.AUTH_ENABLED === 'true') {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hard-fail when production-mode auth has no stable session secret. Without
|
||||||
|
// this, express-session falls back to an in-memory random secret which
|
||||||
|
// invalidates every session on restart and breaks multi-node deployments.
|
||||||
if (process.env.AUTH_ENABLED === 'true' && !process.env.SESSION_SECRET) {
|
if (process.env.AUTH_ENABLED === 'true' && !process.env.SESSION_SECRET) {
|
||||||
console.error('[fatal] SESSION_SECRET is required when AUTH_ENABLED=true');
|
console.error('[fatal] SESSION_SECRET is required when AUTH_ENABLED=true');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Session — actually wired this time. See specs/2026-05-27-auth-system-design.md.
|
||||||
app.use(session({
|
app.use(session({
|
||||||
store: new PgStore({ pool, tableName: 'sessions', pruneSessionInterval: 60 * 15 }),
|
store: new PgStore({ pool, tableName: 'sessions', pruneSessionInterval: 60 * 15 /* seconds = 15 min */ }),
|
||||||
secret: process.env.SESSION_SECRET,
|
secret: process.env.SESSION_SECRET,
|
||||||
name: 'dragonflight.sid',
|
name: 'dragonflight.sid',
|
||||||
cookie: {
|
cookie: {
|
||||||
|
|
@ -79,26 +94,31 @@ app.use(session({
|
||||||
path: '/',
|
path: '/',
|
||||||
maxAge: 8 * 3600 * 1000,
|
maxAge: 8 * 3600 * 1000,
|
||||||
},
|
},
|
||||||
rolling: false,
|
rolling: false, // sliding renewal handled in requireAuth so idle + absolute can be enforced separately
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// ── Health ────────────────────────────────────────────────────────────────────
|
||||||
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
|
app.get('/health', (_req, res) => res.json({ status: 'ok' }));
|
||||||
|
|
||||||
const UNAUTH_PATHS = new Set([
|
// ── Auth gate ─────────────────────────────────────────────────────────────────
|
||||||
'/auth/login', '/auth/login/totp', '/auth/setup', '/auth/setup-required',
|
// req.path is relative to the /api/v1 mount, so /auth/login NOT /api/v1/auth/login.
|
||||||
'/auth/google', '/auth/google/callback', '/auth/google/enabled',
|
const UNAUTH_PATHS = new Set(['/auth/login', '/auth/setup', '/auth/setup-required']);
|
||||||
]);
|
// node-agent now authenticates /cluster/heartbeat with a bound api_token
|
||||||
|
// (migration 019 + bound_hostname on the token). requireAuth handles the
|
||||||
|
// bearer lookup and sets req.tokenBoundHostname; the heartbeat handler in
|
||||||
|
// routes/cluster.js verifies body.hostname matches that binding.
|
||||||
app.use('/api/v1', requireUiHeader);
|
app.use('/api/v1', requireUiHeader);
|
||||||
app.use('/api/v1', (req, res, next) => {
|
app.use('/api/v1', (req, res, next) => {
|
||||||
if (UNAUTH_PATHS.has(req.path)) return next();
|
if (UNAUTH_PATHS.has(req.path)) return next();
|
||||||
return requireAuth(req, res, next);
|
return requireAuth(req, res, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── API Routes ────────────────────────────────────────────────────────────────
|
||||||
app.use('/api/v1/auth', authRouter);
|
app.use('/api/v1/auth', authRouter);
|
||||||
app.use('/api/v1/auth/users', requireAdmin, usersRouter);
|
app.use('/api/v1/auth/users', usersRouter);
|
||||||
app.use('/api/v1/users', requireAdmin, usersRouter);
|
app.use('/api/v1/users', usersRouter); // alias for the existing SPA Users page that calls /api/v1/users; keeps the same auth gate
|
||||||
app.use('/api/v1/auth/tokens', requireAuth, tokensRouter);
|
app.use('/api/v1/auth/tokens', requireAuth, tokensRouter);
|
||||||
app.use('/api/v1/assets', assetsRouter);
|
app.use('/api/v1/assets', assetsRouter);
|
||||||
app.use('/api/v1/projects', projectsRouter);
|
app.use('/api/v1/projects', projectsRouter);
|
||||||
|
|
@ -107,10 +127,9 @@ app.use('/api/v1/jobs', jobsRouter);
|
||||||
app.use('/api/v1/capture', captureRouter);
|
app.use('/api/v1/capture', captureRouter);
|
||||||
app.use('/api/v1/upload', uploadRouter);
|
app.use('/api/v1/upload', uploadRouter);
|
||||||
app.use('/api/v1/recorders', recordersRouter);
|
app.use('/api/v1/recorders', recordersRouter);
|
||||||
app.use('/api/v1/playout', playoutRouter);
|
|
||||||
app.use('/api/v1/settings', settingsRouter);
|
app.use('/api/v1/settings', settingsRouter);
|
||||||
app.use('/api/v1/ampp', amppRouter);
|
app.use('/api/v1/ampp', amppRouter);
|
||||||
app.use('/api/v1/groups', requireAdmin, groupsRouter);
|
app.use('/api/v1/groups', groupsRouter);
|
||||||
app.use('/api/v1/sequences', sequencesRouter);
|
app.use('/api/v1/sequences', sequencesRouter);
|
||||||
app.use('/api/v1/system', systemRouter);
|
app.use('/api/v1/system', systemRouter);
|
||||||
app.use('/api/v1/cluster', clusterRouter);
|
app.use('/api/v1/cluster', clusterRouter);
|
||||||
|
|
@ -121,14 +140,21 @@ app.use('/api/v1/assets/:assetId/comments', commentsRouter);
|
||||||
app.use('/api/v1/imports', importsRouter);
|
app.use('/api/v1/imports', importsRouter);
|
||||||
app.use('/api/v1/storage', storageRouter);
|
app.use('/api/v1/storage', storageRouter);
|
||||||
|
|
||||||
|
// ── Error handler ─────────────────────────────────────────────────────────────
|
||||||
app.use(errorHandler);
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
// ── Start ────────────────────────────────────────────────────────────────────
|
||||||
import { readdirSync, readFileSync } from 'node:fs';
|
import { readdirSync, readFileSync } from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
|
|
||||||
const __dirnameMig = dirname(fileURLToPath(import.meta.url));
|
const __dirnameMig = dirname(fileURLToPath(import.meta.url));
|
||||||
async function runMigrations() {
|
async function runMigrations() {
|
||||||
|
// Issue #107 — previously the loop swallowed errors and let the server boot
|
||||||
|
// on a half-migrated schema. Now: track applied migrations in a table, run
|
||||||
|
// every pending one inside a transaction, and exit non-zero on failure so
|
||||||
|
// the orchestrator restarts (and so an operator notices) instead of serving
|
||||||
|
// 500s for the next month.
|
||||||
const dir = join(__dirnameMig, 'db', 'migrations');
|
const dir = join(__dirnameMig, 'db', 'migrations');
|
||||||
let files = [];
|
let files = [];
|
||||||
try { files = readdirSync(dir).filter(f => f.endsWith('.sql')).sort(); } catch { return; }
|
try { files = readdirSync(dir).filter(f => f.endsWith('.sql')).sort(); } catch { return; }
|
||||||
|
|
@ -141,6 +167,7 @@ async function runMigrations() {
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// Allow forcing a re-run via env when iterating locally.
|
||||||
const force = process.env.MIGRATIONS_FORCE === '1';
|
const force = process.env.MIGRATIONS_FORCE === '1';
|
||||||
const allowFailures = process.env.MIGRATIONS_ALLOW_FAILURES === '1';
|
const allowFailures = process.env.MIGRATIONS_ALLOW_FAILURES === '1';
|
||||||
|
|
||||||
|
|
@ -166,6 +193,7 @@ async function runMigrations() {
|
||||||
console.error('[migration] FAILED ' + f + ': ' + err.message);
|
console.error('[migration] FAILED ' + f + ': ' + err.message);
|
||||||
client.release();
|
client.release();
|
||||||
if (allowFailures) continue;
|
if (allowFailures) continue;
|
||||||
|
// Hard fail — better to crash now than serve traffic on a broken schema.
|
||||||
console.error('[migration] aborting startup. Set MIGRATIONS_ALLOW_FAILURES=1 to override.');
|
console.error('[migration] aborting startup. Set MIGRATIONS_ALLOW_FAILURES=1 to override.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
@ -174,9 +202,13 @@ async function runMigrations() {
|
||||||
}
|
}
|
||||||
await runMigrations();
|
await runMigrations();
|
||||||
|
|
||||||
|
// Load S3 config from DB so any settings saved via the Settings page override env vars
|
||||||
await loadS3ConfigFromDb();
|
await loadS3ConfigFromDb();
|
||||||
|
|
||||||
|
// ── Cluster self-heartbeat ────────────────────────────────────────────────────
|
||||||
function getLocalIp() {
|
function getLocalIp() {
|
||||||
|
// Prefer an explicit override — useful when running inside Docker where
|
||||||
|
// os.networkInterfaces() returns container bridge IPs, not the host LAN IP.
|
||||||
if (process.env.NODE_IP) return process.env.NODE_IP;
|
if (process.env.NODE_IP) return process.env.NODE_IP;
|
||||||
|
|
||||||
const ifaces = os.networkInterfaces();
|
const ifaces = os.networkInterfaces();
|
||||||
|
|
@ -188,6 +220,9 @@ function getLocalIp() {
|
||||||
return '127.0.0.1';
|
return '127.0.0.1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect NVIDIA GPUs available to this container via nvidia-smi.
|
||||||
|
// Returns an array like [{ index: 0, name: 'Tesla P4', memory_mb: 7680 }, ...]
|
||||||
|
// or an empty array if nvidia-smi is unavailable or no GPUs found.
|
||||||
function detectGpus() {
|
function detectGpus() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
exec(
|
exec(
|
||||||
|
|
@ -209,10 +244,6 @@ function detectGpus() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Primary mam-api node self-registers in cluster_nodes every 30s. Must write
|
|
||||||
// BOTH last_seen (legacy column) and last_seen_at (added by mig 031, used by
|
|
||||||
// playout failover) — otherwise the primary appears stale to the failover
|
|
||||||
// query and channels get re-placed off it incorrectly.
|
|
||||||
async function selfHeartbeat() {
|
async function selfHeartbeat() {
|
||||||
const load = os.loadavg()[0];
|
const load = os.loadavg()[0];
|
||||||
const total = os.totalmem();
|
const total = os.totalmem();
|
||||||
|
|
@ -224,15 +255,14 @@ async function selfHeartbeat() {
|
||||||
pool.query(
|
pool.query(
|
||||||
`INSERT INTO cluster_nodes
|
`INSERT INTO cluster_nodes
|
||||||
(hostname, ip_address, role, version, api_url,
|
(hostname, ip_address, role, version, api_url,
|
||||||
cpu_usage, mem_used_mb, mem_total_mb, capabilities, last_seen, last_seen_at)
|
cpu_usage, mem_used_mb, mem_total_mb, capabilities, last_seen)
|
||||||
VALUES ($1,$2,'primary',$3,$4,$5,$6,$7,$8,NOW(),NOW())
|
VALUES ($1,$2,'primary',$3,$4,$5,$6,$7,$8,NOW())
|
||||||
ON CONFLICT (hostname) DO UPDATE SET
|
ON CONFLICT (hostname) DO UPDATE SET
|
||||||
ip_address = EXCLUDED.ip_address,
|
ip_address = EXCLUDED.ip_address,
|
||||||
cpu_usage = EXCLUDED.cpu_usage,
|
cpu_usage = EXCLUDED.cpu_usage,
|
||||||
mem_used_mb = EXCLUDED.mem_used_mb,
|
mem_used_mb = EXCLUDED.mem_used_mb,
|
||||||
mem_total_mb = EXCLUDED.mem_total_mb,
|
mem_total_mb = EXCLUDED.mem_total_mb,
|
||||||
capabilities = EXCLUDED.capabilities,
|
capabilities = EXCLUDED.capabilities,
|
||||||
last_seen_at = NOW(),
|
|
||||||
last_seen = NOW()`,
|
last_seen = NOW()`,
|
||||||
[
|
[
|
||||||
process.env.NODE_HOSTNAME || os.hostname(),
|
process.env.NODE_HOSTNAME || os.hostname(),
|
||||||
|
|
@ -257,26 +287,39 @@ const server = app.listen(PORT, () => {
|
||||||
if (process.env.AUTH_ENABLED === 'true' && process.env.TRUST_PROXY !== 'true') {
|
if (process.env.AUTH_ENABLED === 'true' && process.env.TRUST_PROXY !== 'true') {
|
||||||
console.warn('[auth] WARNING: AUTH_ENABLED=true but TRUST_PROXY=false — req.ip will be the proxy IP, login rate-limit will throttle all clients together. Set TRUST_PROXY=true when behind nginx/HTTPS.');
|
console.warn('[auth] WARNING: AUTH_ENABLED=true but TRUST_PROXY=false — req.ip will be the proxy IP, login rate-limit will throttle all clients together. Set TRUST_PROXY=true when behind nginx/HTTPS.');
|
||||||
}
|
}
|
||||||
|
// Boot the recorder scheduler tick loop after the HTTP server is live so
|
||||||
|
// the loop's self-calls to /recorders/:id/start|stop reach a ready socket.
|
||||||
startSchedulerLoop();
|
startSchedulerLoop();
|
||||||
|
|
||||||
|
// Boot the temp-segment cleanup loop (runs hourly).
|
||||||
startCleanupLoop();
|
startCleanupLoop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Issue #100 — graceful shutdown. Without this, `docker stop` (SIGTERM) killed
|
||||||
|
// the process mid-scheduler-tick, leaving Redis connections and Docker
|
||||||
|
// sockets dangling and producing partial DB writes. Now: stop the scheduler,
|
||||||
|
// finish in-flight HTTP requests, close PG/Redis pools, and exit cleanly
|
||||||
|
// (or hard-exit after 25 s if something is stuck).
|
||||||
let _shuttingDown = false;
|
let _shuttingDown = false;
|
||||||
async function gracefulShutdown(signal) {
|
async function gracefulShutdown(signal) {
|
||||||
if (_shuttingDown) return;
|
if (_shuttingDown) return;
|
||||||
_shuttingDown = true;
|
_shuttingDown = true;
|
||||||
console.log(`[shutdown] received ${signal} — closing gracefully…`);
|
console.log(`[shutdown] received ${signal} — closing gracefully…`);
|
||||||
|
|
||||||
|
// Stop accepting new requests + wind down the scheduler tick.
|
||||||
try { stopSchedulerLoop(); } catch (_) {}
|
try { stopSchedulerLoop(); } catch (_) {}
|
||||||
|
|
||||||
|
// Force-exit watchdog so a hung connection can't keep us alive forever.
|
||||||
const killSwitch = setTimeout(() => {
|
const killSwitch = setTimeout(() => {
|
||||||
console.error('[shutdown] forced exit after 25s timeout');
|
console.error('[shutdown] forced exit after 25s timeout');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}, 25_000);
|
}, 25_000);
|
||||||
killSwitch.unref();
|
killSwitch.unref();
|
||||||
|
|
||||||
|
// Stop the HTTP server (waits for in-flight requests to finish).
|
||||||
await new Promise(resolve => server.close(resolve));
|
await new Promise(resolve => server.close(resolve));
|
||||||
|
|
||||||
|
// Close DB pool + S3 client + any other resources. Best-effort.
|
||||||
try { await pool.end(); } catch (e) { console.warn('[shutdown] pool.end:', e.message); }
|
try { await pool.end(); } catch (e) { console.warn('[shutdown] pool.end:', e.message); }
|
||||||
|
|
||||||
console.log('[shutdown] clean exit');
|
console.log('[shutdown] clean exit');
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,10 @@
|
||||||
import crypto from 'crypto';
|
|
||||||
import pool from '../db/pool.js';
|
import pool from '../db/pool.js';
|
||||||
import { parseBearer, hashToken } from '../auth/tokens.js';
|
import { parseBearer, hashToken } from '../auth/tokens.js';
|
||||||
|
|
||||||
// In-process service token for the scheduler's loopback self-calls
|
|
||||||
// (scheduler.js -> /recorders|/playout). The scheduler runs in THIS process, so
|
|
||||||
// a per-boot random constant needs no env/compose config and is never exposed:
|
|
||||||
// it only travels over the loopback fetch inside the same process. Multi-replica
|
|
||||||
// is safe because each replica's scheduler only ever calls 127.0.0.1 (itself),
|
|
||||||
// matching that replica's token. Requests bearing it are treated as the seeded
|
|
||||||
// admin (DEV_USER) so RBAC + FK-bearing routes work.
|
|
||||||
export const INTERNAL_TOKEN = crypto.randomBytes(32).toString('hex');
|
|
||||||
const INTERNAL_HEADER = 'x-internal-token';
|
|
||||||
|
|
||||||
function isInternalCall(req) {
|
|
||||||
const got = req.headers[INTERNAL_HEADER];
|
|
||||||
if (typeof got !== 'string' || got.length !== INTERNAL_TOKEN.length) return false;
|
|
||||||
return crypto.timingSafeEqual(Buffer.from(got), Buffer.from(INTERNAL_TOKEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stable UUID matching migration 023's seeded dev user.
|
// Stable UUID matching migration 023's seeded dev user.
|
||||||
/** UUID of the seeded dev-mode placeholder. NOT a sentinel for "any unauthenticated user". */
|
/** UUID of the seeded dev-mode placeholder. NOT a sentinel for "any unauthenticated user". */
|
||||||
export const DEV_USER_ID = '00000000-0000-4000-8000-000000000000';
|
export const DEV_USER_ID = '00000000-0000-4000-8000-000000000000';
|
||||||
// role 'admin' so dev mode (AUTH_ENABLED=false) keeps full access through the
|
export const DEV_USER = { id: DEV_USER_ID, username: 'dev', display_name: 'Dev (AUTH_ENABLED=false)' };
|
||||||
// RBAC v2 gates — matches migration 023's seeded dev row.
|
|
||||||
export const DEV_USER = { id: DEV_USER_ID, username: 'dev', display_name: 'Dev (AUTH_ENABLED=false)', role: 'admin' };
|
|
||||||
|
|
||||||
const ABSOLUTE_MS = 8 * 3600 * 1000;
|
const ABSOLUTE_MS = 8 * 3600 * 1000;
|
||||||
const IDLE_MS = 1 * 3600 * 1000;
|
const IDLE_MS = 1 * 3600 * 1000;
|
||||||
|
|
@ -37,18 +18,11 @@ async function destroyAnd401(req, res) {
|
||||||
|
|
||||||
async function loadUser(id) {
|
async function loadUser(id) {
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
`SELECT id, username, display_name, role, totp_enabled FROM users WHERE id = $1`, [id]);
|
`SELECT id, username, display_name, role FROM users WHERE id = $1`, [id]);
|
||||||
return rows[0] || null;
|
return rows[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function requireAuth(req, res, next) {
|
export async function requireAuth(req, res, next) {
|
||||||
// Internal loopback self-call (scheduler). Acts as the seeded admin so RBAC
|
|
||||||
// and FK-bearing routes work, regardless of AUTH_ENABLED.
|
|
||||||
if (isInternalCall(req)) {
|
|
||||||
req.user = DEV_USER;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dev mode — attach the seeded dev user so FK-bearing routes work.
|
// Dev mode — attach the seeded dev user so FK-bearing routes work.
|
||||||
if (process.env.AUTH_ENABLED !== 'true') {
|
if (process.env.AUTH_ENABLED !== 'true') {
|
||||||
req.user = DEV_USER;
|
req.user = DEV_USER;
|
||||||
|
|
@ -99,14 +73,6 @@ export async function requireAuth(req, res, next) {
|
||||||
return res.status(401).json({ error: 'unauthorized' });
|
return res.status(401).json({ error: 'unauthorized' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gate a route to admins only. requireAuth must run first (it sets req.user).
|
|
||||||
// 401 when unauthenticated, 403 when authenticated but not an admin.
|
|
||||||
export function requireAdmin(req, res, next) {
|
|
||||||
if (!req.user) return res.status(401).json({ error: 'unauthorized' });
|
|
||||||
if (req.user.role !== 'admin') return res.status(403).json({ error: 'forbidden' });
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Belt-and-suspenders CSRF: SameSite=Lax already blocks most cross-site
|
// Belt-and-suspenders CSRF: SameSite=Lax already blocks most cross-site
|
||||||
// cookie sends, but a custom header that no <form> can produce hardens
|
// cookie sends, but a custom header that no <form> can produce hardens
|
||||||
// against the edge cases. Applied to mutating verbs only.
|
// against the edge cases. Applied to mutating verbs only.
|
||||||
|
|
@ -122,8 +88,6 @@ const CSRF_EXEMPT_PATHS = new Set(['/cluster/heartbeat']);
|
||||||
|
|
||||||
export function requireUiHeader(req, res, next) {
|
export function requireUiHeader(req, res, next) {
|
||||||
if (!MUTATING.has(req.method)) return next();
|
if (!MUTATING.has(req.method)) return next();
|
||||||
// Internal loopback self-call (scheduler) — not a browser, can't be drive-by'd.
|
|
||||||
if (isInternalCall(req)) return next();
|
|
||||||
// Bearer-authed requests (Premiere panel, scripts) are exempt — they're not
|
// Bearer-authed requests (Premiere panel, scripts) are exempt — they're not
|
||||||
// browsers and can't be drive-by'd from another origin.
|
// browsers and can't be drive-by'd from another origin.
|
||||||
if (req.headers.authorization?.toLowerCase().startsWith('bearer ')) return next();
|
if (req.headers.authorization?.toLowerCase().startsWith('bearer ')) return next();
|
||||||
|
|
|
||||||
|
|
@ -7,36 +7,9 @@ import pool from '../db/pool.js';
|
||||||
import { getSignedUrlForObject, deleteObject, s3Client, getS3Bucket } from '../s3/client.js';
|
import { getSignedUrlForObject, deleteObject, s3Client, getS3Bucket } from '../s3/client.js';
|
||||||
import { GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
|
import { GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
|
||||||
import { validateUuid } from '../middleware/errors.js';
|
import { validateUuid } from '../middleware/errors.js';
|
||||||
import { assertProjectAccess, accessibleProjectIds } from '../auth/authz.js';
|
|
||||||
import { requireAdmin } from '../middleware/auth.js';
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
router.param('id', (req, res, next) => validateUuid('id')(req, res, next));
|
||||||
// Every /:id asset route is scoped to the asset's project. The param handler
|
|
||||||
// validates the UUID, resolves the owning project_id, and asserts at least
|
|
||||||
// 'view' access (the baseline for touching an asset at all). Mutating routes
|
|
||||||
// additionally assert 'edit' via req.assetProjectId. A missing asset is a clean
|
|
||||||
// 404 here rather than leaking existence to users without access.
|
|
||||||
router.param('id', async (req, res, next) => {
|
|
||||||
validateUuid('id')(req, res, () => {});
|
|
||||||
if (res.headersSent) return;
|
|
||||||
try {
|
|
||||||
const { rows } = await pool.query('SELECT project_id FROM assets WHERE id = $1', [req.params.id]);
|
|
||||||
if (rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
|
||||||
req.assetProjectId = rows[0].project_id;
|
|
||||||
await assertProjectAccess(req.user, req.assetProjectId, 'view');
|
|
||||||
next();
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Route-level guard for mutating /:id endpoints — escalates the param handler's
|
|
||||||
// 'view' baseline to 'edit'. Reuses req.assetProjectId (already resolved).
|
|
||||||
async function requireAssetEdit(req, res, next) {
|
|
||||||
try {
|
|
||||||
await assertProjectAccess(req.user, req.assetProjectId, 'edit');
|
|
||||||
next();
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// BullMQ queue connection (mirrors worker/src/index.js)
|
// BullMQ queue connection (mirrors worker/src/index.js)
|
||||||
const parseRedisUrl = (url) => {
|
const parseRedisUrl = (url) => {
|
||||||
|
|
@ -60,10 +33,6 @@ const filmstripQueue = new Queue('filmstrip', {
|
||||||
connection: parseRedisUrl(process.env.REDIS_URL || 'redis://queue:6379'),
|
connection: parseRedisUrl(process.env.REDIS_URL || 'redis://queue:6379'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const hlsQueue = new Queue('hls', {
|
|
||||||
connection: parseRedisUrl(process.env.REDIS_URL || 'redis://queue:6379'),
|
|
||||||
});
|
|
||||||
|
|
||||||
// GET / - List assets with filtering
|
// GET / - List assets with filtering
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -93,15 +62,6 @@ router.get('/', async (req, res, next) => {
|
||||||
const params = [];
|
const params = [];
|
||||||
let paramCount = 1;
|
let paramCount = 1;
|
||||||
|
|
||||||
// Scope to projects the caller can access (admins are unfiltered). Without
|
|
||||||
// this, a granted user would see every asset across every project.
|
|
||||||
const access = await accessibleProjectIds(req.user);
|
|
||||||
if (!access.all) {
|
|
||||||
if (access.ids.size === 0) return res.json({ assets: [], total: 0 });
|
|
||||||
query += ` AND a.project_id = ANY($${paramCount++}::uuid[])`;
|
|
||||||
params.push([...access.ids]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exclude archived unless explicitly requested — independent of status filter
|
// Exclude archived unless explicitly requested — independent of status filter
|
||||||
if (include_archived !== 'true') {
|
if (include_archived !== 'true') {
|
||||||
query += ` AND a.status <> 'archived'`;
|
query += ` AND a.status <> 'archived'`;
|
||||||
|
|
@ -168,9 +128,6 @@ router.post('/', async (req, res, next) => {
|
||||||
return res.status(400).json({ error: 'projectId and clipName are required' });
|
return res.status(400).json({ error: 'projectId and clipName are required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registering an asset writes into a project — require edit access there.
|
|
||||||
await assertProjectAccess(req.user, projectId, 'edit');
|
|
||||||
|
|
||||||
const durationNum = duration !== undefined && duration !== null ? Number(duration) : null;
|
const durationNum = duration !== undefined && duration !== null ? Number(duration) : null;
|
||||||
if (durationNum !== null && !Number.isFinite(durationNum)) {
|
if (durationNum !== null && !Number.isFinite(durationNum)) {
|
||||||
return res.status(400).json({ error: 'duration must be a finite number (seconds)' });
|
return res.status(400).json({ error: 'duration must be a finite number (seconds)' });
|
||||||
|
|
@ -259,8 +216,8 @@ router.post('/', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /cleanup-live — cross-project maintenance, admin only.
|
// POST /cleanup-live
|
||||||
router.post('/cleanup-live', requireAdmin, async (req, res, next) => {
|
router.post('/cleanup-live', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const maxAgeHours = Math.max(1, parseInt(req.query.max_age_hours || '4', 10));
|
const maxAgeHours = Math.max(1, parseInt(req.query.max_age_hours || '4', 10));
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
|
|
@ -273,8 +230,8 @@ router.post('/cleanup-live', requireAdmin, async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /cleanup-live-orphans — cross-project maintenance, admin only.
|
// POST /cleanup-live-orphans
|
||||||
router.post('/cleanup-live-orphans', requireAdmin, async (_req, res, next) => {
|
router.post('/cleanup-live-orphans', async (_req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const liveRoot = process.env.LIVE_DIR || '/live';
|
const liveRoot = process.env.LIVE_DIR || '/live';
|
||||||
let entries;
|
let entries;
|
||||||
|
|
@ -316,22 +273,10 @@ router.get('/:id', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// PATCH /:id
|
// PATCH /:id
|
||||||
router.patch('/:id', requireAssetEdit, async (req, res, next) => {
|
router.patch('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { display_name, tags, notes, bin_id } = req.body;
|
const { display_name, tags, notes, bin_id } = req.body;
|
||||||
|
|
||||||
// bin_id must reference a bin in the asset's OWN project — otherwise an
|
|
||||||
// editor in project A could stuff their asset into project B's bin tree.
|
|
||||||
// Null/empty clears the bin, which is always allowed.
|
|
||||||
if (bin_id) {
|
|
||||||
const bin = await pool.query('SELECT project_id FROM bins WHERE id = $1', [bin_id]);
|
|
||||||
if (bin.rows.length === 0) return res.status(400).json({ error: 'bin_id not found' });
|
|
||||||
if (bin.rows[0].project_id !== req.assetProjectId) {
|
|
||||||
return res.status(400).json({ error: 'bin_id belongs to a different project' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const updates = [], params = [];
|
const updates = [], params = [];
|
||||||
let paramCount = 1;
|
let paramCount = 1;
|
||||||
if (display_name !== undefined) { updates.push(`display_name = $${paramCount++}`); params.push(display_name); }
|
if (display_name !== undefined) { updates.push(`display_name = $${paramCount++}`); params.push(display_name); }
|
||||||
|
|
@ -350,32 +295,13 @@ router.patch('/:id', requireAssetEdit, async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /:id/copy
|
// POST /:id/copy
|
||||||
router.post('/:id/copy', requireAssetEdit, async (req, res, next) => {
|
router.post('/:id/copy', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { binId, projectId } = req.body;
|
const { binId, projectId } = req.body;
|
||||||
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
||||||
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
||||||
const src = r.rows[0];
|
const src = r.rows[0];
|
||||||
|
|
||||||
// Destination project defaults to source's. If the caller overrides it,
|
|
||||||
// assert edit on the target — without this, an editor in project A could
|
|
||||||
// clone any asset they can see into project B with no grant on B.
|
|
||||||
const destProjectId = projectId || src.project_id;
|
|
||||||
if (projectId && projectId !== src.project_id) {
|
|
||||||
await assertProjectAccess(req.user, destProjectId, 'edit');
|
|
||||||
}
|
|
||||||
// Destination bin (if any) must belong to the destination project — same
|
|
||||||
// class of bug as the PATCH bin_id hole.
|
|
||||||
const destBinId = binId === undefined ? src.bin_id : (binId || null);
|
|
||||||
if (destBinId) {
|
|
||||||
const bin = await pool.query('SELECT project_id FROM bins WHERE id = $1', [destBinId]);
|
|
||||||
if (bin.rows.length === 0) return res.status(400).json({ error: 'binId not found' });
|
|
||||||
if (bin.rows[0].project_id !== destProjectId) {
|
|
||||||
return res.status(400).json({ error: 'binId belongs to a different project than the destination' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newId = uuidv4();
|
const newId = uuidv4();
|
||||||
// Bug #60: null out proxy_s3_key and thumbnail_s3_key on the copy to avoid
|
// Bug #60: null out proxy_s3_key and thumbnail_s3_key on the copy to avoid
|
||||||
// sharing S3 objects with the source. Set status to 'processing' so the copy
|
// sharing S3 objects with the source. Set status to 'processing' so the copy
|
||||||
|
|
@ -390,8 +316,8 @@ router.post('/:id/copy', requireAssetEdit, async (req, res, next) => {
|
||||||
$1,$2,$3,$4,$5,$6,$7,$8,NULL,NULL,$9,$10,$11,$12,$13,$14,$15,$16,NOW(),NOW()
|
$1,$2,$3,$4,$5,$6,$7,$8,NULL,NULL,$9,$10,$11,$12,$13,$14,$15,$16,NOW(),NOW()
|
||||||
) RETURNING *`,
|
) RETURNING *`,
|
||||||
[
|
[
|
||||||
newId, destProjectId,
|
newId, projectId || src.project_id,
|
||||||
destBinId,
|
binId === undefined ? src.bin_id : (binId || null),
|
||||||
src.filename, src.display_name, 'processing', src.media_type,
|
src.filename, src.display_name, 'processing', src.media_type,
|
||||||
src.original_s3_key,
|
src.original_s3_key,
|
||||||
src.codec, src.resolution, src.fps, src.duration_ms, src.start_tc,
|
src.codec, src.resolution, src.fps, src.duration_ms, src.start_tc,
|
||||||
|
|
@ -416,7 +342,7 @@ router.post('/:id/copy', requireAssetEdit, async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /:id/mark-empty
|
// POST /:id/mark-empty
|
||||||
router.post('/:id/mark-empty', requireAssetEdit, async (req, res, next) => {
|
router.post('/:id/mark-empty', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
// Bug #66: first check the asset exists and what status it is in
|
// Bug #66: first check the asset exists and what status it is in
|
||||||
|
|
@ -454,7 +380,7 @@ router.post('/:id/mark-empty', requireAssetEdit, async (req, res, next) => {
|
||||||
// the existing live row -> 409 -> asset stuck 'live', no jobs. Finalising by id
|
// the existing live row -> 409 -> asset stuck 'live', no jobs. Finalising by id
|
||||||
// flips it out of 'live', records duration + S3 keys, and kicks off the
|
// flips it out of 'live', records duration + S3 keys, and kicks off the
|
||||||
// proxy -> thumbnail -> filmstrip job chain.
|
// proxy -> thumbnail -> filmstrip job chain.
|
||||||
router.post('/:id/finalize', requireAssetEdit, async (req, res, next) => {
|
router.post('/:id/finalize', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { hiresKey, proxyKey, duration } = req.body;
|
const { hiresKey, proxyKey, duration } = req.body;
|
||||||
|
|
@ -506,7 +432,7 @@ router.post('/:id/finalize', requireAssetEdit, async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /:id/generate-proxy
|
// POST /:id/generate-proxy
|
||||||
router.post('/:id/generate-proxy', requireAssetEdit, async (req, res, next) => {
|
router.post('/:id/generate-proxy', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
||||||
|
|
@ -522,8 +448,8 @@ router.post('/:id/generate-proxy', requireAssetEdit, async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /backfill-proxies — cross-project maintenance, admin only.
|
// POST /backfill-proxies
|
||||||
router.post('/backfill-proxies', requireAdmin, async (_req, res, next) => {
|
router.post('/backfill-proxies', async (_req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const targets = await pool.query(
|
const targets = await pool.query(
|
||||||
`SELECT id, original_s3_key FROM assets
|
`SELECT id, original_s3_key FROM assets
|
||||||
|
|
@ -547,12 +473,12 @@ router.post('/backfill-proxies', requireAdmin, async (_req, res, next) => {
|
||||||
|
|
||||||
// POST /:id/reprocess?type=proxy|thumbnail|filmstrip
|
// POST /:id/reprocess?type=proxy|thumbnail|filmstrip
|
||||||
// Force-requeue a processing job regardless of current asset status.
|
// Force-requeue a processing job regardless of current asset status.
|
||||||
router.post('/:id/reprocess', requireAssetEdit, async (req, res, next) => {
|
router.post('/:id/reprocess', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const type = req.query.type || 'proxy';
|
const type = req.query.type || 'proxy';
|
||||||
if (!['proxy', 'thumbnail', 'filmstrip', 'hls'].includes(type)) {
|
if (!['proxy', 'thumbnail', 'filmstrip'].includes(type)) {
|
||||||
return res.status(400).json({ error: 'type must be "proxy", "thumbnail", "filmstrip", or "hls"' });
|
return res.status(400).json({ error: 'type must be "proxy", "thumbnail", or "filmstrip"' });
|
||||||
}
|
}
|
||||||
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
||||||
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
||||||
|
|
@ -575,12 +501,6 @@ router.post('/:id/reprocess', requireAssetEdit, async (req, res, next) => {
|
||||||
await filmstripQueue.add('generate', { assetId: id, proxyKey: asset.proxy_s3_key });
|
await filmstripQueue.add('generate', { assetId: id, proxyKey: asset.proxy_s3_key });
|
||||||
return res.json({ queued: 'filmstrip', assetId: id });
|
return res.json({ queued: 'filmstrip', assetId: id });
|
||||||
}
|
}
|
||||||
if (type === 'hls') {
|
|
||||||
// Backfill: remux the existing proxy MP4 into an HLS rendition (no re-encode).
|
|
||||||
if (!asset.proxy_s3_key) return res.status(400).json({ error: 'Asset has no proxy — generate proxy first' });
|
|
||||||
await hlsQueue.add('generate', { assetId: id, proxyKey: asset.proxy_s3_key });
|
|
||||||
return res.json({ queued: 'hls', assetId: id });
|
|
||||||
}
|
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -598,7 +518,7 @@ router.get('/:id/filmstrip', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /:id/retry
|
// POST /:id/retry
|
||||||
router.post('/:id/retry', requireAssetEdit, async (req, res, next) => {
|
router.post('/:id/retry', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
const r = await pool.query('SELECT * FROM assets WHERE id = $1', [id]);
|
||||||
|
|
@ -617,7 +537,7 @@ router.post('/:id/retry', requireAssetEdit, async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// DELETE /:id
|
// DELETE /:id
|
||||||
router.delete('/:id', requireAssetEdit, async (req, res, next) => {
|
router.delete('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { hard } = req.query;
|
const { hard } = req.query;
|
||||||
|
|
@ -665,20 +585,6 @@ router.get('/:id/stream', async (req, res, next) => {
|
||||||
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
||||||
const a = r.rows[0];
|
const a = r.rows[0];
|
||||||
if (a.status === 'live') return res.json({ url: `/live/${a.id}/index.m3u8`, type: 'hls', live: true });
|
if (a.status === 'live') return res.json({ url: `/live/${a.id}/index.m3u8`, type: 'hls', live: true });
|
||||||
// `url` is the directly-downloadable MP4 proxy; `hls_url` is the HLS
|
|
||||||
// rendition for in-browser playback (whole-file segment GETs avoid the
|
|
||||||
// RustFS ranged-GET stitching the MP4 path needs). The Premiere plugin
|
|
||||||
// downloads `url` to a file and imports it, so `url` must NOT be the
|
|
||||||
// .m3u8 playlist — Premiere can't import a playlist ("unsupported
|
|
||||||
// compression type"). The web player prefers `hls_url` when present.
|
|
||||||
if (a.hls_s3_key) {
|
|
||||||
return res.json({
|
|
||||||
url: `/api/v1/assets/${id}/video`,
|
|
||||||
type: 'mp4',
|
|
||||||
source: a.proxy_s3_key ? 'proxy' : 'original',
|
|
||||||
hls_url: `/api/v1/assets/${id}/hls/playlist.m3u8`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const VIDEO_EXTS = ['.mp4', '.mov', '.mxf', '.ts', '.m4v', '.mkv', '.avi', '.webm'];
|
const VIDEO_EXTS = ['.mp4', '.mov', '.mxf', '.ts', '.m4v', '.mkv', '.avi', '.webm'];
|
||||||
const key = a.proxy_s3_key ||
|
const key = a.proxy_s3_key ||
|
||||||
(a.original_s3_key && VIDEO_EXTS.some(ext => a.original_s3_key.toLowerCase().endsWith(ext))
|
(a.original_s3_key && VIDEO_EXTS.some(ext => a.original_s3_key.toLowerCase().endsWith(ext))
|
||||||
|
|
@ -690,42 +596,6 @@ router.get('/:id/stream', async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /:id/hls/:file — serve an HLS rendition file (playlist / init / segment).
|
|
||||||
// Whole-object passthrough from S3: no Range handling, so this sidesteps the
|
|
||||||
// RustFS ranged-GET bug entirely (every segment is a small, complete GET).
|
|
||||||
// :file is strictly validated to prevent path traversal into the bucket.
|
|
||||||
const HLS_FILE_RE = /^(playlist\.m3u8|init\.mp4|segment_\d+\.m4s)$/;
|
|
||||||
router.get('/:id/hls/:file', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { id, file } = req.params;
|
|
||||||
if (!HLS_FILE_RE.test(file)) return res.status(400).json({ error: 'Invalid HLS file' });
|
|
||||||
|
|
||||||
const r = await pool.query('SELECT hls_s3_key FROM assets WHERE id = $1', [id]);
|
|
||||||
if (r.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
|
||||||
const playlistKey = r.rows[0].hls_s3_key;
|
|
||||||
if (!playlistKey) return res.status(404).json({ error: 'No HLS rendition for this asset' });
|
|
||||||
|
|
||||||
// Derive the prefix from the stored playlist key (hls/<id>/playlist.m3u8)
|
|
||||||
// and request the specific file under it.
|
|
||||||
const prefix = playlistKey.replace(/\/[^/]+$/, '');
|
|
||||||
const key = `${prefix}/${file}`;
|
|
||||||
|
|
||||||
const isPlaylist = file.endsWith('.m3u8');
|
|
||||||
const s3Res = await s3Client.send(new GetObjectCommand({ Bucket: getS3Bucket(), Key: key }));
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': isPlaylist ? 'application/vnd.apple.mpegurl' : 'video/mp4',
|
|
||||||
'Cache-Control': isPlaylist ? 'no-cache' : 'private, max-age=3600',
|
|
||||||
...(s3Res.ContentLength ? { 'Content-Length': String(s3Res.ContentLength) } : {}),
|
|
||||||
});
|
|
||||||
s3Res.Body.pipe(res);
|
|
||||||
} catch (err) {
|
|
||||||
if (err && (err.name === 'NoSuchKey' || err.$metadata?.httpStatusCode === 404)) {
|
|
||||||
return res.status(404).json({ error: 'HLS file not found' });
|
|
||||||
}
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// GET /:id/live-path
|
// GET /:id/live-path
|
||||||
router.get('/:id/live-path', async (req, res, next) => {
|
router.get('/:id/live-path', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -734,21 +604,16 @@ router.get('/:id/live-path', async (req, res, next) => {
|
||||||
if (a.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
if (a.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
||||||
const asset = a.rows[0];
|
const asset = a.rows[0];
|
||||||
if (asset.status !== 'live') return res.status(409).json({ error: 'Asset is not currently growing', status: asset.status });
|
if (asset.status !== 'live') return res.status(409).json({ error: 'Asset is not currently growing', status: asset.status });
|
||||||
// Growing-files mode is now per-recorder (recorders.growing_enabled), so we
|
const s = await pool.query(`SELECT key, value FROM settings WHERE key IN ('growing_enabled','growing_smb_url')`);
|
||||||
// no longer gate on the removed global `growing_enabled` setting. A
|
|
||||||
// status='live' asset already proves a growing recorder is producing this
|
|
||||||
// file; we only need the editor-facing SMB URL to build the UNC path.
|
|
||||||
const s = await pool.query(`SELECT key, value FROM settings WHERE key = 'growing_smb_url'`);
|
|
||||||
const cfg = {};
|
const cfg = {};
|
||||||
for (const { key, value } of s.rows) cfg[key] = value;
|
for (const { key, value } of s.rows) cfg[key] = value;
|
||||||
if (!cfg.growing_smb_url) return res.status(409).json({ error: 'No SMB URL configured — set the editor SMB URL in Settings → Storage' });
|
if (cfg.growing_enabled !== 'true') return res.status(409).json({ error: 'Growing-files mode is disabled' });
|
||||||
// The growing master is ALWAYS MXF OP1a (XDCAM HD422) on the share, regardless
|
if (!cfg.growing_smb_url) return res.status(409).json({ error: 'No SMB URL configured — set growing_smb_url in Settings' });
|
||||||
// of the recorder's configured finalized container — that is the format
|
const rec = await pool.query(
|
||||||
// Premiere supports for edit-while-record growing files (incremental index
|
`SELECT recording_container FROM recorders WHERE current_session_id = $1 ORDER BY updated_at DESC LIMIT 1`,
|
||||||
// segments written into body partitions, readable with no footer). The file
|
[asset.id]
|
||||||
// on the share is `<clip>.mxf`. Keep this in lock-step with GROWING_EXT in
|
);
|
||||||
// services/capture/src/capture-manager.js.
|
const ext = rec.rows[0]?.recording_container || 'mov';
|
||||||
const ext = 'mxf';
|
|
||||||
const smbRoot = cfg.growing_smb_url.replace(/\/+$/, '');
|
const smbRoot = cfg.growing_smb_url.replace(/\/+$/, '');
|
||||||
const winPath = smbRoot.replace(/^smb:\/\//, '\\\\').replace(/\//g, '\\') + `\\${asset.project_id}\\${asset.display_name}.${ext}`;
|
const winPath = smbRoot.replace(/^smb:\/\//, '\\\\').replace(/\//g, '\\') + `\\${asset.project_id}\\${asset.display_name}.${ext}`;
|
||||||
const posix = smbRoot.replace(/^smb:\/\//, '//') + `/${asset.project_id}/${asset.display_name}.${ext}`;
|
const posix = smbRoot.replace(/^smb:\/\//, '//') + `/${asset.project_id}/${asset.display_name}.${ext}`;
|
||||||
|
|
@ -983,15 +848,6 @@ router.post('/batch-trim', async (req, res, next) => {
|
||||||
return res.status(400).json({ error: 'Each clip must have assetId, filename, sourceInFrames, sourceOutFrames, timelineInFrames, timelineOutFrames, and a non-negative integer trackIndex' });
|
return res.status(400).json({ error: 'Each clip must have assetId, filename, sourceInFrames, sourceOutFrames, timelineInFrames, timelineOutFrames, and a non-negative integer trackIndex' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Authorize every source asset's project (edit) before queuing any work.
|
|
||||||
const trimAssetIds = [...new Set(clips.map(c => c.assetId))];
|
|
||||||
const owning = await pool.query('SELECT id, project_id FROM assets WHERE id = ANY($1::uuid[])', [trimAssetIds]);
|
|
||||||
const projById = new Map(owning.rows.map(r => [r.id, r.project_id]));
|
|
||||||
for (const aid of trimAssetIds) {
|
|
||||||
const pid = projById.get(aid);
|
|
||||||
if (!pid) return res.status(404).json({ error: 'Asset not found: ' + aid });
|
|
||||||
await assertProjectAccess(req.user, pid, 'edit');
|
|
||||||
}
|
|
||||||
const jobId = uuidv4();
|
const jobId = uuidv4();
|
||||||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000);
|
||||||
await pool.query(
|
await pool.query(
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,6 @@ import pool from '../db/pool.js';
|
||||||
import { DEV_USER_ID, requireAuth } from '../middleware/auth.js';
|
import { DEV_USER_ID, requireAuth } from '../middleware/auth.js';
|
||||||
import { hashPassword, comparePassword } from '../auth/passwords.js';
|
import { hashPassword, comparePassword } from '../auth/passwords.js';
|
||||||
import { ipBackoff } from '../auth/rate-limit.js';
|
import { ipBackoff } from '../auth/rate-limit.js';
|
||||||
import {
|
|
||||||
generateSecret, verifyToken, otpauthURI, generateRecoveryCodes,
|
|
||||||
} from '../auth/totp.js';
|
|
||||||
import { issueTicket, redeemTicket } from '../auth/mfa-tickets.js';
|
|
||||||
import {
|
|
||||||
isConfigured as googleConfigured, buildAuthUrl, exchangeAndVerify,
|
|
||||||
} from '../auth/google-oauth.js';
|
|
||||||
import { randomBytes } from 'node:crypto';
|
|
||||||
|
|
||||||
const DUMMY_PASSWORD_HASH = '$2b$12$gSeC58PregWedNFK/8Q61OephUo.JJ7EUs0LCTdnJV5AzCS5qQH7K';
|
const DUMMY_PASSWORD_HASH = '$2b$12$gSeC58PregWedNFK/8Q61OephUo.JJ7EUs0LCTdnJV5AzCS5qQH7K';
|
||||||
|
|
||||||
|
|
@ -84,7 +76,7 @@ router.post('/login', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rows } = await pool.query(
|
const { rows } = await pool.query(
|
||||||
`SELECT id, username, display_name, password_hash, totp_enabled FROM users WHERE username = $1 AND id <> $2`,
|
`SELECT id, username, display_name, password_hash FROM users WHERE username = $1 AND id <> $2`,
|
||||||
[username.trim(), DEV_USER_ID]
|
[username.trim(), DEV_USER_ID]
|
||||||
);
|
);
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
|
|
@ -101,29 +93,6 @@ router.post('/login', async (req, res, next) => {
|
||||||
return res.status(401).json({ error: 'invalid credentials' });
|
return res.status(401).json({ error: 'invalid credentials' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second factor: if TOTP is enabled, don't create a session yet. Hand back
|
|
||||||
// a short-lived ticket the client redeems via /login/totp with a code.
|
|
||||||
// Crucially: do NOT clear the per-IP failure counter here. If we did, each
|
|
||||||
// /login retry would reset the backoff and let an attacker brute the 6-digit
|
|
||||||
// TOTP space (10^6) with no per-attempt delay. The counter is cleared
|
|
||||||
// inside establishSession() once MFA has actually passed.
|
|
||||||
if (user.totp_enabled) {
|
|
||||||
return res.json({
|
|
||||||
mfa_required: true,
|
|
||||||
ticket: issueTicket(user.id, { ip, userAgent: req.get('user-agent') }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await establishSession(req, user, ip);
|
|
||||||
res.json({ user: { id: user.id, username: user.username, display_name: user.display_name } });
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Write the session and wait for it to persist before responding. Extracted so
|
|
||||||
// both the password-only and the MFA-completion paths share one implementation.
|
|
||||||
// Clears the per-IP failure counter only here — after every required factor has
|
|
||||||
// actually been proven (password [+ TOTP if enabled, or OAuth + TOTP]).
|
|
||||||
async function establishSession(req, user, ip) {
|
|
||||||
req.session.user_id = user.id;
|
req.session.user_id = user.id;
|
||||||
req.session.first_seen_at = Date.now();
|
req.session.first_seen_at = Date.now();
|
||||||
req.session.last_seen_at = Date.now();
|
req.session.last_seen_at = Date.now();
|
||||||
|
|
@ -131,93 +100,14 @@ async function establishSession(req, user, ip) {
|
||||||
// Without this, the SPA's next request races the store write, hits 401, and
|
// Without this, the SPA's next request races the store write, hits 401, and
|
||||||
// the prior bounce-to-login logic produced an infinite loop.
|
// the prior bounce-to-login logic produced an infinite loop.
|
||||||
await new Promise((resolve, reject) => req.session.save(err => err ? reject(err) : resolve()));
|
await new Promise((resolve, reject) => req.session.save(err => err ? reject(err) : resolve()));
|
||||||
if (ip) ipBackoff.recordSuccess(ip);
|
|
||||||
pool.query(`UPDATE users SET last_login_at = NOW() WHERE id = $1`, [user.id])
|
pool.query(`UPDATE users SET last_login_at = NOW() WHERE id = $1`, [user.id])
|
||||||
.catch(err => console.error('[auth] last_login_at update failed:', err.message));
|
.catch(err => console.error('[auth] last_login_at update failed:', err.message));
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/v1/auth/login/totp { ticket?, code } — second login step. `code` is
|
ipBackoff.recordSuccess(ip);
|
||||||
// either a 6-digit TOTP or a one-time recovery code. The ticket comes from the
|
res.json({ user: { id: user.id, username: user.username, display_name: user.display_name } }); } catch (err) { next(err); }
|
||||||
// request body (password-login path) or req.session.mfa_ticket (Google path).
|
|
||||||
router.post('/login/totp', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
|
||||||
// Rate-limit the second factor with the same per-IP backoff as /login so
|
|
||||||
// the 6-digit code space can't be hammered.
|
|
||||||
const delay = ipBackoff.delayMs(ip);
|
|
||||||
if (delay > 0) await new Promise(r => setTimeout(r, delay));
|
|
||||||
|
|
||||||
const { ticket: bodyTicket, code } = req.body || {};
|
|
||||||
const ticket = bodyTicket || req.session?.mfa_ticket;
|
|
||||||
if (req.session?.mfa_ticket) delete req.session.mfa_ticket;
|
|
||||||
// Bound to the issuing request's IP + UA — replays from a different origin
|
|
||||||
// redeem to null. See mfa-tickets.js for the binding model.
|
|
||||||
const userId = redeemTicket(ticket, { ip, userAgent: req.get('user-agent') });
|
|
||||||
if (!userId) {
|
|
||||||
ipBackoff.recordFailure(ip);
|
|
||||||
return res.status(401).json({ error: 'invalid or expired ticket' });
|
|
||||||
}
|
|
||||||
if (!code) return res.status(400).json({ error: 'code required' });
|
|
||||||
|
|
||||||
const { rows } = await pool.query(
|
|
||||||
`SELECT id, username, display_name, totp_secret, totp_enabled, totp_last_counter
|
|
||||||
FROM users WHERE id = $1`, [userId]);
|
|
||||||
const user = rows[0];
|
|
||||||
if (!user || !user.totp_enabled || !user.totp_secret) {
|
|
||||||
return res.status(401).json({ error: 'invalid credentials' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// verifyToken returns the matched counter on success. Reject codes at
|
|
||||||
// counters ≤ totp_last_counter to prevent replay within the same step.
|
|
||||||
// The CAS-style UPDATE makes this race-free under concurrent submissions.
|
|
||||||
const matchedCounter = verifyToken(user.totp_secret, code);
|
|
||||||
let ok = false;
|
|
||||||
if (matchedCounter !== null) {
|
|
||||||
const lastCounter = BigInt(user.totp_last_counter || 0);
|
|
||||||
if (BigInt(matchedCounter) > lastCounter) {
|
|
||||||
const upd = await pool.query(
|
|
||||||
`UPDATE users SET totp_last_counter = $1
|
|
||||||
WHERE id = $2 AND totp_last_counter < $1`,
|
|
||||||
[String(matchedCounter), user.id]
|
|
||||||
);
|
|
||||||
ok = upd.rowCount === 1;
|
|
||||||
}
|
|
||||||
// matchedCounter ≤ last → silent replay; falls through to recovery-code
|
|
||||||
// path which also fails → 401. Same UX as a wrong code, no info leak.
|
|
||||||
}
|
|
||||||
if (!ok) ok = await consumeRecoveryCode(user.id, code);
|
|
||||||
if (!ok) {
|
|
||||||
ipBackoff.recordFailure(ip);
|
|
||||||
// The ticket was single-use; the client must restart from /login.
|
|
||||||
return res.status(401).json({ error: 'invalid code' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// recordSuccess is called by establishSession once the session lands —
|
|
||||||
// that's the first moment we know every required factor has passed.
|
|
||||||
await establishSession(req, user, ip);
|
|
||||||
res.json({ user: { id: user.id, username: user.username, display_name: user.display_name } });
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check a recovery code against the user's unused codes; mark it spent on match.
|
|
||||||
// The marking is atomic (UPDATE ... WHERE used_at IS NULL with a rowCount check)
|
|
||||||
// so two concurrent redemptions of the same code can't both succeed.
|
|
||||||
async function consumeRecoveryCode(userId, code) {
|
|
||||||
const cleaned = String(code).trim().toLowerCase();
|
|
||||||
if (!/^[0-9a-f]{5}-[0-9a-f]{5}$/.test(cleaned)) return false;
|
|
||||||
const { rows } = await pool.query(
|
|
||||||
`SELECT id, code_hash FROM user_recovery_codes WHERE user_id = $1 AND used_at IS NULL`, [userId]);
|
|
||||||
for (const row of rows) {
|
|
||||||
if (await comparePassword(cleaned, row.code_hash)) {
|
|
||||||
const upd = await pool.query(
|
|
||||||
`UPDATE user_recovery_codes SET used_at = NOW() WHERE id = $1 AND used_at IS NULL`, [row.id]);
|
|
||||||
// Lost the race if another request already consumed it.
|
|
||||||
return upd.rowCount === 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/v1/auth/logout — destroys the session and clears the cookie.
|
// POST /api/v1/auth/logout — destroys the session and clears the cookie.
|
||||||
router.post('/logout', (req, res) => {
|
router.post('/logout', (req, res) => {
|
||||||
if (!req.session) return res.status(204).end();
|
if (!req.session) return res.status(204).end();
|
||||||
|
|
@ -235,7 +125,6 @@ router.get('/me', requireAuth, (req, res) => {
|
||||||
username: req.user.username,
|
username: req.user.username,
|
||||||
display_name: req.user.display_name,
|
display_name: req.user.display_name,
|
||||||
role: req.user.role,
|
role: req.user.role,
|
||||||
totp_enabled: !!req.user.totp_enabled,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -260,202 +149,5 @@ router.post('/password', requireAuth, async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── TOTP enrollment (all require an active session) ─────────────────────────
|
|
||||||
|
|
||||||
// POST /api/v1/auth/totp/setup — begin enrollment. Generates a fresh secret,
|
|
||||||
// stores it (but leaves totp_enabled=false), and returns the otpauth URI + the
|
|
||||||
// base32 secret for manual entry. Enrollment isn't active until /enable
|
|
||||||
// confirms a code, so a started-but-abandoned setup never locks the user out.
|
|
||||||
router.post('/totp/setup', requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { rows } = await pool.query(`SELECT totp_enabled FROM users WHERE id = $1`, [req.user.id]);
|
|
||||||
if (rows[0]?.totp_enabled) return res.status(409).json({ error: 'TOTP already enabled' });
|
|
||||||
|
|
||||||
const secret = generateSecret();
|
|
||||||
await pool.query(`UPDATE users SET totp_secret = $1 WHERE id = $2`, [secret, req.user.id]);
|
|
||||||
const uri = otpauthURI(secret, req.user.username || 'user');
|
|
||||||
|
|
||||||
// QR rendering is optional — the otpauth URI + manual secret are sufficient
|
|
||||||
// to enroll. Render a data-URL QR only if the optional `qrcode` dep is
|
|
||||||
// present, so a missing dependency degrades instead of 500-ing.
|
|
||||||
let qr = null;
|
|
||||||
try {
|
|
||||||
const QRCode = (await import('qrcode')).default;
|
|
||||||
qr = await QRCode.toDataURL(uri);
|
|
||||||
} catch { /* qrcode not installed — client falls back to manual entry */ }
|
|
||||||
|
|
||||||
res.json({ secret, otpauth_uri: uri, qr });
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// POST /api/v1/auth/totp/enable { code } — confirm enrollment with a code from
|
|
||||||
// the authenticator. On success, flips totp_enabled and returns one-time
|
|
||||||
// recovery codes (shown exactly once).
|
|
||||||
router.post('/totp/enable', requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { code } = req.body || {};
|
|
||||||
if (!code) return badRequest(res, 'code required');
|
|
||||||
|
|
||||||
const { rows } = await pool.query(
|
|
||||||
`SELECT totp_secret, totp_enabled FROM users WHERE id = $1`, [req.user.id]);
|
|
||||||
const row = rows[0];
|
|
||||||
if (!row?.totp_secret) return badRequest(res, 'start setup first');
|
|
||||||
if (row.totp_enabled) return res.status(409).json({ error: 'TOTP already enabled' });
|
|
||||||
const enrollCounter = verifyToken(row.totp_secret, code);
|
|
||||||
if (enrollCounter === null) return badRequest(res, 'incorrect code');
|
|
||||||
|
|
||||||
const recovery = generateRecoveryCodes(10);
|
|
||||||
const hashes = await Promise.all(recovery.map(c => hashPassword(c)));
|
|
||||||
// Enable + seed totp_last_counter to the enrollment code's counter so the
|
|
||||||
// same code can't be reused on first login. Replace any stale recovery
|
|
||||||
// codes atomically.
|
|
||||||
const client = await pool.connect();
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN');
|
|
||||||
await client.query(
|
|
||||||
`UPDATE users SET totp_enabled = TRUE, totp_last_counter = $2 WHERE id = $1`,
|
|
||||||
[req.user.id, String(enrollCounter)]
|
|
||||||
);
|
|
||||||
await client.query(`DELETE FROM user_recovery_codes WHERE user_id = $1`, [req.user.id]);
|
|
||||||
for (const h of hashes) {
|
|
||||||
await client.query(
|
|
||||||
`INSERT INTO user_recovery_codes (user_id, code_hash) VALUES ($1, $2)`, [req.user.id, h]);
|
|
||||||
}
|
|
||||||
await client.query('COMMIT');
|
|
||||||
} catch (e) {
|
|
||||||
await client.query('ROLLBACK').catch(() => {});
|
|
||||||
throw e;
|
|
||||||
} finally { client.release(); }
|
|
||||||
|
|
||||||
res.json({ enabled: true, recovery_codes: recovery });
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// POST /api/v1/auth/totp/disable { password } — turn off 2FA. Requires the
|
|
||||||
// account password as a confirmation so a hijacked live session can't silently
|
|
||||||
// strip the second factor.
|
|
||||||
router.post('/totp/disable', requireAuth, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { password } = req.body || {};
|
|
||||||
if (!password) return badRequest(res, 'password required');
|
|
||||||
const { rows } = await pool.query(`SELECT password_hash FROM users WHERE id = $1`, [req.user.id]);
|
|
||||||
if (!rows.length) return res.status(401).json({ error: 'unauthorized' });
|
|
||||||
if (!(await comparePassword(password, rows[0].password_hash))) {
|
|
||||||
return badRequest(res, 'incorrect password');
|
|
||||||
}
|
|
||||||
await pool.query(
|
|
||||||
`UPDATE users SET totp_enabled = FALSE, totp_secret = NULL, totp_last_counter = 0 WHERE id = $1`,
|
|
||||||
[req.user.id]
|
|
||||||
);
|
|
||||||
await pool.query(`DELETE FROM user_recovery_codes WHERE user_id = $1`, [req.user.id]);
|
|
||||||
res.status(204).end();
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Google OAuth (OIDC) sign-in ─────────────────────────────────────────────
|
|
||||||
// All Google routes are config-gated: without GOOGLE_CLIENT_ID/SECRET and
|
|
||||||
// OAUTH_REDIRECT_URL they 404, so a deployment without SSO is unaffected.
|
|
||||||
|
|
||||||
// GET /api/v1/auth/google/enabled — cheap, no auth. Lets the login screen decide
|
|
||||||
// whether to render the "Sign in with Google" button.
|
|
||||||
router.get('/google/enabled', (_req, res) => {
|
|
||||||
res.json({ enabled: googleConfigured() });
|
|
||||||
});
|
|
||||||
|
|
||||||
// GET /api/v1/auth/google — kick off the OAuth dance. Stores an anti-CSRF state
|
|
||||||
// in the session and redirects to Google's consent screen.
|
|
||||||
router.get('/google', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
if (!googleConfigured()) return res.status(404).json({ error: 'google sign-in not configured' });
|
|
||||||
const state = randomBytes(16).toString('hex');
|
|
||||||
req.session.oauth_state = state;
|
|
||||||
await new Promise((resolve, reject) => req.session.save(err => err ? reject(err) : resolve()));
|
|
||||||
res.redirect(await buildAuthUrl(state));
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// GET /api/v1/auth/google/callback — Google redirects back here with ?code&state.
|
|
||||||
// Verifies the ID token, enforces the allowed domain, auto-provisions a viewer
|
|
||||||
// on first login, establishes the session, then redirects to the SPA.
|
|
||||||
router.get('/google/callback', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
if (!googleConfigured()) return res.status(404).json({ error: 'google sign-in not configured' });
|
|
||||||
const { code, state } = req.query;
|
|
||||||
const expected = req.session.oauth_state;
|
|
||||||
delete req.session.oauth_state;
|
|
||||||
if (!code || !state || !expected || state !== expected) {
|
|
||||||
return res.status(400).json({ error: 'invalid oauth state' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const profile = await exchangeAndVerify(code);
|
|
||||||
const user = await resolveGoogleUser(profile);
|
|
||||||
|
|
||||||
// If this account has TOTP enabled, Google is only the FIRST factor — route
|
|
||||||
// through the same second-factor step as password login. The ticket lives in
|
|
||||||
// the session (not the URL) and the SPA prompts for the code.
|
|
||||||
if (user.totp_enabled) {
|
|
||||||
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
|
||||||
req.session.mfa_ticket = issueTicket(user.id, {
|
|
||||||
ip,
|
|
||||||
userAgent: req.get('user-agent'),
|
|
||||||
});
|
|
||||||
await new Promise((resolve, reject) => req.session.save(err => err ? reject(err) : resolve()));
|
|
||||||
return res.redirect('/?mfa=1');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ip = req.ip || req.socket?.remoteAddress || 'unknown';
|
|
||||||
await establishSession(req, user, ip);
|
|
||||||
|
|
||||||
// Redirect to the SPA root; AuthGate will re-check /auth/me and render the app.
|
|
||||||
res.redirect('/');
|
|
||||||
} catch (err) {
|
|
||||||
// Surface a friendly message on the login screen rather than a raw 500.
|
|
||||||
if (err.status === 403) return res.redirect('/?auth_error=domain');
|
|
||||||
if (err.status === 401) return res.redirect('/?auth_error=google');
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Map a verified Google profile to a Dragonflight user row.
|
|
||||||
//
|
|
||||||
// Resolution order:
|
|
||||||
// 1. Existing link by google_sub → that user.
|
|
||||||
// 2. Otherwise auto-provision a fresh 'viewer'.
|
|
||||||
//
|
|
||||||
// We deliberately do NOT auto-link to an existing account by matching email:
|
|
||||||
// that would let anyone who controls a Google address with the same email sign
|
|
||||||
// in as a pre-existing local (possibly admin) account, bypassing its password
|
|
||||||
// and TOTP. Linking an existing account to Google is an explicit, authenticated
|
|
||||||
// action (a future "connect Google" under Settings), not something a login does.
|
|
||||||
async function resolveGoogleUser(profile) {
|
|
||||||
const found = await pool.query(
|
|
||||||
`SELECT id, username, display_name, totp_enabled FROM users WHERE google_sub = $1`, [profile.sub]);
|
|
||||||
if (found.rows.length) return found.rows[0];
|
|
||||||
|
|
||||||
const base = (profile.email.split('@')[0] || 'user').replace(/[^a-z0-9._-]/gi, '').toLowerCase() || 'user';
|
|
||||||
let username = base, n = 1;
|
|
||||||
while ((await pool.query(`SELECT 1 FROM users WHERE username = $1`, [username])).rows.length) {
|
|
||||||
username = base + (++n);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const ins = await pool.query(
|
|
||||||
`INSERT INTO users (username, password_hash, display_name, role, email, google_sub)
|
|
||||||
VALUES ($1, NULL, $2, 'viewer', $3, $4)
|
|
||||||
RETURNING id, username, display_name, totp_enabled`,
|
|
||||||
[username, profile.name, profile.email, profile.sub]);
|
|
||||||
return ins.rows[0];
|
|
||||||
} catch (err) {
|
|
||||||
// Concurrent first-login race: the unique google_sub index rejected our
|
|
||||||
// INSERT because a sibling request just created the row. Re-resolve.
|
|
||||||
if (err.code === '23505') {
|
|
||||||
const retry = await pool.query(
|
|
||||||
`SELECT id, username, display_name, totp_enabled FROM users WHERE google_sub = $1`, [profile.sub]);
|
|
||||||
if (retry.rows.length) return retry.rows[0];
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
export { realUserCount, resolveGoogleUser, consumeRecoveryCode };
|
export { realUserCount };
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,25 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import pool from '../db/pool.js';
|
import pool from '../db/pool.js';
|
||||||
import { validateUuid } from '../middleware/errors.js';
|
import { validateUuid } from '../middleware/errors.js';
|
||||||
import { assertProjectAccess, accessibleProjectIds } from '../auth/authz.js';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
router.param('id', (req, res, next) => validateUuid('id')(req, res, next));
|
||||||
|
|
||||||
// Resolve the owning project for a /:id bin, assert 'view' baseline, stash the
|
// GET / - List bins. Filter by project_id when supplied; otherwise return
|
||||||
// project_id for mutating routes to escalate to 'edit'.
|
// every bin across every project so the Library / asset-context-menu can
|
||||||
router.param('id', async (req, res, next) => {
|
// present a global "move to bin" picker.
|
||||||
validateUuid('id')(req, res, () => {});
|
|
||||||
if (res.headersSent) return;
|
|
||||||
try {
|
|
||||||
const { rows } = await pool.query('SELECT project_id FROM bins WHERE id = $1', [req.params.id]);
|
|
||||||
if (rows.length === 0) return res.status(404).json({ error: 'Bin not found' });
|
|
||||||
req.binProjectId = rows[0].project_id;
|
|
||||||
await assertProjectAccess(req.user, req.binProjectId, 'view');
|
|
||||||
next();
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
async function requireBinEdit(req, res, next) {
|
|
||||||
try {
|
|
||||||
await assertProjectAccess(req.user, req.binProjectId, 'edit');
|
|
||||||
next();
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET / - List bins. When project_id is supplied, scope to it (after an access
|
|
||||||
// check); otherwise return bins across every project the caller can access.
|
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { project_id } = req.query;
|
const { project_id } = req.query;
|
||||||
|
|
||||||
|
const params = [];
|
||||||
|
let where = '';
|
||||||
if (project_id) {
|
if (project_id) {
|
||||||
await assertProjectAccess(req.user, project_id, 'view');
|
where = 'WHERE b.project_id = $1';
|
||||||
const result = await pool.query(
|
params.push(project_id);
|
||||||
`SELECT b.*, p.name AS project_name,
|
|
||||||
(SELECT COUNT(*)::int FROM assets a WHERE a.bin_id = b.id) AS asset_count
|
|
||||||
FROM bins b
|
|
||||||
LEFT JOIN projects p ON p.id = b.project_id
|
|
||||||
WHERE b.project_id = $1
|
|
||||||
ORDER BY b.created_at DESC`,
|
|
||||||
[project_id]
|
|
||||||
);
|
|
||||||
return res.json(result.rows);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const access = await accessibleProjectIds(req.user);
|
|
||||||
let where = '';
|
|
||||||
const params = [];
|
|
||||||
if (!access.all) {
|
|
||||||
if (access.ids.size === 0) return res.json([]);
|
|
||||||
where = 'WHERE b.project_id = ANY($1::uuid[])';
|
|
||||||
params.push([...access.ids]);
|
|
||||||
}
|
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
`SELECT b.*, p.name AS project_name,
|
`SELECT b.*, p.name AS project_name,
|
||||||
(SELECT COUNT(*)::int FROM assets a WHERE a.bin_id = b.id) AS asset_count
|
(SELECT COUNT(*)::int FROM assets a WHERE a.bin_id = b.id) AS asset_count
|
||||||
|
|
@ -64,13 +29,14 @@ router.get('/', async (req, res, next) => {
|
||||||
ORDER BY b.created_at DESC`,
|
ORDER BY b.created_at DESC`,
|
||||||
params
|
params
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json(result.rows);
|
res.json(result.rows);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST / - Create bin (requires edit on the target project).
|
// POST / - Create bin
|
||||||
router.post('/', async (req, res, next) => {
|
router.post('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { project_id, name, parent_id } = req.body;
|
const { project_id, name, parent_id } = req.body;
|
||||||
|
|
@ -78,7 +44,6 @@ router.post('/', async (req, res, next) => {
|
||||||
if (!project_id || !name) {
|
if (!project_id || !name) {
|
||||||
return res.status(400).json({ error: 'project_id and name are required' });
|
return res.status(400).json({ error: 'project_id and name are required' });
|
||||||
}
|
}
|
||||||
await assertProjectAccess(req.user, project_id, 'edit');
|
|
||||||
|
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
|
||||||
|
|
@ -96,7 +61,7 @@ router.post('/', async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// PATCH /:id - Update bin
|
// PATCH /:id - Update bin
|
||||||
router.patch('/:id', requireBinEdit, async (req, res, next) => {
|
router.patch('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { name, parent_id } = req.body;
|
const { name, parent_id } = req.body;
|
||||||
|
|
@ -142,7 +107,7 @@ router.patch('/:id', requireBinEdit, async (req, res, next) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// DELETE /:id - Delete bin
|
// DELETE /:id - Delete bin
|
||||||
router.delete('/:id', requireBinEdit, async (req, res, next) => {
|
router.delete('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
|
|
@ -161,8 +126,8 @@ router.delete('/:id', requireBinEdit, async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// POST /:id/assets - Add asset to bin (requires edit on the bin's project).
|
// POST /:id/assets - Add asset to bin
|
||||||
router.post('/:id/assets', requireBinEdit, async (req, res, next) => {
|
router.post('/:id/assets', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { asset_id } = req.body;
|
const { asset_id } = req.body;
|
||||||
|
|
@ -171,13 +136,10 @@ router.post('/:id/assets', requireBinEdit, async (req, res, next) => {
|
||||||
return res.status(400).json({ error: 'asset_id is required' });
|
return res.status(400).json({ error: 'asset_id is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Asset must live in the bin's own project. Without this, an editor in
|
// Verify bin exists
|
||||||
// project A (where the bin lives) could pull an asset from project B (no
|
const binCheck = await pool.query('SELECT id FROM bins WHERE id = $1', [id]);
|
||||||
// grant) into A's bin tree, exposing it in A's views.
|
if (binCheck.rows.length === 0) {
|
||||||
const a = await pool.query('SELECT project_id FROM assets WHERE id = $1', [asset_id]);
|
return res.status(404).json({ error: 'Bin not found' });
|
||||||
if (a.rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
|
||||||
if (a.rows[0].project_id !== req.binProjectId) {
|
|
||||||
return res.status(400).json({ error: 'asset belongs to a different project than the bin' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update asset's bin_id
|
// Update asset's bin_id
|
||||||
|
|
@ -196,8 +158,8 @@ router.post('/:id/assets', requireBinEdit, async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// DELETE /:id/assets/:assetId - Remove asset from bin (requires edit).
|
// DELETE /:id/assets/:assetId - Remove asset from bin
|
||||||
router.delete('/:id/assets/:assetId', requireBinEdit, async (req, res, next) => {
|
router.delete('/:id/assets/:assetId', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { id, assetId } = req.params;
|
const { id, assetId } = req.params;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
// authz: intentionally any-logged-in (no per-project scoping). This is a thin
|
|
||||||
// proxy to shared capture hardware with no project_id of its own; the resulting
|
|
||||||
// asset is scoped when it's registered via the /assets route. Gated by the
|
|
||||||
// global requireAuth in index.js, like the rest of /api/v1.
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,9 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import os from 'os';
|
|
||||||
import pool from '../db/pool.js';
|
import pool from '../db/pool.js';
|
||||||
import { requireAdmin } from '../middleware/auth.js';
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// Hostname the primary mam-api self-registers as (mirrors selfHeartbeat()).
|
|
||||||
const SELF_HOSTNAME = process.env.NODE_HOSTNAME || os.hostname();
|
|
||||||
|
|
||||||
// Format a process uptime (seconds) the way the Cluster UI expects — a short
|
|
||||||
// human string like "3d 4h" / "12m". Workers don't report uptime today, so the
|
|
||||||
// primary is the only row that populates this.
|
|
||||||
function formatUptime(seconds) {
|
|
||||||
const s = Math.floor(seconds);
|
|
||||||
const d = Math.floor(s / 86400);
|
|
||||||
const h = Math.floor((s % 86400) / 3600);
|
|
||||||
const m = Math.floor((s % 3600) / 60);
|
|
||||||
if (d > 0) return `${d}d ${h}h`;
|
|
||||||
if (h > 0) return `${h}h ${m}m`;
|
|
||||||
return `${m}m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /onboard-info – admin-only. Supplies the Add Node wizard with the bits it
|
|
||||||
// needs to build a `curl … | bash` onboarding command: the primary API URL the
|
|
||||||
// remote node-agent should heartbeat to, the raw URL of onboard-node.sh, and
|
|
||||||
// the deploy branch. apiUrl is a best guess the UI lets the operator edit.
|
|
||||||
router.get('/onboard-info', requireAdmin, (req, res) => {
|
|
||||||
const branch = process.env.DEPLOY_BRANCH || 'main';
|
|
||||||
const apiUrl = process.env.PUBLIC_API_URL
|
|
||||||
|| `${req.protocol}://${req.hostname}:${process.env.API_PORT || 47432}`;
|
|
||||||
const scriptUrl =
|
|
||||||
`https://forge.wilddragon.net/zgaetano/wild-dragon/raw/branch/${branch}/deploy/onboard-node.sh`;
|
|
||||||
res.json({ apiUrl, scriptUrl, branch });
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the agent reported Docker's default bridge IP (172.17.x) but the request
|
// If the agent reported Docker's default bridge IP (172.17.x) but the request
|
||||||
// itself came from a real LAN address, prefer the request source IP instead.
|
// itself came from a real LAN address, prefer the request source IP instead.
|
||||||
// We only check 172.17.x — the default docker0 bridge — not the full RFC1918
|
// We only check 172.17.x — the default docker0 bridge — not the full RFC1918
|
||||||
|
|
@ -72,6 +41,7 @@ function dockerRequest(path, method = 'GET', body = null) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET / – list all registered cluster nodes with online status
|
||||||
router.get('/', async (req, res, next) => {
|
router.get('/', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const r = await pool.query(
|
const r = await pool.query(
|
||||||
|
|
@ -80,45 +50,25 @@ router.get('/', async (req, res, next) => {
|
||||||
FROM cluster_nodes
|
FROM cluster_nodes
|
||||||
ORDER BY registered_at ASC`
|
ORDER BY registered_at ASC`
|
||||||
);
|
);
|
||||||
res.json(r.rows.map(row => {
|
res.json(r.rows.map(row => ({
|
||||||
const out = { ...row, online: Number(row.stale_seconds) < 120 };
|
...row,
|
||||||
// The primary (this mam-api host) does not heartbeat via the node-agent,
|
online: Number(row.stale_seconds) < 120,
|
||||||
// so its version/uptime are never populated. Self-populate them here so
|
})));
|
||||||
// the Cluster screen renders them like worker nodes instead of dashes.
|
|
||||||
if (row.role === 'primary' && row.hostname === SELF_HOSTNAME) {
|
|
||||||
out.version = process.env.npm_package_version || row.version || null;
|
|
||||||
out.uptime = formatUptime(process.uptime());
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}));
|
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /containers – list all containers on the local Docker host
|
||||||
router.get('/containers', async (req, res, next) => {
|
router.get('/containers', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const containers = await dockerRequest('/containers/json?all=true');
|
const containers = await dockerRequest('/containers/json?all=true');
|
||||||
if (!Array.isArray(containers)) return res.json([]);
|
if (!Array.isArray(containers)) return res.json([]);
|
||||||
const out = await Promise.all(containers.map(async c => {
|
const out = containers.map(c => {
|
||||||
const rawName = (c.Names[0] || '').replace(/^\//, '');
|
const rawName = (c.Names[0] || '').replace(/^\//, '');
|
||||||
const name = rawName.replace(/^wild-dragon-/, '').replace(/-\d+$/, '');
|
const name = rawName.replace(/^wild-dragon-/, '').replace(/-\d+$/, '');
|
||||||
const ports = (c.Ports || [])
|
const ports = (c.Ports || [])
|
||||||
.filter(p => p.PublicPort)
|
.filter(p => p.PublicPort)
|
||||||
.map(p => `${p.PublicPort}→${p.PrivatePort}`)
|
.map(p => `${p.PublicPort}→${p.PrivatePort}`)
|
||||||
.join(', ');
|
.join(', ');
|
||||||
// Live memory usage requires a per-container stats call (the list endpoint
|
|
||||||
// doesn't include it). One extra Docker call each, but the list is small.
|
|
||||||
// memory_stats.usage includes page cache; subtract it to match `docker stats`.
|
|
||||||
let memBytes = null;
|
|
||||||
if (c.State === 'running') {
|
|
||||||
try {
|
|
||||||
const stats = await dockerRequest(`/containers/${c.Id}/stats?stream=false`);
|
|
||||||
const ms = stats && stats.memory_stats;
|
|
||||||
if (ms && typeof ms.usage === 'number') {
|
|
||||||
const cache = (ms.stats && ms.stats.cache) || 0;
|
|
||||||
memBytes = ms.usage - cache;
|
|
||||||
}
|
|
||||||
} catch (_) { memBytes = null; }
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
id: c.Id.slice(0, 12),
|
id: c.Id.slice(0, 12),
|
||||||
name,
|
name,
|
||||||
|
|
@ -128,9 +78,9 @@ router.get('/containers', async (req, res, next) => {
|
||||||
healthy: (c.Status || '').includes('healthy'),
|
healthy: (c.Status || '').includes('healthy'),
|
||||||
ports,
|
ports,
|
||||||
cpu: 0,
|
cpu: 0,
|
||||||
memBytes,
|
mem: 0,
|
||||||
};
|
};
|
||||||
}));
|
});
|
||||||
res.json(out);
|
res.json(out);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT' || err.code === 'EACCES') return res.json([]);
|
if (err.code === 'ENOENT' || err.code === 'EACCES') return res.json([]);
|
||||||
|
|
@ -138,6 +88,7 @@ router.get('/containers', async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /containers/:nameOrId/restart
|
||||||
router.post('/containers/:nameOrId/restart', async (req, res, next) => {
|
router.post('/containers/:nameOrId/restart', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
await dockerRequest(`/containers/${encodeURIComponent(req.params.nameOrId)}/restart`, 'POST');
|
await dockerRequest(`/containers/${encodeURIComponent(req.params.nameOrId)}/restart`, 'POST');
|
||||||
|
|
@ -145,6 +96,7 @@ router.post('/containers/:nameOrId/restart', async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /heartbeat – upsert this node's registration (includes hardware capabilities)
|
||||||
router.post('/heartbeat', async (req, res, next) => {
|
router.post('/heartbeat', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
|
|
@ -156,6 +108,11 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
|
|
||||||
if (!hostname) return res.status(400).json({ error: 'hostname is required' });
|
if (!hostname) return res.status(400).json({ error: 'hostname is required' });
|
||||||
|
|
||||||
|
// Issue #106 — any authenticated user used to be able to POST a heartbeat
|
||||||
|
// for an arbitrary hostname and overwrite the primary node's `api_url`,
|
||||||
|
// effectively hijacking job dispatch. Now: if the caller's token is bound
|
||||||
|
// to a hostname (node-agent tokens are bound at issue time), the body
|
||||||
|
// hostname must match. Admin users with no binding are allowed for ops.
|
||||||
if (process.env.AUTH_ENABLED === 'true') {
|
if (process.env.AUTH_ENABLED === 'true') {
|
||||||
const bound = req.tokenBoundHostname;
|
const bound = req.tokenBoundHostname;
|
||||||
if (bound && bound !== hostname) {
|
if (bound && bound !== hostname) {
|
||||||
|
|
@ -175,8 +132,8 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
const r = await pool.query(
|
const r = await pool.query(
|
||||||
`INSERT INTO cluster_nodes
|
`INSERT INTO cluster_nodes
|
||||||
(hostname, ip_address, role, version, api_url,
|
(hostname, ip_address, role, version, api_url,
|
||||||
cpu_usage, mem_used_mb, mem_total_mb, last_seen, last_seen_at, capabilities, metadata, metrics)
|
cpu_usage, mem_used_mb, mem_total_mb, last_seen, capabilities, metadata, metrics)
|
||||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW(),NOW(),$9,$10,$11)
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW(),$9,$10,$11)
|
||||||
ON CONFLICT (hostname) DO UPDATE SET
|
ON CONFLICT (hostname) DO UPDATE SET
|
||||||
ip_address = EXCLUDED.ip_address,
|
ip_address = EXCLUDED.ip_address,
|
||||||
role = EXCLUDED.role,
|
role = EXCLUDED.role,
|
||||||
|
|
@ -186,7 +143,6 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
mem_used_mb = EXCLUDED.mem_used_mb,
|
mem_used_mb = EXCLUDED.mem_used_mb,
|
||||||
mem_total_mb = EXCLUDED.mem_total_mb,
|
mem_total_mb = EXCLUDED.mem_total_mb,
|
||||||
last_seen = NOW(),
|
last_seen = NOW(),
|
||||||
last_seen_at = NOW(),
|
|
||||||
capabilities = EXCLUDED.capabilities,
|
capabilities = EXCLUDED.capabilities,
|
||||||
metadata = EXCLUDED.metadata,
|
metadata = EXCLUDED.metadata,
|
||||||
metrics = COALESCE(EXCLUDED.metrics, cluster_nodes.metrics)
|
metrics = COALESCE(EXCLUDED.metrics, cluster_nodes.metrics)
|
||||||
|
|
@ -209,25 +165,42 @@ router.post('/heartbeat', async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /devices/blackmagic/signal – live video-presence state for every
|
||||||
|
// DeckLink port across the cluster. For each port we check whether there is
|
||||||
|
// an active SDI recorder assigned to it and, if so, query the capture
|
||||||
|
// container for its real signal state (receiving / lost / connecting /
|
||||||
|
// error). Ports without a recorder get signal = 'no-recorder'.
|
||||||
|
//
|
||||||
|
// Response shape (array):
|
||||||
|
// { node_id, hostname, index, device, model,
|
||||||
|
// signal, framesReceived, currentFps, recorder_id, recorder_status }
|
||||||
router.get('/devices/blackmagic/signal', async (req, res, next) => {
|
router.get('/devices/blackmagic/signal', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
|
// 1. Fetch all cluster nodes with DeckLink capabilities.
|
||||||
const nodesResult = await pool.query(
|
const nodesResult = await pool.query(
|
||||||
`SELECT id, hostname, ip_address, api_url, capabilities,
|
`SELECT id, hostname, ip_address, api_url, capabilities,
|
||||||
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
||||||
FROM cluster_nodes
|
FROM cluster_nodes
|
||||||
WHERE capabilities IS NOT NULL`
|
WHERE capabilities IS NOT NULL`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 2. Fetch all SDI recorders that are pinned to a node+device_index.
|
||||||
const recResult = await pool.query(
|
const recResult = await pool.query(
|
||||||
`SELECT id, name, status, container_id, node_id, device_index,
|
`SELECT id, name, status, container_id, node_id, device_index,
|
||||||
source_config
|
source_config
|
||||||
FROM recorders
|
FROM recorders
|
||||||
WHERE source_type = 'sdi' AND node_id IS NOT NULL`
|
WHERE source_type = 'sdi' AND node_id IS NOT NULL`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Build a fast lookup: "${node_id}:${device_index}" → recorder row.
|
||||||
const recByPort = new Map();
|
const recByPort = new Map();
|
||||||
for (const r of recResult.rows) {
|
for (const r of recResult.rows) {
|
||||||
const devIdx = r.device_index ?? r.source_config?.device ?? 0;
|
const devIdx = r.device_index ?? r.source_config?.device ?? 0;
|
||||||
recByPort.set(`${r.node_id}:${devIdx}`, r);
|
recByPort.set(`${r.node_id}:${devIdx}`, r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. For each port, determine signal state. We fire all capture-container
|
||||||
|
// fetches concurrently so the endpoint stays fast even with many ports.
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
for (const node of nodesResult.rows) {
|
for (const node of nodesResult.rows) {
|
||||||
const nodeOnline = Number(node.stale_seconds) < 120;
|
const nodeOnline = Number(node.stale_seconds) < 120;
|
||||||
|
|
@ -235,51 +208,79 @@ router.get('/devices/blackmagic/signal', async (req, res, next) => {
|
||||||
const model = (node.capabilities && node.capabilities.blackmagic_model) || null;
|
const model = (node.capabilities && node.capabilities.blackmagic_model) || null;
|
||||||
const localHostname = process.env.NODE_HOSTNAME || '';
|
const localHostname = process.env.NODE_HOSTNAME || '';
|
||||||
const isRemote = node.api_url && node.hostname !== localHostname;
|
const isRemote = node.api_url && node.hostname !== localHostname;
|
||||||
|
|
||||||
bm.forEach((d, idx) => {
|
bm.forEach((d, idx) => {
|
||||||
const portIndex = d.index !== undefined ? d.index : idx;
|
const portIndex = d.index !== undefined ? d.index : idx;
|
||||||
const rec = recByPort.get(`${node.id}:${portIndex}`);
|
const rec = recByPort.get(`${node.id}:${portIndex}`);
|
||||||
|
|
||||||
tasks.push((async () => {
|
tasks.push((async () => {
|
||||||
const base = {
|
const base = {
|
||||||
node_id: node.id, hostname: node.hostname, index: portIndex,
|
node_id: node.id,
|
||||||
device: d.device || null, model, node_online: nodeOnline,
|
hostname: node.hostname,
|
||||||
recorder_id: rec ? rec.id : null, recorder_name: rec ? rec.name : null,
|
index: portIndex,
|
||||||
|
device: d.device || null,
|
||||||
|
model,
|
||||||
|
node_online: nodeOnline,
|
||||||
|
recorder_id: rec ? rec.id : null,
|
||||||
|
recorder_name: rec ? rec.name : null,
|
||||||
recorder_status: rec ? rec.status : null,
|
recorder_status: rec ? rec.status : null,
|
||||||
signal: 'no-recorder', framesReceived: null, currentFps: null,
|
signal: 'no-recorder',
|
||||||
|
framesReceived: null,
|
||||||
|
currentFps: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!rec || rec.status !== 'recording' || !rec.container_id) {
|
if (!rec || rec.status !== 'recording' || !rec.container_id) {
|
||||||
|
// No active capture — if there's a recorder but it's not recording,
|
||||||
|
// report that; otherwise the port is unassigned.
|
||||||
if (rec && rec.status !== 'recording') base.signal = 'idle';
|
if (rec && rec.status !== 'recording') base.signal = 'idle';
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active recording — query the capture container for real signal.
|
||||||
try {
|
try {
|
||||||
let live = null;
|
let live = null;
|
||||||
if (isRemote) {
|
if (isRemote) {
|
||||||
const r = await fetch(`${node.api_url}/sidecar/${rec.container_id}/status`, { signal: AbortSignal.timeout(2500) });
|
const r = await fetch(
|
||||||
|
`${node.api_url}/sidecar/${rec.container_id}/status`,
|
||||||
|
{ signal: AbortSignal.timeout(2500) }
|
||||||
|
);
|
||||||
if (r.ok) live = (await r.json()).live;
|
if (r.ok) live = (await r.json()).live;
|
||||||
} else {
|
} else {
|
||||||
const r = await fetch(`http://recorder-${rec.id}:3001/capture/status`, { signal: AbortSignal.timeout(2000) });
|
const r = await fetch(
|
||||||
|
`http://recorder-${rec.id}:3001/capture/status`,
|
||||||
|
{ signal: AbortSignal.timeout(2000) }
|
||||||
|
);
|
||||||
if (r.ok) live = await r.json();
|
if (r.ok) live = await r.json();
|
||||||
}
|
}
|
||||||
if (live && live.signal) {
|
if (live && live.signal) {
|
||||||
base.signal = live.signal;
|
base.signal = live.signal;
|
||||||
base.framesReceived = live.framesReceived ?? null;
|
base.framesReceived = live.framesReceived ?? null;
|
||||||
base.currentFps = live.currentFps ?? null;
|
base.currentFps = live.currentFps ?? null;
|
||||||
} else { base.signal = 'connecting'; }
|
} else {
|
||||||
} catch (_) { base.signal = 'connecting'; }
|
base.signal = 'connecting';
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
base.signal = 'connecting';
|
||||||
|
}
|
||||||
return base;
|
return base;
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.all(tasks);
|
const results = await Promise.all(tasks);
|
||||||
res.json(results);
|
res.json(results);
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /devices/blackmagic – flatten every node's DeckLink cards for the
|
||||||
|
// recorder picker. Returns one entry per device with the host node info.
|
||||||
router.get('/devices/blackmagic', async (req, res, next) => {
|
router.get('/devices/blackmagic', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const r = await pool.query(
|
const r = await pool.query(
|
||||||
`SELECT id, hostname, ip_address, role, capabilities,
|
`SELECT id, hostname, ip_address, role, capabilities,
|
||||||
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
||||||
FROM cluster_nodes WHERE capabilities IS NOT NULL`
|
FROM cluster_nodes
|
||||||
|
WHERE capabilities IS NOT NULL`
|
||||||
);
|
);
|
||||||
const out = [];
|
const out = [];
|
||||||
for (const row of r.rows) {
|
for (const row of r.rows) {
|
||||||
|
|
@ -287,70 +288,118 @@ router.get('/devices/blackmagic', async (req, res, next) => {
|
||||||
const bm = (row.capabilities && row.capabilities.blackmagic) || [];
|
const bm = (row.capabilities && row.capabilities.blackmagic) || [];
|
||||||
const model = (row.capabilities && row.capabilities.blackmagic_model) || null;
|
const model = (row.capabilities && row.capabilities.blackmagic_model) || null;
|
||||||
bm.forEach((d, idx) => {
|
bm.forEach((d, idx) => {
|
||||||
out.push({ node_id: row.id, hostname: row.hostname, ip_address: row.ip_address,
|
out.push({
|
||||||
role: row.role, online, model, index: d.index !== undefined ? d.index : idx, device: d.device });
|
node_id: row.id,
|
||||||
|
hostname: row.hostname,
|
||||||
|
ip_address: row.ip_address,
|
||||||
|
role: row.role,
|
||||||
|
online,
|
||||||
|
model,
|
||||||
|
index: d.index !== undefined ? d.index : idx,
|
||||||
|
device: d.device,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.json(out);
|
res.json(out);
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /devices/deltacast – flatten every node's Deltacast cards for the
|
||||||
|
// recorder picker. Mirrors /devices/blackmagic shape so the UI can treat
|
||||||
|
// both card types uniformly.
|
||||||
router.get('/devices/deltacast', async (req, res, next) => {
|
router.get('/devices/deltacast', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const r = await pool.query(
|
const r = await pool.query(
|
||||||
`SELECT id, hostname, ip_address, role, capabilities,
|
`SELECT id, hostname, ip_address, role, capabilities,
|
||||||
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
||||||
FROM cluster_nodes WHERE capabilities IS NOT NULL`
|
FROM cluster_nodes
|
||||||
|
WHERE capabilities IS NOT NULL`
|
||||||
);
|
);
|
||||||
const out = [];
|
const out = [];
|
||||||
for (const row of r.rows) {
|
for (const row of r.rows) {
|
||||||
const online = Number(row.stale_seconds) < 120;
|
const online = Number(row.stale_seconds) < 120;
|
||||||
const dc = (row.capabilities && row.capabilities.deltacast) || [];
|
const dc = (row.capabilities && row.capabilities.deltacast) || [];
|
||||||
const model = (row.capabilities && row.capabilities.deltacast_model) || null;
|
const model = (row.capabilities && row.capabilities.deltacast_model) || null;
|
||||||
|
// Also synthesise entries from DELTACAST_PORT_COUNT if no entries reported yet —
|
||||||
|
// useful for nodes that haven't sent a heartbeat since the agent was updated.
|
||||||
dc.forEach((d, idx) => {
|
dc.forEach((d, idx) => {
|
||||||
out.push({ node_id: row.id, hostname: row.hostname, ip_address: row.ip_address,
|
out.push({
|
||||||
role: row.role, online, model: model || 'Deltacast',
|
node_id: row.id,
|
||||||
index: d.index !== undefined ? d.index : idx, device: d.device,
|
hostname: row.hostname,
|
||||||
present: d.present !== false, port_count: dc.length });
|
ip_address: row.ip_address,
|
||||||
|
role: row.role,
|
||||||
|
online,
|
||||||
|
model: model || 'Deltacast',
|
||||||
|
index: d.index !== undefined ? d.index : idx,
|
||||||
|
device: d.device,
|
||||||
|
present: d.present !== false,
|
||||||
|
port_count: dc.length,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.json(out);
|
res.json(out);
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /devices/deltacast/signal – live signal state for Deltacast ports.
|
||||||
|
// Same pattern as /devices/blackmagic/signal.
|
||||||
router.get('/devices/deltacast/signal', async (req, res, next) => {
|
router.get('/devices/deltacast/signal', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const [nodesRes, recordersRes] = await Promise.all([
|
const [nodesRes, recordersRes] = await Promise.all([
|
||||||
pool.query(`SELECT id, hostname, ip_address, api_url, capabilities,
|
pool.query(
|
||||||
|
`SELECT id, hostname, ip_address, api_url, capabilities,
|
||||||
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
||||||
FROM cluster_nodes WHERE capabilities IS NOT NULL`),
|
FROM cluster_nodes
|
||||||
pool.query(`SELECT id, node_id, device_index, status, source_type, container_id
|
WHERE capabilities IS NOT NULL`
|
||||||
FROM recorders WHERE source_type = 'deltacast'`),
|
),
|
||||||
|
pool.query(
|
||||||
|
`SELECT id, node_id, device_index, status, source_type, container_id
|
||||||
|
FROM recorders WHERE source_type = 'deltacast'`
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const recByNodePort = {};
|
const recByNodePort = {};
|
||||||
for (const rec of recordersRes.rows) {
|
for (const rec of recordersRes.rows) {
|
||||||
recByNodePort[`${rec.node_id}:${rec.device_index}`] = rec;
|
recByNodePort[`${rec.node_id}:${rec.device_index}`] = rec;
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = [];
|
const results = [];
|
||||||
const fetchPromises = [];
|
const fetchPromises = [];
|
||||||
|
|
||||||
for (const node of nodesRes.rows) {
|
for (const node of nodesRes.rows) {
|
||||||
const online = Number(node.stale_seconds) < 120;
|
const online = Number(node.stale_seconds) < 120;
|
||||||
const dc = (node.capabilities && node.capabilities.deltacast) || [];
|
const dc = (node.capabilities && node.capabilities.deltacast) || [];
|
||||||
const model = (node.capabilities && node.capabilities.deltacast_model) || 'Deltacast';
|
const model = (node.capabilities && node.capabilities.deltacast_model) || 'Deltacast';
|
||||||
|
|
||||||
for (const port of dc) {
|
for (const port of dc) {
|
||||||
const idx = port.index !== undefined ? port.index : dc.indexOf(port);
|
const idx = port.index !== undefined ? port.index : dc.indexOf(port);
|
||||||
const rec = recByNodePort[`${node.id}:${idx}`];
|
const rec = recByNodePort[`${node.id}:${idx}`];
|
||||||
const base = { node_id: node.id, hostname: node.hostname, ip_address: node.ip_address,
|
const base = {
|
||||||
online, model, index: idx, device: port.device, present: port.present !== false,
|
node_id: node.id,
|
||||||
recorder_id: rec ? rec.id : null, recorder_status: rec ? rec.status : null,
|
hostname: node.hostname,
|
||||||
signal: 'no-recorder', framesReceived: null, currentFps: null };
|
ip_address: node.ip_address,
|
||||||
|
online,
|
||||||
|
model,
|
||||||
|
index: idx,
|
||||||
|
device: port.device,
|
||||||
|
present: port.present !== false,
|
||||||
|
recorder_id: rec ? rec.id : null,
|
||||||
|
recorder_status: rec ? rec.status : null,
|
||||||
|
signal: 'no-recorder',
|
||||||
|
framesReceived: null,
|
||||||
|
currentFps: null,
|
||||||
|
};
|
||||||
|
|
||||||
if (!rec) { results.push(base); continue; }
|
if (!rec) { results.push(base); continue; }
|
||||||
if (rec.status !== 'recording') { base.signal = 'idle'; results.push(base); continue; }
|
if (rec.status !== 'recording') { base.signal = 'idle'; results.push(base); continue; }
|
||||||
|
|
||||||
|
// Active recording — query capture container for real signal.
|
||||||
const fetchIdx = results.length;
|
const fetchIdx = results.length;
|
||||||
results.push(base);
|
results.push(base);
|
||||||
fetchPromises.push((async () => {
|
fetchPromises.push((async () => {
|
||||||
try {
|
try {
|
||||||
const url = node.api_url ? `${node.api_url}/sidecar/${rec.container_id}/status`
|
const url = node.api_url
|
||||||
|
? `${node.api_url}/sidecar/${rec.container_id}/status`
|
||||||
: `http://recorder-${rec.id}:3001/capture/status`;
|
: `http://recorder-${rec.id}:3001/capture/status`;
|
||||||
const r = await fetch(url, { signal: AbortSignal.timeout(2500) });
|
const r = await fetch(url, { signal: AbortSignal.timeout(2500) });
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
|
|
@ -361,24 +410,35 @@ router.get('/devices/deltacast/signal', async (req, res, next) => {
|
||||||
results[fetchIdx].currentFps = live.currentFps ?? null;
|
results[fetchIdx].currentFps = live.currentFps ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) { results[fetchIdx].signal = 'connecting'; }
|
} catch (_) {
|
||||||
|
results[fetchIdx].signal = 'connecting';
|
||||||
|
}
|
||||||
})());
|
})());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(fetchPromises);
|
await Promise.all(fetchPromises);
|
||||||
res.json(results);
|
res.json(results);
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /:id/ping – probe the node's api_url/health endpoint directly
|
||||||
router.get('/:id/ping', async (req, res, next) => {
|
router.get('/:id/ping', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const r = await pool.query('SELECT id, hostname, api_url FROM cluster_nodes WHERE id = $1', [req.params.id]);
|
const r = await pool.query(
|
||||||
|
'SELECT id, hostname, api_url FROM cluster_nodes WHERE id = $1',
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' });
|
if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' });
|
||||||
|
|
||||||
const node = r.rows[0];
|
const node = r.rows[0];
|
||||||
if (!node.api_url) return res.json({ reachable: false, reason: 'no api_url registered' });
|
if (!node.api_url) return res.json({ reachable: false, reason: 'no api_url registered' });
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
try {
|
try {
|
||||||
const upstream = await fetch(`${node.api_url}/health`, { signal: AbortSignal.timeout(4000) });
|
const upstream = await fetch(`${node.api_url}/health`, {
|
||||||
|
signal: AbortSignal.timeout(4000),
|
||||||
|
});
|
||||||
const latency_ms = Date.now() - start;
|
const latency_ms = Date.now() - start;
|
||||||
const body = await upstream.json().catch(() => ({}));
|
const body = await upstream.json().catch(() => ({}));
|
||||||
res.json({ reachable: upstream.ok, latency_ms, status: upstream.status, agent: body });
|
res.json({ reachable: upstream.ok, latency_ms, status: upstream.status, agent: body });
|
||||||
|
|
@ -388,83 +448,8 @@ router.get('/:id/ping', async (req, res, next) => {
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Capture-driver / SDK deployment ────────────────────────────────────────
|
|
||||||
// Admins install/update vendor capture-card drivers on a node from the UI.
|
|
||||||
// We resolve the node's api_url (like /:id/ping) and forward to its node-agent,
|
|
||||||
// which runs deploy/install-driver.sh <vendor> in a privileged one-shot
|
|
||||||
// container against the host kernel. Vendor is allowlisted here AND on the
|
|
||||||
// agent. We never echo the agent token or proprietary paths back to the client.
|
|
||||||
const DRIVER_VENDORS = ['blackmagic', 'aja', 'deltacast', 'ndi'];
|
|
||||||
|
|
||||||
// Bearer the agent expects (its NODE_TOKEN). Configured server-side; never
|
|
||||||
// derived from client input and never returned to the browser.
|
|
||||||
function agentAuthHeaders() {
|
|
||||||
const tok = process.env.NODE_AGENT_TOKEN || '';
|
|
||||||
return tok ? { Authorization: `Bearer ${tok}` } : {};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resolveNode(id) {
|
|
||||||
const r = await pool.query('SELECT id, hostname, api_url, capabilities FROM cluster_nodes WHERE id = $1', [id]);
|
|
||||||
return r.rowCount === 0 ? null : r.rows[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get('/:id/driver-status', requireAdmin, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const node = await resolveNode(req.params.id);
|
|
||||||
if (!node) return res.status(404).json({ error: 'Node not found' });
|
|
||||||
if (!node.api_url) return res.status(409).json({ error: 'Node has no api_url registered' });
|
|
||||||
try {
|
|
||||||
const upstream = await fetch(`${node.api_url}/driver/status`, {
|
|
||||||
headers: agentAuthHeaders(),
|
|
||||||
signal: AbortSignal.timeout(6000),
|
|
||||||
});
|
|
||||||
const body = await upstream.json().catch(() => ({}));
|
|
||||||
if (!upstream.ok) {
|
|
||||||
return res.status(502).json({ error: 'Agent driver-status failed', status: upstream.status });
|
|
||||||
}
|
|
||||||
res.json(body);
|
|
||||||
} catch (err) {
|
|
||||||
res.status(502).json({ error: 'Node unreachable', reason: err.message });
|
|
||||||
}
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/:id/install-driver', requireAdmin, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const vendor = String(req.body?.vendor || '').toLowerCase();
|
|
||||||
if (!DRIVER_VENDORS.includes(vendor)) {
|
|
||||||
return res.status(400).json({ error: `Invalid vendor (allowed: ${DRIVER_VENDORS.join(', ')})` });
|
|
||||||
}
|
|
||||||
const node = await resolveNode(req.params.id);
|
|
||||||
if (!node) return res.status(404).json({ error: 'Node not found' });
|
|
||||||
if (!node.api_url) return res.status(409).json({ error: 'Node has no api_url registered' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
// DKMS builds can take minutes — generous timeout.
|
|
||||||
const upstream = await fetch(`${node.api_url}/driver/install`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json', ...agentAuthHeaders() },
|
|
||||||
body: JSON.stringify({ vendor }),
|
|
||||||
signal: AbortSignal.timeout(600000),
|
|
||||||
});
|
|
||||||
const body = await upstream.json().catch(() => ({}));
|
|
||||||
// Relay logs/result. install-driver.sh never echoes secrets; the agent
|
|
||||||
// returns only its structured [install-driver] log lines + status.
|
|
||||||
res.status(upstream.ok ? 200 : 502).json({
|
|
||||||
ok: !!body.ok,
|
|
||||||
vendor,
|
|
||||||
exitCode: body.exitCode ?? null,
|
|
||||||
rebootRequired: !!body.rebootRequired,
|
|
||||||
status: body.status ?? null,
|
|
||||||
logs: typeof body.logs === 'string' ? body.logs : '',
|
|
||||||
error: body.ok ? undefined : (body.error || 'Install failed — see logs'),
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
res.status(502).json({ error: 'Node unreachable or install timed out', reason: err.message });
|
|
||||||
}
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// GET /metrics - live per-node utilization (CPU, RAM, GPU)
|
||||||
router.get('/metrics', async (req, res, next) => {
|
router.get('/metrics', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const r = await pool.query(
|
const r = await pool.query(
|
||||||
|
|
@ -472,37 +457,59 @@ router.get('/metrics', async (req, res, next) => {
|
||||||
cpu_usage, mem_used_mb, mem_total_mb,
|
cpu_usage, mem_used_mb, mem_total_mb,
|
||||||
capabilities, metrics,
|
capabilities, metrics,
|
||||||
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
EXTRACT(EPOCH FROM (NOW() - last_seen)) AS stale_seconds
|
||||||
FROM cluster_nodes ORDER BY registered_at ASC`
|
FROM cluster_nodes
|
||||||
|
ORDER BY registered_at ASC`
|
||||||
);
|
);
|
||||||
|
|
||||||
const nodes = r.rows.map(row => {
|
const nodes = r.rows.map(row => {
|
||||||
const capGpus = (row.capabilities && row.capabilities.gpus) || [];
|
const capGpus = (row.capabilities && row.capabilities.gpus) || [];
|
||||||
const liveGpus = (row.metrics && row.metrics.gpus) || [];
|
const liveGpus = (row.metrics && row.metrics.gpus) || [];
|
||||||
|
|
||||||
const gpus = capGpus.map((g, idx) => {
|
const gpus = capGpus.map((g, idx) => {
|
||||||
const live = liveGpus.find(l => l.index === g.index) || liveGpus[idx] || {};
|
const live = liveGpus.find(l => l.index === g.index) || liveGpus[idx] || {};
|
||||||
return { name: g.name || null, util_pct: live.util_pct != null ? live.util_pct : null,
|
return {
|
||||||
|
name: g.name || null,
|
||||||
|
util_pct: live.util_pct != null ? live.util_pct : null,
|
||||||
memory_used_mb: live.memory_used_mb != null ? live.memory_used_mb : null,
|
memory_used_mb: live.memory_used_mb != null ? live.memory_used_mb : null,
|
||||||
memory_total_mb: g.memory_mb != null ? g.memory_mb : (live.memory_total_mb ?? null) };
|
memory_total_mb: g.memory_mb != null ? g.memory_mb : (live.memory_total_mb ?? null),
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
// include any live GPUs not in static capabilities
|
||||||
for (const lg of liveGpus) {
|
for (const lg of liveGpus) {
|
||||||
if (!capGpus.some(g => g.index === lg.index)) {
|
if (!capGpus.some(g => g.index === lg.index)) {
|
||||||
gpus.push({ name: lg.name || null, util_pct: lg.util_pct != null ? lg.util_pct : null,
|
gpus.push({
|
||||||
|
name: lg.name || null,
|
||||||
|
util_pct: lg.util_pct != null ? lg.util_pct : null,
|
||||||
memory_used_mb: lg.memory_used_mb != null ? lg.memory_used_mb : null,
|
memory_used_mb: lg.memory_used_mb != null ? lg.memory_used_mb : null,
|
||||||
memory_total_mb: lg.memory_total_mb != null ? lg.memory_total_mb : null });
|
memory_total_mb: lg.memory_total_mb != null ? lg.memory_total_mb : null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { id: row.id, hostname: row.hostname, role: row.role,
|
|
||||||
online: Number(row.stale_seconds) < 120, last_seen: row.last_seen,
|
return {
|
||||||
|
id: row.id,
|
||||||
|
hostname: row.hostname,
|
||||||
|
role: row.role,
|
||||||
|
online: Number(row.stale_seconds) < 120,
|
||||||
|
last_seen: row.last_seen,
|
||||||
cpu_util_pct: row.cpu_usage != null ? Number(row.cpu_usage) : null,
|
cpu_util_pct: row.cpu_usage != null ? Number(row.cpu_usage) : null,
|
||||||
ram_used_mb: row.mem_used_mb != null ? row.mem_used_mb : null,
|
ram_used_mb: row.mem_used_mb != null ? row.mem_used_mb : null,
|
||||||
ram_total_mb: row.mem_total_mb != null ? row.mem_total_mb : null, gpus };
|
ram_total_mb: row.mem_total_mb != null ? row.mem_total_mb : null,
|
||||||
|
gpus,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ nodes });
|
res.json({ nodes });
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// DELETE /:id – deregister a node
|
||||||
router.delete('/:id', async (req, res, next) => {
|
router.delete('/:id', async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const r = await pool.query('DELETE FROM cluster_nodes WHERE id = $1 RETURNING id', [req.params.id]);
|
const r = await pool.query(
|
||||||
|
'DELETE FROM cluster_nodes WHERE id = $1 RETURNING id',
|
||||||
|
[req.params.id]
|
||||||
|
);
|
||||||
if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' });
|
if (r.rowCount === 0) return res.status(404).json({ error: 'Node not found' });
|
||||||
res.json({ ok: true });
|
res.json({ ok: true });
|
||||||
} catch (err) { next(err); }
|
} catch (err) { next(err); }
|
||||||
|
|
|
||||||
|
|
@ -5,23 +5,9 @@
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import pool from '../db/pool.js';
|
import pool from '../db/pool.js';
|
||||||
import { assertProjectAccess } from '../auth/authz.js';
|
|
||||||
|
|
||||||
const router = express.Router({ mergeParams: true });
|
const router = express.Router({ mergeParams: true });
|
||||||
|
|
||||||
// Scope every comment route to the parent asset's project: resolve project_id
|
|
||||||
// via the asset, then require 'view' to read and 'edit' to write. A non-UUID or
|
|
||||||
// unknown asset is a clean 404 before any access decision leaks its existence.
|
|
||||||
const MUTATING = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
||||||
router.use(async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { rows } = await pool.query('SELECT project_id FROM assets WHERE id = $1', [req.params.assetId]);
|
|
||||||
if (rows.length === 0) return res.status(404).json({ error: 'Asset not found' });
|
|
||||||
await assertProjectAccess(req.user, rows[0].project_id, MUTATING.has(req.method) ? 'edit' : 'view');
|
|
||||||
next();
|
|
||||||
} catch (err) { next(err); }
|
|
||||||
});
|
|
||||||
|
|
||||||
function rowToJson(r) {
|
function rowToJson(r) {
|
||||||
return {
|
return {
|
||||||
id: r.id,
|
id: r.id,
|
||||||
|
|
@ -63,9 +49,8 @@ router.post('/', async (req, res, next) => {
|
||||||
if (!body || !String(body).trim()) {
|
if (!body || !String(body).trim()) {
|
||||||
return res.status(400).json({ error: 'body is required' });
|
return res.status(400).json({ error: 'body is required' });
|
||||||
}
|
}
|
||||||
// Author is the authenticated user (requireAuth sets req.user for both
|
// Best-effort author lookup — pull from the session if AUTH_ENABLED is on.
|
||||||
// session and bearer auth, and the dev user when AUTH_ENABLED=false).
|
const userId = req.session?.userId || null;
|
||||||
const userId = req.user?.id || null;
|
|
||||||
|
|
||||||
const ins = await pool.query(
|
const ins = await pool.query(
|
||||||
`INSERT INTO asset_comments (asset_id, user_id, body, frame_ms)
|
`INSERT INTO asset_comments (asset_id, user_id, body, frame_ms)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import express from 'express';
|
||||||
import { Queue } from 'bullmq';
|
import { Queue } from 'bullmq';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import pool from '../db/pool.js';
|
import pool from '../db/pool.js';
|
||||||
import { assertProjectAccess } from '../auth/authz.js';
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
@ -61,8 +60,6 @@ router.post('/youtube', async (req, res, next) => {
|
||||||
if (projCheck.rows.length === 0) {
|
if (projCheck.rows.length === 0) {
|
||||||
return res.status(404).json({ error: 'Project not found' });
|
return res.status(404).json({ error: 'Project not found' });
|
||||||
}
|
}
|
||||||
// Importing writes an asset into the project — require edit access.
|
|
||||||
await assertProjectAccess(req.user, projectId, 'edit');
|
|
||||||
|
|
||||||
const assetId = uuidv4();
|
const assetId = uuidv4();
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue