Files
SDI-bridge/sdi-bridge.ps1

232 lines
8.8 KiB
PowerShell

# ============================================================
# SDI Bridge Orchestrator
# - Checks FFmpeg build (must be with --enable-decklink)
# - Reads Blackmagic driver version from registry
# - Detects DeckLink channels
# - Reads streams.txt
# - Stops any previously running workers
# - Launches one worker.cmd per stream in a separate window
# ============================================================
# --- Config ---
$FFmpeg = 'C:\sdi-bridge\ffmpeg.exe'
$StreamsFile = 'C:\sdi-bridge\streams.txt'
$WorkerScript = 'C:\sdi-bridge\worker.cmd'
$StopFlag = 'C:\sdi-bridge\STOP.flag'
# --- Helper: run native command and capture stdout+stderr ---
function Invoke-Native {
param([string] $Exe, [string[]] $Arguments)
$prev = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try { $out = & $Exe @Arguments 2>&1 | Out-String }
finally { $ErrorActionPreference = $prev }
return $out
}
# ============================================================
# 0. Sanity checks: ffmpeg exists, supports DeckLink
# ============================================================
if (-not (Test-Path $FFmpeg)) {
Write-Host "ERROR: ffmpeg.exe not found: $FFmpeg" -ForegroundColor Red
exit 1
}
Write-Host 'Checking FFmpeg build...' -ForegroundColor Cyan
$versionOut = Invoke-Native -Exe $FFmpeg -Arguments @('-hide_banner', '-version')
if ($versionOut -notmatch '--enable-decklink') {
Write-Host 'ERROR: this FFmpeg build was compiled WITHOUT DeckLink support.' -ForegroundColor Red
Write-Host ' Look for a build with --enable-decklink in its configuration.' -ForegroundColor Red
Write-Host ' Current build configuration:' -ForegroundColor DarkGray
Write-Host $versionOut
exit 1
}
Write-Host 'FFmpeg build OK (DeckLink enabled).' -ForegroundColor Green
# ============================================================
# 0b. Blackmagic driver version (from registry)
# ============================================================
function Get-BlackmagicDriverVersion {
$candidates = @(
@{ Path = 'HKLM:\SOFTWARE\Blackmagic Design\Desktop Video'; Name = 'Version' },
@{ Path = 'HKLM:\SOFTWARE\Blackmagic Design\Desktop Video'; Name = 'DriverVersion' },
@{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Blackmagic Design\Desktop Video'; Name = 'Version' },
@{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Blackmagic Design\Desktop Video'; Name = 'DriverVersion' }
)
foreach ($c in $candidates) {
try {
$val = Get-ItemProperty -Path $c.Path -Name $c.Name -ErrorAction Stop
if ($val.$($c.Name)) { return $val.$($c.Name) }
} catch { }
}
# Fallback: look for the "Decklink" driver under Display adapters / Sound
try {
$drv = Get-CimInstance Win32_PnPSignedDriver -ErrorAction Stop |
Where-Object { $_.DeviceName -match 'Blackmagic|DeckLink' } |
Select-Object -First 1
if ($drv -and $drv.DriverVersion) { return $drv.DriverVersion }
} catch { }
return $null
}
$bmVersion = Get-BlackmagicDriverVersion
if ($bmVersion) {
Write-Host ("Blackmagic driver: {0}" -f $bmVersion) -ForegroundColor Green
} else {
Write-Host 'Blackmagic driver: version not detected (registry key missing)' -ForegroundColor DarkYellow
}
# ============================================================
# 1. Detect DeckLink channels
# ============================================================
Write-Host 'Detecting DeckLink channels...' -ForegroundColor Cyan
$sinks = Invoke-Native -Exe $FFmpeg -Arguments @('-hide_banner', '-sinks', 'decklink')
$matches = [regex]::Matches($sinks, '\[([^\]]+?)\s*\((\d+)\)\]')
$found = @()
foreach ($m in $matches) {
$found += [PSCustomObject]@{
Number = [int]$m.Groups[2].Value
Name = $m.Groups[1].Value.Trim()
}
}
if ($found.Count -eq 0) {
Write-Host 'ERROR: No DeckLink channels detected.' -ForegroundColor Red
Write-Host '--- Raw output from ffmpeg -sinks decklink ---' -ForegroundColor DarkGray
Write-Host $sinks
exit 1
}
# Deduplicate + force sort by number, store as plain int array
$channelNumbers = @($found | Sort-Object Number -Unique | ForEach-Object { $_.Number })
$cardName = ($found | Select-Object -First 1).Name -replace '\s*\(\d+\)$',''
Write-Host ("Card: {0}" -f $cardName) -ForegroundColor Green
Write-Host ("Channels: {0}" -f ($channelNumbers -join ', ')) -ForegroundColor Green
# ============================================================
# 2. Read streams file
# ============================================================
if (-not (Test-Path $StreamsFile)) {
Write-Host "ERROR: streams file not found: $StreamsFile" -ForegroundColor Red
exit 1
}
$lines = Get-Content $StreamsFile |
ForEach-Object { $_.Trim() } |
Where-Object { $_ -and -not $_.StartsWith('#') }
if ($lines.Count -eq 0) {
Write-Host "ERROR: no streams in $StreamsFile" -ForegroundColor Red
exit 1
}
Write-Host ("Loaded {0} stream line(s), {1} channel(s) available" -f $lines.Count, $channelNumbers.Count) -ForegroundColor Green
if ($lines.Count -gt $channelNumbers.Count) {
Write-Host ("WARNING: {0} stream(s) will be skipped (not enough channels)" -f ($lines.Count - $channelNumbers.Count)) -ForegroundColor Yellow
}
# ============================================================
# 3. Build assignment: channel -> url
# ============================================================
$usedChannels = @{}
$assignments = @()
$skipped = @()
foreach ($line in $lines) {
if ($line -match '^(\d+)\s*:\s*(.+)$') {
# Explicit channel: "3:rtsp://..."
$ch = [int]$matches[1]
$url = $matches[2].Trim()
if (-not ($channelNumbers -contains $ch)) {
Write-Host " SKIP: channel $ch not present -> $url" -ForegroundColor Yellow
$skipped += $line
continue
}
if ($usedChannels.ContainsKey($ch)) {
Write-Host " SKIP: channel $ch already taken -> $url" -ForegroundColor Yellow
$skipped += $line
continue
}
} else {
# Auto-assign next free channel
$ch = $null
$url = $line
foreach ($c in $channelNumbers) {
if (-not $usedChannels.ContainsKey($c)) { $ch = $c; break }
}
if ($null -eq $ch) {
Write-Host " SKIP: no free channels left -> $url" -ForegroundColor Yellow
$skipped += $line
continue
}
}
$assignments += [PSCustomObject]@{ Channel = $ch; Url = $url }
$usedChannels[$ch] = $true
}
if ($assignments.Count -eq 0) {
Write-Host 'ERROR: no valid assignments could be made.' -ForegroundColor Red
exit 1
}
# ============================================================
# 4. Cleanup: stop any previously running workers
# ============================================================
Write-Host ''
Write-Host 'Cleaning up previous workers...' -ForegroundColor Cyan
# 4a. Signal all workers to stop
New-Item -Path $StopFlag -ItemType File -Force | Out-Null
# 4b. Kill ffmpeg so workers unblock and see the flag
Get-Process ffmpeg -ErrorAction SilentlyContinue |
Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# 4c. Kill any leftover worker cmd windows by their title
Get-Process cmd -ErrorAction SilentlyContinue |
Where-Object { $_.MainWindowTitle -like 'SDI-*' } |
Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Milliseconds 500
# 4d. Remove the flag so new workers can start normally
Remove-Item $StopFlag -ErrorAction SilentlyContinue
Write-Host 'Cleanup complete.' -ForegroundColor DarkGray
# ============================================================
# 5. Launch workers in separate cmd windows
# ============================================================
Write-Host ''
Write-Host 'Launching workers:' -ForegroundColor Cyan
$table = @()
foreach ($a in ($assignments | Sort-Object Channel)) {
$argLine = "/k `"`"$WorkerScript`" $($a.Channel) `"$($a.Url)`" `"$cardName`"`""
Start-Process -FilePath 'cmd.exe' -ArgumentList $argLine | Out-Null
$table += [PSCustomObject]@{
SDI = "SDI-$($a.Channel)"
Stream = $a.Url
Status = 'launched'
}
}
# ============================================================
# 6. Summary
# ============================================================
Write-Host ''
$table | Format-Table -AutoSize | Out-String | Write-Host
Write-Host ("Launched: {0}" -f $assignments.Count) -ForegroundColor Green
if ($skipped.Count -gt 0) {
Write-Host ("Skipped: {0}" -f $skipped.Count) -ForegroundColor Yellow
foreach ($s in $skipped) { Write-Host " $s" -ForegroundColor DarkYellow }
}
Write-Host ''
Write-Host 'To stop everything, run: powershell -ExecutionPolicy Bypass -File C:\sdi-bridge\stop-all.ps1' -ForegroundColor DarkGray