232 lines
8.8 KiB
PowerShell
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 |