151 lines
4.7 KiB
PowerShell
151 lines
4.7 KiB
PowerShell
param(
|
|
[string]$Config = ".\config\localcast.json",
|
|
[string]$FfmpegPath = "ffmpeg",
|
|
[switch]$DryRun
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$Root = Split-Path -Parent (Split-Path -Parent $PSCommandPath)
|
|
$LogRoot = Join-Path $Root "logs"
|
|
|
|
function Resolve-ConfigPath {
|
|
param([string]$Path)
|
|
$resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop
|
|
return $resolved.Path
|
|
}
|
|
|
|
function Assert-Value {
|
|
param(
|
|
[object]$Value,
|
|
[string]$Message
|
|
)
|
|
if ($null -eq $Value -or "$Value".Trim().Length -eq 0) {
|
|
throw $Message
|
|
}
|
|
}
|
|
|
|
function Get-SrtUrl {
|
|
param(
|
|
[object]$Receiver,
|
|
[int]$PortOffset
|
|
)
|
|
|
|
$port = [int]$Receiver.basePort + $PortOffset
|
|
$query = "mode=caller&latency=$($Receiver.srtLatencyMicros)"
|
|
if ($Receiver.passphrase -and "$($Receiver.passphrase)".Length -gt 0) {
|
|
$escaped = [uri]::EscapeDataString("$($Receiver.passphrase)")
|
|
$query = "$query&passphrase=$escaped&pbkeylen=16"
|
|
}
|
|
return "srt://$($Receiver.host):$($port)?$query"
|
|
}
|
|
|
|
function Format-CommandLine {
|
|
param(
|
|
[string]$Executable,
|
|
[string[]]$Arguments
|
|
)
|
|
$parts = @($Executable)
|
|
foreach ($arg in $Arguments) {
|
|
if ($arg -match '[\s"&?=]') {
|
|
$escaped = $arg.Replace('"', '\"')
|
|
$parts += '"' + $escaped + '"'
|
|
} else {
|
|
$parts += $arg
|
|
}
|
|
}
|
|
return ($parts -join " ")
|
|
}
|
|
|
|
function Format-ArgumentList {
|
|
param([string[]]$Arguments)
|
|
$parts = @()
|
|
foreach ($arg in $Arguments) {
|
|
if ($arg -match '[\s"&?=]') {
|
|
$escaped = $arg.Replace('"', '\"')
|
|
$parts += '"' + $escaped + '"'
|
|
} else {
|
|
$parts += $arg
|
|
}
|
|
}
|
|
return ($parts -join " ")
|
|
}
|
|
|
|
function Start-SourceProcess {
|
|
param(
|
|
[string]$Name,
|
|
[string[]]$Arguments
|
|
)
|
|
|
|
$line = Format-CommandLine -Executable $FfmpegPath -Arguments $Arguments
|
|
if ($DryRun) {
|
|
Write-Host ""
|
|
Write-Host "[$Name]"
|
|
Write-Host $line
|
|
return
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force $LogRoot | Out-Null
|
|
$stdout = Join-Path $LogRoot "$Name.out.log"
|
|
$stderr = Join-Path $LogRoot "$Name.err.log"
|
|
$argumentLine = Format-ArgumentList -Arguments $Arguments
|
|
$process = Start-Process -FilePath $FfmpegPath -ArgumentList $argumentLine -RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru -WindowStyle Hidden
|
|
Write-Host "$Name started. PID=$($process.Id) stdout=$stdout stderr=$stderr"
|
|
}
|
|
|
|
$configPath = Resolve-ConfigPath -Path $Config
|
|
$settings = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
|
|
|
|
Assert-Value -Value $settings.receiver.host -Message "receiver.host is required"
|
|
Assert-Value -Value $settings.receiver.basePort -Message "receiver.basePort is required"
|
|
if (-not $settings.receiver.srtLatencyMicros) {
|
|
$settings.receiver | Add-Member -NotePropertyName srtLatencyMicros -NotePropertyValue 120000
|
|
}
|
|
|
|
if ($settings.video -and $settings.video.enabled) {
|
|
$videoUrl = Get-SrtUrl -Receiver $settings.receiver -PortOffset ([int]$settings.video.portOffset)
|
|
$videoArgs = @(
|
|
"-hide_banner",
|
|
"-nostdin",
|
|
"-loglevel", "info",
|
|
"-f", "gdigrab",
|
|
"-framerate", "$($settings.video.framerate)",
|
|
"-video_size", "$($settings.video.width)x$($settings.video.height)",
|
|
"-i", "$($settings.video.source)",
|
|
"-an",
|
|
"-c:v", "h264_nvenc",
|
|
"-preset", "$($settings.video.preset)",
|
|
"-tune", "$($settings.video.tune)",
|
|
"-b:v", "$($settings.video.bitrate)",
|
|
"-maxrate", "$($settings.video.maxrate)",
|
|
"-bufsize", "$($settings.video.bufsize)",
|
|
"-pix_fmt", "yuv420p",
|
|
"-g", "$([int]$settings.video.framerate * 2)",
|
|
"-f", "mpegts",
|
|
$videoUrl
|
|
)
|
|
Start-SourceProcess -Name "video" -Arguments $videoArgs
|
|
}
|
|
|
|
foreach ($audio in @($settings.audioSources)) {
|
|
Assert-Value -Value $audio.name -Message "audioSources[].name is required"
|
|
Assert-Value -Value $audio.device -Message "audioSources[].device is required"
|
|
$audioUrl = Get-SrtUrl -Receiver $settings.receiver -PortOffset ([int]$audio.portOffset)
|
|
$audioArgs = @(
|
|
"-hide_banner",
|
|
"-nostdin",
|
|
"-loglevel", "info",
|
|
"-f", "dshow",
|
|
"-i", "audio=$($audio.device)",
|
|
"-vn",
|
|
"-c:a", "$(if ($audio.codec) { $audio.codec } else { "aac" })",
|
|
"-b:a", "$($audio.bitrate)",
|
|
"-f", "mpegts",
|
|
$audioUrl
|
|
)
|
|
Start-SourceProcess -Name "audio-$($audio.name)" -Arguments $audioArgs
|
|
}
|
|
|
|
if ($DryRun) {
|
|
Write-Host ""
|
|
Write-Host "Dry run complete. Start OBS Media Sources in listener mode before running without -DryRun."
|
|
}
|