Add parallel companion processing dashboard
This commit is contained in:
+230
-65
@@ -2,7 +2,8 @@ param(
|
||||
[int]$Port = 17873,
|
||||
[string]$OutputRoot = "\\VaultOfData\AKAMedia\음악\처리됨",
|
||||
[string]$ArtistName = "SUNO",
|
||||
[string]$FfmpegPath = ""
|
||||
[string]$FfmpegPath = "",
|
||||
[int]$MaxWorkers = 3
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -14,9 +15,17 @@ $logs = Join-Path $scriptRoot "companion-logs"
|
||||
New-Item -ItemType Directory -Force -Path $inbox, $logs | Out-Null
|
||||
$queue = [System.Collections.Queue]::new()
|
||||
$queueIds = [System.Collections.Generic.HashSet[string]]::new()
|
||||
$activeJob = $null
|
||||
$activeJobs = [System.Collections.ArrayList]::new()
|
||||
$activeIds = [System.Collections.Generic.HashSet[string]]::new()
|
||||
$stats = [ordered]@{
|
||||
total = 0
|
||||
completed = 0
|
||||
failed = 0
|
||||
skipped = 0
|
||||
}
|
||||
$lastProgressAt = [DateTime]::MinValue
|
||||
$lastProgressText = ""
|
||||
$lastDashboardAt = [DateTime]::MinValue
|
||||
|
||||
function Resolve-LibraryRoot {
|
||||
param(
|
||||
@@ -118,9 +127,13 @@ function Read-HttpRequest {
|
||||
}
|
||||
|
||||
function Start-SunoProcess {
|
||||
param([string]$ManifestPath)
|
||||
param(
|
||||
[string]$ManifestPath,
|
||||
[string]$TrackId
|
||||
)
|
||||
|
||||
$jobId = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$shortId = if ($TrackId) { "$TrackId".Substring(0, [Math]::Min(8, "$TrackId".Length)) } else { "track" }
|
||||
$jobId = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmss-fff"), $shortId
|
||||
$stdout = Join-Path $logs "$jobId.out.log"
|
||||
$stderr = Join-Path $logs "$jobId.err.log"
|
||||
$args = @(
|
||||
@@ -158,41 +171,6 @@ function Save-Manifest {
|
||||
return $manifestPath
|
||||
}
|
||||
|
||||
function Update-Queue {
|
||||
if ($script:activeJob -and -not $script:activeJob.Process.HasExited) {
|
||||
Publish-ActiveJobProgress
|
||||
return
|
||||
}
|
||||
|
||||
if ($script:activeJob -and $script:activeJob.Process.HasExited) {
|
||||
Publish-ActiveJobProgress -Force
|
||||
Write-Host "Finished job $($script:activeJob.JobId) exit=$($script:activeJob.Process.ExitCode)"
|
||||
$script:activeJob = $null
|
||||
}
|
||||
|
||||
while (-not $script:activeJob -and $script:queue.Count -gt 0) {
|
||||
$item = $script:queue.Dequeue()
|
||||
[void]$script:queueIds.Remove($item.Id)
|
||||
$manifest = [pscustomobject]@{
|
||||
pageUrl = $item.PageUrl
|
||||
capturedAt = (Get-Date).ToString("o")
|
||||
count = 1
|
||||
tracks = @($item.Track)
|
||||
}
|
||||
$manifestPath = Save-Manifest $manifest
|
||||
$job = Start-SunoProcess $manifestPath
|
||||
$script:activeJob = [pscustomobject]@{
|
||||
JobId = $job.jobId
|
||||
Process = $job.process
|
||||
TrackId = $item.Id
|
||||
Title = $item.Title
|
||||
Log = $job.log
|
||||
ErrorLog = $job.errorLog
|
||||
}
|
||||
Write-Host "Started queued job $($job.jobId): $($item.Title) [$($item.Id)]"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LastLogLine {
|
||||
param([string[]]$Paths)
|
||||
|
||||
@@ -206,20 +184,158 @@ function Get-LastLogLine {
|
||||
return ""
|
||||
}
|
||||
|
||||
function Publish-ActiveJobProgress {
|
||||
param([switch]$Force)
|
||||
function Get-JobLine {
|
||||
param($Job)
|
||||
|
||||
if (-not $script:activeJob) { return }
|
||||
if (-not $Job) { return "" }
|
||||
return Get-LastLogLine @($Job.ErrorLog, $Job.Log)
|
||||
}
|
||||
|
||||
function Publish-JobProgress {
|
||||
param($Job, [switch]$Force)
|
||||
|
||||
if (-not $Job) { return }
|
||||
$now = Get-Date
|
||||
if (-not $Force -and ($now - $script:lastProgressAt).TotalSeconds -lt 10) { return }
|
||||
|
||||
$line = Get-LastLogLine @($script:activeJob.ErrorLog, $script:activeJob.Log)
|
||||
$line = Get-JobLine $Job
|
||||
if (-not $line) { return }
|
||||
if (-not $Force -and $line -eq $script:lastProgressText) { return }
|
||||
$fingerprint = "$($Job.JobId):$line"
|
||||
if (-not $Force -and $fingerprint -eq $script:lastProgressText) { return }
|
||||
|
||||
$script:lastProgressAt = $now
|
||||
$script:lastProgressText = $line
|
||||
Write-Host "[$($script:activeJob.JobId)] $($script:activeJob.Title): $line"
|
||||
$script:lastProgressText = $fingerprint
|
||||
Write-Host "[$($Job.JobId)] $($Job.Title): $line"
|
||||
}
|
||||
|
||||
function Get-JobSnapshot {
|
||||
param($Job)
|
||||
|
||||
$elapsed = [int]((Get-Date) - $Job.StartedAt).TotalSeconds
|
||||
return [pscustomobject]@{
|
||||
jobId = $Job.JobId
|
||||
pid = $Job.Process.Id
|
||||
trackId = $Job.TrackId
|
||||
title = $Job.Title
|
||||
elapsedSeconds = $elapsed
|
||||
log = Get-JobLine $Job
|
||||
exitCode = if ($Job.Process.HasExited) { $Job.Process.ExitCode } else { $null }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-QueueSnapshot {
|
||||
$active = @($script:activeJobs | ForEach-Object { Get-JobSnapshot $_ })
|
||||
$total = [int]$script:stats.total
|
||||
$done = [int]$script:stats.completed + [int]$script:stats.failed
|
||||
$percent = if ($total -gt 0) { [Math]::Round(($done / $total) * 100, 1) } else { 100 }
|
||||
$next = @($script:queue | Select-Object -First 5 | ForEach-Object {
|
||||
[pscustomobject]@{
|
||||
id = $_.Id
|
||||
title = $_.Title
|
||||
}
|
||||
})
|
||||
|
||||
return [pscustomobject]@{
|
||||
maxWorkers = $MaxWorkers
|
||||
queued = $script:queue.Count
|
||||
activeCount = $active.Count
|
||||
active = if ($active.Count) { ($active | Select-Object -First 1).title } else { "" }
|
||||
activeJobs = $active
|
||||
next = $next
|
||||
total = $total
|
||||
completed = [int]$script:stats.completed
|
||||
failed = [int]$script:stats.failed
|
||||
skipped = [int]$script:stats.skipped
|
||||
done = $done
|
||||
percent = $percent
|
||||
}
|
||||
}
|
||||
|
||||
function Show-Dashboard {
|
||||
param([switch]$Force)
|
||||
|
||||
$now = Get-Date
|
||||
if (-not $Force -and ($now - $script:lastDashboardAt).TotalSeconds -lt 2) { return }
|
||||
$script:lastDashboardAt = $now
|
||||
$snapshot = Get-QueueSnapshot
|
||||
|
||||
Clear-Host
|
||||
Write-Host "SUNO Companion" -ForegroundColor Cyan
|
||||
Write-Host "Listening: http://127.0.0.1:$Port"
|
||||
Write-Host "Library: $libraryRoot"
|
||||
Write-Host "Logs: $logs"
|
||||
Write-Host ""
|
||||
Write-Host ("Workers: {0}/{1} Queue: {2} Done: {3}/{4} Failed: {5} Progress: {6}%" -f $snapshot.activeCount, $snapshot.maxWorkers, $snapshot.queued, $snapshot.done, $snapshot.total, $snapshot.failed, $snapshot.percent)
|
||||
Write-Host ""
|
||||
|
||||
if ($snapshot.activeJobs.Count) {
|
||||
Write-Host "Active jobs:" -ForegroundColor Yellow
|
||||
foreach ($job in $snapshot.activeJobs) {
|
||||
$elapsed = [TimeSpan]::FromSeconds($job.elapsedSeconds)
|
||||
Write-Host ("- {0} [{1:mm\:ss}] pid={2}" -f $job.title, $elapsed, $job.pid)
|
||||
if ($job.log) { Write-Host " $($job.log)" -ForegroundColor DarkGray }
|
||||
}
|
||||
} else {
|
||||
Write-Host "Active jobs: none" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
if ($snapshot.next.Count) {
|
||||
Write-Host ""
|
||||
Write-Host "Next queue:" -ForegroundColor Yellow
|
||||
foreach ($item in $snapshot.next) {
|
||||
Write-Host "- $($item.title)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Close this window to stop the companion."
|
||||
}
|
||||
|
||||
function Update-Queue {
|
||||
for ($index = $script:activeJobs.Count - 1; $index -ge 0; $index--) {
|
||||
$job = $script:activeJobs[$index]
|
||||
if (-not $job.Process.HasExited) {
|
||||
Publish-JobProgress $job
|
||||
continue
|
||||
}
|
||||
|
||||
Publish-JobProgress $job -Force
|
||||
if ($job.Process.ExitCode -eq 0) {
|
||||
$script:stats.completed++
|
||||
} else {
|
||||
$script:stats.failed++
|
||||
}
|
||||
[void]$script:activeIds.Remove($job.TrackId)
|
||||
$script:activeJobs.RemoveAt($index)
|
||||
Write-Host "Finished job $($job.JobId) exit=$($job.Process.ExitCode): $($job.Title)"
|
||||
}
|
||||
|
||||
while ($script:activeJobs.Count -lt $MaxWorkers -and $script:queue.Count -gt 0) {
|
||||
$item = $script:queue.Dequeue()
|
||||
[void]$script:queueIds.Remove($item.Id)
|
||||
$manifest = [pscustomobject]@{
|
||||
pageUrl = $item.PageUrl
|
||||
capturedAt = (Get-Date).ToString("o")
|
||||
count = 1
|
||||
tracks = @($item.Track)
|
||||
}
|
||||
$manifestPath = Save-Manifest $manifest
|
||||
$job = Start-SunoProcess $manifestPath $item.Id
|
||||
$active = [pscustomobject]@{
|
||||
JobId = $job.jobId
|
||||
Process = $job.process
|
||||
TrackId = $item.Id
|
||||
Title = $item.Title
|
||||
Log = $job.log
|
||||
ErrorLog = $job.errorLog
|
||||
StartedAt = Get-Date
|
||||
}
|
||||
[void]$script:activeJobs.Add($active)
|
||||
[void]$script:activeIds.Add($item.Id)
|
||||
Write-Host "Started queued job $($job.jobId): $($item.Title) [$($item.Id)]"
|
||||
}
|
||||
|
||||
Show-Dashboard
|
||||
}
|
||||
|
||||
function Add-TracksToQueue {
|
||||
@@ -232,7 +348,7 @@ function Add-TracksToQueue {
|
||||
$id = "$($track.id)".Trim()
|
||||
if (-not $id) { $skipped++; continue }
|
||||
if ($queueIds.Contains($id)) { $skipped++; continue }
|
||||
if ($activeJob -and $activeJob.TrackId -eq $id) { $skipped++; continue }
|
||||
if ($activeIds.Contains($id)) { $skipped++; continue }
|
||||
$title = if ($track.title) { "$($track.title)" } else { $id }
|
||||
$queue.Enqueue([pscustomobject]@{
|
||||
Id = $id
|
||||
@@ -243,12 +359,21 @@ function Add-TracksToQueue {
|
||||
[void]$queueIds.Add($id)
|
||||
$added += $track
|
||||
}
|
||||
$script:stats.total += @($added).Count
|
||||
$script:stats.skipped += $skipped
|
||||
Update-Queue
|
||||
$snapshot = Get-QueueSnapshot
|
||||
return [pscustomobject]@{
|
||||
added = @($added).Count
|
||||
skipped = $skipped
|
||||
queued = $queue.Count
|
||||
active = if ($activeJob) { $activeJob.Title } else { "" }
|
||||
queued = $snapshot.queued
|
||||
active = $snapshot.active
|
||||
activeCount = $snapshot.activeCount
|
||||
maxWorkers = $snapshot.maxWorkers
|
||||
completed = $snapshot.completed
|
||||
failed = $snapshot.failed
|
||||
total = $snapshot.total
|
||||
percent = $snapshot.percent
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +450,9 @@ Write-Host "SUNO companion listening on http://127.0.0.1:$Port"
|
||||
Write-Host "OutputRoot: $OutputRoot"
|
||||
Write-Host "Library: $libraryRoot"
|
||||
Write-Host "Logs: $logs"
|
||||
Write-Host "Workers: $MaxWorkers"
|
||||
Write-Host "Press Ctrl+C to stop."
|
||||
Show-Dashboard -Force
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
@@ -346,8 +473,26 @@ try {
|
||||
|
||||
if ($request.Method -eq "GET" -and $request.Path -eq "/health") {
|
||||
Update-Queue
|
||||
Write-Host "Health check OK: $(Get-Date -Format 'HH:mm:ss')"
|
||||
Write-Response $stream 200 "OK" (@{ ok = $true; outputRoot = $OutputRoot; libraryRoot = $libraryRoot; artist = $ArtistName; queued = $queue.Count; active = if ($activeJob) { $activeJob.Title } else { "" } } | ConvertTo-Json -Compress)
|
||||
$snapshot = Get-QueueSnapshot
|
||||
$payload = [ordered]@{
|
||||
ok = $true
|
||||
outputRoot = $OutputRoot
|
||||
libraryRoot = $libraryRoot
|
||||
artist = $ArtistName
|
||||
queued = $snapshot.queued
|
||||
active = $snapshot.active
|
||||
activeCount = $snapshot.activeCount
|
||||
activeJobs = $snapshot.activeJobs
|
||||
next = $snapshot.next
|
||||
maxWorkers = $snapshot.maxWorkers
|
||||
total = $snapshot.total
|
||||
completed = $snapshot.completed
|
||||
failed = $snapshot.failed
|
||||
skipped = $snapshot.skipped
|
||||
done = $snapshot.done
|
||||
percent = $snapshot.percent
|
||||
}
|
||||
Write-Response $stream 200 "OK" ($payload | ConvertTo-Json -Depth 10 -Compress)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -375,30 +520,38 @@ try {
|
||||
continue
|
||||
}
|
||||
|
||||
$sync = Get-MissingTracks $manifest.tracks
|
||||
$manifest.tracks = @($sync.missing)
|
||||
$manifest.count = @($manifest.tracks).Count
|
||||
if ($manifest.count -eq 0) {
|
||||
$result = Add-TracksToQueue @($manifest.tracks) "$($manifest.pageUrl)"
|
||||
if ($result.added -eq 0) {
|
||||
Write-Host "Process skipped: library already synced"
|
||||
Write-Response $stream 200 "OK" (@{
|
||||
ok = $true
|
||||
skipped = $true
|
||||
message = "library already synced"
|
||||
message = "library already synced or already queued"
|
||||
queued = $result.queued
|
||||
active = $result.active
|
||||
activeCount = $result.activeCount
|
||||
maxWorkers = $result.maxWorkers
|
||||
completed = $result.completed
|
||||
failed = $result.failed
|
||||
total = $result.total
|
||||
percent = $result.percent
|
||||
} | ConvertTo-Json -Compress)
|
||||
continue
|
||||
}
|
||||
|
||||
$manifestPath = Join-Path $inbox ("suno-library-{0}.manifest.json" -f (Get-Date -Format "yyyyMMdd-HHmmss"))
|
||||
$manifest | ConvertTo-Json -Depth 50 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
|
||||
$job = Start-SunoProcess $manifestPath
|
||||
|
||||
Write-Host "Started job $($job.jobId) for $(@($manifest.tracks).Count) tracks"
|
||||
Write-Host "Queued process request: added=$($result.added), queued=$($result.queued), active=$($result.activeCount)"
|
||||
Write-Response $stream 200 "OK" (@{
|
||||
ok = $true
|
||||
jobId = $job.jobId
|
||||
pid = $job.pid
|
||||
log = $job.log
|
||||
errorLog = $job.errorLog
|
||||
added = $result.added
|
||||
skipped = $result.skipped
|
||||
queued = $result.queued
|
||||
active = $result.active
|
||||
activeCount = $result.activeCount
|
||||
maxWorkers = $result.maxWorkers
|
||||
completed = $result.completed
|
||||
failed = $result.failed
|
||||
total = $result.total
|
||||
percent = $result.percent
|
||||
} | ConvertTo-Json -Compress)
|
||||
continue
|
||||
}
|
||||
@@ -417,6 +570,12 @@ try {
|
||||
skipped = $result.skipped
|
||||
queued = $result.queued
|
||||
active = $result.active
|
||||
activeCount = $result.activeCount
|
||||
maxWorkers = $result.maxWorkers
|
||||
completed = $result.completed
|
||||
failed = $result.failed
|
||||
total = $result.total
|
||||
percent = $result.percent
|
||||
} | ConvertTo-Json -Compress)
|
||||
continue
|
||||
}
|
||||
@@ -431,6 +590,12 @@ try {
|
||||
skipped = $result.skipped
|
||||
queued = $result.queued
|
||||
active = $result.active
|
||||
activeCount = $result.activeCount
|
||||
maxWorkers = $result.maxWorkers
|
||||
completed = $result.completed
|
||||
failed = $result.failed
|
||||
total = $result.total
|
||||
percent = $result.percent
|
||||
} | ConvertTo-Json -Compress)
|
||||
continue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user