Compare commits

..

No commits in common. "main" and "v0.phase1-editor" have entirely different histories.

351 changed files with 9001 additions and 68337 deletions

View file

@ -22,56 +22,5 @@ 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 # Auth (set to 'true' to require login; false for open/dev mode)
# Bearer token for node-agent to authenticate with mam-api /driver/* endpoints. AUTH_ENABLED=false
# Generate with: openssl rand -hex 32
NODE_AGENT_TOKEN=changeme
# 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
# 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
# CORS allowlist — comma-separated origins that may carry credentials to the API.
# Same-origin requests via the nginx reverse proxy do not need to be listed here.
# Leave empty to allow any origin (DEV ONLY).
ALLOWED_ORIGINS=
# Reverse-proxy trust — set 'true' when the API sits behind nginx terminating HTTPS,
# 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).
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

16
.gitignore vendored
View file

@ -14,24 +14,8 @@ yarn-error.log*
# Build output # Build output
dist/ dist/
build/ build/
# ...but the Premiere panel's packaging pipeline lives at build/ — keep it tracked.
!services/premiere-plugin/build/
!services/premiere-plugin/build/**
# OS # OS
.DS_Store .DS_Store
.env.swp .env.swp
.env.swo .env.swo
services/editor/node_modules
services/editor/**/node_modules
services/editor/**/dist
services/editor/.pnpm-store
# Blackmagic DeckLink SDK headers are now committed (private/internal repo) under services/capture/sdk/.
# Runtime .so libs (libDeckLinkAPI.so) come from the DesktopVideo driver install and are not committed.
services/capture/lib/
# Editor backups
*.bak
*.bak2
.env.bak.*

153
DESIGN.md
View file

@ -1,153 +0,0 @@
# Design system
Documented from the live tokens in `services/web-ui/public/styles.css` and the patterns used across `screens-*.jsx`. Treat this as the source of truth for shared primitives; pages may override locally but should not invent new color or type scales.
## Color
Dark theme only. Tokens live in `:root` in `styles.css`. Tinted neutrals — no `#000`, no `#fff`.
### Surfaces
| Token | Hex | Use |
|---|---|---|
| `--bg-0` | `#0B0D11` | Page background, deepest surface |
| `--bg-1` | `#14171E` | Panels, sidebars, primary chrome |
| `--bg-2` | `#1B1F27` | Panel headers, hover state |
| `--bg-3` | `#232833` | Inputs, raised buttons, badges |
| `--bg-4` | `#2D3340` | Strongly raised elements (rare) |
### Borders + overlays
| Token | Use |
|---|---|
| `--border` (rgba white 6%) | Default 1px separator |
| `--border-strong` (rgba white 10%) | Emphasized separator |
| `--border-stronger` (rgba white 14%) | Hover/active border |
| `--hover` (rgba white 4%) | Subtle hover fill |
| `--hover-strong` (rgba white 7%) | Stronger hover fill |
### Text
| Token | Hex | Use |
|---|---|---|
| `--text-1` | `#F2F3F6` | Primary content |
| `--text-2` | `#A8AEBC` | Secondary content |
| `--text-3` | `#6B7280` | Labels, metadata |
| `--text-4` | `#4B5260` | Section labels, off-month, disabled |
### Accent
`--accent: #5B7CFA` (Frame.io-ish blue). Soft variants `--accent-soft` (14%) and `--accent-soft-2` (22%) for fills. `--accent-text: #B4C3FF` for high-contrast accent text.
**Restrained strategy.** Accent is the only saturated color in chrome. Status colors below appear only where they reflect actual state. Project colors appear only on project chips.
### Status
| Token | Hex | Use |
|---|---|---|
| `--success` | `#2DD4A8` | Done, healthy, ready |
| `--warning` | `#F5A623` | Processing, attention-needed |
| `--danger` | `#FF5B5B` | Failed, error |
| `--live` | `#FF3B30` | Currently recording (broadcast red, intentionally hotter than `--danger`) |
| `--purple` | `#B57CFA` | Editor / dev tags |
Each has a `*-soft` variant at ~14% for background fills.
### Project palette
Six fixed colors cycled by index in `data.jsx` (`PROJECT_COLORS`):
`#5B7CFA · #2DD4A8 · #FF5B5B · #F5A623 · #B57CFA · #6B7280`
Used only on the project chip / rail dot. Do not reuse for status meaning.
## Typography
- **Sans:** Geist (with `cv11`, `ss01` features enabled). All UI text by default.
- **Mono:** Geist Mono. URLs, IDs, timestamps, durations, technical metadata.
Body scale runs small for density:
| Size | Use |
|---|---|
| 10.5px, 600, uppercase, 0.060.08em letterspacing | Column heads, section labels |
| 1111.5px | Metadata, secondary rows |
| 1212.5px | Body in lists/tables |
| 13px, 500600 | Row labels, button text |
| 14px | Default body |
| 15px, 600 | Modal titles, panel heads |
| 2228px, 600+ | Page H1 (`.page-header h1`) |
Never use `gradient text` (impeccable absolute ban). Emphasis via weight and size only.
## Layout
- Sidebar: 232px fixed (`--sidebar-w`).
- Topbar: 56px (`--topbar-h`).
- Row height: 44px default, 36px compact (`--row-h`, `data-density="compact"`).
- Gap unit: 16px default, 12px compact (`--gap`).
- Border radius scale: 4 / 6 / 8 / 12 / 16 px (`--r-xs` → `--r-xl`).
- Panels (`.panel`): `--bg-1` + 1px `--border` + `--r-lg`. Use for grouped lists, not for every section.
- Do NOT nest panels.
### Page header
Standard screens use `.page > .page-header > h1`. Three screens are documented exceptions because they need full-bleed layouts and their own top-chrome:
- **Home** uses `.launcher` (lobby pattern: hero logo + tile grid + status pip).
- **Library** uses `.library-layout` (dual-pane rail + main). The h1 sits inside `.library-toolbar` as `.toolbar-title`.
- **Editor** uses `.editor-shell` (NLE with timeline + monitors). The beta banner doubles as its top chrome.
All other screens should render `<div className="page"><div className="page-header"><h1>…</h1>…</div>…</div>` for consistent IA and screen-reader hierarchy.
## Shadow
Two tokens, used sparingly:
- `--shadow-card`: subtle inset highlight + soft outer. Default for raised inputs.
- `--shadow-pop`: modal / popover / context menu.
No drop shadows on flat surfaces. No glow effects (except: the EPG now-line uses a tight `box-shadow` for the broadcast-red glow, and live status dots have a pulse halo; these are state cues, not decoration).
## Motion
- Default transition: 80120ms on background/border (`transition: background 80ms, border 80ms`).
- Heavier reveals: 200ms.
- Easing: prefer ease-out (no bounce, no elastic).
- Don't animate layout (width/height/top); animate transforms and opacity.
## Patterns
### Status badges (`.badge`)
Variants: `live` (red, animated dot), `success`, `danger`, `warning`, `accent`, `neutral`, `outline`. Tiny — 910px font, ~2px vertical padding. Reserved for state, not labels.
### Row tables (`.user-row`, `.token-row`, `.job-row`, `.schedule-row`, etc.)
CSS-grid with explicit columns. Header row uses `.head` (uppercase 10.5px). No card stacking — these are dense data lists.
### Schedule EPG (`.epg-*`)
Broadcast timeline pattern. Recorder rows × time-of-day axis. Single scrolling container with sticky left gutter (220px) and sticky top ruler (32px). Hour rhythm via `repeating-linear-gradient`. Now-line is a 1px hot-red vertical bar with `--live` glow, animated by re-rendering the line component every second (transform-only positioning, no layout thrash). Event blocks are absolute-positioned within each row, colored via `--epg-block-color` set per recorder's project color. Live events get a red gradient + pulse; failures get a glyph + full red border; past events fade to 0.55 opacity.
### Inputs
`.field-input``--bg-3` fill, 1px `--border`, `--r-sm`, 12.5px font. Focus: `--border-strong`.
### Status dot (`.signal-dot`, `.rec-dot`, etc.)
Small (~68px) circle, used inline with text. Recording dots pulse with a keyframe animation.
## Impeccable absolute bans (apply project-wide)
- No `border-left` / `border-right` greater than 1px as a colored accent (rewrite with full borders, leading icons, or background tint).
- No `background-clip: text` gradient text.
- No glassmorphism (blur + translucent) decoratively.
- No hero-metric template (big number, small label, gradient accent, supporting stats).
- No identical card grids.
- Modal as last resort — exhaust inline alternatives first.
- No em dashes in code or copy. Use commas, colons, parentheses, periods.
## To extend
When a new design need arises, prefer adding a variant to an existing primitive over inventing a new token. New tokens land in `styles.css`. New components land in the relevant `screens-*.jsx` only if reused; otherwise keep them local.

View file

@ -1,61 +0,0 @@
# Product
## What it is
Dragonflight (codebase name; UI brand: "Dragonflight · Wild Dragon Broadcast") is an on-prem broadcast media asset manager and live ingest controller. Operators capture from SRT, RTMP, and SDI sources, schedule windowed recordings against named recorders, transcode/proxy in the background, browse and clip in a library, import from YouTube, and hand off to Premiere or an in-house editor.
The deployable surface is a React (Babel-in-browser) single-page app served by nginx, talking to a Node/Express API backed by Postgres + Redis + BullMQ, with capture and worker containers handling the actual media.
Stack lives at `services/web-ui` (UI), `services/mam-api` (API), `services/capture`, `services/worker`, `services/editor`.
## Register
**Product.** This is operator UI for a working broadcast tool, not a brand site or marketing surface. Design serves the operator's job, not the brand's identity.
## Users
Primary: broadcast operators and engineers running live productions. They schedule and supervise back-to-back recordings across multiple recorders in a single shift. They care about: what's recording right now, what's about to start, what failed, and which recorder is bound to what source.
Secondary: editors and producers who consume the resulting library, comment on assets, request proxy regeneration. They mostly live in the Library and Asset detail screens, not the scheduler.
Tertiary: admins managing recorders, users, cluster nodes, and storage. Live in the Admin screens.
## Product purpose
Replace a stack of one-trick tools (NewBlue scheduler, vMix capture, ad-hoc Premiere ingest, manual S3 syncs) with a single operator surface that supervises recorders, owns the asset catalog through proxy generation, and stays out of the editor's way once footage lands.
## Tone
Function-first. Dense. Operations-room. Mono fonts where data lives. Small type. Restrained chrome. The operator should be able to glance at any screen and read the state of the system in under a second; they should never wonder "is this still happening" or "did that finish."
Not: marketing-warm, conversational, gamified, congratulatory.
## Strategic principles
1. **Glance-readable status.** Every list, every cell, every badge must answer "what is the state of this thing right now" without a hover.
2. **Trust the operator.** No confirmation modals for reversible actions. No nag, no toasts for routine success. Errors stay visible until acknowledged.
3. **Time is the spine.** This product is about time-based events (recordings, schedules, jobs). UIs should privilege time as a primary axis, not bury it under categorical filters.
4. **Density over whitespace.** Operators run multi-monitor setups and want maximum signal per pixel. Generous whitespace is a brand-site reflex; reject it here.
5. **No half-states.** Pending UIs disable controls; live UIs show live data; failed UIs show the failure inline, not in a separate notification feed.
## Anti-references
Steer away from:
- **Linear-pastel SaaS aesthetics.** Purples, mint accents, friendly empty states with cartoon line illustrations.
- **Google-Calendar generic.** A neutral month grid with rounded event chips, no operational signal, optimized for "is Friday free" rather than "is recorder A in conflict at 14:00."
- **Gantt-chart project-management feel.** Implies long-horizon planning of tasks with dependencies; this product is hour-scale broadcast operations.
- **Cards-for-everything.** Identical card grids of icon+label+value. Particularly the SaaS hero-metric template.
- **Decorative blur / glassmorphism / gradient text.** Read as decorative AI slop in a broadcast-ops context.
- **NewBlue / Wirecast skinning.** Heavy bevels, gradient buttons, drop shadows. Read as outdated broadcast software.
## Decision context (Schedule v2)
The Schedule screen was rebuilt as an EPG (electronic program guide) timeline. Operator confirmed:
- Density: **heavy / back-to-back, many recorders all day** — month grid was the wrong primary.
- At-a-glance signals: now/next, recorder bookings (conflicts), project context, failure history.
- Aesthetic: **studio / cinematic — dark, type-led, accent moments.** DaVinci-Resolve-panel territory.
- Scope: full rethink — replace the primary view.
Implementation: recorder rows × time-of-day horizontal axis, sticky gutter + ruler, vertical hot-red now-line, event blocks colored by project, status pills in a top strip. Today / Week / List views.

264
README.md
View file

@ -1,258 +1,60 @@
# Dragonflight # Wild Dragon
Self-hosted broadcast media-asset management system that replaces legacy tools like Grass Valley AMPP and FramelightX. Handles live ingest, growing-file editing, scheduling, transcoding, and asset management in a single operator-focused interface. Self-hosted Media Asset Management platform built to replace Grass Valley AMPP FramelightX.
> Repo renamed from `wild-dragon``dragonflight` (2026-05-23). The old URL still redirects. ## Services
## Home Dashboard | Service | Port | Description |
|---------|------|-------------|
<img src="docs/screenshots/01-home.png" alt="Home Dashboard" width="800" /> | **web-ui** | 8080 | Browser-based MAM interface + capture controls |
| **mam-api** | 3000 | REST API — assets, projects, bins, jobs |
The home screen provides quick access to all major features and displays system status at a glance: | **capture** | 3001 | SDI capture daemon (Blackmagic DeckLink + FFmpeg) |
- **Library** — Browse projects, bins, and assets with hover-scrub previews | **worker** | — | Async job processor (proxy gen, thumbnails, conform) |
- **Recorders** — View configured capture devices and their status | **db** | 5432 | PostgreSQL 16 metadata store |
- **Editor** — Timeline editor with cross-clip preview and render queue | **queue** | 6379 | Redis 7 job queue (BullMQ) |
- **Jobs** — Proxy and thumbnail queue with retry controls
- **Settings** — Configure storage, encoder, growing files, and capture SDK
- **Dashboard** — Operations view showing recent activity, job queue, and cluster health
---
## Core Features
### 1. Live Ingest & Capture
**Multi-protocol source capture with per-recorder codec settings**
Dragonflight ingests from multiple sources simultaneously:
- **SRT** (Secure Reliable Transport) — caller and listener modes
- **RTMP** — standard streaming protocol
- **SDI** — via Blackmagic DeckLink cards with FFmpeg SDK 16.x patches
Each recorder can be configured with independent codec settings:
- ProRes (hi-res masters)
- H.264 / H.265 (proxies)
- DNxHR (Avid compatibility)
Audio routing and per-source configuration ensure flexibility for multi-camera productions.
### 2. Growing-File Editing
**Live editing in Premiere Pro while capture is still writing**
Editors mount the SMB landing zone directly in Premiere Pro and edit the live master file as it's being written. The included CEP (Custom Extension Panel) provides:
- Real-time clip detection and frame-accurate trimming
- One-click relink to final S3 master after promotion
- No waiting for capture to finish before editorial begins
### 3. Recorder Scheduler
**Time-windowed recording automation**
Schedule recordings with:
- One-shot, daily, or weekly recurrence
- Automatic start/stop via 15-second tick loop
- Conflict detection across recorders
- Project and bin assignment at schedule time
### 4. Library & Asset Management
**Browse, search, and organize captured footage**
The Library screen provides:
- Project and bin hierarchy
- Asset detail view with frame-anchored persistent comments
- Right-click context menu (move-to-bin, rename, delete)
- Global cmd/ctrl-K search across assets, projects, recorders, jobs, and users
- Hover-scrub preview with HLS playback
### 5. Jobs Queue
**BullMQ-backed proxy and thumbnail generation**
Automated background processing:
- Per-job retry logic with exponential backoff
- Bulk "retry all failed" for batch recovery
- Inline error messages with actionable diagnostics
- Status tracking: ingesting → processing → ready
Proxy encoder options:
- CPU-based: libx264 (H.264)
- GPU-accelerated: NVENC (NVIDIA) or VAAPI (AMD/Intel)
### 6. Timeline Conform & Export
**FCP XML export with server-side FFmpeg rendering**
The Premiere Pro panel exports FCP XML with:
- Server-side conform via FFmpeg
- Multiple output formats: H.264, H.265, ProRes
- Resolution presets: Broadcast, Web, Archive
- Batch processing with job queue integration
### 7. Hi-Res Auto-Relink
**One-click batch relink of proxy clips to frame-accurate server-trimmed masters**
After editing on proxies:
- Select clips in Premiere
- Trigger relink from the CEP panel
- Server trims hi-res segments to exact in/out points
- Concurrent trim worker pool for speed
- 24-hour TTL with automatic cleanup
### 8. Settings & Configuration
**Centralized control for storage, encoding, and capture**
Configure:
- **S3 Storage** — endpoint, bucket, credentials (with env-var fallback)
- **Proxy Encoder** — CPU vs GPU, bitrate, resolution
- **Growing Files** — SMB path, retention, auto-promotion
- **Capture SDK** — Blackmagic, AJA, or Deltacast uploader selection
### 9. Cluster & Distributed Capture
**Primary + worker topology with remote DeckLink nodes**
- Primary node runs API, scheduler, and web UI
- Worker nodes handle proxy/thumbnail jobs
- Remote capture nodes run DeckLink cards off-host
- Heartbeat health monitoring
- Automatic failover and recovery
### 10. Admin & User Management
**Role-based access, token auth, and cluster monitoring**
- User creation and role assignment
- API token generation for integrations
- Container and cluster node status
- System health dashboard
---
## Quick Start ## Quick Start
```bash ```bash
# Clone (repo renamed; old URL still redirects) # Clone
git clone https://forge.wilddragon.net/zgaetano/dragonflight.git git clone https://forge.wilddragon.net/zgaetano/wild-dragon.git
cd dragonflight cd wild-dragon
# Configure # Configure
cp .env.example .env cp .env.example .env
# Edit .env — S3 credentials + SESSION_SECRET at minimum # Edit .env with your S3 credentials and secrets
# Launch # Launch
docker compose up -d docker compose up -d
# Open # Open
open http://localhost:47434 open http://localhost:8080
``` ```
## Architecture ## Architecture
``` ```
SDI / SRT / RTMP ──► capture (FFmpeg) SDI Input (DeckLink) → capture service → dual FFmpeg streams
├─ HLS preview tee ──► /live/<assetId>/index.m3u8 ├─ HiRes (ProRes) → S3
└─ master output └─ Proxy (H.264) → S3
├─ growing_enabled=true:
│ /growing/<projectId>/<clip>.mov web-ui ← mam-api ← PostgreSQL ← worker (BullMQ)
│ (Premiere mounts SMB, edits live) ├─ proxy_gen
│ └─► promotion worker uploads to S3 ├─ thumbnail
└─ conform (EDL → FFmpeg → export)
└─ growing_enabled=false:
multipart stream → S3
assets POST ──► proxy job ──► worker
├─ libx264 (CPU) or NVENC/VAAPI (GPU)
├─ thumbnail job
└─ status: ingesting → processing → ready
``` ```
## Tech Stack ## Tech Stack
- **Runtime:** Node.js 22, Docker Compose - **Backend:** Node.js / Express
- **Backend:** Express, PostgreSQL 16, Redis 7 + BullMQ - **Frontend:** Vanilla HTML/CSS/JS
- **Frontend:** Vanilla React via in-browser Babel (no bundler), hls.js - **Database:** PostgreSQL 16
- **Media:** FFmpeg 7.1 with SDK 16 DeckLink patches - **Queue:** Redis 7 + BullMQ
- **Codecs:** ProRes, H.264, H.265, DNxHR, MOV/MP4/MXF containers - **Storage:** S3-compatible (RustFS)
- **Storage:** S3-compatible (RustFS) for masters, proxies, thumbnails - **Media Processing:** FFmpeg
- **Capture:** Blackmagic DeckLink SDK
## Services - **Deployment:** Docker Compose
| Service | Port | Purpose |
|---------|------|---------|
| **web-ui** | 47434 | Browser SPA + capture controls |
| **mam-api** | 47432 | REST API + recorder orchestration + scheduler |
| **capture** | 47433 / 9000 / 1935 | DeckLink/SRT/RTMP ingest sidecar |
| **worker** | — | BullMQ proxy + thumbnail workers |
| **db** | 5432 | PostgreSQL 16 |
| **queue** | 6379 | Redis 7 |
---
## Workflow Example: Live-to-Edit
1. **Operator** schedules a recording on Recorder A for 14:0015:30, assigns to "News/Segment-A" project
2. **Capture** starts at 14:00, writes ProRes master to SMB landing zone
3. **Editor** mounts SMB in Premiere, opens the live .mov file via the CEP panel
4. **Editor** trims and marks in/out points while capture is still writing
5. **Capture** finishes at 15:30, promotion worker uploads master to S3
6. **Editor** clicks "Relink to Master" in CEP panel
7. **Server** trims hi-res segment to exact in/out, stores for 24 hours
8. **Premiere** relinks proxy clips to trimmed master
9. **Editor** exports final timeline via FCP XML conform
Total time from end of capture to relinked master: ~2 minutes.
---
## Operations
- `deploy/api-smoke.sh` — verify every API endpoint after deploy
- `deploy/onboard-node.sh` — provision a remote worker host
- `deploy/test-cluster.sh` — primary↔worker connectivity smoke test
- `docs/GROWING_FILES_QUICKSTART.md` — Premiere CEP panel install + growing-file flow
## Authentication
Dragonflight uses local username/password authentication with two transports:
- **Browser:** session cookie (`dragonflight.sid`), 8 hour absolute + 1 hour idle timeout.
- **Premiere panel / scripts:** SHA-256-hashed bearer tokens issued from `Settings → API Tokens`.
### First-run setup
On a fresh install with `AUTH_ENABLED=true`, navigate to the web UI in a browser.
With no users in the database, the login screen renders a "First-run setup" form
instead — fill it in to create the first admin and you are logged in immediately.
Subsequent users are created from `Settings → Users` (any signed-in user can
create others — flat access).
### Dev mode
Setting `AUTH_ENABLED=false` disables all auth checks; a synthetic `dev` user
is attached to every request. **Never deploy this way.** The dev user row is
seeded with a hash that no real password can match, so flipping
`AUTH_ENABLED=true` later does not expose the dev account.
### Recovering a forgotten admin password
Any signed-in user can reset another user's password from `Settings → Users`.
If no one can sign in (all admins forgot their passwords), reset directly in
Postgres:
```sql
-- generate a fresh bcrypt hash with:
-- node -e "import('bcrypt').then(b => b.default.hash(process.argv[1], 12).then(h => console.log(h)))" 'new-passphrase-here'
UPDATE users SET password_hash = '<bcrypt-hash>', password_updated_at = NOW()
WHERE username = 'admin';
```
### `AUTH_ENABLED` transition
When flipping `AUTH_ENABLED=false``true` on an existing install:
1. Ensure `SESSION_SECRET` is set to a stable value (rotating it logs everyone out).
2. Set `ALLOWED_ORIGINS` to the public origin(s) of the web UI.
3. Set `TRUST_PROXY=true` when behind nginx (required for rate-limit accuracy).
4. Restart `mam-api`.
5. Visit the UI — first-run setup will appear if no real users exist yet.
---
## License ## License
Proprietary — Wild Dragon LLC, all rights reserved. MIT

View file

@ -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.

View file

@ -1,104 +0,0 @@
#!/usr/bin/env bash
# Dragonflight MAM API smoke test
#
# Hits every read-only endpoint and a handful of safe write endpoints
# against a running mam-api. Reports per-endpoint HTTP code + a one-line
# pass/fail. Exits non-zero on any failure.
#
# Usage:
# deploy/api-smoke.sh # against http://localhost:47432
# API=http://10.0.0.25:47432 deploy/api-smoke.sh
set -u
API="${API:-http://localhost:47432}"
PASS=0
FAIL=0
# Per-endpoint check. Args: METHOD PATH EXPECTED_HTTP_CODE [BODY]
# Treats anything < 500 as OK by default; auth-gated endpoints typically
# return 401 with AUTH_ENABLED, also acceptable.
hit() {
local method="$1" path="$2" expect="${3:-2..}" body="${4:-}"
local args=(-s -o /dev/null -w '%{http_code}' -X "$method" "${API}${path}")
if [ -n "$body" ]; then args+=(-H 'Content-Type: application/json' -d "$body"); fi
local code
code=$(curl "${args[@]}" 2>/dev/null || echo "000")
if [[ "$code" =~ ^(2|3|401|400)[0-9][0-9]$ ]]; then
printf " %s %-40s %s OK\n" "$method" "$path" "$code"
PASS=$((PASS + 1))
else
printf " %s %-40s %s FAIL\n" "$method" "$path" "$code"
FAIL=$((FAIL + 1))
fi
}
echo "Dragonflight API smoke test — target ${API}"
echo ""
echo "── auth ──────────────────────────────────────────"
hit GET /api/v1/auth/me
echo ""
echo "── core lists ────────────────────────────────────"
hit GET /api/v1/projects
hit GET /api/v1/assets
hit GET /api/v1/assets?limit=5
hit GET /api/v1/recorders
hit GET /api/v1/jobs
hit GET /api/v1/bins
hit GET /api/v1/users
hit GET /api/v1/groups
hit GET /api/v1/cluster
hit GET /api/v1/cluster/containers
hit GET /api/v1/cluster/devices/blackmagic
echo ""
echo "── settings ──────────────────────────────────────"
hit GET /api/v1/settings/s3
hit GET /api/v1/settings/transcoding
hit GET /api/v1/settings/growing
hit GET /api/v1/settings/ampp
hit GET /api/v1/settings/hardware
hit GET /api/v1/settings/capture-service
echo ""
echo "── feature endpoints ─────────────────────────────"
hit GET /api/v1/metrics/home
hit GET /api/v1/metrics/home?hours=1
hit GET /api/v1/schedules
hit GET /api/v1/schedules?status=upcoming
hit GET /api/v1/sdk
echo ""
echo "── deep-link sanity (one asset) ──────────────────"
ASSET_ID=$(curl -s "${API}/api/v1/assets?limit=1" 2>/dev/null \
| sed -n 's/.*"id":"\([0-9a-f-]\{36\}\)".*/\1/p' | head -1)
if [ -n "$ASSET_ID" ]; then
echo " using asset_id=$ASSET_ID"
hit GET "/api/v1/assets/$ASSET_ID"
hit GET "/api/v1/assets/$ASSET_ID/comments"
hit GET "/api/v1/assets/$ASSET_ID/stream"
hit GET "/api/v1/assets/$ASSET_ID/thumbnail"
else
echo " (no assets to deep-link; skipping per-asset endpoints)"
fi
echo ""
echo "── deep-link sanity (one recorder) ───────────────"
REC_ID=$(curl -s "${API}/api/v1/recorders" 2>/dev/null \
| sed -n 's/.*"id":"\([0-9a-f-]\{36\}\)".*/\1/p' | head -1)
if [ -n "$REC_ID" ]; then
echo " using recorder_id=$REC_ID"
hit GET "/api/v1/recorders/$REC_ID"
hit GET "/api/v1/recorders/$REC_ID/status"
else
echo " (no recorders to deep-link)"
fi
echo ""
echo "── summary ───────────────────────────────────────"
echo " PASS: $PASS"
echo " FAIL: $FAIL"
[ "$FAIL" -eq 0 ]

View file

@ -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

View file

@ -1,268 +0,0 @@
#!/usr/bin/env bash
# =============================================================================
# Wild Dragon MAM — Cluster Node Onboarding
# =============================================================================
#
# Provisions a Linux machine as a cluster worker node in one command.
#
# Quick-start (pipe to bash):
# export MAM_API_URL=http://10.0.0.25:47432
# export NODE_TOKEN=wd_xxxx # create via Z-AMPP → Admin → Tokens
# curl -sL https://forge.wilddragon.net/zgaetano/wild-dragon/raw/branch/main/deploy/onboard-node.sh | bash
#
# Or run from a cloned repo:
# MAM_API_URL=http://10.0.0.25:47432 NODE_TOKEN=wd_xxxx ./deploy/onboard-node.sh
#
# Environment variables:
# MAM_API_URL REQUIRED Primary MAM API base URL
# NODE_TOKEN API bearer token (required if AUTH_ENABLED=true)
# NODE_ROLE Role tag reported to the cluster (default: auto-detect)
# NODE_IP Override the LAN IP reported back (default: auto-detect)
# AGENT_PORT Host port for the node agent (default: 7436)
# INSTALL_DIR Where to clone/find the repo (default: /opt/wild-dragon)
# PROFILES Compose profiles, space-sep (default: auto-detect from hardware)
# Override only to force, e.g. "worker capture"
# BMD_MODEL DeckLink card model name (e.g. "DeckLink Duo 2")
# REPO_URL Override the Forgejo clone URL
# =============================================================================
set -euo pipefail
# ── Config ───────────────────────────────────────────────────────────────────
REPO_URL="${REPO_URL:-https://forge.wilddragon.net/zgaetano/wild-dragon.git}"
INSTALL_DIR="${INSTALL_DIR:-/opt/wild-dragon}"
MAM_API_URL="${MAM_API_URL:-}"
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_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}"
PROFILES="${PROFILES:-}"
BMD_MODEL="${BMD_MODEL:-}"
PROJECT_NAME="wild-dragon-worker"
# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; YEL='\033[1;33m'; GRN='\033[0;32m'; CYN='\033[0;36m'
BLD='\033[1m'; NC='\033[0m'
log() { echo -e "${GRN}${NC} $*"; }
info() { echo -e "${CYN}${NC} $*"; }
warn() { echo -e "${YEL}${NC} $*"; }
header() { echo -e "\n${BLD}${CYN}── $* ──────────────────────────────────────${NC}"; }
die() { echo -e "${RED} ✗ ERROR:${NC} $*" >&2; exit 1; }
# ── Auto-detect LAN IP ───────────────────────────────────────────────────────
# Node-agent runs in a container; os.networkInterfaces() inside the container
# returns the docker-bridge IP unless we pass NODE_IP through. We resolve the
# host's primary LAN IP here so the cluster page shows the right address.
detect_lan_ip() {
local ip=""
if command -v ip &>/dev/null; then
ip=$(ip -4 route get 1.1.1.1 2>/dev/null \
| awk '/src/ {for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}' \
|| true)
fi
if [[ -z "$ip" ]] && command -v hostname &>/dev/null; then
ip=$(hostname -I 2>/dev/null | awk '{print $1}' || true)
fi
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 ────────────────────────────────────────────────────────────────
echo -e "\n${BLD}${CYN}Wild Dragon MAM — Cluster Node Onboarding${NC}\n"
[[ -z "$MAM_API_URL" ]] && die "MAM_API_URL is required.\n\n Example:\n export MAM_API_URL=http://10.0.0.25:47432\n export NODE_TOKEN=wd_xxxx\n ./deploy/onboard-node.sh"
if [[ -z "$NODE_IP" ]]; then
NODE_IP="$(detect_lan_ip)"
if [[ -n "$NODE_IP" ]]; then
info "Auto-detected LAN IP: $NODE_IP"
else
warn "Could not auto-detect LAN IP — agent will fall back to interface heuristics."
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 "Role : $NODE_ROLE"
info "Agent port : $AGENT_PORT"
info "Install dir : $INSTALL_DIR"
[[ -n "$NODE_IP" ]] && info "Node IP : $NODE_IP"
[[ -n "$BMD_MODEL" ]] && info "DeckLink : $BMD_MODEL"
[[ -n "$PROFILES" ]] && info "Profiles : $PROFILES"
if [[ -z "$NODE_TOKEN" ]]; then
warn "NODE_TOKEN is not set."
warn "If AUTH_ENABLED=true on the primary, heartbeats will be rejected."
warn "Create a token: Z-AMPP web UI → Admin → Tokens → New Token"
fi
# ── Step 1: Docker ───────────────────────────────────────────────────────────
header "1/4 Docker"
if ! command -v docker &>/dev/null; then
warn "Docker not found — installing via get.docker.com"
curl -fsSL https://get.docker.com | bash
systemctl enable --now docker 2>/dev/null || true
usermod -aG docker "${SUDO_USER:-$USER}" 2>/dev/null || true
log "Docker installed"
else
log "Docker $(docker --version | grep -oP '\d+\.\d+\.\d+' | head -1) already installed"
fi
if ! docker info &>/dev/null; then
die "Docker daemon not accessible.\n Try: sudo systemctl start docker\n Or add your user to the docker group and re-login."
fi
# ── Step 2: Repository ───────────────────────────────────────────────────────
header "2/4 Repository"
if [[ -d "$INSTALL_DIR/.git" ]]; then
info "Updating $INSTALL_DIR"
git -C "$INSTALL_DIR" pull --ff-only
log "Repository up to date ($(git -C "$INSTALL_DIR" rev-parse --short HEAD))"
else
info "Cloning $REPO_URL$INSTALL_DIR"
mkdir -p "$(dirname "$INSTALL_DIR")"
git clone "$REPO_URL" "$INSTALL_DIR"
log "Repository cloned"
fi
# ── Step 3: Environment ──────────────────────────────────────────────────────
header "3/4 Configuration"
ENV_FILE="$INSTALL_DIR/.env.worker"
info "Writing $ENV_FILE"
{
echo "# Wild Dragon worker node — generated $(date -u +%Y-%m-%dT%H:%M:%SZ) by onboard-node.sh"
echo "MAM_API_URL=$MAM_API_URL"
echo "NODE_TOKEN=$NODE_TOKEN"
echo "NODE_ROLE=$NODE_ROLE"
echo "NODE_NAME=$NODE_NAME"
echo "NODE_IP=$NODE_IP"
echo "AGENT_PORT=$AGENT_PORT"
echo "HEARTBEAT_MS=30000"
[[ -n "$BMD_MODEL" ]] && echo "BMD_MODEL=$BMD_MODEL"
for v in REDIS_URL DATABASE_URL S3_ENDPOINT S3_BUCKET S3_ACCESS_KEY S3_SECRET_KEY S3_REGION; do
val="${!v:-}"
[[ -n "$val" ]] && echo "$v=$val"
done
} > "$ENV_FILE"
log "Env file written"
# ── Step 4: Start services ───────────────────────────────────────────────────
header "4/4 Starting services"
COMPOSE="docker compose -f $INSTALL_DIR/docker-compose.worker.yml --env-file $ENV_FILE --project-name $PROJECT_NAME"
PROFILE_FLAGS=""
for p in $PROFILES; do
PROFILE_FLAGS="$PROFILE_FLAGS --profile $p"
done
info "Building images (this may take a minute on first run)…"
$COMPOSE build
info "Starting containers…"
# shellcheck disable=SC2086
$COMPOSE $PROFILE_FLAGS up -d
# ── Verify ───────────────────────────────────────────────────────────────────
echo ""
info "Waiting 6 seconds for agent to initialise…"
sleep 6
HEALTH_URL="http://localhost:$AGENT_PORT/health"
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
log "Node agent healthy at $HEALTH_URL"
REPORTED_IP=$(curl -sf "$HEALTH_URL" | sed -nE 's/.*"ip":"([^"]+)".*/\1/p')
[[ -n "$REPORTED_IP" ]] && log "Reporting IP: $REPORTED_IP"
else
warn "Could not reach $HEALTH_URL — check logs:"
warn " $COMPOSE logs node-agent"
fi
# ── Done ─────────────────────────────────────────────────────────────────────
echo ""
echo -e "${BLD}${GRN}Onboarding complete!${NC}"
echo ""
echo -e " Node agent ${BLD}:$AGENT_PORT${NC} (heartbeating every 30s)"
echo -e " Primary API ${BLD}$MAM_API_URL${NC}"
echo -e " Role ${BLD}$NODE_ROLE${NC}"
[[ -n "$NODE_IP" ]] && echo -e " Node IP ${BLD}$NODE_IP${NC}"
echo ""
echo -e " ${CYN}Useful commands:${NC}"
echo -e " Status : $COMPOSE ps"
echo -e " Logs : $COMPOSE logs -f"
echo -e " Stop : $COMPOSE down"
echo -e " Update : git -C $INSTALL_DIR pull && $COMPOSE build && $COMPOSE up -d"
echo ""
echo -e " Open the Z-AMPP web UI → ${BLD}Admin → Cluster${NC} to see this node."

View file

@ -1,186 +0,0 @@
#!/usr/bin/env bash
# =============================================================================
# Wild Dragon MAM — API Smoke Test
# =============================================================================
# Hits every major endpoint and reports pass/fail.
#
# Usage:
# MAM_API_URL=http://10.0.0.25:47432 ./deploy/test-api.sh
# MAM_API_URL=http://10.0.0.25:47432 NODE_TOKEN=wd_xxxx ./deploy/test-api.sh
# =============================================================================
set -euo pipefail
BASE="${MAM_API_URL:-http://localhost:47432}"
TOKEN="${NODE_TOKEN:-}"
PASS=0; FAIL=0; SKIP=0
GRN='\033[0;32m'; RED='\033[0;31m'; YEL='\033[1;33m'; CYN='\033[0;36m'; BLD='\033[1m'; NC='\033[0m'
pass() { PASS=$((PASS+1)); echo -e " ${GRN}PASS${NC} $1"; }
fail() { FAIL=$((FAIL+1)); echo -e " ${RED}FAIL${NC} $1 ${RED}$2${NC}"; }
skip() { SKIP=$((SKIP+1)); echo -e " ${YEL}SKIP${NC} $1 ${YEL}($2)${NC}"; }
header() { echo -e "\n${BLD}$1${NC}"; }
AUTH_ARGS=()
[[ -n "$TOKEN" ]] && AUTH_ARGS+=(-H "Authorization: Bearer $TOKEN")
# GET — check HTTP status code (no -f so 4xx/5xx are visible as their real code)
check_status() {
local label="$1" path="$2" want="$3"
local got
got=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" "$BASE$path" 2>/dev/null)
[[ -z "$got" ]] && got="000"
if [[ "$got" == "$want" ]]; then
pass "$label [HTTP $got]"
else
fail "$label [HTTP $got]" "expected $want"
fi
}
# GET — check response body contains literal string (fgrep avoids regex interpretation)
check_body() {
local label="$1" path="$2" needle="$3"
local body
body=$(curl -s "${AUTH_ARGS[@]}" "$BASE$path" 2>/dev/null) || { fail "$label" "request failed"; return; }
if echo "$body" | grep -qF "$needle"; then
pass "$label"
else
fail "$label" "'$needle' not in response"
fi
}
# POST — check HTTP status code
check_post() {
local label="$1" path="$2" data="$3" want="$4"
local got
got=$(curl -s -o /dev/null -w "%{http_code}" \
"${AUTH_ARGS[@]}" \
-H "Content-Type: application/json" \
-X POST -d "$data" \
"$BASE$path" 2>/dev/null)
[[ -z "$got" ]] && got="000"
if [[ "$got" == "$want" ]]; then
pass "$label [HTTP $got]"
else
fail "$label [HTTP $got]" "expected $want"
fi
}
# ─────────────────────────────────────────────────────────────────────────────
echo ""
echo -e "${BLD}${CYN}Wild Dragon MAM — API Smoke Test${NC}"
echo -e " Base URL : ${BLD}$BASE${NC}"
[[ -n "$TOKEN" ]] && echo -e " Auth : Bearer token" || echo -e " Auth : none"
echo ""
# ── Connectivity ─────────────────────────────────────────────────────────────
header "Connectivity"
CONNECT=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/health" 2>/dev/null)
if [[ "$CONNECT" == "200" ]]; then
pass "API server reachable [/health → 200]"
else
fail "API server reachable [HTTP $CONNECT]" "cannot reach $BASE"
echo -e "\n ${RED}Cannot reach the server — aborting.${NC}"
exit 1
fi
# ── Auth ─────────────────────────────────────────────────────────────────────
header "Auth"
check_status "GET /auth/me" "/api/v1/auth/me" 200
check_body "GET /auth/me returns username" "/api/v1/auth/me" '"username"'
check_post "POST /auth/login (missing body → 400)" "/api/v1/auth/login" '{}' 400
# ── Assets ───────────────────────────────────────────────────────────────────
header "Assets"
check_status "GET /assets" "/api/v1/assets" 200
check_body "GET /assets returns assets key" "/api/v1/assets" '"assets"'
check_status "GET /assets bogus id → 404" "/api/v1/assets/00000000-0000-0000-0000-000000000000" 404
# ── Projects ─────────────────────────────────────────────────────────────────
header "Projects"
check_status "GET /projects" "/api/v1/projects" 200
check_body "GET /projects returns array" "/api/v1/projects" '['
# ── Jobs ─────────────────────────────────────────────────────────────────────
header "Jobs"
check_status "GET /jobs" "/api/v1/jobs" 200
check_body "GET /jobs returns array" "/api/v1/jobs" '['
# ── Recorders ────────────────────────────────────────────────────────────────
header "Recorders"
check_status "GET /recorders" "/api/v1/recorders" 200
# ── Sequences (requires project_id param) ────────────────────────────────────
header "Sequences"
check_status "GET /sequences (no project_id → 400)" "/api/v1/sequences" 400
check_status "GET /sequences bogus project_id → 200" "/api/v1/sequences?project_id=00000000-0000-0000-0000-000000000000" 200
# ── Settings ─────────────────────────────────────────────────────────────────
header "Settings"
check_status "GET /settings/ampp" "/api/v1/settings/ampp" 200
# ── Cluster ──────────────────────────────────────────────────────────────────
header "Cluster"
check_status "GET /cluster" "/api/v1/cluster" 200
check_body "GET /cluster returns array" "/api/v1/cluster" '['
# Heartbeat: register a temporary smoke-test node, verify it appears, remove it
TEST_HOST="smoke-test-$(date +%s)"
check_post "POST /cluster/heartbeat" "/api/v1/cluster/heartbeat" \
"{\"hostname\":\"$TEST_HOST\",\"role\":\"smoketest\",\"cpu_usage\":0,\"mem_used_mb\":512,\"mem_total_mb\":4096}" \
200
NODE_ID=$(curl -s "${AUTH_ARGS[@]}" "$BASE/api/v1/cluster" 2>/dev/null \
| grep -o '"id":"[^"]*"' | head -1 | grep -o '[0-9a-f-]\{36\}' || true)
if [[ -n "$NODE_ID" ]]; then
pass "Cluster node visible in registry"
DEL=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" \
-X DELETE "$BASE/api/v1/cluster/$NODE_ID" 2>/dev/null)
[[ "$DEL" == "200" ]] && pass "DELETE /cluster/:id (cleanup) [HTTP $DEL]" \
|| fail "DELETE /cluster/:id (cleanup)" "HTTP $DEL"
else
skip "Cluster node visible in registry" "could not parse node id from response"
fi
# ── System / Containers ───────────────────────────────────────────────────────
header "System"
check_status "GET /system/containers" "/api/v1/system/containers" 200
check_body "Containers returns array" "/api/v1/system/containers" '['
# ── Capture (proxies to capture service) ─────────────────────────────────────
header "Capture"
# 200 = capture active and responding
# 404 = capture in sidecar/idle mode (no active recorder — expected in dev)
# 5xx = capture container unreachable
CAPTURE_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH_ARGS[@]}" \
"$BASE/api/v1/capture/status" 2>/dev/null)
if [[ "$CAPTURE_CODE" == "200" ]]; then
pass "GET /capture/status [HTTP 200 — capture active]"
elif [[ "$CAPTURE_CODE" == "404" ]]; then
skip "GET /capture/status [HTTP 404]" "capture in idle/sidecar mode (normal when not recording)"
elif [[ "$CAPTURE_CODE" =~ ^5 ]]; then
skip "GET /capture/status [HTTP $CAPTURE_CODE]" "capture container unreachable"
else
fail "GET /capture/status [HTTP $CAPTURE_CODE]" "unexpected status"
fi
# ── Users / Tokens ───────────────────────────────────────────────────────────
header "Users / Tokens"
check_status "GET /users" "/api/v1/users" 200
check_status "GET /tokens" "/api/v1/tokens" 200
# ── Summary ───────────────────────────────────────────────────────────────────
TOTAL=$((PASS + FAIL + SKIP))
echo ""
echo -e "${BLD}Results:${NC} ${GRN}${PASS} passed${NC} / ${RED}${FAIL} failed${NC} / ${YEL}${SKIP} skipped${NC} / $TOTAL total"
echo ""
if [[ $FAIL -gt 0 ]]; then
echo -e "${RED}Some tests failed — check output above.${NC}"
exit 1
else
echo -e "${GRN}All tests passed.${NC}"
fi

View file

@ -1,183 +0,0 @@
#!/usr/bin/env bash
# =============================================================================
# Wild Dragon MAM — Cluster Smoke Test
# =============================================================================
#
# Validates the cluster end-to-end from any node that can reach the primary.
# Designed to be run after `onboard-node.sh` finishes on every worker.
#
# MAM_API_URL=http://10.0.0.25:47432 ./deploy/test-cluster.sh
# MAM_API_URL=... AUTH_TOKEN=wd_xxxx ./deploy/test-cluster.sh
#
# Checks:
# 1. Primary API health
# 2. Cluster registry (no duplicate hostnames, IPs are real LAN addresses)
# 3. Each worker's /health endpoint
# 4. GPU detection (nvidia-smi exits clean on nodes that report GPUs)
# 5. NVENC encode probe (5s of synthetic h264_nvenc → /tmp)
# 6. Blackmagic device enumeration
#
# Exit 0 = all pass, 1 = any failure. Failures are logged inline.
# =============================================================================
set -uo pipefail
MAM_API_URL="${MAM_API_URL:-}"
AUTH_TOKEN="${AUTH_TOKEN:-}"
if [[ -z "$MAM_API_URL" ]]; then
echo "✗ MAM_API_URL is required" >&2
exit 1
fi
RED='\033[0;31m'; YEL='\033[1;33m'; GRN='\033[0;32m'; CYN='\033[0;36m'; BLD='\033[1m'; NC='\033[0m'
PASS=0; FAIL=0
pass() { echo -e "${GRN}${NC} $*"; PASS=$((PASS+1)); }
fail() { echo -e "${RED}${NC} $*"; FAIL=$((FAIL+1)); }
note() { echo -e "${CYN}${NC} $*"; }
warn() { echo -e "${YEL} !${NC} $*"; }
api() {
local method="${1:-GET}"; shift
local path="$1"; shift
local args=(-sS -X "$method" -H 'Content-Type: application/json')
[[ -n "$AUTH_TOKEN" ]] && args+=(-H "Authorization: Bearer $AUTH_TOKEN")
curl "${args[@]}" "$@" "${MAM_API_URL}${path}"
}
echo -e "${BLD}${CYN}Wild Dragon — Cluster Smoke Test${NC}"
echo -e "Primary: $MAM_API_URL"
echo ""
# ── 1. Primary API health ───────────────────────────────────────────────
echo -e "${BLD}1. Primary API health${NC}"
if api GET /health | grep -q '"status":"ok"'; then
pass "primary /health responds"
else
fail "primary /health did not return ok"
fi
echo ""
# ── 2. Cluster registry ─────────────────────────────────────────────────
echo -e "${BLD}2. Cluster registry${NC}"
NODES_JSON=$(api GET /api/v1/cluster || echo '[]')
TOTAL=$(echo "$NODES_JSON" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null || echo 0)
note "$TOTAL nodes registered"
if [[ "$TOTAL" -gt 0 ]]; then
# No duplicate hostnames
DUP=$(echo "$NODES_JSON" | python3 -c '
import sys, json
nodes = json.load(sys.stdin)
seen = {}
dups = []
for n in nodes:
h = n.get('hostname')
if h in seen: dups.append(h)
seen[h] = True
print(",".join(sorted(set(dups))))' 2>/dev/null)
if [[ -z "$DUP" ]]; then
pass "no duplicate hostnames"
else
fail "duplicate hostnames: $DUP — run migration 007"
fi
# No private docker IPs (172.16.0.0/12 reserved for docker bridges)
BAD_IPS=""
while IFS=$'\t' read -r host ip; do
[[ -z "$ip" ]] && continue
first="${ip%%.*}"; rest="${ip#*.}"; second="${rest%%.*}"
if [[ "$first" == "172" && "$second" == "17" ]]; then
BAD_IPS+="${host}=${ip},"
fi
done < <(echo "$NODES_JSON" | jq -r '.[] | [.hostname, (.ip_address // "")] | @tsv')
BAD_IPS="${BAD_IPS%,}"
if [[ -z "$BAD_IPS" ]]; then
pass "all node IPs are real LAN addresses"
else
fail "nodes still reporting docker bridge IPs: $BAD_IPS"
warn " → set NODE_IP in .env.worker and restart the node-agent"
fi
# All nodes recently seen
STALE=$(echo "$NODES_JSON" | python3 -c '
import sys, json
nodes = json.load(sys.stdin)
stale = [n["hostname"] for n in nodes if float(n.get("stale_seconds") or 9999) > 120]
print(",".join(stale))' 2>/dev/null)
if [[ -z "$STALE" ]]; then
pass "all nodes heartbeated within 2 min"
else
warn "stale nodes (>2 min since heartbeat): $STALE"
fi
fi
echo ""
# ── 3. Per-node /health probes ──────────────────────────────────────────
echo -e "${BLD}3. Worker agent /health endpoints${NC}"
echo "$NODES_JSON" | python3 -c '
import sys, json
for n in json.load(sys.stdin):
if n.get("role") == "primary": continue
print(n["id"], n["hostname"], n.get("api_url") or "")
' 2>/dev/null | while read -r ID HOST URL; do
[[ -z "$URL" ]] && { warn "$HOST: no api_url registered"; continue; }
if curl -sf --max-time 4 "$URL/health" >/dev/null 2>&1; then
pass "$HOST ($URL/health)"
else
fail "$HOST agent unreachable at $URL/health"
fi
done
echo ""
# ── 4. Local GPU + NVENC probe (when run on a GPU node) ─────────────────
echo -e "${BLD}4. Local GPU + NVENC${NC}"
if command -v nvidia-smi >/dev/null 2>&1; then
GPU_COUNT=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | wc -l)
if [[ "$GPU_COUNT" -gt 0 ]]; then
pass "$GPU_COUNT NVIDIA GPU(s) visible to host"
if command -v ffmpeg >/dev/null 2>&1; then
if ffmpeg -hide_banner -loglevel error \
-f lavfi -i testsrc=duration=5:size=1280x720:rate=30 \
-c:v h264_nvenc -preset p1 -b:v 4M \
-t 5 -f null - 2>/tmp/wd-nvenc.log; then
pass "NVENC encode test succeeded"
else
fail "NVENC encode failed — see /tmp/wd-nvenc.log"
fi
else
warn "ffmpeg not installed locally — skipping NVENC encode test"
fi
else
warn "nvidia-smi found but reports 0 GPUs"
fi
else
warn "nvidia-smi not present (not a GPU node)"
fi
echo ""
# ── 5. Blackmagic device enumeration ────────────────────────────────────
echo -e "${BLD}5. Blackmagic devices (cluster-wide)${NC}"
BMD_JSON=$(api GET /api/v1/cluster/devices/blackmagic || echo '[]')
BMD_COUNT=$(echo "$BMD_JSON" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))' 2>/dev/null || echo 0)
if [[ "$BMD_COUNT" -gt 0 ]]; then
pass "$BMD_COUNT DeckLink port(s) registered"
echo "$BMD_JSON" | jq -r '.[] | " \(.hostname) port=\(.index) model=\(.model // "unknown") online=\(.online)"'
else
warn "no DeckLink devices reported by any node"
fi
echo ""
# ── 6. Local Blackmagic device files ────────────────────────────────────
echo -e "${BLD}6. Local /dev/blackmagic${NC}"
if [[ -d /dev/blackmagic ]]; then
ls /dev/blackmagic/ | sed 's/^/ /'
pass "$(ls /dev/blackmagic/ | wc -l) device node(s) under /dev/blackmagic"
else
warn "no /dev/blackmagic on this machine"
fi
echo ""
# ── Summary ─────────────────────────────────────────────────────────────
echo -e "${BLD}Summary:${NC} ${GRN}$PASS pass${NC} ${RED}$FAIL fail${NC}"
[[ "$FAIL" -gt 0 ]] && exit 1 || exit 0

View file

@ -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'"

View file

@ -1,33 +0,0 @@
# Wild Dragon MAM — GPU overlay
# Apply on top of docker-compose.yml on nodes with NVIDIA GPUs.
#
# Prerequisites: NVIDIA Container Toolkit installed on the host.
# Install: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/
#
# Usage (core MAM node with GPUs):
# docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d
#
# Usage (worker node with GPUs):
# docker compose -f docker-compose.worker.yml -f docker-compose.gpu.yml --profile worker up -d
#
# This overlay:
# - Rebuilds worker from Dockerfile.gpu (CUDA base + ffmpeg NVENC)
# - Passes all NVIDIA GPUs into the worker container
# - Sets NVENC_ENABLED=true so the worker prioritises h264_nvenc/hevc_nvenc
services:
worker:
build:
context: ./services/worker
dockerfile: Dockerfile.gpu
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
environment:
NVENC_ENABLED: "true"
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility

View file

@ -1,150 +0,0 @@
# Wild Dragon MAM — Worker Node Stack
# ─────────────────────────────────────
# Deploy on any machine you want to join the cluster as a worker.
# The primary stack (mam-api, db, redis) continues running on TrueNAS.
#
# Required env vars (set in .env.worker or export before running):
# MAM_API_URL URL of the primary MAM API e.g. http://10.0.0.25:47432
# NODE_TOKEN Bearer token from the primary's Tokens page
# NODE_IP Host LAN IP to report (set by onboard-node.sh)
#
# Optional hardware overrides (if Docker can't see /dev directly):
# GPU_COUNT Number of NVIDIA GPUs on this node (default: auto-detect from /dev/nvidia*)
# BMD_COUNT Number of Blackmagic DeckLink cards (default: auto-detect from /dev/blackmagic/)
# BMD_MODEL Marketed card name (e.g. "DeckLink Duo 2") — drives the port-diagram UI
#
# Optional env vars (needed only if starting the worker or capture profiles):
# REDIS_URL, DATABASE_URL, S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY
# BMD_DEVICE_0 DeckLink device path (default: /dev/blackmagic/dv0)
# (DeckLink IO / Quad cards expose /dev/blackmagic/io* instead — set BMD_DEVICE_PREFIX=io)
# BMD_DEVICE_1 DeckLink device path (default: /dev/blackmagic/dv1)
# BMD_DEVICE_PREFIX Naming prefix for synthesized BMD_COUNT-based devices (default: dv). Use 'io' for IO/Quad.
# LIVE_DIR Host path for HLS live segments (default: /mnt/NVME/MAM/wild-dragon-live)
#
# Profiles:
# (default) node-agent only — cluster visibility + hardware heartbeat
# --profile worker + CPU/GPU job worker (proxy generation, transcoding)
# --profile capture + SDI capture service (requires Blackmagic DeckLink card)
#
# To enable GPU transcoding, also apply docker-compose.gpu.yml:
# docker compose -f docker-compose.worker.yml -f docker-compose.gpu.yml --profile worker up -d
#
# NOTE: The node-agent mounts /var/run/docker.sock to spawn on-demand SDI
# capture sidecars when the primary mam-api routes a recorder to this node.
# Build the capture image before first use:
# docker compose -f docker-compose.worker.yml build capture
services:
# node-agent runs in host network mode so it can see the real host
# interfaces, GPU devices and DeckLink cards without bridging tricks.
# The reported IP / hostname will be the host's, not the container's.
node-agent:
build: ./services/node-agent
restart: unless-stopped
network_mode: host
environment:
MAM_API_URL: ${MAM_API_URL}
NODE_TOKEN: ${NODE_TOKEN:-}
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:-}
AGENT_PORT: ${AGENT_PORT:-7436}
HEARTBEAT_MS: ${HEARTBEAT_MS:-30000}
GPU_COUNT: ${GPU_COUNT:--1}
BMD_COUNT: ${BMD_COUNT:--1}
BMD_MODEL: ${BMD_MODEL:-}
BMD_DEVICE_PREFIX: ${BMD_DEVICE_PREFIX:-dv}
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:
- /var/run/docker.sock:/var/run/docker.sock
- /dev:/dev: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:
- /dev/blackmagic:/dev/blackmagic
worker:
build: ./services/worker
profiles: [worker]
restart: unless-stopped
environment:
REDIS_URL: ${REDIS_URL}
DATABASE_URL: ${DATABASE_URL}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_BUCKET: ${S3_BUCKET}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_REGION: ${S3_REGION:-us-east-1}
NVENC_ENABLED: ${NVENC_ENABLED:-false}
networks:
- wild-dragon-worker
# SDI capture service — only start on nodes with Blackmagic DeckLink cards
# Set BMD_DEVICE_0 in .env.worker to the actual device path, e.g. /dev/blackmagic/dv0
capture:
build: ./services/capture
profiles: [capture]
restart: unless-stopped
environment:
REDIS_URL: ${REDIS_URL}
DATABASE_URL: ${DATABASE_URL}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_BUCKET: ${S3_BUCKET}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
CAPTURE_PORT: 3001
devices:
- ${BMD_DEVICE_0:-/dev/blackmagic/dv0}:/dev/blackmagic/dv0
- ${BMD_DEVICE_1:-/dev/blackmagic/dv1}:/dev/blackmagic/dv1
ports:
- "${CAPTURE_PORT:-7437}:3001"
networks:
- wild-dragon-worker
# 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.
worker-l4:
profiles: [gpu]
build:
context: ./services/worker
dockerfile: Dockerfile.gpu
image: wild-dragon-worker-gpu:latest
runtime: nvidia
restart: unless-stopped
environment:
REDIS_URL: ${REDIS_URL}
DATABASE_URL: ${DATABASE_URL}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_BUCKET: ${S3_BUCKET}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_REGION: ${S3_REGION:-us-east-1}
WORKER_QUEUES: proxy,conform,trim
PROXY_CONCURRENCY: "3"
NVIDIA_VISIBLE_DEVICES: GPU-13acf439-8bf4-a5e0-7804-c1071bca547a
WORKER_LABEL: "zampp2 / L4"
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility
networks:
- wild-dragon-worker
networks:
wild-dragon-worker:
driver: bridge

View file

@ -5,8 +5,6 @@ services:
POSTGRES_DB: ${POSTGRES_DB} POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER} POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "${PORT_DB:-5432}:5432"
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
- ./services/mam-api/src/db/schema.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro - ./services/mam-api/src/db/schema.sql:/docker-entrypoint-initdb.d/001-schema.sql:ro
@ -20,8 +18,6 @@ services:
queue: queue:
image: redis:7-alpine image: redis:7-alpine
ports:
- "${PORT_REDIS:-6379}:6379"
volumes: volumes:
- redis_data:/data - redis_data:/data
networks: networks:
@ -38,14 +34,6 @@ services:
- "${PORT_MAM_API:-7432}:3000" - "${PORT_MAM_API:-7432}:3000"
volumes: volumes:
- /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-growing:/growing
- /mnt/NVME/MAM/wild-dragon-media:/media
- /mnt/NVME/MAM/sdk:/sdk
- /dev/shm:/dev/shm
- /run/dbus:/run/dbus
- /run/systemd:/run/systemd
- /usr/bin/nvidia-smi:/usr/bin/nvidia-smi:ro
environment: environment:
DATABASE_URL: ${DATABASE_URL} DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL} REDIS_URL: ${REDIS_URL}
@ -56,26 +44,7 @@ services:
S3_REGION: ${S3_REGION:-us-east-1} S3_REGION: ${S3_REGION:-us-east-1}
SESSION_SECRET: ${SESSION_SECRET} SESSION_SECRET: ${SESSION_SECRET}
AUTH_ENABLED: ${AUTH_ENABLED:-false} AUTH_ENABLED: ${AUTH_ENABLED:-false}
TRUST_PROXY: ${TRUST_PROXY:-false}
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-}
DOCKER_NETWORK: wild-dragon_wild-dragon DOCKER_NETWORK: wild-dragon_wild-dragon
NODE_IP: ${NODE_IP}
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}
PLAYOUT_IMAGE: ${PLAYOUT_IMAGE:-wild-dragon-playout:latest}
PLAYOUT_AMCP_BASE_PORT: ${PLAYOUT_AMCP_BASE_PORT:-5250}
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
networks: networks:
- wild-dragon - wild-dragon
@ -95,28 +64,11 @@ 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}
MAM_API_URL: ${MAM_API_URL:-http://mam-api:3000} MAM_API_URL: ${MAM_API_URL:-http://mam-api:3000}
volumes:
- /mnt/NVME/MAM/wild-dragon-live:/live
- /dev/shm:/dev/shm
- /run/dbus:/run/dbus
- /run/systemd:/run/systemd
networks: networks:
- wild-dragon - wild-dragon
# ── GPU worker pool (capability-routed) ────────────────────────────── worker:
# worker-p4: HEAVY tier (proxy/conform/trim) on the Tesla P4 (NVENC). build: ./services/worker
# Also runs the promotion scanner (RUN_PROMOTION) — exactly one worker must.
worker-p4:
build:
context: ./services/worker
dockerfile: Dockerfile.gpu
image: wild-dragon-worker-gpu:latest
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
@ -128,62 +80,6 @@ services:
S3_ACCESS_KEY: ${S3_ACCESS_KEY} S3_ACCESS_KEY: ${S3_ACCESS_KEY}
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
# Includes `import` (YouTube importer): the import queue had no consumer
# 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"
PROXY_CONCURRENCY: "2"
PLAYOUT_MEDIA_DIR: /media
NVIDIA_VISIBLE_DEVICES: GPU-79afca3e-2ab2-a6ea-1c44-706c1f0a26d6
WORKER_LABEL: "zampp1 / Tesla P4"
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility
volumes:
# NOTE: /growing is NOT a host bind anymore — the promotion scanner mounts
# 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:
- wild-dragon
# worker-p400a/b: LIGHT tier (thumbnail/filmstrip) on the two Quadro P400s.
worker-p400a:
image: wild-dragon-worker-gpu:latest
runtime: nvidia
depends_on: [queue, db, worker-p4]
environment:
REDIS_URL: ${REDIS_URL}
DATABASE_URL: ${DATABASE_URL}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_BUCKET: ${S3_BUCKET}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_REGION: ${S3_REGION:-us-east-1}
WORKER_QUEUES: thumbnail,filmstrip
NVIDIA_VISIBLE_DEVICES: GPU-331c53ea-2ed9-0007-e364-c1451775948f
WORKER_LABEL: "zampp1 / P400 #1"
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility
networks:
- wild-dragon
worker-p400b:
image: wild-dragon-worker-gpu:latest
runtime: nvidia
depends_on: [queue, db, worker-p4]
environment:
REDIS_URL: ${REDIS_URL}
DATABASE_URL: ${DATABASE_URL}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_BUCKET: ${S3_BUCKET}
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
S3_SECRET_KEY: ${S3_SECRET_KEY}
S3_REGION: ${S3_REGION:-us-east-1}
WORKER_QUEUES: thumbnail,filmstrip
NVIDIA_VISIBLE_DEVICES: GPU-b514a592-9077-44bd-d9e8-9efa0591ef88
WORKER_LABEL: "zampp1 / P400 #2"
NVIDIA_DRIVER_CAPABILITIES: video,compute,utility
networks: networks:
- wild-dragon - wild-dragon
@ -191,24 +87,9 @@ services:
build: ./services/web-ui build: ./services/web-ui
ports: ports:
- "${PORT_WEB_UI:-7434}:80" - "${PORT_WEB_UI:-7434}:80"
volumes:
- /mnt/NVME/MAM/wild-dragon-live:/live
- /mnt/NVME/MAM/wild-dragon-media:/media:ro
- /dev/shm:/dev/shm
- /run/dbus:/run/dbus
- /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:

View file

@ -1,186 +0,0 @@
# Dragonflight · Feature Overview
Dragonflight is a self-hosted broadcast media-asset management system that replaces legacy tools like Grass Valley AMPP and FramelightX. It handles live ingest, growing-file editing, scheduling, transcoding, and asset management in a single operator-focused interface.
## Home Dashboard
![Home Dashboard](./screenshots/01-home.png)
The home screen provides quick access to all major features and displays system status at a glance:
- **Library** — Browse projects, bins, and assets with hover-scrub previews
- **Recorders** — View configured capture devices and their status
- **Editor** — Timeline editor with cross-clip preview and render queue
- **Jobs** — Proxy and thumbnail queue with retry controls
- **Settings** — Configure storage, encoder, growing files, and capture SDK
- **Dashboard** — Operations view showing recent activity, job queue, and cluster health
---
## Core Features
### 1. Live Ingest & Capture
**Multi-protocol source capture with per-recorder codec settings**
Dragonflight ingests from multiple sources simultaneously:
- **SRT** (Secure Reliable Transport) — caller and listener modes
- **RTMP** — standard streaming protocol
- **SDI** — via Blackmagic DeckLink cards with FFmpeg SDK 16.x patches
Each recorder can be configured with independent codec settings:
- ProRes (hi-res masters)
- H.264 / H.265 (proxies)
- DNxHR (Avid compatibility)
Audio routing and per-source configuration ensure flexibility for multi-camera productions.
### 2. Growing-File Editing
**Live editing in Premiere Pro while capture is still writing**
Editors mount the SMB landing zone directly in Premiere Pro and edit the live master file as it's being written. The included CEP (Custom Extension Panel) provides:
- Real-time clip detection and frame-accurate trimming
- One-click relink to final S3 master after promotion
- No waiting for capture to finish before editorial begins
### 3. Recorder Scheduler
**Time-windowed recording automation**
Schedule recordings with:
- One-shot, daily, or weekly recurrence
- Automatic start/stop via 15-second tick loop
- Conflict detection across recorders
- Project and bin assignment at schedule time
### 4. Library & Asset Management
**Browse, search, and organize captured footage**
The Library screen provides:
- Project and bin hierarchy
- Asset detail view with frame-anchored persistent comments
- Right-click context menu (move-to-bin, rename, delete)
- Global cmd/ctrl-K search across assets, projects, recorders, jobs, and users
- Hover-scrub preview with HLS playback
### 5. Jobs Queue
**BullMQ-backed proxy and thumbnail generation**
Automated background processing:
- Per-job retry logic with exponential backoff
- Bulk "retry all failed" for batch recovery
- Inline error messages with actionable diagnostics
- Status tracking: ingesting → processing → ready
Proxy encoder options:
- CPU-based: libx264 (H.264)
- GPU-accelerated: NVENC (NVIDIA) or VAAPI (AMD/Intel)
### 6. Timeline Conform & Export
**FCP XML export with server-side FFmpeg rendering**
The Premiere Pro panel exports FCP XML with:
- Server-side conform via FFmpeg
- Multiple output formats: H.264, H.265, ProRes
- Resolution presets: Broadcast, Web, Archive
- Batch processing with job queue integration
### 7. Hi-Res Auto-Relink
**One-click batch relink of proxy clips to frame-accurate server-trimmed masters**
After editing on proxies:
- Select clips in Premiere
- Trigger relink from the CEP panel
- Server trims hi-res segments to exact in/out points
- Concurrent trim worker pool for speed
- 24-hour TTL with automatic cleanup
### 8. Settings & Configuration
**Centralized control for storage, encoding, and capture**
Configure:
- **S3 Storage** — endpoint, bucket, credentials (with env-var fallback)
- **Proxy Encoder** — CPU vs GPU, bitrate, resolution
- **Growing Files** — SMB path, retention, auto-promotion
- **Capture SDK** — Blackmagic, AJA, or Deltacast uploader selection
### 9. Cluster & Distributed Capture
**Primary + worker topology with remote DeckLink nodes**
- Primary node runs API, scheduler, and web UI
- Worker nodes handle proxy/thumbnail jobs
- Remote capture nodes run DeckLink cards off-host
- Heartbeat health monitoring
- Automatic failover and recovery
### 10. Admin & User Management
**Role-based access, token auth, and cluster monitoring**
- User creation and role assignment
- API token generation for integrations
- Container and cluster node status
- System health dashboard
---
## Architecture
```
SDI / SRT / RTMP ──► capture (FFmpeg)
├─ HLS preview tee ──► /live/<assetId>/index.m3u8
└─ master output
├─ growing_enabled=true:
│ /growing/<projectId>/<clip>.mov
│ (Premiere mounts SMB, edits live)
│ └─► promotion worker uploads to S3
└─ growing_enabled=false:
multipart stream → S3
assets POST ──► proxy job ──► worker
├─ libx264 (CPU) or NVENC/VAAPI (GPU)
├─ thumbnail job
└─ status: ingesting → processing → ready
```
## Tech Stack
- **Runtime:** Node.js 22, Docker Compose
- **Backend:** Express, PostgreSQL 16, Redis 7 + BullMQ
- **Frontend:** Vanilla React via in-browser Babel (no bundler), hls.js
- **Media:** FFmpeg 7.1 with SDK 16 DeckLink patches
- **Codecs:** ProRes, H.264, H.265, DNxHR, MOV/MP4/MXF containers
- **Storage:** S3-compatible (RustFS) for masters, proxies, thumbnails
## Services
| Service | Port | Purpose |
|---------|------|---------|
| **web-ui** | 47434 | Browser SPA + capture controls |
| **mam-api** | 47432 | REST API + recorder orchestration + scheduler |
| **capture** | 47433 / 9000 / 1935 | DeckLink/SRT/RTMP ingest sidecar |
| **worker** | — | BullMQ proxy + thumbnail workers |
| **db** | 5432 | PostgreSQL 16 |
| **queue** | 6379 | Redis 7 |
---
## Workflow Example: Live-to-Edit
1. **Operator** schedules a recording on Recorder A for 14:0015:30, assigns to "News/Segment-A" project
2. **Capture** starts at 14:00, writes ProRes master to SMB landing zone
3. **Editor** mounts SMB in Premiere, opens the live .mov file via the CEP panel
4. **Editor** trims and marks in/out points while capture is still writing
5. **Capture** finishes at 15:30, promotion worker uploads master to S3
6. **Editor** clicks "Relink to Master" in CEP panel
7. **Server** trims hi-res segment to exact in/out, stores for 24 hours
8. **Premiere** relinks proxy clips to trimmed master
9. **Editor** exports final timeline via FCP XML conform
Total time from end of capture to relinked master: ~2 minutes.
---
## Operations
- `deploy/api-smoke.sh` — verify every API endpoint after deploy
- `deploy/onboard-node.sh` — provision a remote worker host
- `deploy/test-cluster.sh` — primary↔worker connectivity smoke test
- `docs/GROWING_FILES_QUICKSTART.md` — Premiere CEP panel install + growing-file flow

View file

@ -1,99 +0,0 @@
# Growing Files + Premiere Panel — Test Plan
A local SMB landing zone for capture so Premiere can edit the master while
it is still recording. The promotion worker uploads the finalized file to S3
and the panel relinks Premiere to the hi-res original.
## Cluster state (deployed 2026-05-22)
- TrueNAS dataset: `NVME/MAM-growing` (LZ4, 0777)
- TrueNAS SMB share: `mam-growing``/mnt/NVME/MAM-growing`
- Host symlink for docker compose: `/mnt/NVME/MAM/wild-dragon-growing` → the dataset
- mam-api + worker containers mount it at `/growing`
- Settings (live): `growing_enabled=true`, `growing_smb_url=smb://10.0.0.25/mam-growing`
## Capture flow (when growing_enabled=true)
1. Recorder starts. mam-api spawns a capture sidecar with `GROWING_ENABLED=true`
and binds `/mnt/NVME/MAM/wild-dragon-growing:/growing`.
2. FFmpeg writes the hi-res master directly to
`/growing/<projectId>/<clipName>.<ext>` (no S3 stream).
3. The HLS tee continues to publish `/live/<assetId>/index.m3u8`, so the
Recorders + Monitors pages get a real video preview.
4. On stop — or when the file's mtime is idle for
`growing_promote_after_seconds` — the promotion worker:
- uploads the local file to S3 at `projects/<projectId>/masters/<clipName>.<ext>`
- queues a proxy job
- flips the asset to `status=ready`
- deletes the local copy.
## Premiere panel install
Grab the latest release artifact and run it — the installer handles the file
copy, registry/plist debug-mode flip, and removes any legacy
`com.wilddragon.mam.panel` install:
- **Windows:** `dragonflight-premiere-panel-<version>-windows-setup.exe`
- **macOS / Win:** `dragonflight-premiere-panel-<version>.zxp` — install via
[Anastasiy's ZXP Installer](https://install.anastasiy.com/) (free GUI)
Releases live at
<https://forge.wilddragon.net/zgaetano/dragonflight/releases>.
Building locally (requires Windows for the `.exe`, any OS for the `.zxp`):
```
cd services/premiere-plugin/build
npm install
powershell -File build-all.ps1 # or: node build-zxp.mjs
```
The Windows installer takes care of `PlayerDebugMode`. If you installed the
ZXP and the panel does not appear in **Window → Extensions**, enable debug
mode manually:
```
# macOS
defaults write com.adobe.CSXS.11 PlayerDebugMode 1
# Windows
reg add "HKCU\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f
```
Mount the SMB share once at OS level: `smb://10.0.0.25/mam-growing`.
In Premiere: Window → Extensions → Wild Dragon MAM.
## Test the live → finalized swap
1. Start a recorder (Ingest → Recorders → Record).
2. The Recorder row's preview becomes a real HLS `<video>` element.
3. In Premiere, with the growing asset selected (status=live), click
**Mount Live**. The panel calls `GET /api/v1/assets/:id/live-path`,
resolves the SMB UNC path, and `app.project.importFiles()` it. Premiere
imports the still-growing MOV.
4. Stop the recorder. After `growing_promote_after_seconds` of mtime
inactivity, the promotion worker uploads to S3 and flips status.
5. The panel polls every 5 s. When it sees `status=ready` it surfaces
**Relink to Hi-Res** — clicking that downloads the finalized hi-res
and calls `ProjectItem.changeMediaPath()` to relink in place. Timeline
cuts are preserved.
## Knobs (Settings → Growing files (SMB))
- `growing_enabled` — master switch
- `growing_path` — container mount path (default `/growing`)
- `growing_smb_url` — what the Premiere panel hands to the editor
- `growing_promote_after_seconds` — idle threshold for promotion
## What's NOT yet here
- Auth on the SMB share — currently passwordless. Add Samba auth via
`midclt call sharing.smb.update` and put creds in the editor's keychain
before exposing this beyond the LAN.
- Concurrent S3 backup of the growing file. Today's MVP writes to SMB only;
S3 happens at promotion. If you need belt-and-suspenders, add `-f tee` in
capture-manager to fan out to both destinations.
- Cleanup for stranded files (e.g. recorder crashes mid-capture). The idle
threshold will eventually promote them, but a stale-file sweeper would be
more graceful.

View file

@ -1,116 +0,0 @@
# Work Log — May 2026
## Summary
Session focused on auth system architecture, dashboard redesign, and audio track inspector. Multiple iterations on auth approach; settled on simplified local-account model with RBAC. Dashboard rebuilt as control-room status board. Audio tab completed with full metering and fader controls.
## Auth System Work
### Commits
- `002e5ac` — auth: top-to-bottom rework — local accounts, RBAC + client tag, audit log, env-bootstrap
- `d1f9557` — auth: park login flow — circle back
- `9726dbb` — Revert "auth: top-to-bottom rework..."
- `4172b0d` — rip out entire auth/login flow
### What Happened
Attempted comprehensive auth rewrite including:
- Local user account system with bcrypt hashing
- Role-based access control (admin/editor/viewer)
- Client tagging for audit trails
- Environment-based bootstrap (AUTH_ENABLED flag)
- Session management with PostgreSQL backing
**Decision**: Reverted entire auth work. Reason: complexity vs. current product stage. System was over-engineered for self-hosted use case where auth is optional.
**Current State**: Auth disabled by default (AUTH_ENABLED=false). When enabled, system returns synthetic /auth/me endpoint. No persistent user management yet.
### Related Fixes
- `cfcbec0` — fix(auth): make AUTH_ENABLED=true workable end-to-end
- `e71c330` — fix(auth): remove manual session.save() — was suppressing Set-Cookie header
- `65684aa` — fix(auth): ensure sessions table exists + log session.save errors
## Dashboard Redesign
### Commits
- `a48e1d9` — dashboard: rebuild as control-room status board (on air / up next / attention / work)
- `e5e0656` — dashboard: redesign stat cards, compress header, improve density
- `5de1e3d` — dashboard: add dense stat cards, cluster bars, job rows, sparkline fixes
- `48d54a3` — dashboard: add missing dash-* CSS classes; cluster: add stat-row/stat-card CSS
### What Changed
Transformed dashboard from generic metrics view to broadcast control-room interface:
- **On Air** section: live stream status, bitrate, duration
- **Up Next** section: queued clips/segments
- **Attention** section: warnings, errors, resource alerts
- **Work** section: active jobs, encoding progress
Added visual components:
- Dense stat cards with icon + value + trend
- Cluster health bars (CPU, memory, disk per node)
- Job progress rows with ETA
- Sparkline charts for trend visualization
CSS infrastructure added for consistent spacing/sizing across dashboard components.
## Audio Tab Implementation
### Commit
- `c48c7e6` — feat(audio-tab): full audio track inspector with meters, mute/solo, faders
### Features
- Per-track audio meters (VU-style, real-time)
- Mute/solo buttons per track
- Fader controls (0-100 dB range)
- Master output meter
- Track naming/labeling
- Visual feedback for clipping/peaks
## Other Work
### Storage & Admin
- `64d739b` — feat(admin): unified Storage settings page with mount/bucket health diagnostics
- `a44d8bd` — feat(admin): live video-presence indicators on cluster DeckLink ports
### Player & Streaming
- `a86c1c7` — fix(player): stitch S3 ranges around RustFS empty-body bug (#143)
- `d257a19` — fix(player): buffer indicator + 416 instead of 500 on out-of-range S3
- `37247fd` — fix(video): direct S3 signed URL for streaming + proxy bitrate 1.5Mbps
- `e4d4c00` — feat(proxy): VBR 500k-1M encoding for proxy generation
### Cluster & Hardware
- `55ff2e7` — feat(cluster): full hardware breakdown per node
- `5ff507b` — fix(node-agent): use nsenter to run nvidia-smi in host mount namespace
- `558c18e` — fix(node-agent): detect GPUs via docker run --gpus all ubuntu:22.04
- `a6f045b` — fix(node-agent): probe GPU via Docker API async at startup, cache result
### Release & Cleanup
- `04ce096` — chore: 1.2 ship-prep sweep — close 38 issues
- `f0f6156` — release: add v1.1.0 ZXP artifact (Growing tab + visual system alignment)
## Blockers / Open Questions
### Auth System
- **Decision needed**: Should auth be mandatory for production? Current design assumes optional.
- **API endpoints missing**: `/users`, `/auth/me`, `/groups` routes not yet implemented in mam-api
- **Frontend expects**: Users list, groups management, role-based UI filtering
### Dashboard
- Real data integration needed (currently mock data)
- Cluster stats endpoint integration
- Job queue polling/WebSocket updates
### Audio
- Backend audio processing pipeline not yet connected
- Metering data source undefined
- Fader changes need routing to encoder
## Next Steps (Recommended)
1. **Clarify auth requirements**: Is user management needed for v1.2? If yes, implement `/users` and `/groups` endpoints.
2. **Connect dashboard to live data**: Wire cluster stats, job queue, stream status to real endpoints.
3. **Audio backend integration**: Define audio processing pipeline and metering data flow.
4. **Testing**: Add integration tests for auth flow, dashboard data binding, audio control.
---
**Session ended**: 2026-05-27 06:31 CDT
**Status**: Work logged, auth decision documented, next steps identified

View file

@ -1,197 +0,0 @@
# All-Intra HEVC (NVENC) Growing-File Ingest
Date: 2026-05-29 | Status: design, pending validation gate (see §8)
Authors: Zac + Claude
## 1. Purpose
Replace the CPU-bound ProRes capture encode with **All-Intra HEVC on NVENC**
as the growing-file master codec, so we can:
- **Offload ingest encode from CPU to GPU** (the current scaling wall), and
- **Keep edit-while-record** (all-intra => growing file stays editable), and
- **Scale to up to 8 simultaneous signals per machine**, across Blackmagic
today and Deltacast + AJA later.
This doc captures the target design AND the current working system it builds on,
so it is self-contained for whoever implements it.
## 2. Why this codec
Growing-file editing (Premiere/Avid mounting a still-recording file over SMB)
requires two things: **intra-frame** (every frame a keyframe, so a partial file
is decodable to the last whole frame) and a **container whose index is not
deferred to EOF**. ProRes/DNxHR satisfy this but are CPU-only (NVIDIA has no
ProRes encoder). Long-GOP H.264/HEVC/AV1 do NOT work for edit-while-record.
**All-Intra HEVC (`-g 1 -bf 0`) via `hevc_nvenc`** is the one path that is both
GPU-accelerated AND all-intra: it breaks the "ProRes must be CPU" constraint
without losing edit-while-record. Trade-off: All-Intra bitrate approaches
ProRes, so the win is **CPU offload, not storage**. AV1 is rejected (no NLE
edit support; av1_nvenc absent from our ffmpeg builds).
## 3. Current working system (what we build on)
### Topology
- **zampp1** (172.18.91.200): primary. Runs db (postgres), queue (redis),
mam-api (:47432), web-ui (:47434), and the GPU worker pool. GPUs: Tesla P4 +
2x Quadro P400. Repo at /opt/wild-dragon (its own clone).
- **zampp2** (172.18.91.216): worker/capture node. 12-vCPU QEMU VM, NVIDIA L4,
4x Blackmagic DeckLink (exposed as /dev/blackmagic/io0..io3). Runs node-agent
(:7436). Repo at /opt/wild-dragon (separate clone).
- The repo is checked out independently on BOTH nodes; node-specific files
(node-agent, capture, worker overlay) are edited on the node that runs them.
### Capture (current)
mam-api `POST /recorders/:id/start` pre-creates a `live` asset and dispatches
`POST /sidecar/start` to the recorder's node-agent, which spawns a
`wild-dragon-capture:latest` container (host network, privileged,
/dev/blackmagic bound). The capture ffmpeg:
- input: `-f decklink -i "DeckLink Duo (N)"`
- filter: `yadif` (CPU deinterlace)
- output 0 (master): `prores_ks` (CPU) -> S3 (pipe) or growing SMB file
- output 1 (preview): `libx264` veryfast HLS -> /live/{assetId} (CPU)
DeckLink does capture (cheap); BOTH encodes are CPU. ~5 vCPU per 1080i signal
=> ~2 signals saturate the 12-vCPU VM. GPUs are idle during capture.
### Stop / finalize (working)
node-agent stops the sidecar with a **180s grace** (was 10s -> SIGKILL bug).
Capture's SIGTERM handler finalises the session and calls
`POST /assets/:id/finalize` (the live asset id passed as ASSET_ID), which flips
the asset out of `live`, records duration + S3 keys, and kicks the
proxy -> thumbnail -> filmstrip chain. (Earlier 409 bug: it used to POST a new
asset and collide with the live row.)
### Live monitor (working)
SDI HLS preview is a 2nd output of the capture ffmpeg (one DeckLink read ->
split -> ProRes + H.264 HLS), written to /live/{assetId} on the capture node.
node-agent serves GET /live/* over HTTP; mam-api proxies
GET /api/v1/recorders/:id/live/* to the recorder's node-agent; the web-ui
HlsPreview loads the proxied URL. Browser auth is the session cookie
(same-origin).
### GPU worker pool (working, post-capture)
BullMQ on shared Redis; queues are type-named (proxy/thumbnail/filmstrip/
conform/trim). Workers are capability-routed by `WORKER_QUEUES`, one GPU-pinned
container per card (`NVIDIA_VISIBLE_DEVICES` by UUID):
- HEAVY (proxy/conform/trim): Tesla P4 (zampp1) + L4 (zampp2), `h264_nvenc`.
- LIGHT (thumbnail/filmstrip): 2x Quadro P400 (zampp1).
DB setting `gpu_transcode_enabled=true` + `gpu_codec=h264_nvenc` enable NVENC.
Each worker stamps `WORKER_LABEL` onto job data -> Jobs UI "Node" column.
`RUN_PROMOTION=true` on exactly one worker runs the growing-files->S3 scan.
The worker GPU image is built from services/worker/Dockerfile.gpu (CUDA base +
Ubuntu ffmpeg with h264/hevc_nvenc; NO av1_nvenc).
### Deploy gotchas (learned)
- Service source is BAKED into images; edits need rebuild + recreate (or the
GPU image rebuild reuses cached layers so only final COPY changes -> fast).
- The capture image can only build on zampp2 (DeckLink SDK present there).
- Per-node `.env`: zampp2's REDIS_URL/DATABASE_URL/S3_* now point at zampp1
(.200); secrets live only in .env, never in committed compose.
- Clear all containers on both nodes before a full redeploy (user preference).
## 4. Target design
### 4.1 Capture ffmpeg gains NVENC
The capture image's custom FFmpeg 7.1 is currently built WITHOUT nvenc (only
prores_ks/dnxhd/libx264). Rebuild `services/capture/Dockerfile` ffmpeg with:
`--enable-cuda-nvcc --enable-libnpp --enable-nvenc --enable-cuvid` plus
nv-codec-headers (ffnvcodec) installed before configure. Keep `--enable-decklink`
and the existing codecs (ProRes stays available as a selectable fallback).
Verify `ffmpeg -encoders | grep nvenc` shows hevc_nvenc/h264_nvenc afterwards.
### 4.2 Capture sidecar gets a GPU
node-agent `handleSidecarStart` currently spawns the capture container with no
GPU. Add NVIDIA runtime + device pinning to the sidecar create spec:
`HostConfig.Runtime='nvidia'` (or DeviceRequests with the node's GPU) and env
`NVIDIA_VISIBLE_DEVICES=<uuid>` + `NVIDIA_DRIVER_CAPABILITIES=video,compute,utility`.
The capture node's GPU is shared with its worker-l4 (see capacity, §5).
### 4.3 Encode parameters (master)
All-Intra HEVC on NVENC:
`-c:v hevc_nvenc -preset p4 -rc vbr -g 1 -bf 0 -profile:v main10 -pix_fmt p010le` (10-bit 4:2:2 is not NVENC-native; NVENC HEVC is 4:2:0 8/10-bit.
If 4:2:2 mezzanine is required, that is a HARD blocker for NVENC and we stay on
ProRes for those feeds — see §8). Bitrate target tuned per format (1080i59.94
~100-160 Mbps to rival ProRes HQ). `-g 1 -bf 0` => every frame IDR (all-intra).
### 4.4 Container (growing-file)
Write the master to a growing file on the SMB share (GROWING_PATH), same path
the promotion worker already uploads on EOF. Container candidates, in order of
preference for Premiere growing-file mounts:
1. **MXF OP1a** (`-f mxf`) — broadcast standard, designed for growing/edit-while-
ingest; best Avid/Premiere support. HEVC-in-MXF support in Premiere is the
key unknown to validate (§8).
2. **Fragmented MOV/MP4** (`-movflags +frag_keyframe+empty_moov+default_base_moof`)
— no moov-at-EOF, readable while growing; fallback if MXF+HEVC is unsupported.
The HLS preview path is unchanged except it can also move to h264_nvenc now that
capture has NVENC (frees the last libx264 CPU cost).
## 5. Capacity & scaling (8 signals/machine)
After the move, per-signal CPU is just: DeckLink capture + yadif + mux + frame
upload to the GPU. The heavy HEVC encode is on NVENC. The constraint shifts from
CPU to **NVENC throughput + GPU memory + PCIe/host bandwidth**:
- The **L4 is a datacenter card => unlimited NVENC sessions** (no consumer
3-session cap). 8x 1080i HEVC-I encode sessions are well within an L4.
- GPU memory: ~8 concurrent 1080 NVENC sessions + frame buffers fit in 24 GB.
- The capture node's L4 is shared between capture (per-signal HEVC-I) and the
worker-l4 proxy jobs. Under 8-signal load, give capture priority; consider
moving worker-l4 (post-record proxies) to zampp1's P4 only, or gate worker-l4
intake while signals are live.
- yadif on CPU is still ~0.5-1 vCPU/signal; consider `yadif_cuda`/`bwdif_cuda`
(GPU deinterlace) once frames are uploaded to the GPU, keeping CPU near-idle.
**Node sizing:** a 12-vCPU VM was the ProRes wall; with GPU encode the same VM
should carry many more signals, but for 8x SDI + GPU + card passthrough prefer a
larger VM or bare metal with proper PCIe passthrough. Or spread signals across
multiple capture nodes (the node-agent model already supports N nodes; mam-api
routes each recorder to its node).
## 6. Multi-vendor capture (Blackmagic / Deltacast / AJA)
Today capture is hard-wired to `-f decklink`. Before three vendors accrue
special-cases, introduce a **source-backend abstraction** in capture-manager:
each backend returns ffmpeg input args + device discovery.
- **Blackmagic**: `-f decklink -i "<name>"` (current). Devices via
`ffmpeg -sources decklink`.
- **Deltacast**: VideoMaster SDK. No native ffmpeg demuxer upstream — needs an
SDK-backed capture (their SDK -> pipe to ffmpeg, or a small grabber). Plan a
`deltacast` backend that shells their tool into ffmpeg stdin (rawvideo).
- **AJA**: libajantv2. Also no upstream ffmpeg input; AJA ships `ntv2` capture
tools. Plan an `aja` backend feeding rawvideo into ffmpeg.
All backends converge on the SAME encode/output stage (HEVC-I NVENC + HLS), so
only the input differs. node-agent already binds the right /dev nodes per
sourceType (decklink/deltacast); extend for AJA.
## 7. Risks
- **4:2:2 / 10-bit chroma:** NVENC HEVC is 4:2:0 (8/10-bit). ProRes HQ is 4:2:2
10-bit. If a workflow REQUIRES 4:2:2 mezzanine, NVENC HEVC cannot match it and
those feeds stay on ProRes (CPU). Decide per-workflow.
- **Premiere growing HEVC support:** edit-while-record for HEVC-in-MXF (or frag
MOV) is unproven in our stack — this is the make-or-break validation (§8).
- **GPU contention** between live capture and post-record proxies on the same
L4; mitigate by prioritising capture / relocating proxy load.
- **Storage:** All-Intra HEVC bitrate ~ ProRes; expect similar disk usage.
- **Editor performance:** HEVC-I decode in Premiere is heavier than ProRes on
the edit workstation (decode cost moves to the editor). Validate scrubbing.
- **NVENC quality at all-intra** vs ProRes for archival; tune bitrate/preset.
## 8. Validation gate (do FIRST, before building the pipeline)
Prove the editor story on ONE channel before wiring 8:
1. Rebuild capture ffmpeg with NVENC; give the sidecar the L4.
2. Capture one DeckLink feed to All-Intra HEVC, writing a GROWING file to the
SMB share in (a) MXF OP1a, then (b) fragmented MOV.
3. While still recording, mount it in Premiere over SMB and confirm:
edit-while-record works, scrubbing is acceptable, audio in sync, file remains
valid after stop. Pick the container that works; if neither does, HEVC-I is
capture-only (no growing edit) and we keep ProRes for growing workflows.
## 9. Rollout
1. Validation gate (§8) on one channel.
2. Make capture codec/container a recorder setting; default growing feeds to
HEVC-I NVENC, keep ProRes selectable.
3. Move HLS preview to h264_nvenc.
4. Source-backend abstraction (§6) — land before Deltacast/AJA hardware.
5. GPU deinterlace + capacity test to 8 signals; finalise node sizing.

Binary file not shown.

View file

@ -1,57 +0,0 @@
# Cluster Hardening + Codec Settings Revamp
> Status: **mostly shipped 2026-05-21**. One follow-up remains: the recorders.html UI rewrite. See "Pending" below.
## Goal
Four user-driven asks from 2026-05-20:
1. Fix cluster page: workers were registering with docker bridge IPs, and three duplicate "zampp2" rows kept appearing.
2. Expand recorder codec settings: per-recorder control over bitrate, framerate, audio channels, container format.
3. Better DeckLink port picker: "BM1/BM2" dropdown was unusable -- diagram the card so operators pick a port visually.
4. Validate the cluster end-to-end now that GPUs are in place.
## What shipped (commit list)
| Commit | Area | Summary |
|---|---|---|
| `a39c983` | mam-api | Migration 007 -- dedupe `cluster_nodes` rows + unique index on `hostname`. |
| `049beb8` | mam-api | Migration 008 -- expanded codec columns on `recorders` (video/audio bitrate, framerate, audio channels, container, plus `node_id` / `device_index` pinning). |
| `3b4af6e` | node-agent | Prefer `NODE_IP` env override; skip docker bridge / veth / cni interfaces when auto-detecting. Version bumped to 1.1.0. |
| `0efef0d` | mam-api | `routes/cluster.js`: `pickIp()` fallback to request source IP. New `GET /api/v1/cluster/devices/blackmagic` flattens every node's DeckLink capabilities. |
| `40a66ba` | compose | `docker-compose.worker.yml`: `network_mode: host` for node-agent so it inherits host hostname + LAN IP. |
| `0ebb3cf` | deploy | `onboard-node.sh`: auto-detect host LAN IP and write `NODE_IP` + `BMD_MODEL` to `.env.worker`. |
| `f4a83ee` | capture | `capture-manager.js`: dynamic ffmpeg args. Exports `VIDEO_CODECS`, `AUDIO_CODECS`, `CONTAINER_FMT`, `CONTAINER_EXT`. |
| `485af25` | capture | `index.js` bootstrap forwards every codec env var to `captureManager.start()`. |
| `4c65753` | mam-api | `routes/recorders.js`: full codec field whitelist; `/start` passes settings to the capture sidecar. |
| `d39f86d` | web-ui | `services/web-ui/public/js/bmd-card.js` -- SVG renderer for DeckLink port selection. Models: Duo 2, Quad 2, Mini Recorder 4K, Mini Monitor 4K, UltraStudio 4K Mini. |
| `8aa3783` | deploy | `deploy/test-cluster.sh` cluster smoke test. |
| `4a3a672` | cluster | `mam-api` self-heartbeat reads `NODE_HOSTNAME` (otherwise every restart spawns a new primary row). Smoke test rewritten with `jq` after Python f-strings were found to silently false-pass the docker-bridge check. Bridge alarm narrowed to 172.17.x since this LAN occupies 172.18.0.0/16. |
## Verified cluster state (post-deploy, 2026-05-21)
```
$ MAM_API_URL=http://localhost:47432 bash deploy/test-cluster.sh
6 pass 0 fail
```
Two nodes registered, no duplicate hostnames, real LAN IPs (zampp1=172.18.91.216 primary, zampp2=172.18.91.217 worker), fresh heartbeats, 3 NVIDIA GPUs visible on zampp1, DeckLink Duo 2 reporting all 4 ports on zampp2.
## Deploy state
- **zampp1**: at `4a3a672`, rebuilt `mam-api`/`web-ui`/`worker`/`capture`, migrations 007+008 applied at startup. `.env` has `NODE_HOSTNAME=zampp1`, `NODE_IP=172.18.91.216`.
- **zampp2**: at `4a3a672`, rebuilt `node-agent` + `worker`. `.env` has `NODE_IP=172.18.91.217`, `BMD_COUNT=4`, `BMD_MODEL="DeckLink Duo 2"`, `BMD_DEVICE_0..3` populated.
- **Forgejo PAT** is at `/root/.git-credentials` on zampp1 (mode 600). Pushes from zampp1 need `HOME=/root`.
## LAN topology gotcha
The user's LAN is **172.18.91.0/24** -- inside Docker's reserved 172.16.0.0/12 range. Any heuristic that flags all of 172.16-172.31 as "docker bridge" will produce false positives. The smoke test now alarms only on 172.17.x (default docker0). The server-side `pickIp()` in `routes/cluster.js` has the same vulnerability but the node-agent's `NODE_IP` env-var override masks it in practice.
## Pending
- [ ] **`services/web-ui/public/recorders.html` rewrite.** The supporting pieces are in `main` but the HTML wiring was lost to a context-compaction event mid-session. Required UI:
- Tabbed codec settings (Video / Audio / Container) for both master and proxy.
- SDI source picker: node dropdown + inline `BMDCards.render(...)` SVG with click-to-select.
- Load BMD card data from `GET /api/v1/cluster/devices/blackmagic`.
- `<script src="js/bmd-card.js?v=1"></script>` in the head.
- SVG styles (`.bmd-card-svg`, `.bmd-port-ring`, `.bmd-port-group.is-selected`, ...) inlined or split into a CSS file.
- [ ] **Visual polish pass** with flyonui MCP -- the user noted the current UI "still looks AI-designed". Should happen AFTER the recorders.html rewrite.
## How to pick this up
1. `cd /opt/wild-dragon && git pull` on zampp1 (or zampp2).
2. Read this file end-to-end. Then `services/web-ui/public/js/bmd-card.js` (top JSDoc explains the API) and `services/capture/src/capture-manager.js` (codec catalogs).
3. Inspect `recorders.html` -- it still has the pre-revamp "BM1/BM2" dropdown and flat codec fields. Compare against the `recorders` table columns in `008-codec-settings.sql` for the full field set the UI should drive.
4. Iterate against a live deployment: `bash deploy/test-cluster.sh` for regression check, plus the actual `/recorders.html` page in a browser (web-ui on port 8080, mam-api on 47432).
5. Commit through Forgejo MCP if the diff is small; otherwise push from zampp1 (see Deploy state above for creds location). **Cloudflare WAF blocks large MCP uploads** (the blocked domain is `anthropic.com`, not Forgejo) -- pushing from a host with creds is faster for anything over ~3 KB.

View file

@ -1,502 +0,0 @@
# UI Shell Rework — Wave 2 (Low-risk page migrations) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development or executing-plans. Steps use `- [ ]` checkboxes.
**Goal:** Migrate the 6 lowest-risk shell pages (login, home, settings, tokens, users, containers) from `css/common.css` + bespoke per-page CSS to the new `/dist/app.css` primitive bundle from wave 1. Each page goes from "old look" to "new look" with the same functionality. Also fold in the deferred token cleanups from the wave-1 code review.
**Architecture:** Each page migration is a self-contained markup rewrite. Pattern: swap the `<link>` to `/dist/app.css`, replace the sidebar + topbar markup with `wd-*` primitives, restyle page-specific content with `wd-card-asset` / `wd-card-op` / `wd-list-row` / `wd-form-*` / `wd-btn` / etc. Preserve every existing `<script>` and `id` so JS keeps working. Deploy after each page; check.
**Tech Stack:** Tailwind+flyon-ui bundle from wave 1 (already live), nginx static, no JS changes expected.
**Reference spec:** `docs/superpowers/specs/2026-05-21-ui-shell-rework-design.md`
**Wave 1 plan:** `docs/superpowers/plans/2026-05-21-ui-shell-rework-wave-1-plan.md`
---
## File structure
**Files this wave modifies:**
```
services/web-ui/
├── public/
│ ├── login.html (REWRITE: small, no sidebar, just form)
│ ├── home.html (REWRITE: hero + stat tiles, has sidebar)
│ ├── settings.html (REWRITE: tabbed settings forms, has sidebar)
│ ├── tokens.html (REWRITE: list of tokens + create panel, has sidebar)
│ ├── users.html (REWRITE: user list + edit slide-panel, has sidebar)
│ └── containers.html (REWRITE: docker container list + logs, has sidebar)
└── src/css/components/
└── tokens.css (MODIFY: add deferred token cleanups)
```
**Files this wave does NOT touch:** the other 9 pages (index, projects, upload, jobs, api-tokens, recorders, cluster, capture, edit, editor, player). They're wave 3 / 4 / excluded.
---
## Tasks
### Task 0: Fold deferred token cleanups into tokens.css
Address items 1, 2, 4, 6 from the wave-1 code review BEFORE the page migrations multiply duplication of raw oklch values.
**Files:**
- Modify: `services/web-ui/src/css/components/tokens.css`
- Modify: `services/web-ui/src/css/components/button.css` (use new tokens)
- Modify: `services/web-ui/src/css/components/card-operational.css` (use new tokens)
- Modify: `services/web-ui/src/css/components/sidebar.css`, `topbar.css`, `slide-panel.css`, `card-asset.css`, `form-controls.css`, `field-group.css`, `list-row.css`, `toast.css` (use shared --ease / --dur tokens)
- [ ] **Step 1: Extend tokens.css with the missing tokens**
Append to `services/web-ui/src/css/components/tokens.css` inside `:root`:
```css
/* Hover-darker variants of accent + signals — promoted from
* inline oklch() arithmetic that was duplicated across button.css
* and card-operational.css */
--accent-hover: oklch(52% 0.20 266);
--accent-bright: oklch(70% 0.18 266);
--signal-bad-hover: oklch(68% 0.22 25);
--signal-good-hover: oklch(74% 0.18 148);
--signal-warn-hover: oklch(84% 0.16 90);
/* Pure-black-ish tinted toward brand hue for thumbnails & overlays.
* Numerically still ~black but the hue channel is set so future
* derivations stay on-brand. */
--thumb-black: oklch(0% 0 266);
--overlay: oklch(8% 0.010 266 / 0.65);
--shadow: oklch(0% 0 266 / 0.5);
/* Motion + ease tokens — promoted from raw cubic-bezier strings
* that appeared in 8 of 12 primitive files */
--ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--dur-fast: 120ms;
--dur-normal: 180ms;
--dur-slide: 240ms;
/* Z layers — promoted from sidebar/topbar where 30 was hard-coded */
--z-topbar: 30;
```
- [ ] **Step 2: Find-and-replace raw oklch hover values across primitives**
For each of these files, replace the literal oklch string with the new token. Use `sed -i` for the substitutions, but verify each file afterward.
```bash
cd /opt/wild-dragon/services/web-ui/src/css/components
# button.css
sed -i 's|background: oklch(52% 0.20 266);|background: var(--accent-hover);|' button.css
sed -i 's|background: oklch(68% 0.22 25);|background: var(--signal-bad-hover);|' button.css
# card-operational.css — gradient stop in signal-strip-fill
sed -i 's|oklch(70% 0.18 266)|var(--accent-bright)|' card-operational.css
# card-asset.css — pure-black thumb background
sed -i 's|background: oklch(0% 0 0);|background: var(--thumb-black);|' card-asset.css
# slide-panel.css — overlay color
sed -i 's|oklch(8% 0.010 266 / 0.65)|var(--overlay)|' slide-panel.css
# toast.css — shadow
sed -i 's|oklch(0% 0 0 / 0.7)|var(--shadow)|' toast.css
```
- [ ] **Step 3: Replace raw cubic-bezier strings with --ease + --dur tokens**
```bash
cd /opt/wild-dragon/services/web-ui/src/css/components
# Replace exact "120ms cubic-bezier(0.25, 1, 0.5, 1)" with the tokens
for f in sidebar.css topbar.css slide-panel.css card-asset.css card-operational.css form-controls.css field-group.css list-row.css button.css; do
sed -i 's|120ms cubic-bezier(0\.25, 1, 0\.5, 1)|var(--dur-fast) var(--ease-out-quart)|g' "$f"
done
# slide-panel slide-in (240ms ease-out-expo)
sed -i 's|240ms cubic-bezier(0\.16, 1, 0\.3, 1)|var(--dur-slide) var(--ease-out-expo)|' slide-panel.css
# Tab indicator
sed -i 's|240ms cubic-bezier(0\.25, 1, 0\.5, 1)|var(--dur-slide) var(--ease-out-quart)|' slide-panel.css
sed -i 's|200ms cubic-bezier(0\.25, 1, 0\.5, 1)|200ms var(--ease-out-quart)|g' form-controls.css
sed -i 's|240ms cubic-bezier(0\.16, 1, 0\.3, 1)|var(--dur-slide) var(--ease-out-expo)|' card-operational.css
```
- [ ] **Step 4: Replace hard-coded z-index 30 with --z-topbar**
```bash
cd /opt/wild-dragon/services/web-ui/src/css/components
sed -i 's|z-index: 30;|z-index: var(--z-topbar);|' topbar.css
```
- [ ] **Step 5: Rebuild + verify primitives still ship correctly**
```bash
cd /opt/wild-dragon
docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
docker exec wild-dragon-web-ui-1 grep -c '.wd-' /usr/share/nginx/html/dist/app.css
# Expect: same large number (~116+) — no rules dropped
docker exec wild-dragon-web-ui-1 grep -c '\-\-accent-hover\|\-\-ease-out-quart\|\-\-z-topbar' /usr/share/nginx/html/dist/app.css
# Expect: at least 3 hits (tokens now defined + referenced)
```
- [ ] **Step 6: Visual regression check on smoke page**
```bash
curl -sk -o /dev/null -w 'smoke=%{http_code}/%{size_download}\n' http://localhost:47434/_primitives-smoke.html
# Expect: HTTP 200, ~12 KB (unchanged from wave 1)
```
Manually load the smoke page in a browser; everything should look identical to wave 1. If anything changed visually, the sed substitutions introduced a regression.
- [ ] **Step 7: Commit + push**
```bash
cd /opt/wild-dragon
HOME=/root git add services/web-ui/src/css/components/
HOME=/root git diff --cached --stat
HOME=/root git -c user.email=zgaetano@wilddragon.net -c user.name='Zac Gaetano' commit -m 'web-ui: token cleanups from wave-1 code review
- Promote --accent-hover, --signal-bad-hover, --signal-good-hover,
--signal-warn-hover, --accent-bright tokens (were duplicated raw
oklch arithmetic in button.css / card-operational.css)
- Promote --thumb-black, --overlay, --shadow tokens (tinted toward
brand hue 266 so future derivations stay on-brand)
- Promote --ease-out-quart, --ease-out-expo, --dur-fast/normal/slide
tokens (cubic-bezier strings appeared in 8 of 12 primitive files)
- Promote --z-topbar (was hard-coded 30 in topbar.css while every
other layer was tokenized)
- Replace all usages across the 12 primitive files via sed.
Bundle byte count unchanged (~138 KB); visual regression on smoke
page = zero. Code-review concerns from wave 1 now resolved before
wave 2 page migrations begin.'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 1: Migrate login.html
Smallest page. No sidebar, no topbar — just a centered card with email/password. Migrating first because if it breaks nothing else does.
**Files:**
- Modify: `services/web-ui/public/login.html`
- [ ] **Step 1: Read the current page to see what's there**
```bash
cd /opt/wild-dragon/services/web-ui/public
cat login.html | head -80
```
Note: form ids, input names, any inline JS handlers. Preserve all of them.
- [ ] **Step 2: Write the new login.html**
The new structure:
- `<link rel="stylesheet" href="/dist/app.css">` instead of the old `common.css`
- Centered `<main>` with a single `.wd-card-op`-shaped panel (operational card primitive, sized small)
- Inside: brand logo + "Z-AMPP" wordmark at top, then `<form>` with two `.wd-form-group` (email + password), then `.wd-btn.wd-btn--primary.wd-btn--md` submit
- Keep every existing `id`, `name`, `type`, and `<script>` tag from the old file
- If there's an "error message" div, replace its class with `.wd-toast.wd-toast--error` (inline, not floating)
Replace the entire `<head>` and `<body>` with the new shell. JS at the bottom stays as-is.
- [ ] **Step 3: Deploy on zampp1 (no Docker rebuild needed — HTML is static)**
```bash
# Actually nginx serves from the image's filesystem, not the host's
# /opt/wild-dragon/services/web-ui/public/. So we DO need a rebuild.
cd /opt/wild-dragon
docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'login=%{http_code}/%{size_download}\n' http://localhost:47434/login.html
# Confirm new bundle is referenced
curl -sk http://localhost:47434/login.html | grep -E 'dist/app.css|common.css'
# Expect: dist/app.css present, common.css absent
```
- [ ] **Step 4: Visual + functional check**
Open `http://172.18.91.216:47434/login.html` in a browser. Verify:
- Page renders with new brand styling
- Email + password fields look like the wd-input primitive
- Submit button looks like wd-btn--primary
- Logging in still actually works (POST to /api/v1/auth/login)
- [ ] **Step 5: Commit + push**
```bash
HOME=/root git add services/web-ui/public/login.html
HOME=/root git commit -m 'web-ui(wave 2): migrate login.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 2: Migrate home.html
Has sidebar + topbar + dashboard stat tiles. The first page that exercises the full shell.
**Files:**
- Modify: `services/web-ui/public/home.html`
- [ ] **Step 1: Read the current page**
```bash
cd /opt/wild-dragon/services/web-ui/public
wc -l home.html
head -80 home.html
```
Identify: page title, what's in the topbar right side, the stat tile structure, any chart libraries, and the bottom `<script>` blocks. Preserve all script references and JS state.
- [ ] **Step 2: Migrate the markup**
The migration recipe for every shell page:
1. **`<head>`**: replace `<link rel=stylesheet href=css/common.css>` with `<link rel=stylesheet href=/dist/app.css>`. Keep favicon, viewport meta.
2. **`<body>` root**: wrap in `<div class="wd-shell">` (style inline: `display:flex;min-height:100vh`).
3. **Sidebar**: copy verbatim from the smoke page's `<nav class="wd-sidebar">` block. Mark the active nav item with `is-active` on Home.
4. **Right column**: `<div style="flex:1;display:flex;flex-direction:column;">`
5. **Topbar**: `<header class="wd-topbar">` with breadcrumb in `.wd-topbar-left` containing just "Home", any existing right-side button as `.wd-btn.wd-btn--primary.wd-btn--sm`.
6. **Main content**: `<main style="padding:20px 20px 32px;">`
7. **Stat tiles**: replace with `.wd-card-op-grid` containing `.wd-card-op` (small, content-only — no footer needed if there's no action).
8. **Auth-guard script** at the bottom — stays exactly as-is.
- [ ] **Step 3: Deploy + check**
```bash
cd /opt/wild-dragon
docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'home=%{http_code}/%{size_download}\n' http://localhost:47434/home.html
curl -sk http://localhost:47434/home.html | grep -c 'wd-sidebar\|wd-topbar\|wd-card'
# Expect: 8+ matches
```
Load `http://172.18.91.216:47434/home.html` in a browser. Sidebar should be the new one, breadcrumb shows "Home", stat tiles render as operational cards.
- [ ] **Step 4: Commit + push**
```bash
HOME=/root git add services/web-ui/public/home.html
HOME=/root git commit -m 'web-ui(wave 2): migrate home.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 3: Migrate settings.html
System settings form. Lots of form-groups, possibly tabbed.
**Files:**
- Modify: `services/web-ui/public/settings.html`
- [ ] **Step 1: Read current page + identify all form sections**
```bash
cd /opt/wild-dragon/services/web-ui/public
grep -E '<h[12]|form-group|form-section-label' settings.html | head -30
```
- [ ] **Step 2: Migrate using the standard recipe**
Same recipe as task 2, except for the form content:
- Replace each form section with a `.wd-field-group` (header + body, no tabs unless the section is genuinely tabbed)
- Replace every `<input>` with `class="wd-input"`, every `<select>` with `class="wd-select"`, every `<label>` with `class="wd-label"`
- Replace every `<button>` with `class="wd-btn wd-btn--primary wd-btn--md"` (or `--secondary` / `--ghost` / `--danger` as appropriate)
- Wrap rows of inputs in `.wd-form-row`
- Preserve every `id`, `name`, `type`, and JS handler
Set `.wd-nav-item.is-active` on Settings.
- [ ] **Step 3: Deploy + check**
```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'settings=%{http_code}/%{size_download}\n' http://localhost:47434/settings.html
```
Load in browser. Verify the form actually saves (test by changing one value and clicking save).
- [ ] **Step 4: Commit + push**
```bash
HOME=/root git add services/web-ui/public/settings.html
HOME=/root git commit -m 'web-ui(wave 2): migrate settings.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 4: Migrate tokens.html
Lists API tokens, allows creation of new ones with a slide-panel. First page that exercises the slide-panel primitive in the migrated context.
**Files:**
- Modify: `services/web-ui/public/tokens.html`
- [ ] **Step 1: Read + identify the slide-panel structure**
```bash
cd /opt/wild-dragon/services/web-ui/public
grep -E 'slide-panel|slide-overlay|wd-list-row' tokens.html | head -20
```
- [ ] **Step 2: Migrate**
- Standard shell recipe (sidebar with `is-active` on Tokens, topbar with "Tokens" breadcrumb and "New token" primary button)
- Token list → `.wd-list` containing `.wd-list-row` for each token: name (cell--name), created date (cell--meta), badge for scope, action buttons in cell--actions
- Create-token form moves into `.wd-slide-panel` (with overlay, header, body, footer pattern exactly as in the smoke page's field-group)
- Preserve every JS handler — especially the copy-to-clipboard one for the newly-generated token
- [ ] **Step 3: Deploy + check**
```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'tokens=%{http_code}/%{size_download}\n' http://localhost:47434/tokens.html
```
Load + verify: clicking "New token" opens the slide-panel (codec-clipping bug fix from wave 1 applies — body should scroll if it overflows), creating a token shows the copy-once display.
- [ ] **Step 4: Commit + push**
```bash
HOME=/root git add services/web-ui/public/tokens.html
HOME=/root git commit -m 'web-ui(wave 2): migrate tokens.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 5: Migrate users.html
User management. List + edit slide-panel. Pattern matches tokens.html closely.
**Files:**
- Modify: `services/web-ui/public/users.html`
- [ ] **Step 1: Read + identify**
```bash
cd /opt/wild-dragon/services/web-ui/public
grep -E '<h[12]|wd-list|slide-panel' users.html | head
```
- [ ] **Step 2: Migrate using the tokens.html recipe**
Identical pattern to task 4: shell + list + slide-panel for create/edit. Mark Users active. Preserve every JS handler (role dropdown, password reset, etc.).
- [ ] **Step 3: Deploy + check**
```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'users=%{http_code}/%{size_download}\n' http://localhost:47434/users.html
```
Verify: list renders, edit panel opens, save works.
- [ ] **Step 4: Commit + push**
```bash
HOME=/root git add services/web-ui/public/users.html
HOME=/root git commit -m 'web-ui(wave 2): migrate users.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 6: Migrate containers.html
Docker container list. List rows with status badges + action buttons (logs / restart). No slide-panel (logs typically opens in a separate tab or inline).
**Files:**
- Modify: `services/web-ui/public/containers.html`
- [ ] **Step 1: Read + identify**
```bash
cd /opt/wild-dragon/services/web-ui/public
head -80 containers.html
```
- [ ] **Step 2: Migrate**
- Standard shell recipe (Containers active)
- Container list → `.wd-list` with `.wd-list-row` per container:
- cell--name: container name
- cell with image: cell--meta
- cell with status: `.wd-badge.wd-badge--good` (Up) / `.wd-badge--bad` (Down) / `.wd-badge--warn` (Restarting)
- cell--actions: ghost buttons for Logs / Restart / Stop
- Auto-refresh polling JS stays unchanged
- [ ] **Step 3: Deploy + check**
```bash
cd /opt/wild-dragon && docker compose up -d --build web-ui 2>&1 | grep -E 'Built|Started' | tail -3
sleep 4
curl -sk -o /dev/null -w 'containers=%{http_code}/%{size_download}\n' http://localhost:47434/containers.html
```
Load + verify: all containers visible, status badges color-coded, Logs button still opens logs.
- [ ] **Step 4: Commit + push**
```bash
HOME=/root git add services/web-ui/public/containers.html
HOME=/root git commit -m 'web-ui(wave 2): migrate containers.html to new primitives'
HOME=/root git push 2>&1 | tail -3
```
---
### Task 7: Wave-2 user QA gate
- [ ] **Step 1: Verify all 6 migrated pages serve correctly**
```bash
for p in login home settings tokens users containers; do
printf ' %s.html: HTTP=%s\n' "$p" "$(curl -sk -o /dev/null -w '%{http_code}' http://localhost:47434/$p.html)"
done
```
Expected: all 200.
- [ ] **Step 2: Verify all 6 pages reference /dist/app.css and NOT common.css**
```bash
for p in login home settings tokens users containers; do
CNT_NEW=$(curl -sk http://localhost:47434/$p.html | grep -c dist/app.css)
CNT_OLD=$(curl -sk http://localhost:47434/$p.html | grep -c common.css)
printf ' %s.html: new=%s old=%s\n' "$p" "$CNT_NEW" "$CNT_OLD"
done
```
Expected: new=1 old=0 for every page.
- [ ] **Step 3: Verify wave-3 / wave-4 pages are STILL on the old CSS (no accidental change)**
```bash
for p in index projects upload jobs api-tokens recorders cluster capture editor; do
CNT_OLD=$(curl -sk http://localhost:47434/$p.html | grep -c common.css)
printf ' %s.html: still-on-old=%s\n' "$p" "$CNT_OLD"
done
```
Expected: still-on-old=1 for every page (none of them migrated yet).
- [ ] **Step 4: User visual QA**
Stop. Ask user to load each of the 6 migrated pages and confirm the new look is correct, navigation still works, forms still save, lists still poll. If anything looks wrong, fix it before wave 3 starts.
---
## Self-review notes
- **Spec coverage**: Every page in the wave-2 list from the design spec is in the plan. Token cleanups from wave-1 review are folded in as task 0.
- **Placeholders**: none. Every step has the actual command / file change.
- **Type consistency**: every migrated page uses `.wd-shell` / `.wd-sidebar` / `.wd-topbar` / `.wd-nav-item.is-active` / `.wd-card-op` / `.wd-list` / `.wd-list-row` / `.wd-btn` / `.wd-input` / `.wd-select` / `.wd-label` / `.wd-form-row` / `.wd-field-group` — exact class names from the wave-1 bundle.
- **Open risk**: each page migration is a manual markup rewrite. The implementer subagent needs to actually read each existing page before rewriting, not work from the description alone, because each page has page-specific JS handlers that must be preserved verbatim.

View file

@ -1,73 +0,0 @@
# NLE Editor: React SPA Polish — Phases 13 Implementation Plan
> **Date:** 2026-05-24
> **Status:** Phase 1 IN PROGRESS
> **Progress:** Tasks 1.11.6 code-complete, pending test/deploy
---
## Phase 1 — Core Editor (IN PROGRESS)
### Task 1.1: Sequence API helpers in data.jsx ✅
- Added `getSequences`, `createSequence`, `getSequence`, `updateSequence`, `deleteSequence`, `syncSequenceClips`, `exportSequenceEDL` to `data.jsx`
- All exported on `window.ZAMPP_API`
### Task 1.2: Timecode.js wired into SPA ✅
- Added `<script src="js/timecode.js">` and `<script src="js/timeline.js">` to `index.html` before `screens-editor.jsx`
- `window.TC` available globally for 59.94 DF timecode math
### Task 1.3: TimelinePanel React component ✅
- `tlRef` container div in editor layout
- `useEffect` mounts `window.Timeline.init()` on first render
- `useEffect` pushes scale changes via `window.Timeline.setScale()`
- `onClipsChanged` / `onPlayheadMoved` callbacks connect timeline engine to React state
### Task 1.4: screens-editor.jsx rewrite ✅ (455 lines, was 162)
Full rewrite with:
- **App state**: `projectId`, `sequences`, `currentSeq`, `assets`, `sourceAsset`, `srcIn`/`srcOut`, `playheadFrames`, `history` (undo stack), `scale`, `tool`, `saveStatus`, `isDirty`
- **Source monitor**: `<video>` + `apiFetch('/assets/:id/stream')` + Mark In/Out + Insert button
- **Program monitor**: Virtual clip-by-clip playback — loads V1 clips sorted by `timeline_in_frames`, advances on `timeUpdate`/`ended` events
- **Media panel**: Asset list from `ZAMPP_DATA.ASSETS`, filter by bin, `AssetThumb` thumbnails, double-click loads source
- **Sequence management**: Picker `<select>`, "New sequence" modal, `openSequence()` loads via API
- **Auto-save**: `markDirty()` → debounce 2s → `syncSequenceClips()` → status updates
- **Undo/redo**: 50-step history stack, Ctrl+Z / Ctrl+Shift+Z
- **EDL export**: Button triggers `window.ZAMPP_API.exportSequenceEDL()`
- **Tool toolbar**: V/C/H buttons synced with `Timeline.setTool()`
- **Zoom slider**: Range input driving `window.Timeline.setScale()`
- **Keyboard handler**: I/O, V/C/H, Ctrl+Z/Shift+Z, Ctrl+S
### Task 1.5: "In Development" overlay removed ✅
- Deleted the `position: absolute; inset: 0; backdropFilter: blur(6px)` overlay div (was lines 98-117)
- Removed `FauxFrame` component reference
- All buttons are now functional
### Task 1.6: Editor nav badge removed ✅
- `shell.jsx`: `{ id: "editor", label: "Editor", icon: "editor" }` — no more `badge: { kind: "dev", text: "DEV" }`
### NEXT: Task 1.7 — Test & Deploy
1. `docker compose up -d --build web-ui`
2. Navigate to Editor route in browser
3. Verify: source monitor loads video, timeline renders with 4 track rows, Insert places clip, auto-save fires, EDL export downloads
---
## Phase 2 — UX Polish & Growing File (PENDING)
- [ ] 2.1 Multi-track refinements (ripple delete, snap, locking, overlap prevention)
- [ ] 2.2 Zoom slider + adaptive ruler
- [ ] 2.3 JKL transport + frame stepping
- [ ] 2.4 Waveform display on audio tracks
- [ ] 2.5 Inspector panel wiring
- [ ] 2.6 Style migration to Tailwind primitives
- [ ] 2.7 HLS live preview during capture
## Phase 3 — Export, Conform & Features (PENDING)
- [ ] 3.1 FCP XML export + conform queue
- [ ] 3.2 Hi-Res Auto-Relink
- [ ] 3.3 Timecoded Comments
- [ ] 3.4 Player Rebuild (P1)
- [ ] 3.5 Subclips (P2)
- [ ] 3.6 Multi-select & Bulk Ops (P3)
- [ ] 3.7 Smart Bins (P6)
- [ ] 3.8 Metadata Templates (P7)

File diff suppressed because it is too large Load diff

View file

@ -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 160191)
- [ ] **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 ~50200KB.
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

View file

@ -1,198 +0,0 @@
# UI Shell Rework — Design Spec
> Status: **design approved 2026-05-21**, awaiting user review of the spec before the implementation plan is written.
## Context
The Wild Dragon MAM web-ui currently ships 15 static HTML pages served by nginx, sharing a single hand-written `common.css`. The token system is already strong (oklch palette, brand hue 266, 5-step depth surfaces, semantic signal tokens, 4pt spacing). What does not work is the gap between tokens and execution: cards across pages have undifferentiated spacing, generic chrome, weak hierarchy, and identical shape regardless of role. The user feedback that prompted this work was "the UI still looks AI-designed."
The rework adopts flyon-ui (Tailwind plugin) as the component primitive layer, ports the oklch palette into a custom flyon-ui theme so brand identity is preserved, and rebuilds every page that uses the standard shell against the new primitives. The personality target is *quiet pro tool* — closer to Sony Media Cloud and DaVinci Resolve than to a consumer SaaS dashboard.
## Goals & non-goals
**Goals**
- A coherent visual system across every shell page (15 pages minus 3 excluded).
- Higher information density at every screen — closer to Sony / DaVinci than to today's spacing.
- Four distinct card families so the eye reads role from shape.
- A theme port that preserves brand hue 266 and the existing oklch palette under flyon-ui.
- Accessibility floor: WCAG AA contrast, full keyboard nav, reduced-motion honored.
**Non-goals**
- Mobile UX. Phones get an explicit "desktop only" splash. Tablet gets a collapsed icon-rail sidebar but no further accommodation.
- Replacing the brand color, the font stack, or the dark theme.
- Animations beyond functional state transitions (no celebrations, no page-fade, no sound design).
- Adding new pages or features. This is purely visual / structural.
- Rebuilding `edit.html`, `editor.html`, or `player.html` (deliberately excluded — see Rollout).
## Personality, scene & color strategy
- **Register:** product (app UI, design serves the product), not brand.
- **Theme scene sentence:** "MAM operator at a 27-inch monitor in a dim control room, scanning a grid of 100+ video assets at 2am while a live recording timer runs." Forces dark, low-chroma, tabular-numeric trust signals.
- **Color strategy:** restrained. Tinted neutrals (chroma 0.0100.015, hue 266) plus a single amber accent used in ≤10% of surfaces — for active states, recording indicators, primary CTAs, focus rings.
- **Anti-patterns explicitly banned:** glassmorphism as default, gradient text, side-stripe borders (>1px on the side of cards/rows/callouts), hero-metric SaaS template, identical card grids across roles, em dashes in copy. Cliché category-reflex check passes: this design lands as DAW / NLE / NLE-adjacent operational tool, not "dark blue observability dashboard."
## Architecture
### 1. Build system & theme port
The `services/web-ui` Docker image gains a Node build stage. During `docker build`, `tailwindcss --minify` runs once, scans `public/**/*.html` for class usage, and emits `public/dist/app.[hash].css`. The runtime stage stays nginx-static — no runtime Node, longer startup, or extra moving parts.
A `tailwind.config.js` at `services/web-ui/` defines a custom flyon-ui theme that maps the existing oklch palette into flyon-ui's color slots. Brand hue 266 is preserved; the 5-step depth surfaces become Tailwind's `bg-deep` / `bg-base` / `bg-panel` / `bg-surface` / `bg-raised` / `bg-hover` utility chain. Signal tokens (`signal-good` / `signal-bad` / `signal-warn` / `signal-idle`) map directly. Spacing scale uses Tailwind's default 4pt scale, which already matches the existing `--sp-*` tokens — utilities like `p-3` and `gap-4` replace `var(--sp-3)` and `gap: var(--sp-4)`.
Fonts (Inter + JetBrains Mono) move from Google CDN to self-hosted woff2 in `public/fonts/`. The four "legacy alias" entries in the current `:root` (`--status-amber`, `--status-amber-bg`, etc.) get cleaned up during the port.
The custom theme also disables flyon-ui utility classes for the banned patterns: no `glass-*`, no `gradient-text`, no card-shadow defaults.
### 2. Sidebar
- **Dimensions:** 200px wide (down from 220px). Items 28px tall (down from ~36px), 8px horizontal padding, 4px vertical.
- **Type:** Inter 13px / 500 for items. Section labels 10px / 600 / 0.14em tracked / uppercase / `text-tertiary`.
- **Header:** 18px dragon logo + Inter 13px / 600 / -0.01em "Z-AMPP" wordmark. Total header height 48px to align with topbar.
- **Active state:** `bg-surface` background + `text-primary` text + 4px leading accent dot (8px tall, vertically centered). No side-stripe border (banned). No accent background fill.
- **Hover:** `bg-hover` fade-in over 120ms ease-out. No transform.
- **IN DEV badge** (injected by `auth-guard.js`): retained, restyled as 9px / 700 / 0.12em tracked amber pill.
- **Footer user widget:** 28px round avatar, name + role stacked, logout button reveals on row hover.
### 3. Topbar
- **Dimensions:** 48px tall. Padding 16px left / 12px right. Bottom border `border-faint`.
- **Left:** breadcrumb pattern, not flat title. Inter 13px / 500 / `text-secondary` for ancestor crumbs, 13px / 600 / `text-primary` for current. 10px chevron separator in `text-tertiary` with 8px gutters.
- **Center:** page-scoped search input on pages that have searchable content (Library, Recorders, Projects, Jobs, Cluster). 360px wide, 28px tall, leading magnifier, monospace placeholder.
- **Right:** primary CTA rightmost (28px button with 12px leading icon), 1px vertical divider, then 28px-square icon-only ghost buttons for secondary actions (filter, sort, view-toggle).
- **Sticky:** `position: sticky; top: 0; z-index: 30` inside `.main`. Sidebar does not scroll separately; topbar stays visible while content scrolls.
### 4. Card families
Four distinct card shapes, each with one clear job. Same-shape repetition is banned.
**Asset card** — Library, Projects asset grid, Recorders recording cards
- 16:9 thumbnail, full-bleed. Duration chip bottom-right (JetBrains Mono 10px, `bg-deep` 70% opacity). Comment-count chip bottom-left (when >0). Selection checkbox top-left (only on hover or when any are selected). Version badge top-right when applicable.
- Metadata: filename (Inter 13px / 500), then `{author} · {date}` row (11px / `text-tertiary`, mono numerics).
- Role pill at bottom: full-width, light tint of role color, 10px / 600 / 0.08em tracked / uppercase. Dotted-border placeholder when unset.
- 1px `border-faint`, 6px radius, `bg-panel`. Hover: thumbnail +4% brightness, border lifts to `border`. No scale, no shadow.
**Operational card** — Recorders cards, Cluster nodes, Jobs queue
- Header / content / footer rows. Wider than tall (min 8:3 ratio). 14px padding, 10px row gap.
- Header: 16px name + status pill (semantic signal tokens).
- Content: role-specific. Recorder gets live preview + signal strip + timer; cluster node gets CPU/mem mini-bars; job gets progress strip.
- Footer: 1px top hairline, metadata-left + actions-right.
- Border 1px `border-faint`, becomes 1px `accent-border` when active (recording / online / running). Color change only — no glow, no shadow, no animation.
**Inline list row** — Containers, Users, Tokens, API tokens
- Not a card. Table row with extra breathing room. 44px tall, hairline divider (`border-faint`).
- Hover: `bg-hover` row tint. Selected: `bg-surface` tint + 4px leading accent dot (same indicator language as sidebar active state).
**Empty state**
- Centered 28px line icon (`text-tertiary`), 14px / 600 title, 13px body, primary action button. No card chrome — the empty state IS the page.
- Declarative copy. No exclamation points, no emojis.
### 5. Grids
- Asset grid: `repeat(auto-fill, minmax(220px, 1fr))` with 12px gap.
- Operational grid: `repeat(auto-fill, minmax(380px, 1fr))` with 14px gap.
- Page content padding: 20px sides, 16px top, 32px bottom.
### 6. Forms, slide-panels & inputs
**Slide-panel structure (the codec-clipping bug fix codified as a primitive):**
- 460px wide. `height: 100vh; display: flex; flex-direction: column; overflow: hidden`. Header `flex-shrink: 0`. Body `flex: 1; min-height: 0; overflow-y: auto`. Footer `flex-shrink: 0; bg-deep`.
- Header 52px, 18px padding, title + close button. Bottom border `border-faint`.
- Body 18px padding, `display: flex; flex-direction: column; gap: 16px`.
**Form primitives:**
- Label: 11px / 600 / 0.08em tracked / uppercase / `text-tertiary`.
- Input / select / textarea: 32px tall, 10px horizontal padding, 13px text, 1px `border` outline, 4px radius. Focus: `accent-border` outline + 2px `accent-subtle` ring.
- Form hint: 11px / `text-tertiary` / 1.5 line-height. JetBrains Mono code spans.
- Form row: `grid-template-columns: 1fr 1fr; gap: 14px`.
**Field-group (tabbed sections, generalized from the codec-block pattern):**
- Titled header strip (36px, `bg-surface`) + tab row (32px, `bg-deep`) + tab panels (14px padding).
- Active tab: 2px `accent` bottom border, text shifts from `text-tertiary` to `accent`. Tab switches are instant — no animation.
**Buttons:**
- Sizes: `sm` 28px / `md` 32px / `lg` 36px.
- Variants: `primary` (accent bg), `secondary` (`bg-surface` + border), `ghost` (transparent + secondary text, `bg-hover` on hover), `danger` (status-red bg).
- Leading icon: 12px svg, 6px gap. Disabled: 40% opacity. Active press: 60ms `opacity: 0.85`. No gradient, no shadow, no scale.
**Toggle:** 34×18, track `bg-hover``accent`, 200ms ease-out on dot only.
**Date / time inputs:** native `<input type="date">` styled to match the input primitive. No third-party picker library.
### 7. States, motion & feedback
**Loading:** skeleton blocks matched to content shape. Asset grid → 12 placeholder cards with 1.8s gradient shimmer (not opacity pulse). In-card actions get inline 12px ring spinner. In-button: label replaced by spinner, width preserved.
**Empty states:** fade in 240ms on first load; instant when user-initiated.
**Errors:**
- *Toast* (bottom-right, 320px): `bg-panel` + 1px `status-red` border + 4px `status-red` top strip. Auto-dismiss 4s success / 8s warning / manual error. Stack up to 3.
- *Inline*: red 11px text below offending field. No icon, no shake.
- *Page-level*: full-page card with icon + plain-English title + Retry + Get-help buttons. Never blocks the sidebar.
**Success:** "Recorder saved" toast. Affected card briefly tints (200ms `accent-subtle` background, fades back over 1.2s). One-time. No checkmark celebrations.
**Live / realtime (recording-in-progress):**
- Signal strip shimmer 1.8s ease-in-out (down from 2.4s linear).
- "LIVE" preview-stamp dot stutter pattern: bright 0.9s / dim 0.3s / bright 0.9s (broadcast tally light).
- Timer: 600 weight, `status-red` while recording. Already correct.
**Hover / focus:**
- All transitions 120ms ease-out on `border-color` and `background-color` only. Never on `width`, `height`, `transform`.
- Focus ring: 2px `accent-subtle` outline, 1px offset, `:focus-visible` only.
**Page transitions:** none. Click nav → page renders. Slide-panel keeps 240ms slide-in from right.
**Notifications:** no bell, no global status banner. Failures surface inline on affected pages.
### 8. Accessibility & responsive
**A11y floor:**
- WCAG AA contrast on every text/background pair. `text-tertiary` lightness bumped from 52% to 56% to clear AA cleanly.
- `:focus-visible` ring on every interactive element.
- Full keyboard nav. Slide-panel traps focus while open; Esc closes overlays.
- Every icon-only button gets `aria-label`. Toasts use `role="status" aria-live="polite"`.
- `prefers-reduced-motion: reduce` honored: kills shimmer / pulse / slide-in, state changes become instant.
**Responsive:**
- Desktop-first tool. *Fully supported* minimum viewport: 1280×800. Anything narrower is best-effort only.
- ≥1600px: standard layout, content max-width 1440px, centered.
- 12801599px: standard layout, no max-width cap.
- 7681279px (tablet, best-effort): sidebar collapses to a 56px icon-rail, breadcrumb truncates to last crumb. Functional but not polished.
- <768px (phone): explicit "Z-AMPP is desktop-only" splash. No fake mobile experience.
**Browser support:** Chromium latest 2 versions + Safari latest 2. Firefox best-effort. No IE / legacy Edge.
## Rollout
Four waves, ordered by blast radius. Each wave is its own commit / deploy / verify cycle on zampp1.
**Wave 1 — Foundation (zero user-visible change).** Build pipeline, Tailwind + flyon-ui config, theme port, primitive CSS components, shell markup migration. New primitives exist but no page uses them yet. *Validates the build system works end-to-end.*
**Wave 2 — Shell + low-risk pages.** `login.html`, `home.html`, `settings.html`, `tokens.html`, `users.html`, `containers.html`. New shell, new card / list patterns, new forms. Low risk: no live data, simple flows.
**Wave 3 — Content-heavy pages.** `index.html` (Library), `projects.html`, `upload.html`, `jobs.html`, `api-tokens.html`. Asset grid, project tree, upload queue, job queue.
**Wave 4 — Operational pages.** `recorders.html`, `cluster.html`, `capture.html`. Live data, HLS preview, signal polling, BMD picker, codec slide-panel. Done last so the primitives are battle-tested before they meet the most fragile pages.
**Excluded from rework:** `edit.html`, `editor.html` (in-development construction screen is the right treatment), `player.html` (standalone embed, no shell).
**Definition of done per page:** new shell + new primitives + AA contrast verified + keyboard-nav check + responsive at 1280/1440/1920 widths + no JS regressions (live-recording flow on recorders.html is the canary).
## Risks & mitigations
| Risk | Mitigation |
|---|---|
| Tailwind build pipeline introduction breaks docker build | Wave 1 ships the build *without* migrating any page. If build fails, we revert without losing functionality. |
| Theme port loses brand hue 266 character | Custom flyon-ui theme explicitly maps existing oklch tokens; QA on wave 1 includes side-by-side color comparison vs. current. |
| Recorders rewrite (just stabilized) gets re-touched in wave 4 | Wave 4 is last on purpose — primitives are battle-tested by then. The codec-tab pattern from the recent recorders rewrite is generalized into the `.field-group` primitive in wave 1, so wave 4's recorders rewrite is mostly markup migration, not pattern reinvention. |
| Density target is too aggressive — 13px text / 28px rows feel cramped on smaller monitors | Wave 1 ships density + AA verified at 1280×800. If feedback says cramped, bump base text to 14px in a single token change. |
| Page-level skeleton loaders are extra implementation work | Acceptable cost. Spinners-only would feel cheaper than the rest of the design. |
| Native `<input type="date">` looks inconsistent across Chromium / Safari | Acceptable. Inconsistency is small; bundle weight savings of avoiding a date-picker library is real. |
## Implementation plan handoff
Once this spec is approved by the user, the next step is invoking the `superpowers:writing-plans` skill to produce a wave-by-wave implementation plan with concrete commit / deploy steps. The plan will live at `docs/superpowers/plans/2026-05-21-ui-shell-rework-plan.md` and reference this spec.
## Open questions
None. All seven design sections were approved by the user (Zac) during brainstorming on 2026-05-21. No placeholder values remain in this spec.

View file

@ -1,272 +0,0 @@
# YouTube Importer — Design Spec
> Status: **design approved 2026-05-23**, awaiting user review of the spec before the implementation plan is written.
## Context
The Ingest group in Dragonflight today covers file Upload, Recorders (SRT/RTMP/SDI), Capture (DeckLink), Monitors, and Schedule. There is no path to bring in media that already lives on the public web. The frequent ask is: "I want to grab a YouTube link and have it become an asset in my project, with the same proxy/thumbnail pipeline as anything else." This spec adds a YouTube importer that mirrors the existing upload flow: paste a URL, pick a project, click Import, and the asset shows up in the Library once the worker is done.
The importer rides on the existing job pipeline. After the download lands in S3, the asset re-enters the same proxy → thumbnail → ready path as a regular upload, so there is no parallel "imported asset" lifecycle to maintain.
## Goals & non-goals
**Goals**
- Paste a public YouTube URL, end up with a `ready` asset in the chosen project.
- Reuse the existing `assets` table, S3 layout, BullMQ pipeline, and Jobs screen — no parallel state machine.
- Progress visible from both the import screen (queue rows) and the Jobs screen.
- Clear, actionable errors for the obvious failure modes (private, age-gated, removed, geo-blocked, network).
**Non-goals**
- Playlists, channels, or batch-paste of multiple URLs. Single URL per submission. (Easy to add later.)
- Cookies / login. Private, members-only, and age-gated videos are out of scope v1.
- Quality picker. Always grabs best MP4 (with M4A audio merge fallback).
- Non-YouTube sources (Vimeo, Twitch VODs, Dropbox links, etc.). The route is `/imports/youtube` precisely to leave room for siblings later.
- Auto-update of yt-dlp inside the running container. Updates land via image rebuild.
- Copyright enforcement. We surface a one-line "only import videos you have rights to use" note and stop there.
## Architecture
The importer threads through four existing layers:
```
[web-ui] YouTube screen ──POST /imports/youtube──▶ [mam-api]
assets row (status='ingesting')
jobs row (type='youtube_import')
BullMQ "import" queue
[worker]
yt-dlp download → S3 originals/
ffprobe metadata → assets row
status='processing'
BullMQ "proxy" queue ◀── existing path
proxy → thumbnail → ready
```
Once the worker hands off to the `proxy` queue, the asset is indistinguishable from one that came through Upload — same proxy worker, same thumbnail worker, same Library list.
## 1. UX
### Nav
A 6th child is added to the **Ingest** group in `shell.jsx`, between Upload and Recorders:
```js
{ id: "youtube", label: "YouTube", icon: "download" },
```
The `download` glyph already exists in `icons.jsx`. The matching `ingestChildren` array in `shell.jsx` and the crumbs map in `app.jsx` both gain `"youtube"`.
### Screen
A new `YouTubeImport` component lives in `screens-ingest.jsx` and is exported on `window` alongside `Upload`, `Recorders`, etc. It is registered as a route in `app.jsx`.
Layout — visually a sibling of the Upload screen:
- **Header**: title "YouTube", subtitle "Paste a link — we download and import the best available MP4."
- **Project selector**: same `select` element as Upload's, pre-selected to the first project.
- **URL input**: a single-line `field-input` with placeholder "Paste a YouTube URL (youtube.com/watch, youtu.be, or shorts)…" and an inline **Import** button. Enter submits. The button is disabled until a URL pattern matches.
- **Subtitle line under the input**: "Only import videos you have rights to use. Private, age-gated, and members-only videos are not supported."
- **Queue panel**: identical structure to Upload's queue — one row per submitted URL, showing:
- Source icon (use `link` glyph) and the URL (truncated middle, full URL in `title` tooltip).
- Title once known (filled in by a poll on the asset row).
- Progress bar tied to job `progress` (0100). The worker drives this between 5 and 60 % for download and 60 to 100 % for upload + DB writes.
- Status pill: queued → downloading → processing → done / failed.
- Error text if the job fails (red, one line).
- A "Clear done" button at the top of the queue.
The queue persists for the session in component state only — no separate UI table. Jobs screen remains the canonical history.
### URL validation (client-side, before POST)
Accept (case-insensitive) any of these patterns:
- `https?://(www\.|m\.)?youtube\.com/watch\?[^ ]*v=[A-Za-z0-9_-]{11}`
- `https?://youtu\.be/[A-Za-z0-9_-]{11}`
- `https?://(www\.)?youtube\.com/shorts/[A-Za-z0-9_-]{11}`
Anything else is rejected inline ("That doesn't look like a YouTube URL") without an API call. The server re-validates as a defense-in-depth check.
### Out-of-scope v1 (called out, not built)
- Pasting a playlist URL. Server returns 400 "Playlists aren't supported yet."
- Multi-line paste. Single URL only.
- Quality picker. yt-dlp format string is hard-coded.
- Cookies upload. Private videos fail with a clear message.
## 2. API
### Route
New file `services/mam-api/src/routes/imports.js`, mounted at `/api/v1/imports` in `services/mam-api/src/index.js`.
**`POST /api/v1/imports/youtube`**
Request body:
```json
{ "url": "https://youtu.be/dQw4w9WgXcQ", "projectId": "uuid", "binId": "uuid?" }
```
Behavior:
1. Validate `url` against the same three regexes as the client. 400 on miss.
2. Reject playlist URLs (URL contains `list=`) with 400 "Playlists aren't supported yet."
3. Generate `assetId = uuidv4()`.
4. Insert into `assets` with:
- `status='ingesting'`
- `media_type='video'`
- `filename = url` (placeholder; worker overwrites with the sanitized title once yt-dlp prints metadata — keeps the row queryable in the meantime)
- `display_name = url` (same; worker overwrites)
- `original_s3_key = NULL` (worker fills in)
- `source_url = url` (new column — see Schema)
- `project_id`, `bin_id`, timestamps.
5. Insert into `jobs` with `type='youtube_import'`, `asset_id`, `payload={ url }`, `status='queued'`, `progress=0`.
6. Enqueue BullMQ job on the `import` queue:
```js
await importQueue.add('youtube', { assetId, url });
```
7. Respond `200 { assetId, jobId }`.
Errors:
- Missing fields → 400.
- Bad URL → 400 with `error: 'Invalid YouTube URL'`.
- Playlist URL → 400 with `error: 'Playlists aren't supported yet'`.
- Project not found → 404.
- DB / queue failure → 500 (next(err)).
### Jobs screen integration
`services/web-ui/public/screens-jobs.jsx` already normalizes job types via a `kindMap`. Add one entry:
```js
const kindMap = { proxy: 'Proxy', thumbnail: 'Thumbnail', conform: 'Conform', transcode: 'Transcode', youtube_import: 'YouTube' };
```
Retry, delete, and the SSE event stream all work for the new type with no further changes because they key off `job.id`, not `job.type`.
## 3. Worker
### Container changes
`services/worker/Dockerfile` gains two packages:
```dockerfile
RUN apk add --no-cache ffmpeg yt-dlp python3
```
`yt-dlp` is in the Alpine `community` repo and pulls `python3` as a runtime dep — we list it explicitly for clarity. Image grows by ~25 MB.
### New worker
`services/worker/src/workers/youtube-import.js`, registered in `services/worker/src/index.js`:
```js
const workers = [
createWorker('proxy', proxyWorker),
createWorker('thumbnail', thumbnailWorker),
createWorker('conform', conformWorker),
createWorker('import', youtubeImportWorker),
];
```
### Job handler
For a job with `{ assetId, url }`:
1. `job.updateProgress(2)` — accepted.
2. Build a temp directory `tmpdir()/yt-${jobId}`.
3. Run yt-dlp:
```sh
yt-dlp \
--no-playlist \
--no-warnings \
--restrict-filenames \
-f "bv*[ext=mp4]+ba[ext=m4a]/b[ext=mp4]/b" \
--merge-output-format mp4 \
--print-json \
--newline \
-o "<tmpdir>/<assetId>.%(ext)s" \
"<url>"
```
- `--print-json` writes one JSON line at the end with title, duration, width, height, uploader, etc.
- `--newline` makes progress lines newline-terminated so we can parse them.
- `--restrict-filenames` prevents shell-special characters in temp paths.
4. Stream stdout line-by-line. Lines matching `r'\[download\]\s+(\d+(\.\d+)?)%'` map to `job.updateProgress(5 + Math.floor(pct * 0.55))` so download takes us from 5 to 60 %.
5. On yt-dlp non-zero exit: parse stderr for the first line containing `ERROR:` and use it as the job's error message. Mark the asset `status='error'`, mark the job failed, throw so BullMQ records it. Surface a friendly substitution for the common cases:
- "Private video" → "Private video — not supported."
- "Sign in to confirm your age" → "Age-restricted video — not supported."
- "Video unavailable" → "Video unavailable or removed."
- "This video is not available in your country" → "Video is geo-blocked from this region."
- HTTP 429 → "YouTube rate-limited the importer — try again later."
- Anything else → use yt-dlp's stderr line verbatim, truncated to 300 chars.
6. Parse the last stdout line as JSON to read metadata. The resulting file is `<tmpdir>/<assetId>.mp4`.
7. `getMediaInfo` (existing helper in `services/worker/src/ffmpeg/executor.js`) on that path. Use ffprobe's values for codec/fps/duration when yt-dlp's are missing or wrong.
8. Sanitize the title for the S3 filename: keep `[A-Za-z0-9 ._-]`, collapse runs of whitespace, trim, cap at 120 chars, append `.mp4`. If the sanitized title is empty, fall back to `youtube-<videoId>.mp4`.
9. Upload to `originals/{assetId}/{sanitized-title}.mp4` via the existing `uploadToS3` helper. Progress 60 → 90 %.
10. UPDATE the assets row with:
- `filename = <sanitized title>.mp4`
- `display_name = <yt-dlp title untouched>`
- `original_s3_key = originals/<assetId>/<sanitized-title>.mp4`
- `codec`, `resolution`, `fps`, `duration_ms`, `file_size` from ffprobe.
- `status = 'processing'`
- `updated_at = NOW()`
11. Enqueue a `proxy` job on the existing `proxy` queue with the same payload shape `upload.js` uses:
```js
await proxyQueue.add('generate', {
assetId,
inputKey: asset.original_s3_key,
outputKey: `proxies/${assetId}.mp4`,
});
```
12. `job.updateProgress(100)`. Return — BullMQ marks the import job done. The proxy job picks up the rest exactly like a regular upload.
13. Always `rm -rf` the temp directory in a `finally`.
### Concurrency & retries
- Default BullMQ concurrency for this queue: **1** per worker process. Two simultaneous yt-dlp invocations risk YouTube rate-limiting more than they help throughput. Configurable later via env if needed.
- No automatic BullMQ retry — yt-dlp failures are almost always permanent (private, geo, removed) and a silent retry storm would chew through quota. The Jobs screen's manual Retry button is the right knob for "this should be transient" cases.
## 4. Schema migration
New file `services/mam-api/src/db/migrations/011-youtube-import.sql`:
```sql
-- 1. Add the new job type to the enum.
-- Postgres requires ALTER TYPE ... ADD VALUE for enum changes.
ALTER TYPE job_type ADD VALUE IF NOT EXISTS 'youtube_import';
-- 2. Remember where an asset came from. NULL for everything that
-- pre-dates the importer; populated for any imported asset.
ALTER TABLE assets ADD COLUMN IF NOT EXISTS source_url TEXT;
```
`source_url` is exposed on the asset drawer as a "Source" line ("imported from youtu.be/…") in a follow-up PR — out of scope for this spec, but worth noting that the column exists for it.
## 5. Files touched
**New**
- `services/mam-api/src/routes/imports.js`
- `services/mam-api/src/db/migrations/011-youtube-import.sql`
- `services/worker/src/workers/youtube-import.js`
**Edited**
- `services/mam-api/src/index.js` — mount the new route.
- `services/web-ui/public/screens-ingest.jsx` — add `YouTubeImport`, export on `window`.
- `services/web-ui/public/shell.jsx` — add the nav child, extend `ingestChildren`.
- `services/web-ui/public/app.jsx` — register the route and the crumb.
- `services/web-ui/public/screens-jobs.jsx` — extend `kindMap` with `youtube_import: 'YouTube'`.
- `services/worker/src/index.js` — register the `import` queue worker.
- `services/worker/Dockerfile` — add `yt-dlp` and `python3` to the apk install line.
## 6. Risks & trade-offs
- **Worker egress**. The worker container needs outbound HTTPS to YouTube. Fine in the current homelab; will fail in a locked-down cluster. Documented in the implementation plan.
- **yt-dlp drift**. YouTube changes break old yt-dlp versions every few weeks. The Alpine package lags upstream by days. Fix is to rebuild the worker image. We do not auto-update inside the running container — too risky for an offline / locked-down deploy. If imports start failing en masse, the runbook is `docker compose build worker && docker compose up -d worker`.
- **Single-URL UX feels light**. That is deliberate for v1. Adding multi-URL paste and playlist expansion are both small follow-ups once the single-URL path is stable.
- **No copyright enforcement**. We rely on the one-line notice in the UI. If misuse becomes a real concern, the next step would be an admin allowlist of domains or a per-user import quota — not in this spec.
- **`filename = url` placeholder**. Briefly, the asset row in the Library shows the URL as the name. The worker overwrites it within seconds for a successful import. Acceptable; the Library already handles "ingesting" assets with placeholder names from the upload path.
## 7. Acceptance
The feature is done when:
- A user can navigate to **Ingest → YouTube**, paste a public YouTube URL, pick a project, click Import, and within a minute or two see the asset appear in the Library with proxy and thumbnail.
- A failed import (private video, removed video, bogus URL) shows a clear error message on both the YouTube screen's queue row and the Jobs screen.
- The Jobs screen lists "YouTube" jobs alongside Proxy / Thumbnail / Conform, with Retry working.
- `source_url` is populated on the imported asset row.
- Image rebuild + `docker compose up -d worker` is the documented recovery path if YouTube changes break yt-dlp.

View file

@ -1,269 +0,0 @@
# Dragonflight User Authentication — Design
**Status:** Approved, ready for implementation planning
**Date:** 2026-05-27
**Brainstormed with:** Zac
## Problem
Dragonflight has the skeleton of an auth system spread across the codebase:
- `users` table (`id`, `username`, `password_hash`, `display_name`, `role`)
- `sessions` table (`sid`, `sess`, `expire`) for `connect-pg-simple`
- `groups`, `user_groups`, `api_tokens` tables
- `SESSION_SECRET` env var
- `AUTH_ENABLED` env flag with boot-log toggle
- PR #26 frontend handler that bounces to `/login.html` on 401
- Issue #94 "session security fixes" deployed 2026-05-26 (commit `3ebe5d6`)
But the actual `express-session` middleware was never mounted in `services/mam-api/src/index.js`. There is no `/api/v1/auth/*` router. There is no `requireAuth` middleware. As a result, when `AUTH_ENABLED=true` was tried:
1. User submits login, server returns 200 OK from a stub endpoint.
2. No `Set-Cookie` is ever sent (no session middleware mounted).
3. The next request to a protected route returns 401.
4. Frontend bounces to `/login.html`.
5. **Infinite redirect loop.**
The prior attempts failed because auth was being built reactively in pieces, with no single source of truth for what "logged in" means.
## Goals
- One coherent, readable auth code path.
- Web UI logins survive page reloads and container restarts.
- Premiere panel can authenticate via long-lived bearer tokens.
- First-run setup works on a fresh install with no env var or CLI gymnastics.
- The whole auth flow can be exercised by automated tests, including a regression test for the redirect-loop failure mode.
## Non-goals (v1)
- MFA / TOTP.
- OAuth / OIDC delegation (Forgejo, Google, etc.).
- Per-project or per-recorder permissions. Flat access: logged in = full access.
- Email-based "forgot password" (no SMTP assumed; admin-reset only).
- Audit log of who-did-what (the `last_login_at` column is the minimum).
- Service-to-service auth for `node-agent` — keeps existing `019-node-token-binding` mechanism.
## Decisions
| Decision | Choice | Reasoning |
|---|---|---|
| Client surface | Web UI + Premiere panel | Two transports (cookies + bearer), one identity backend |
| Permission model | Flat (logged in = full access) | Small homogeneous operator population. `groups` / `user_groups` schemas stay inert. |
| Identity provider | Local username/password | On-prem broadcast operators won't tolerate OIDC roundtrips. Matches existing schema. |
| First-user bootstrap | First-run setup page | Hardest to mis-configure. No env vars to leak. No CLI to remember. |
| Session lifetime | 8h absolute + 1h sliding idle | Operator security posture, tighter than typical SaaS. |
| Auth library | Hand-rolled (`express-session` + `connect-pg-simple`) | Explicit, debuggable. Rejected JWT and Passport for this codebase. |
## Architecture
### Single source of truth
"Logged in" means exactly one of two things:
1. The request carries a valid `dragonflight.sid` cookie whose row in `sessions` hasn't expired and isn't past its 1h-idle or 8h-absolute window, OR
2. The request carries `Authorization: Bearer <token>` whose SHA-256 matches an `api_tokens` row that hasn't been revoked or expired.
Nothing else counts. No `localStorage` flags, no JWT, no client-side "I think I'm logged in" hints.
### One middleware, one check
`services/mam-api/src/middleware/auth.js` exposes a single `requireAuth` function:
```js
export async function requireAuth(req, res, next) {
// Dev mode preserved. The 'dev' user is a real row in `users` seeded at
// boot when AUTH_ENABLED !== 'true', so FK-bearing routes (api_tokens,
// future comments, audit fields) keep working without conditional logic.
if (process.env.AUTH_ENABLED !== 'true') {
req.user = DEV_USER; // { id: <UUID of seeded 'dev' user>, username: 'dev' }
return next();
}
// 1. Session check
if (req.session?.user_id) {
const now = Date.now();
if (now - req.session.first_seen_at > 8 * 3600 * 1000) return destroyAnd401(req, res);
if (now - req.session.last_seen_at > 1 * 3600 * 1000) return destroyAnd401(req, res);
req.session.last_seen_at = now;
req.user = await loadUser(req.session.user_id);
if (!req.user) return destroyAnd401(req, res);
return next();
}
// 2. Bearer check
const bearer = parseBearer(req.headers.authorization);
if (bearer) {
const hash = sha256hex(bearer);
const row = await pool.query(
`SELECT t.id, t.user_id, t.expires_at, u.username
FROM api_tokens t JOIN users u ON u.id = t.user_id
WHERE t.token_hash = $1`, [hash]);
if (row.rows.length && (!row.rows[0].expires_at || row.rows[0].expires_at > new Date())) {
pool.query(`UPDATE api_tokens SET last_used_at = NOW() WHERE id = $1`, [row.rows[0].id]).catch(() => {});
req.user = { id: row.rows[0].user_id, username: row.rows[0].username };
return next();
}
}
// 3. Otherwise
return res.status(401).json({ error: 'unauthorized' });
}
```
Mounted at the `/api/v1` level in `services/mam-api/src/index.js`, **before** the individual route mounts, with an allowlist for the three pre-login auth paths:
```js
app.use('/api/v1', (req, res, next) => {
const unauth = ['/auth/login', '/auth/setup', '/auth/setup-required'];
if (unauth.some(p => req.path === p)) return next();
return requireAuth(req, res, next);
});
// then: app.use('/api/v1/assets', assetsRouter), etc.
```
`/health` lives at the root, outside the `/api/v1` mount, so it's naturally unaffected. `/api/v1/cluster/*` keeps its existing `019-node-token-binding` service-auth path: requireAuth runs first, fails with 401 for an unauthenticated request, **but** the cluster routes themselves do their own token check on request bodies, so node-agent traffic must include a valid user session OR an api_token (which is the change — node-agent will need to be issued an api_token at install time). Alternative: carve `/api/v1/cluster/*` out of the requireAuth gate too, and keep node-agent on its existing binding token alone. Implementer should pick — flagged in the implementation order.
### Session middleware (actually wired this time)
In `services/mam-api/src/index.js`, **before any route**:
```js
import session from 'express-session';
import connectPgSimple from 'connect-pg-simple';
const PgStore = connectPgSimple(session);
if (process.env.TRUST_PROXY === 'true') app.set('trust proxy', 1);
app.use(session({
store: new PgStore({ pool, tableName: 'sessions', pruneSessionInterval: 60 * 15 }),
secret: process.env.SESSION_SECRET,
name: 'dragonflight.sid',
cookie: {
httpOnly: true,
sameSite: 'lax',
secure: process.env.TRUST_PROXY === 'true',
path: '/',
maxAge: 8 * 3600 * 1000,
},
rolling: false, // sliding renewal handled in requireAuth so we can enforce idle + absolute separately
resave: false,
saveUninitialized: false,
}));
```
### Auth router
`services/mam-api/src/routes/auth.js`:
| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/api/v1/auth/setup-required` | none | `{ required: bool }`. Cheap, no auth. |
| `POST` | `/api/v1/auth/setup` | none | Only succeeds if `users` is empty. Creates first user, logs them in. |
| `POST` | `/api/v1/auth/login` | none | `{ username, password }` -> 200 + cookie or 401 |
| `POST` | `/api/v1/auth/logout` | required | Destroys session row, clears cookie |
| `GET` | `/api/v1/auth/me` | required | `{ id, username, display_name }` |
| `POST` | `/api/v1/auth/password` | required | Change own password (requires current) |
| `GET/POST/DELETE` | `/api/v1/auth/users[/:id]` | required | User CRUD |
| `GET/POST/DELETE` | `/api/v1/auth/tokens[/:id]` | required | Current user's API tokens |
### Data model
Existing schema is almost right. One small migration:
```sql
-- services/mam-api/src/db/migrations/023-auth-session-timestamps.sql
ALTER TABLE users ADD COLUMN IF NOT EXISTS password_updated_at TIMESTAMPTZ DEFAULT NOW();
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ;
-- idle / absolute timestamps live inside session.sess JSONB; no schema change needed
```
`groups` and `user_groups` stay as-is, unused for v1. `api_tokens` is already correctly shaped.
## Flows
### Browser login (the one that broke last time)
1. SPA boots, `<AuthGate>` calls `GET /api/v1/auth/me`.
2. `requireAuth` returns 401.
3. AuthGate calls `GET /api/v1/auth/setup-required`. If `true`, render Setup screen. Otherwise, render Login screen.
4. User submits `POST /api/v1/auth/login`. Server `bcrypt.compare`s, sets `req.session.user_id`, `first_seen_at`, `last_seen_at`. **Critical:** `await new Promise(r => req.session.save(r))` before responding, so the cookie is persisted to Postgres before the next request can arrive.
5. AuthGate re-calls `/api/v1/auth/me`, gets 200, renders the app.
**Why this doesn't loop:** the explicit `req.session.save()` callback before response guarantees the cookie row exists before the SPA can fire its next request. `requireAuth` returns a clean 401 (not a redirect) so the SPA decides what to render. The static `/login.html` is deleted; there is no HTML bounce.
### Premiere panel bearer
1. Web UI -> Settings -> API Tokens -> "New token" named "Premiere panel".
2. `POST /api/v1/auth/tokens` returns `{ token: 'dfl_<32 hex>', prefix: 'dfl_a3f2', id }` **exactly once**.
3. Premiere panel sends `Authorization: Bearer dfl_<...>` on every request. `requireAuth` SHA-256s it, looks up `api_tokens.token_hash`, updates `last_used_at`.
### Idle + absolute timeout (inside `requireAuth`)
```
if session present:
if now - session.first_seen_at > 8h -> destroy session, 401
if now - session.last_seen_at > 1h -> destroy session, 401
session.last_seen_at = now
req.user = lookup(session.user_id)
next()
```
Bearer tokens have their own optional `expires_at` (`NULL` = never expires); checked the same way.
## Frontend
- **`services/web-ui/src/auth-gate.jsx`** — new component that wraps the SPA. On mount: `GET /me`. On 401: check `setup-required`, render either Setup or Login. On 200: render the app shell.
- **Login screen** — layout B from brainstorm: 22px wordmark over "WILD DRAGON BROADCAST" tagline above a `--bg-1` card containing username, password, "Sign in" button. Matches DESIGN.md tokens.
- **Setup screen** — same chrome; fields = username, password, confirm password; button = "Create admin".
- **Settings -> Account section** — change password.
- **Settings -> API Tokens section** — list / create / revoke. New token shown exactly once with a copy affordance.
- **Fetch wrapper** — the central `ZAMPP_API.fetch` (already exists) gains a 401 handler that re-mounts AuthGate's Login state with the current path saved as `last_path`, restored after re-auth.
### Removed
- The static `/login.html` page (PR #26's bounce target) is deleted. SPA handles login internally; no full-page reload.
## Error handling
| Case | Behavior |
|---|---|
| Wrong username or password | `401 { error: 'invalid credentials' }`. Same message either way, no user enumeration. |
| Login rate limiting | Per-IP exponential backoff (1s, 2s, 4s, 8s, max 30s). In-memory `Map`. Single-instance limitation documented. |
| Idle / absolute expiry | 401 -> AuthGate Login. Last path saved, restored on re-auth. |
| Setup after first user exists | `409 { error: 'setup already complete' }`. Permanently disabled. |
| Token revoke | `DELETE /api/v1/auth/tokens/:id` — only owner can revoke. Subsequent bearer requests 401. |
| Delete-self when only user | `409 { error: 'cannot delete last user' }`. |
| Forgot password | No self-serve. Any logged-in user can reset another via `POST /api/v1/auth/users/:id/password`. Documented as the recovery path. |
| Password rules | Min 12 chars, no max, no character class requirements (NIST SP 800-63B). `bcrypt` cost 12. |
| CSRF | `SameSite=Lax` + same origin + required `X-Requested-With: dragonflight-ui` header on mutating requests (belt-and-suspenders). |
| Session table growth | `connect-pg-simple` `pruneSessionInterval: 60 * 15` (every 15 min). |
## Testing
- **Unit — `services/mam-api/test/middleware/auth.test.js`**: requireAuth with (a) no creds, (b) valid session, (c) idle-expired session, (d) absolute-expired session, (e) valid bearer, (f) invalid bearer, (g) bearer matching a deleted user.
- **Integration — `services/mam-api/test/auth.integration.test.js`**: spin up Express + test Postgres. Walks: setup -> login -> /me -> mutating call -> logout -> /me 401. Second pass: idle timeout simulated by mutating `last_seen_at` in DB. Third pass: bearer issue -> use -> revoke -> 401.
- **Regression test for the redirect-loop bug:** explicit test that after `POST /auth/login` returns 200, a subsequent `GET /auth/me` with the returned cookie returns 200 in the same test client. This is the test that would have caught the original failure.
- **Manual smoke (documented in PR):** fresh install -> setup -> create admin -> land on dashboard -> reload (stays logged in) -> wait 1h idle -> reload -> bounce to login.
## Implementation order
Suggested sequencing for the implementation plan (writing-plans will refine):
1. Migration `023-auth-session-timestamps.sql`. Add idempotent seed of the dev user (`INSERT ... ON CONFLICT DO NOTHING` with a fixed UUID) so dev mode FK-bearing routes work out of the box.
2. `express-session` + `connect-pg-simple` wiring in `index.js`.
3. `requireAuth` middleware (with `DEV_USER` constant resolved from the seeded row).
4. Auth router (setup, login, logout, me, password).
5. Apply `requireAuth` to API router with allowlist. Decide cluster carve-out (see Architecture).
6. Auth tests (unit + integration + regression).
7. Frontend `<AuthGate>` + Login screen + Setup screen.
8. Frontend Settings -> Account + API Tokens.
9. Delete `/login.html`.
10. User CRUD + token CRUD routes.
11. Rate limiting + CSRF header.
12. Documentation: README updates, `AUTH_ENABLED` transition notes.
## Out-of-band notes for the implementer
- The current `cors({ origin: true, credentials: true })` in `index.js` is too permissive once cookies start carrying authority. Tighten to a specific origin list (driven by an `ALLOWED_ORIGINS` env var) at the same time as wiring the session middleware — otherwise we're undoing the `SameSite=Lax` protection from the other side.
- node-agent -> mam-api traffic on `/api/v1/cluster/*` must keep working. Add a route-level carve-out comment that this path uses the existing `019-node-token-binding` token, not the user-auth path.
- The boot log currently says `Authentication: ENABLED` / `DISABLED (set AUTH_ENABLED=true for production)`. Once this lands, the recommended default flips: `AUTH_ENABLED=true` becomes the documented default in `.env.example` and the README, and `AUTH_ENABLED=false` is documented as a dev-only escape hatch.

View file

@ -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).

View file

@ -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. ~46s 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 ~530 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 |

View file

@ -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).

View file

@ -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 14).
---
## `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

View file

@ -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.

View file

View file

@ -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).

View file

@ -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.

View file

View file

@ -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.

View file

View file

@ -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.

View file

@ -1,174 +1,8 @@
# ── 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 ─────────
# 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
# nv-codec-headers (header-only, no driver / no full CUDA toolkit needed)
# so ffmpeg's configure can light up hevc_nvenc / h264_nvenc / cuvid.
# At runtime, /dev/nvidia* + the host driver libs (via the NVIDIA Container
# Toolkit) supply the actual encoder.
FROM debian:bookworm AS ffmpeg-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential nasm yasm pkg-config git ca-certificates python3 \
libssl-dev libx264-dev libx265-dev libvpx-dev libopus-dev \
libmp3lame-dev libsrt-openssl-dev \
libzmq3-dev zlib1g-dev libstdc++-12-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy in BMD DeckLink SDK headers and patch script
COPY sdk/ /decklink-sdk/
COPY patch_decklink.py /patch_decklink.py
COPY decklink-sdk16.patch /decklink-sdk16.patch
# nv-codec-headers — just the ffnvcodec public headers + a pkg-config file.
# Pin to a tag known to work with FFmpeg 7.1 (n12.x series).
RUN git clone --depth=1 --branch n12.1.14.0 https://github.com/FFmpeg/nv-codec-headers.git /nv-codec-headers \
&& make -C /nv-codec-headers PREFIX=/usr/local install
# Pull FFmpeg 7.1 source
RUN git clone --depth=1 --branch release/7.1 https://git.ffmpeg.org/ffmpeg.git /ffmpeg
# Patch FFmpeg DeckLink code for SDK 16.x API changes
RUN python3 /patch_decklink.py
WORKDIR /ffmpeg
# NVENC adds: --enable-nvenc (encoder), --enable-cuvid (decoder), --enable-ffnvcodec.
# We deliberately do NOT enable --enable-cuda-nvcc / --enable-libnpp here — those
# require the full ~3GB CUDA toolkit and are only needed for GPU filters like
# yadif_cuda / scale_cuda. If §5's GPU deinterlace stretch goal goes ahead,
# rebuild this image off nvidia/cuda:12.x-devel and flip those flags on.
RUN ./configure \
--prefix=/usr/local \
--extra-cflags="-I/decklink-sdk -I/usr/local/include" \
--extra-ldflags="-L/usr/local/lib" \
--enable-gpl \
--enable-nonfree \
--enable-libx264 \
--enable-libx265 \
--enable-libvpx \
--enable-libopus \
--enable-libmp3lame \
--enable-libsrt \
--enable-libzmq \
--enable-decklink \
--enable-ffnvcodec \
--enable-nvenc \
--enable-cuvid \
--disable-doc \
--disable-debug \
--disable-ffplay \
&& make -j$(nproc) \
&& make install
# Sanity-check: hevc_nvenc and h264_nvenc must be present in the encoder list,
# otherwise the resulting image is useless for the All-Intra HEVC pipeline.
RUN /usr/local/bin/ffmpeg -hide_banner -encoders 2>&1 | grep -E 'nvenc' \
|| (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 ───────────────────────────────────────────────────
FROM node:20-bookworm FROM node:20-bookworm
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
# 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 \
libx264-164 libx265-199 libvpx7 libopus0 libmp3lame0 \
libsrt1.5-openssl libzmq5 libstdc++6 libc++1 libc++abi1 \
cifs-utils util-linux \
libexpat1 liburiparser1 libuuid1 \
&& rm -rf /var/lib/apt/lists/*
# Copy compiled ffmpeg/ffprobe
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
COPY lib/libDeckLinkAPI.so /usr/lib/libDeckLinkAPI.so
COPY lib/libDeckLinkPreviewAPI.so /usr/lib/libDeckLinkPreviewAPI.so
# 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.
# /live — HLS preview output (bound from host LIVE_DIR by node-agent)
# /growing — growing-file master output (bound from host /mnt/NVME/MAM/growing)
RUN mkdir -p /live /growing
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm install --omit=dev RUN npm install --omit=dev
COPY . . COPY . .
EXPOSE 3001 EXPOSE 3001
CMD ["node", "src/index.js"] CMD ["node", "src/index.js"]

View file

@ -1,30 +0,0 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
echo "=== Checking prerequisites ==="
if [ ! -f sdk/DeckLinkAPI.h ]; then
echo "ERROR: sdk/DeckLinkAPI.h not found."
echo ""
echo "Please download the Blackmagic DeckLink SDK 16.x from:"
echo " https://www.blackmagicdesign.com/developer/product/capture"
echo ""
echo "Then extract the Linux/include/ folder contents into:"
echo " $(pwd)/sdk/"
echo ""
echo "Required files: DeckLinkAPI.h DeckLinkAPIVersion.h DeckLinkAPIDispatch.cpp"
echo " LinuxCOM.h DeckLinkAPIModes.h DeckLinkAPITypes.h"
exit 1
fi
echo "SDK headers found:"
ls sdk/*.h sdk/*.cpp 2>/dev/null
echo ""
echo "=== Building capture container with DeckLink FFmpeg ==="
docker compose -f ../../docker-compose.yml build capture
echo ""
echo "=== Verifying DeckLink support in built image ==="
docker run --rm wild-dragon-capture ffmpeg -f decklink -list_devices true -i dummy 2>&1 | head -20

View file

@ -1,346 +0,0 @@
diff --git a/libavdevice/decklink_common.cpp b/libavdevice/decklink_common.cpp
index fe187cd..47de7ef 100644
--- a/libavdevice/decklink_common.cpp
+++ b/libavdevice/decklink_common.cpp
@@ -25,12 +25,7 @@ extern "C" {
#include "libavformat/internal.h"
}
-#include <DeckLinkAPIVersion.h>
#include <DeckLinkAPI.h>
-#if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0e030000
-#include <DeckLinkAPI_v14_2_1.h>
-#endif
-
#ifdef _WIN32
#include <DeckLinkAPI_i.c>
#else
@@ -517,8 +512,8 @@ int ff_decklink_list_devices(AVFormatContext *avctx,
return AVERROR(EIO);
while (ret == 0 && iter->Next(&dl) == S_OK) {
- IDeckLinkOutput_v14_2_1 *output_config;
- IDeckLinkInput_v14_2_1 *input_config;
+ IDeckLinkOutput *output_config;
+ IDeckLinkInput *input_config;
const char *display_name = NULL;
const char *unique_name = NULL;
AVDeviceInfo *new_device = NULL;
@@ -532,14 +527,14 @@ int ff_decklink_list_devices(AVFormatContext *avctx,
goto next;
if (show_outputs) {
- if (dl->QueryInterface(IID_IDeckLinkOutput_v14_2_1, (void **)&output_config) == S_OK) {
+ if (dl->QueryInterface(IID_IDeckLinkOutput, (void **)&output_config) == S_OK) {
output_config->Release();
add = 1;
}
}
if (show_inputs) {
- if (dl->QueryInterface(IID_IDeckLinkInput_v14_2_1, (void **)&input_config) == S_OK) {
+ if (dl->QueryInterface(IID_IDeckLinkInput, (void **)&input_config) == S_OK) {
input_config->Release();
add = 1;
}
diff --git a/libavdevice/decklink_common.h b/libavdevice/decklink_common.h
index 095b438..6b32dc2 100644
--- a/libavdevice/decklink_common.h
+++ b/libavdevice/decklink_common.h
@@ -29,23 +29,6 @@
#define IDeckLinkProfileAttributes IDeckLinkAttributes
#endif
-#if BLACKMAGIC_DECKLINK_API_VERSION < 0x0e030000
-#define IDeckLinkInput_v14_2_1 IDeckLinkInput
-#define IDeckLinkInputCallback_v14_2_1 IDeckLinkInputCallback
-#define IDeckLinkMemoryAllocator_v14_2_1 IDeckLinkMemoryAllocator
-#define IDeckLinkOutput_v14_2_1 IDeckLinkOutput
-#define IDeckLinkVideoFrame_v14_2_1 IDeckLinkVideoFrame
-#define IDeckLinkVideoInputFrame_v14_2_1 IDeckLinkVideoInputFrame
-#define IDeckLinkVideoOutputCallback_v14_2_1 IDeckLinkVideoOutputCallback
-#define IID_IDeckLinkInput_v14_2_1 IID_IDeckLinkInput
-#define IID_IDeckLinkInputCallback_v14_2_1 IID_IDeckLinkInputCallback
-#define IID_IDeckLinkMemoryAllocator_v14_2_1 IID_IDeckLinkMemoryAllocator
-#define IID_IDeckLinkOutput_v14_2_1 IID_IDeckLinkOutput
-#define IID_IDeckLinkVideoFrame_v14_2_1 IID_IDeckLinkVideoFrame
-#define IID_IDeckLinkVideoInputFrame_v14_2_1 IID_IDeckLinkVideoInputFrame
-#define IID_IDeckLinkVideoOutputCallback_v14_2_1 IID_IDeckLinkVideoOutputCallback
-#endif
-
extern "C" {
#include "libavutil/mem.h"
#include "libavcodec/packet_internal.h"
@@ -93,16 +76,6 @@ static char *dup_cfstring_to_utf8(CFStringRef w)
#define DECKLINK_FREE(s) free((void *) s)
#endif
-#ifdef _WIN32
-#include <guiddef.h> // REFIID, IsEqualIID()
-#define DECKLINK_IsEqualIID IsEqualIID
-#else
-static inline bool DECKLINK_IsEqualIID(const REFIID& riid1, const REFIID& riid2)
-{
- return memcmp(&riid1, &riid2, sizeof(REFIID)) == 0;
-}
-#endif
-
class decklink_output_callback;
class decklink_input_callback;
@@ -120,8 +93,8 @@ typedef struct DecklinkPacketQueue {
struct decklink_ctx {
/* DeckLink SDK interfaces */
IDeckLink *dl;
- IDeckLinkOutput_v14_2_1 *dlo;
- IDeckLinkInput_v14_2_1 *dli;
+ IDeckLinkOutput *dlo;
+ IDeckLinkInput *dli;
IDeckLinkConfiguration *cfg;
IDeckLinkProfileAttributes *attr;
decklink_output_callback *output_callback;
diff --git a/libavdevice/decklink_dec.cpp b/libavdevice/decklink_dec.cpp
index 8830779..418701e 100644
--- a/libavdevice/decklink_dec.cpp
+++ b/libavdevice/decklink_dec.cpp
@@ -31,11 +31,7 @@ extern "C" {
#include "libavformat/internal.h"
}
-#include <DeckLinkAPIVersion.h>
#include <DeckLinkAPI.h>
-#if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0e030000
-#include <DeckLinkAPI_v14_2_1.h>
-#endif
extern "C" {
#include "config.h"
@@ -109,7 +105,7 @@ static VANCLineNumber vanc_line_numbers[] = {
{bmdModeUnknown, 0, -1, -1, -1}
};
-class decklink_allocator : public IDeckLinkMemoryAllocator_v14_2_1
+class decklink_allocator : public IDeckLinkMemoryAllocator
{
public:
decklink_allocator(): _refs(1) { }
@@ -133,21 +129,7 @@ public:
virtual HRESULT STDMETHODCALLTYPE Decommit() { return S_OK; }
// IUnknown methods
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv)
- {
- if (DECKLINK_IsEqualIID(riid, IID_IUnknown)) {
- *ppv = static_cast<IUnknown*>(this);
- } else if (DECKLINK_IsEqualIID(riid, IID_IDeckLinkMemoryAllocator_v14_2_1)) {
- *ppv = static_cast<IDeckLinkMemoryAllocator_v14_2_1*>(this);
- } else {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
-
- AddRef();
- return S_OK;
- }
-
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; }
virtual ULONG STDMETHODCALLTYPE AddRef(void) { return ++_refs; }
virtual ULONG STDMETHODCALLTYPE Release(void)
{
@@ -490,7 +472,7 @@ skip_packet:
}
-static void handle_klv(AVFormatContext *avctx, decklink_ctx *ctx, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame, int64_t pts)
+static void handle_klv(AVFormatContext *avctx, decklink_ctx *ctx, IDeckLinkVideoInputFrame *videoFrame, int64_t pts)
{
const uint8_t KLV_DID = 0x44;
const uint8_t KLV_IN_VANC_SDID = 0x04;
@@ -592,30 +574,17 @@ static void handle_klv(AVFormatContext *avctx, decklink_ctx *ctx, IDeckLinkVideo
}
}
-class decklink_input_callback : public IDeckLinkInputCallback_v14_2_1
+class decklink_input_callback : public IDeckLinkInputCallback
{
public:
explicit decklink_input_callback(AVFormatContext *_avctx);
~decklink_input_callback();
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv)
- {
- if (DECKLINK_IsEqualIID(riid, IID_IUnknown)) {
- *ppv = static_cast<IUnknown*>(this);
- } else if (DECKLINK_IsEqualIID(riid, IID_IDeckLinkInputCallback_v14_2_1)) {
- *ppv = static_cast<IDeckLinkInputCallback_v14_2_1*>(this);
- } else {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
-
- AddRef();
- return S_OK;
- }
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; }
virtual ULONG STDMETHODCALLTYPE AddRef(void);
virtual ULONG STDMETHODCALLTYPE Release(void);
virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents, IDeckLinkDisplayMode*, BMDDetectedVideoInputFormatFlags);
- virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame_v14_2_1*, IDeckLinkAudioInputPacket*);
+ virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame*, IDeckLinkAudioInputPacket*);
private:
std::atomic<int> _refs;
@@ -624,7 +593,7 @@ private:
int no_video;
int64_t initial_video_pts;
int64_t initial_audio_pts;
- IDeckLinkVideoInputFrame_v14_2_1* last_video_frame;
+ IDeckLinkVideoInputFrame* last_video_frame;
};
decklink_input_callback::decklink_input_callback(AVFormatContext *_avctx) : _refs(1)
@@ -656,7 +625,7 @@ ULONG decklink_input_callback::Release(void)
return ret;
}
-static int64_t get_pkt_pts(IDeckLinkVideoInputFrame_v14_2_1 *videoFrame,
+static int64_t get_pkt_pts(IDeckLinkVideoInputFrame *videoFrame,
IDeckLinkAudioInputPacket *audioFrame,
int64_t wallclock,
int64_t abs_wallclock,
@@ -710,7 +679,7 @@ static int64_t get_pkt_pts(IDeckLinkVideoInputFrame_v14_2_1 *videoFrame,
return pts;
}
-static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, AVRational frame_rate, BMDTimecodeFormat tc_format, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame)
+static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, AVRational frame_rate, BMDTimecodeFormat tc_format, IDeckLinkVideoInputFrame *videoFrame)
{
IDeckLinkTimecode *timecode;
int ret = AVERROR(ENOENT);
@@ -732,7 +701,7 @@ static int get_bmd_timecode(AVFormatContext *avctx, AVTimecode *tc, AVRational f
return ret;
}
-static int get_frame_timecode(AVFormatContext *avctx, decklink_ctx *ctx, AVTimecode *tc, IDeckLinkVideoInputFrame_v14_2_1 *videoFrame)
+static int get_frame_timecode(AVFormatContext *avctx, decklink_ctx *ctx, AVTimecode *tc, IDeckLinkVideoInputFrame *videoFrame)
{
AVRational frame_rate = ctx->video_st->r_frame_rate;
int ret;
@@ -757,7 +726,7 @@ static int get_frame_timecode(AVFormatContext *avctx, decklink_ctx *ctx, AVTimec
}
HRESULT decklink_input_callback::VideoInputFrameArrived(
- IDeckLinkVideoInputFrame_v14_2_1 *videoFrame, IDeckLinkAudioInputPacket *audioFrame)
+ IDeckLinkVideoInputFrame *videoFrame, IDeckLinkAudioInputPacket *audioFrame)
{
void *frameBytes;
void *audioFrameBytes;
@@ -1172,7 +1141,7 @@ av_cold int ff_decklink_read_header(AVFormatContext *avctx)
goto error;
/* Get input device. */
- if (ctx->dl->QueryInterface(IID_IDeckLinkInput_v14_2_1, (void **) &ctx->dli) != S_OK) {
+ if (ctx->dl->QueryInterface(IID_IDeckLinkInput, (void **) &ctx->dli) != S_OK) {
av_log(avctx, AV_LOG_ERROR, "Could not open input device from '%s'\n",
avctx->url);
ret = AVERROR(EIO);
diff --git a/libavdevice/decklink_enc.cpp b/libavdevice/decklink_enc.cpp
index d2e246c..cb8f917 100644
--- a/libavdevice/decklink_enc.cpp
+++ b/libavdevice/decklink_enc.cpp
@@ -28,11 +28,7 @@ extern "C" {
#include "libavformat/internal.h"
}
-#include <DeckLinkAPIVersion.h>
#include <DeckLinkAPI.h>
-#if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0e030000
-#include <DeckLinkAPI_v14_2_1.h>
-#endif
extern "C" {
#include "libavformat/avformat.h"
@@ -52,7 +48,7 @@ extern "C" {
#endif
/* DeckLink callback class declaration */
-class decklink_frame : public IDeckLinkVideoFrame_v14_2_1
+class decklink_frame : public IDeckLinkVideoFrame
{
public:
decklink_frame(struct decklink_ctx *ctx, AVFrame *avframe, AVCodecID codec_id, int height, int width) :
@@ -115,20 +111,7 @@ public:
_ancillary->AddRef();
return S_OK;
}
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv)
- {
- if (DECKLINK_IsEqualIID(riid, IID_IUnknown)) {
- *ppv = static_cast<IUnknown*>(this);
- } else if (DECKLINK_IsEqualIID(riid, IID_IDeckLinkVideoFrame_v14_2_1)) {
- *ppv = static_cast<IDeckLinkVideoFrame_v14_2_1*>(this);
- } else {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
-
- AddRef();
- return S_OK;
- }
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; }
virtual ULONG STDMETHODCALLTYPE AddRef(void) { return ++_refs; }
virtual ULONG STDMETHODCALLTYPE Release(void)
{
@@ -155,10 +138,10 @@ private:
std::atomic<int> _refs;
};
-class decklink_output_callback : public IDeckLinkVideoOutputCallback_v14_2_1
+class decklink_output_callback : public IDeckLinkVideoOutputCallback
{
public:
- virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame_v14_2_1 *_frame, BMDOutputFrameCompletionResult result)
+ virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted(IDeckLinkVideoFrame *_frame, BMDOutputFrameCompletionResult result)
{
decklink_frame *frame = static_cast<decklink_frame *>(_frame);
struct decklink_ctx *ctx = frame->_ctx;
@@ -176,20 +159,7 @@ public:
return S_OK;
}
virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped(void) { return S_OK; }
- virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, LPVOID *ppv)
- {
- if (DECKLINK_IsEqualIID(riid, IID_IUnknown)) {
- *ppv = static_cast<IUnknown*>(this);
- } else if (DECKLINK_IsEqualIID(riid, IID_IDeckLinkVideoOutputCallback_v14_2_1)) {
- *ppv = static_cast<IDeckLinkVideoOutputCallback_v14_2_1*>(this);
- } else {
- *ppv = NULL;
- return E_NOINTERFACE;
- }
-
- AddRef();
- return S_OK;
- }
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; }
virtual ULONG STDMETHODCALLTYPE AddRef(void) { return 1; }
virtual ULONG STDMETHODCALLTYPE Release(void) { return 1; }
};
@@ -769,7 +739,7 @@ static int decklink_write_video_packet(AVFormatContext *avctx, AVPacket *pkt)
ctx->first_pts = pkt->pts;
/* Schedule frame for playback. */
- hr = ctx->dlo->ScheduleVideoFrame(frame,
+ hr = ctx->dlo->ScheduleVideoFrame((class IDeckLinkVideoFrame *) frame,
pkt->pts * ctx->bmd_tb_num,
ctx->bmd_tb_num, ctx->bmd_tb_den);
/* Pass ownership to DeckLink, or release on failure */
@@ -904,7 +874,7 @@ av_cold int ff_decklink_write_header(AVFormatContext *avctx)
return ret;
/* Get output device. */
- if (ctx->dl->QueryInterface(IID_IDeckLinkOutput_v14_2_1, (void **) &ctx->dlo) != S_OK) {
+ if (ctx->dl->QueryInterface(IID_IDeckLinkOutput, (void **) &ctx->dlo) != S_OK) {
av_log(avctx, AV_LOG_ERROR, "Could not open output device from '%s'\n",
avctx->url);
ret = AVERROR(EIO);

View file

@ -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
)

View file

@ -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;
}

View file

@ -1,21 +0,0 @@
#!/usr/bin/env python3
# Apply the upstream FFmpeg master decklink SDK-16 compatibility patch on top
# of the release/7.1 source. The patch renames every IDeckLink* interface and
# helper to its _v14_2_1 versioned form so the call sites keep working against
# SDK 16's headers (which only retain the versioned aliases). Cherry-picking
# individual replacements like the previous regex patch produced inconsistent
# code that compiled but silently dropped every video frame.
import subprocess, sys, pathlib
patch = pathlib.Path('/decklink-sdk16.patch')
if not patch.exists():
print('FATAL: /decklink-sdk16.patch not found in build context', file=sys.stderr)
sys.exit(1)
# Patch was produced as `git diff HEAD FETCH_HEAD` where HEAD=release/7.1 and
# FETCH_HEAD=master, so we apply it in REVERSE to move 7.1 → master.
result = subprocess.run(
['git', 'apply', '-R', '--verbose', str(patch)],
cwd='/ffmpeg', capture_output=True, text=True,
)
print(result.stdout)
print(result.stderr, file=sys.stderr)
sys.exit(result.returncode)

File diff suppressed because it is too large Load diff

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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
};

View file

@ -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) */

View file

@ -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) */

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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__

View file

@ -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

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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
};

View file

@ -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) */

View file

@ -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) */

View file

@ -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

View file

@ -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)

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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) */

View file

@ -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)

View file

@ -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

View file

@ -2,211 +2,25 @@ import express from 'express';
import cors from 'cors'; import cors from 'cors';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
import captureRoutes from './routes/capture.js'; import captureRoutes from './routes/capture.js';
import captureManager from './capture-manager.js';
dotenv.config(); dotenv.config();
const app = express(); const app = express();
const PORT = process.env.PORT || 3001; const PORT = process.env.PORT || 3001;
const MAM_API_URL = process.env.MAM_API_URL || 'http://mam-api:3000';
const MAM_API_TOKEN = process.env.MAM_API_TOKEN || '';
// Middleware
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
// Health check
app.get('/health', (req, res) => { app.get('/health', (req, res) => {
res.json({ status: 'ok' }); res.json({ status: 'ok' });
}); });
// Routes
app.use('/capture', captureRoutes); app.use('/capture', captureRoutes);
const server = app.listen(PORT, () => { // Start server
app.listen(PORT, () => {
console.log(`Wild Dragon Capture Service listening on port ${PORT}`); console.log(`Wild Dragon Capture Service listening on port ${PORT}`);
bootstrapAutoStart();
}); });
// Mapped from the env vars routes/recorders.js writes into the container.
// Empty strings collapse to undefined so capture-manager's defaults win.
function envOpt(name) {
const v = process.env[name];
return v === undefined || v === '' ? undefined : v;
}
function envInt(name) {
const v = envOpt(name);
if (v === undefined) return undefined;
const n = parseInt(v, 10);
return Number.isFinite(n) ? n : undefined;
}
function envBool(name) {
const v = envOpt(name);
if (v === undefined) return undefined;
return v === 'true' || v === '1' || v === 'yes';
}
async function bootstrapAutoStart() {
const recorderId = process.env.RECORDER_ID;
const sourceType = process.env.SOURCE_TYPE;
if (!recorderId || !sourceType) {
console.log('[bootstrap] no RECORDER_ID/SOURCE_TYPE - on-demand sidecar');
return;
}
const projectId = process.env.PROJECT_ID;
const clipName = process.env.CLIP_NAME;
if (!projectId || !clipName) {
console.error('[bootstrap] missing PROJECT_ID or CLIP_NAME - cannot start');
return;
}
const listen = process.env.LISTEN === '1' || process.env.LISTEN === 'true';
const listenPort = envInt('LISTEN_PORT');
const streamKey = envOpt('STREAM_KEY');
const sourceUrl = envOpt('SOURCE_URL');
const device = envInt('DEVICE_INDEX');
// SOURCE_CONFIG is the recorder's source_config JSON (set by recorders.js).
// For deltacast it carries the capture channel (`port`) and optional `board`.
let sourceConfig = {};
try { sourceConfig = JSON.parse(process.env.SOURCE_CONFIG || '{}') || {}; }
catch (e) { console.warn('[bootstrap] bad SOURCE_CONFIG JSON:', e.message); }
const port = Number.isInteger(sourceConfig.port) ? sourceConfig.port : undefined;
const board = Number.isInteger(sourceConfig.board) ? sourceConfig.board : undefined;
console.log(`[bootstrap] starting ${sourceType} ingest (listen=${listen} port=${listenPort || 'n/a'})...`);
try {
const session = await captureManager.start({
assetId: envOpt('ASSET_ID') || null,
projectId,
binId: envOpt('BIN_ID') || null,
clipName,
device,
port,
board,
sourceType,
sourceUrl,
listen,
listenPort,
streamKey,
// Recording codec — recorders.js passes these straight through
videoCodec: envOpt('RECORDING_CODEC') || 'prores_hq',
videoBitrate: envOpt('RECORDING_VIDEO_BITRATE'),
framerate: envOpt('RECORDING_FRAMERATE'),
audioCodec: envOpt('RECORDING_AUDIO_CODEC') || 'pcm_s24le',
audioBitrate: envOpt('RECORDING_AUDIO_BITRATE'),
audioChannels: envInt('RECORDING_AUDIO_CHANNELS') ?? 2,
container: envOpt('RECORDING_CONTAINER') || 'mov',
proxyEnabled: envBool('PROXY_ENABLED') ?? true,
proxyVideoCodec: envOpt('PROXY_CODEC') || 'h264',
proxyVideoBitrate: envOpt('PROXY_VIDEO_BITRATE') || '8M',
proxyFramerate: envOpt('PROXY_FRAMERATE'),
proxyAudioCodec: envOpt('PROXY_AUDIO_CODEC') || 'aac',
proxyAudioBitrate: envOpt('PROXY_AUDIO_BITRATE') || '192k',
proxyAudioChannels: envInt('PROXY_AUDIO_CHANNELS') ?? 2,
proxyContainer: envOpt('PROXY_CONTAINER') || 'mp4',
});
console.log(`[bootstrap] session ${session.sessionId} started for clip ${clipName}`);
} catch (err) {
console.error('[bootstrap] failed to start capture:', err);
}
}
let shuttingDown = false;
async function gracefulShutdown(signal) {
if (shuttingDown) return;
shuttingDown = true;
console.log(`[shutdown] ${signal} received`);
const status = captureManager.getStatus();
if (status.recording) {
console.log(`[shutdown] stopping active session ${status.sessionId}...`);
try {
const completed = await captureManager.stop(status.sessionId);
console.log(`[shutdown] session ${completed.sessionId} finalised; duration=${completed.duration}s frames=${completed.framesReceived}`);
const liveAssetId = process.env.ASSET_ID || null;
// No frames received → the source never connected (bad SRT URL, dead
// SDI signal, RTMP stream key mismatch, etc.). The S3 upload at this
// point is 0 bytes and would just clog the proxy queue with "moov
// atom not found" failures. Mark the pre-created live asset as
// 'error' and skip the POST /assets registration entirely.
if (completed.empty) {
console.warn('[shutdown] no frames received — marking asset as error and skipping registration');
if (liveAssetId) {
try {
await fetch(`${MAM_API_URL}/api/v1/assets/${liveAssetId}/mark-empty`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(MAM_API_TOKEN ? { 'Authorization': `Bearer ${MAM_API_TOKEN}` } : {}) },
});
} catch (e) {
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) {
// Finalise the pre-created live asset by id (avoids POST / 409 collision).
try {
const res = await fetch(`${MAM_API_URL}/api/v1/assets/${liveAssetId}/finalize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(MAM_API_TOKEN ? { 'Authorization': `Bearer ${MAM_API_TOKEN}` } : {}) },
body: JSON.stringify({ hiresKey: completed.hiresKey, proxyKey: completed.proxyKey, duration: completed.duration }),
});
if (!res.ok) {
console.warn(`[shutdown] mam-api finalize returned ${res.status}: ${await res.text()}`);
} else {
console.log('[shutdown] live asset finalised with mam-api');
}
} catch (mamErr) {
console.error('[shutdown] failed to finalise asset:', mamErr.message);
}
} else {
try {
const res = await fetch(`${MAM_API_URL}/api/v1/assets`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(MAM_API_TOKEN ? { 'Authorization': `Bearer ${MAM_API_TOKEN}` } : {}) },
body: JSON.stringify({
projectId: completed.projectId,
binId: completed.binId,
clipName: completed.clipName,
sourceType: completed.sourceType,
hiresKey: completed.hiresKey,
proxyKey: completed.proxyKey,
needsProxy: completed.proxyKey === null,
duration: completed.duration,
capturedAt: completed.startedAt,
}),
});
if (!res.ok) {
console.warn(`[shutdown] mam-api /assets returned ${res.status}: ${await res.text()}`);
} else {
console.log('[shutdown] asset registered with mam-api');
}
} catch (mamErr) {
console.error('[shutdown] failed to register asset:', mamErr.message);
}
}
} catch (err) {
console.error('[shutdown] error during stop:', err);
}
}
server.close(() => {
console.log('[shutdown] http server closed - exiting');
process.exit(0);
});
setTimeout(() => process.exit(0), 5000).unref();
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));

View file

@ -1,79 +1,7 @@
import express from 'express'; import express from 'express';
import { execSync, spawn } from 'child_process'; import { execSync } from 'child_process';
import { existsSync, readdirSync } from 'node:fs';
import captureManager from '../capture-manager.js'; import captureManager from '../capture-manager.js';
import dgram from 'dgram';
import net from 'net';
function parseUrl(u) {
try {
const m = String(u).match(/^[a-z]+:\/\/([^:\/?#]+)(?::(\d+))?/i);
if (!m) return null;
return { host: m[1], port: parseInt(m[2] || '0', 10) };
} catch (_) { return null; }
}
async function checkReachable(host, port, sourceType) {
if (!port) return { ok: true };
if (sourceType === 'srt') return await udpSendProbe(host, port);
if (sourceType === 'rtmp') return await tcpConnectProbe(host, port);
return { ok: true };
}
function udpSendProbe(host, port) {
return new Promise((resolve) => {
const sock = dgram.createSocket('udp4');
let done = false;
const finish = (result) => { if (done) return; done = true; try { sock.close(); } catch (_) {} resolve(result); };
sock.on('error', (err) => {
const msg = String(err && err.message || err);
if (/EHOSTUNREACH|ENETUNREACH/i.test(msg)) {
finish({ ok: false, error: 'Host ' + host + ' is unreachable from the capture container (no route). Confirm the IP is correct and the machine is online.', diagnostic: msg });
} else if (/ECONNREFUSED|EPORTUNREACH/i.test(msg)) {
finish({ ok: false, error: 'Nothing is listening on UDP ' + host + ':' + port + '. In vMix, confirm the SRT output is Started and the port matches.', diagnostic: msg });
} else {
finish({ ok: false, error: 'UDP probe failed: ' + msg, diagnostic: msg });
}
});
sock.send(Buffer.from('Z-AMPP-PROBE'), port, host, () => {});
setTimeout(() => finish({ ok: true }), 1500);
});
}
function tcpConnectProbe(host, port) {
return new Promise((resolve) => {
const sock = new net.Socket();
let done = false;
const finish = (r) => { if (done) return; done = true; try { sock.destroy(); } catch (_) {} resolve(r); };
sock.setTimeout(2500);
sock.once('connect', () => finish({ ok: true }));
sock.once('timeout', () => finish({ ok: false, error: 'TCP connect to ' + host + ':' + port + ' timed out. Confirm the host is reachable and a TCP listener is running.' }));
sock.once('error', (err) => {
const msg = String(err && err.message || err);
if (/EHOSTUNREACH|ENETUNREACH/i.test(msg)) finish({ ok: false, error: 'Host ' + host + ' unreachable (no route).', diagnostic: msg });
else if (/ECONNREFUSED/i.test(msg)) finish({ ok: false, error: 'Nothing is listening on TCP ' + host + ':' + port + '.', diagnostic: msg });
else finish({ ok: false, error: 'TCP probe failed: ' + msg, diagnostic: msg });
});
sock.connect(port, host);
});
}
function classifyProbeError(raw, sourceType) {
const r = (raw || '').toLowerCase();
if (sourceType === 'srt') {
if (/connection .* failed: (input\/output|timer expired|connection setup failure)/i.test(raw)) {
return 'SRT handshake failed. In vMix: confirm the External Output is Started, Type=SRT, Mode=Listener, port matches, and any passphrase / stream ID is empty (or copied exactly).';
}
}
if (sourceType === 'rtmp') {
if (/connection refused/i.test(r)) return 'Nothing is listening on RTMP at this address. Start your RTMP source.';
if (/end-of-file|invalid data found/i.test(r)) return 'Got a TCP connection but no RTMP stream. Confirm the source is publishing and the path / stream-key match.';
}
return raw;
}
const router = express.Router(); const router = express.Router();
const MAM_API_URL = process.env.MAM_API_URL || 'http://mam-api:3000'; const MAM_API_URL = process.env.MAM_API_URL || 'http://mam-api:3000';
@ -88,7 +16,7 @@ router.get('/devices', (req, res) => {
let output = ''; let output = '';
try { try {
output = execSync('ffmpeg -sources decklink 2>&1', { output = execSync('ffmpeg -f decklink -list_devices 1 -i dummy 2>&1', {
encoding: 'utf-8', encoding: 'utf-8',
}); });
} catch (error) { } catch (error) {
@ -96,13 +24,13 @@ router.get('/devices', (req, res) => {
output = error.stderr ? error.stderr.toString() : error.toString(); output = error.stderr ? error.stderr.toString() : error.toString();
} }
// Parse ffmpeg output for DeckLink device names. // Parse ffmpeg output for DeckLink device names
// DeckLink source lines: " 81:76669a80:00000000 [DeckLink Duo (1)] (none)" // Format: [decklink @ ...] "DeckLink Quad 2" (input #0)
const lines = output.split('\n'); const lines = output.split('\n');
let deviceIndex = 0; let deviceIndex = 0;
for (const line of lines) { for (const line of lines) {
const match = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/); const match = line.match(/^\s*\[decklink[^\]]*\]\s+"([^"]+)"/);
if (match) { if (match) {
devices.push({ devices.push({
index: deviceIndex, index: deviceIndex,
@ -119,57 +47,6 @@ router.get('/devices', (req, res) => {
} }
}); });
/**
* GET /devices/deltacast
* List available Deltacast ports.
* Reads /dev/deltacast<N> nodes; falls back to env DELTACAST_PORT_COUNT
* so nodes without hardware still report their configured port count
* (test-card mode).
*/
router.get('/devices/deltacast', (req, res) => {
try {
const devices = [];
// First: enumerate actual /dev/deltacast* device nodes.
try {
const devEntries = readdirSync('/dev').filter(n => /^deltacast\d+$/.test(n));
devEntries.sort();
for (const entry of devEntries) {
const m = entry.match(/^deltacast(\d+)$/);
if (m) {
devices.push({
index: parseInt(m[1], 10),
name: `Deltacast Port ${m[1]}`,
device: `/dev/${entry}`,
present: true,
});
}
}
} catch (_) { /* /dev always exists; ignore */ }
// Second: if DELTACAST_PORT_COUNT env is set and larger than what we found,
// fill in the remaining slots as test-card entries (no physical device).
const envCount = parseInt(process.env.DELTACAST_PORT_COUNT || '0', 10);
const found = new Set(devices.map(d => d.index));
for (let i = 0; i < envCount; i++) {
if (!found.has(i)) {
devices.push({
index: i,
name: `Deltacast Port ${i} (test card)`,
device: `/dev/deltacast${i}`,
present: false,
});
}
}
devices.sort((a, b) => a.index - b.index);
res.json({ devices });
} catch (error) {
console.error('Error listing Deltacast devices:', error);
res.status(500).json({ error: 'Failed to list Deltacast devices' });
}
});
/** /**
* GET /status * GET /status
* Get current capture status * Get current capture status
@ -183,103 +60,6 @@ router.get('/status', (req, res) => {
res.status(500).json({ error: 'Failed to get status' }); res.status(500).json({ error: 'Failed to get status' });
} }
}); });
router.post('/probe', async (req, res) => {
try {
const { source_type = 'sdi', source_url, listen = false } = req.body || {};
if (source_type === 'sdi') {
try {
const raw = execSync('ffmpeg -hide_banner -sources decklink 2>&1', { encoding: 'utf-8', timeout: 5000 });
const devices = [];
for (const line of raw.split('\n')) {
const m = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/);
if (m) devices.push(m[1]);
}
return res.json({ ok: true, source_type, devices });
} catch (err) {
const out = (err.stderr || err.stdout || err.toString()).toString();
return res.json({ ok: false, source_type, error: out.slice(0, 800) });
}
}
if (source_type === 'deltacast') {
// Enumerate /dev/deltacast* nodes; report present/absent per index.
try {
const envCount = parseInt(process.env.DELTACAST_PORT_COUNT || '0', 10);
const devEntries = readdirSync('/dev').filter(n => /^deltacast\d+$/.test(n)).sort();
const found = devEntries.map(n => {
const m = n.match(/^deltacast(\d+)$/);
return { index: parseInt(m[1], 10), device: `/dev/${n}`, present: true };
});
const foundIdx = new Set(found.map(d => d.index));
for (let i = 0; i < envCount; i++) {
if (!foundIdx.has(i)) {
found.push({ index: i, device: `/dev/deltacast${i}`, present: false });
}
}
found.sort((a, b) => a.index - b.index);
return res.json({ ok: true, source_type, devices: found });
} catch (err) {
return res.json({ ok: false, source_type, error: err.message });
}
}
if (listen) {
return res.json({ ok: false, source_type, error: 'Listener-mode sources cannot be probed standalone. Start the recorder and watch the signal indicator.' });
}
if (!source_url) return res.status(400).json({ error: 'source_url is required' });
// Pre-flight: parse host:port and check L3/L4 reachability so we can give
// an actionable error instead of the opaque libsrt "Input/output error".
const parsed = parseUrl(source_url);
if (!parsed) {
return res.json({ ok: false, source_type, source_url, error: 'Could not parse host:port from URL.' });
}
const reach = await checkReachable(parsed.host, parsed.port, source_type);
if (!reach.ok) {
return res.json({ ok: false, source_type, source_url, error: reach.error, diagnostic: reach.diagnostic });
}
let url = source_url;
if (source_type === 'srt' && !/mode=/.test(url)) {
url += (url.includes('?') ? '&' : '?') + 'mode=caller';
}
const args = ['-hide_banner','-v','error','-probesize','32M','-analyzeduration','8M','-rw_timeout','7000000','-i', url, '-show_streams','-show_format','-of','json'];
const ff = spawn('ffprobe', args);
let stdout = '', stderr = '';
ff.stdout.on('data', (c) => { stdout += c; });
ff.stderr.on('data', (c) => { stderr += c; });
const killer = setTimeout(() => { try { ff.kill('SIGKILL'); } catch (_) {} }, 10000);
ff.on('close', (code) => {
clearTimeout(killer);
if (code !== 0) {
const rawErr = (stderr || 'ffprobe failed').slice(0, 800);
const friendly = classifyProbeError(rawErr, source_type);
return res.json({ ok: false, source_type, source_url, error: friendly, diagnostic: rawErr });
}
try {
const parsed = JSON.parse(stdout);
const streams = (parsed.streams || []).map(s => ({
index: s.index, codec_type: s.codec_type, codec_name: s.codec_name,
width: s.width, height: s.height, pix_fmt: s.pix_fmt,
r_frame_rate: s.r_frame_rate, avg_frame_rate: s.avg_frame_rate,
sample_rate: s.sample_rate, channels: s.channels,
channel_layout: s.channel_layout, bit_rate: s.bit_rate,
}));
return res.json({ ok: true, source_type, source_url,
format: { format_name: parsed.format && parsed.format.format_name, duration: parsed.format && parsed.format.duration, bit_rate: parsed.format && parsed.format.bit_rate },
streams });
} catch (err) {
return res.json({ ok: false, source_type, source_url, error: 'Could not parse ffprobe output: ' + err.message });
}
});
} catch (error) {
console.error('Probe error:', error);
res.status(500).json({ error: error.message });
}
});
/** /**
* POST /start * POST /start
@ -325,13 +105,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`,
}); });
} }

View file

@ -1,330 +0,0 @@
import express from 'express';
import { execSync, spawn } from 'child_process';
import captureManager from '../capture-manager.js';
import dgram from 'dgram';
import net from 'net';
function parseUrl(u) {
try {
const m = String(u).match(/^[a-z]+:\/\/([^:\/?#]+)(?::(\d+))?/i);
if (!m) return null;
return { host: m[1], port: parseInt(m[2] || '0', 10) };
} catch (_) { return null; }
}
async function checkReachable(host, port, sourceType) {
if (!port) return { ok: true };
if (sourceType === 'srt') return await udpSendProbe(host, port);
if (sourceType === 'rtmp') return await tcpConnectProbe(host, port);
return { ok: true };
}
function udpSendProbe(host, port) {
return new Promise((resolve) => {
const sock = dgram.createSocket('udp4');
let done = false;
const finish = (result) => { if (done) return; done = true; try { sock.close(); } catch (_) {} resolve(result); };
sock.on('error', (err) => {
const msg = String(err && err.message || err);
if (/EHOSTUNREACH|ENETUNREACH/i.test(msg)) {
finish({ ok: false, error: 'Host ' + host + ' is unreachable from the capture container (no route). Confirm the IP is correct and the machine is online.', diagnostic: msg });
} else if (/ECONNREFUSED|EPORTUNREACH/i.test(msg)) {
finish({ ok: false, error: 'Nothing is listening on UDP ' + host + ':' + port + '. In vMix, confirm the SRT output is Started and the port matches.', diagnostic: msg });
} else {
finish({ ok: false, error: 'UDP probe failed: ' + msg, diagnostic: msg });
}
});
sock.send(Buffer.from('Z-AMPP-PROBE'), port, host, () => {});
setTimeout(() => finish({ ok: true }), 1500);
});
}
function tcpConnectProbe(host, port) {
return new Promise((resolve) => {
const sock = new net.Socket();
let done = false;
const finish = (r) => { if (done) return; done = true; try { sock.destroy(); } catch (_) {} resolve(r); };
sock.setTimeout(2500);
sock.once('connect', () => finish({ ok: true }));
sock.once('timeout', () => finish({ ok: false, error: 'TCP connect to ' + host + ':' + port + ' timed out. Confirm the host is reachable and a TCP listener is running.' }));
sock.once('error', (err) => {
const msg = String(err && err.message || err);
if (/EHOSTUNREACH|ENETUNREACH/i.test(msg)) finish({ ok: false, error: 'Host ' + host + ' unreachable (no route).', diagnostic: msg });
else if (/ECONNREFUSED/i.test(msg)) finish({ ok: false, error: 'Nothing is listening on TCP ' + host + ':' + port + '.', diagnostic: msg });
else finish({ ok: false, error: 'TCP probe failed: ' + msg, diagnostic: msg });
});
sock.connect(port, host);
});
}
function classifyProbeError(raw, sourceType) {
const r = (raw || '').toLowerCase();
if (sourceType === 'srt') {
if (/connection .* failed: (input\/output|timer expired|connection setup failure)/i.test(raw)) {
return 'SRT handshake failed. In vMix: confirm the External Output is Started, Type=SRT, Mode=Listener, port matches, and any passphrase / stream ID is empty (or copied exactly).';
}
}
if (sourceType === 'rtmp') {
if (/connection refused/i.test(r)) return 'Nothing is listening on RTMP at this address. Start your RTMP source.';
if (/end-of-file|invalid data found/i.test(r)) return 'Got a TCP connection but no RTMP stream. Confirm the source is publishing and the path / stream-key match.';
}
return raw;
}
const router = express.Router();
const MAM_API_URL = process.env.MAM_API_URL || 'http://mam-api:3000';
/**
* GET /devices
* List available DeckLink devices
*/
router.get('/devices', (req, res) => {
try {
const devices = [];
let output = '';
try {
output = execSync('ffmpeg -f decklink -list_devices 1 -i dummy 2>&1', {
encoding: 'utf-8',
});
} catch (error) {
// ffmpeg returns non-zero, but stderr is still captured
output = error.stderr ? error.stderr.toString() : error.toString();
}
// Parse ffmpeg output for DeckLink device names
// Format: [decklink @ ...] "DeckLink Quad 2" (input #0)
const lines = output.split('\n');
let deviceIndex = 0;
for (const line of lines) {
const match = line.match(/^\s*\[decklink[^\]]*\]\s+"([^"]+)"/);
if (match) {
devices.push({
index: deviceIndex,
name: match[1],
});
deviceIndex++;
}
}
res.json({ devices });
} catch (error) {
console.error('Error listing devices:', error);
res.status(500).json({ error: 'Failed to list devices' });
}
});
/**
* GET /status
* Get current capture status
*/
router.get('/status', (req, res) => {
try {
const status = captureManager.getStatus();
res.json(status);
} catch (error) {
console.error('Error getting status:', error);
res.status(500).json({ error: 'Failed to get status' });
}
});
router.post('/probe', async (req, res) => {
try {
const { source_type = 'sdi', source_url, listen = false } = req.body || {};
if (source_type === 'sdi') {
try {
const raw = execSync('ffmpeg -hide_banner -f decklink -list_devices 1 -i dummy 2>&1', { encoding: 'utf-8', timeout: 5000 });
const devices = [];
for (const line of raw.split('\n')) {
const m = line.match(/\[decklink[^\]]*\]\s+"([^"]+)"/);
if (m) devices.push(m[1]);
}
return res.json({ ok: true, source_type, devices });
} catch (err) {
const out = (err.stderr || err.stdout || err.toString()).toString();
return res.json({ ok: false, source_type, error: out.slice(0, 800) });
}
}
if (listen) {
return res.json({ ok: false, source_type, error: 'Listener-mode sources cannot be probed standalone. Start the recorder and watch the signal indicator.' });
}
if (!source_url) return res.status(400).json({ error: 'source_url is required' });
// Pre-flight: parse host:port and check L3/L4 reachability so we can give
// an actionable error instead of the opaque libsrt "Input/output error".
const parsed = parseUrl(source_url);
if (!parsed) {
return res.json({ ok: false, source_type, source_url, error: 'Could not parse host:port from URL.' });
}
const reach = await checkReachable(parsed.host, parsed.port, source_type);
if (!reach.ok) {
return res.json({ ok: false, source_type, source_url, error: reach.error, diagnostic: reach.diagnostic });
}
let url = source_url;
if (source_type === 'srt' && !/mode=/.test(url)) {
url += (url.includes('?') ? '&' : '?') + 'mode=caller';
}
const args = ['-hide_banner','-v','error','-probesize','32M','-analyzeduration','8M','-rw_timeout','7000000','-i', url, '-show_streams','-show_format','-of','json'];
const ff = spawn('ffprobe', args);
let stdout = '', stderr = '';
ff.stdout.on('data', (c) => { stdout += c; });
ff.stderr.on('data', (c) => { stderr += c; });
const killer = setTimeout(() => { try { ff.kill('SIGKILL'); } catch (_) {} }, 10000);
ff.on('close', (code) => {
clearTimeout(killer);
if (code !== 0) {
const rawErr = (stderr || 'ffprobe failed').slice(0, 800);
const friendly = classifyProbeError(rawErr, source_type);
return res.json({ ok: false, source_type, source_url, error: friendly, diagnostic: rawErr });
}
try {
const parsed = JSON.parse(stdout);
const streams = (parsed.streams || []).map(s => ({
index: s.index, codec_type: s.codec_type, codec_name: s.codec_name,
width: s.width, height: s.height, pix_fmt: s.pix_fmt,
r_frame_rate: s.r_frame_rate, avg_frame_rate: s.avg_frame_rate,
sample_rate: s.sample_rate, channels: s.channels,
channel_layout: s.channel_layout, bit_rate: s.bit_rate,
}));
return res.json({ ok: true, source_type, source_url,
format: { format_name: parsed.format && parsed.format.format_name, duration: parsed.format && parsed.format.duration, bit_rate: parsed.format && parsed.format.bit_rate },
streams });
} catch (err) {
return res.json({ ok: false, source_type, source_url, error: 'Could not parse ffprobe output: ' + err.message });
}
});
} catch (error) {
console.error('Probe error:', error);
res.status(500).json({ error: error.message });
}
});
/**
* POST /start
* Start a new capture session
*
* Body (SDI):
* { project_id, clip_name, device, bin_id?, source_type? }
*
* Body (SRT/RTMP caller):
* { project_id, clip_name, source_type, source_url, bin_id? }
*
* Body (SRT/RTMP listener):
* { project_id, clip_name, source_type, listen: true, listen_port?, stream_key?, bin_id? }
*/
router.post('/start', async (req, res) => {
try {
const {
project_id,
bin_id,
clip_name,
device,
source_type = 'sdi',
source_url,
listen = false,
listen_port,
stream_key,
} = req.body;
if (!project_id || !clip_name) {
return res.status(400).json({
error: 'Missing required fields: project_id, clip_name',
});
}
// Source-specific validation
if (source_type === 'sdi') {
if (device === undefined || device === null) {
return res.status(400).json({ error: 'SDI source requires: device' });
}
} else if (source_type === 'srt' || source_type === 'rtmp') {
if (!listen && !source_url) {
return res.status(400).json({
error: `${source_type.toUpperCase()} caller mode requires: source_url`,
});
}
} else {
return res.status(400).json({
error: `Unknown source_type: ${source_type}. Must be sdi, srt, or rtmp`,
});
}
const session = await captureManager.start({
projectId: project_id,
binId: bin_id || null,
clipName: clip_name,
device,
sourceType: source_type,
sourceUrl: source_url,
listen,
listenPort: listen_port,
streamKey: stream_key,
});
res.json(session);
} catch (error) {
console.error('Error starting capture:', error);
res.status(500).json({ error: error.message });
}
});
/**
* POST /stop
* Stop the current capture session
* Body: { session_id }
*/
router.post('/stop', async (req, res) => {
try {
const { session_id } = req.body;
if (!session_id) {
return res.status(400).json({ error: 'Missing required field: session_id' });
}
const completedSession = await captureManager.stop(session_id);
// Register asset with mam-api.
// If proxyKey is null (SRT/RTMP source), set needsProxy=true so the
// worker generates a proxy from the hires file asynchronously.
try {
const mamResponse = await fetch(`${MAM_API_URL}/api/v1/assets`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projectId: completedSession.projectId,
binId: completedSession.binId,
clipName: completedSession.clipName,
sourceType: completedSession.sourceType,
hiresKey: completedSession.hiresKey,
proxyKey: completedSession.proxyKey,
needsProxy: completedSession.proxyKey === null,
duration: completedSession.duration,
capturedAt: completedSession.startedAt,
}),
});
if (!mamResponse.ok) {
console.warn(
`MAM API registration returned ${mamResponse.status}: ${await mamResponse.text()}`,
);
}
} catch (mamError) {
console.warn('Failed to register asset with MAM API:', mamError.message);
}
res.json(completedSession);
} catch (error) {
console.error('Error stopping capture:', error);
res.status(500).json({ error: error.message });
}
});
export default router;

Some files were not shown because too many files have changed in this diff Show more