Queue Suno tracks one at a time

This commit is contained in:
DESKTOP-KSVGT20\shkim
2026-05-01 23:15:19 +09:00
parent 1414c38bd1
commit 96a54a3eca
3 changed files with 125 additions and 26 deletions
+1 -1
View File
@@ -58,7 +58,7 @@ WAV 다운로드는 SUNO 계정 권한, 변환 준비 상태, CDN 접근 정책
2. SUNO 라이브러리에서 `곡 감지`를 누릅니다.
3. `WAV + 메타데이터 다운`을 누릅니다.
이 경우 Tampermonkey가 manifest를 `http://127.0.0.1:17873/process`로 보내고, 로컬 프로그램이 자동으로 다운로드/FLAC 변환/정리를 시작합니다.
이 경우 Tampermonkey가 로컬 프로그램의 `sync-index`로 이미 저장된 clip id를 확인한 뒤, 없는 곡만 `enqueue`로 한 곡씩 보냅니다. 로컬 프로그램은 큐에 들어온 곡을 비동기로 순서대로 다운로드/FLAC 변환/정리합니다.
기본 출력 위치:
+21 -24
View File
@@ -1,7 +1,7 @@
// ==UserScript==
// @name ChatGPT to Suno Prompt Copier
// @namespace local.suno-helper
// @version 0.6.0
// @version 0.7.0
// @description Copy structured ChatGPT song prompt versions into Suno fields.
// @author local
// @match https://chatgpt.com/*
@@ -975,34 +975,31 @@
return;
}
const preparedTracks = [];
let enqueued = 0;
for (let index = 0; index < missingTracks.length; index += 1) {
const track = { ...missingTracks[index] };
setStatus(statusTarget, `${index + 1}/${missingTracks.length} 없는 곡 WAV 준비 중: ${track.title || track.id}`);
track.wavUrl = await requestSunoWavUrl(track);
preparedTracks.push(track);
const enqueueManifest = {
pageUrl: location.href,
capturedAt: new Date().toISOString(),
count: 1,
tracks: [track],
};
const response = await fetch("http://127.0.0.1:17873/enqueue", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(enqueueManifest),
});
const text = await response.text();
if (!response.ok) {
throw new Error(text || `${response.status} ${response.statusText}`);
}
enqueued += 1;
const data = tryParseJson(text) || {};
setStatus(statusTarget, `${enqueued}/${missingTracks.length}곡 큐 추가: ${track.title || track.id} / 대기 ${data.queued ?? "?"}`);
}
const manifest = {
pageUrl: location.href,
capturedAt: new Date().toISOString(),
count: preparedTracks.length,
tracks: preparedTracks,
};
const response = await fetch("http://127.0.0.1:17873/process", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(manifest),
});
const text = await response.text();
if (!response.ok) {
throw new Error(text || `${response.status} ${response.statusText}`);
}
const data = tryParseJson(text) || {};
if (data.skipped) {
setStatus(statusTarget, "동기화 완료 상태: 처리할 새 곡 없음");
return;
}
setStatus(statusTarget, `로컬 처리 시작: ${data.jobId || "job"} / 새 곡 ${preparedTracks.length}`);
setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개. 로컬 프로그램이 순서대로 처리 중.`);
}
async function testCompanion(statusTarget) {
+103 -1
View File
@@ -12,6 +12,9 @@ $processor = Join-Path $scriptRoot "process-suno-library.ps1"
$inbox = Join-Path $scriptRoot "companion-inbox"
$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
function Resolve-LibraryRoot {
param(
@@ -137,6 +140,80 @@ function Start-SunoProcess {
}
}
function Save-Manifest {
param($Manifest)
$firstTrack = @($Manifest.tracks)[0]
$id = "$($firstTrack.id)".Trim()
$safeId = if ($id) { $id } else { Get-Date -Format "yyyyMMdd-HHmmssfff" }
$manifestPath = Join-Path $inbox ("suno-track-{0}.manifest.json" -f $safeId)
$Manifest | ConvertTo-Json -Depth 50 | Set-Content -LiteralPath $manifestPath -Encoding UTF8
return $manifestPath
}
function Update-Queue {
if ($script:activeJob -and -not $script:activeJob.Process.HasExited) {
return
}
if ($script:activeJob -and $script:activeJob.Process.HasExited) {
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 = Get-Process -Id $job.pid
TrackId = $item.Id
Title = $item.Title
Log = $job.log
ErrorLog = $job.errorLog
}
Write-Host "Started queued job $($job.jobId): $($item.Title) [$($item.Id)]"
}
}
function Add-TracksToQueue {
param($Tracks, [string]$PageUrl)
$sync = Get-MissingTracks $Tracks
$added = @()
$skipped = 0
foreach ($track in @($sync.missing)) {
$id = "$($track.id)".Trim()
if (-not $id) { $skipped++; continue }
if ($queueIds.Contains($id)) { $skipped++; continue }
if ($activeJob -and $activeJob.TrackId -eq $id) { $skipped++; continue }
$title = if ($track.title) { "$($track.title)" } else { $id }
$queue.Enqueue([pscustomobject]@{
Id = $id
Title = $title
Track = $track
PageUrl = $PageUrl
})
[void]$queueIds.Add($id)
$added += $track
}
Update-Queue
return [pscustomobject]@{
added = @($added).Count
skipped = $skipped
queued = $queue.Count
active = if ($activeJob) { $activeJob.Title } else { "" }
}
}
function Get-ExistingSunoIds {
param([string]$Root)
@@ -181,6 +258,7 @@ if (-not (Test-Path -LiteralPath $processor)) {
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Parse("127.0.0.1"), $Port)
$listener.Start()
$listener.Server.ReceiveTimeout = 1000
Write-Host "SUNO companion listening on http://127.0.0.1:$Port"
Write-Host "OutputRoot: $OutputRoot"
@@ -190,6 +268,11 @@ Write-Host "Press Ctrl+C to stop."
try {
while ($true) {
Update-Queue
if (-not $listener.Pending()) {
Start-Sleep -Milliseconds 500
continue
}
$client = $listener.AcceptTcpClient()
try {
$stream = $client.GetStream()
@@ -201,8 +284,9 @@ 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 } | ConvertTo-Json -Compress)
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)
continue
}
@@ -258,6 +342,24 @@ try {
continue
}
if ($request.Method -eq "POST" -and $request.Path -eq "/enqueue") {
$manifest = $request.Body | ConvertFrom-Json
if (-not $manifest.tracks -or @($manifest.tracks).Count -eq 0) {
Write-Response $stream 400 "Bad Request" (@{ ok = $false; error = "tracks is empty" } | ConvertTo-Json -Compress)
continue
}
$result = Add-TracksToQueue @($manifest.tracks) "$($manifest.pageUrl)"
Write-Host "Queue update: added=$($result.added), queued=$($result.queued), active=$($result.active)"
Write-Response $stream 200 "OK" (@{
ok = $true
added = $result.added
skipped = $result.skipped
queued = $result.queued
active = $result.active
} | ConvertTo-Json -Compress)
continue
}
Write-Response $stream 404 "Not Found" (@{ ok = $false; error = "not found" } | ConvertTo-Json -Compress)
} catch {
try {