Queue Suno tracks one at a time
This commit is contained in:
@@ -58,7 +58,7 @@ WAV 다운로드는 SUNO 계정 권한, 변환 준비 상태, CDN 접근 정책
|
|||||||
2. SUNO 라이브러리에서 `곡 감지`를 누릅니다.
|
2. SUNO 라이브러리에서 `곡 감지`를 누릅니다.
|
||||||
3. `WAV + 메타데이터 다운`을 누릅니다.
|
3. `WAV + 메타데이터 다운`을 누릅니다.
|
||||||
|
|
||||||
이 경우 Tampermonkey가 manifest를 `http://127.0.0.1:17873/process`로 보내고, 로컬 프로그램이 자동으로 다운로드/FLAC 변환/정리를 시작합니다.
|
이 경우 Tampermonkey가 로컬 프로그램의 `sync-index`로 이미 저장된 clip id를 확인한 뒤, 없는 곡만 `enqueue`로 한 곡씩 보냅니다. 로컬 프로그램은 큐에 들어온 곡을 비동기로 순서대로 다운로드/FLAC 변환/정리합니다.
|
||||||
|
|
||||||
기본 출력 위치:
|
기본 출력 위치:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name ChatGPT to Suno Prompt Copier
|
// @name ChatGPT to Suno Prompt Copier
|
||||||
// @namespace local.suno-helper
|
// @namespace local.suno-helper
|
||||||
// @version 0.6.0
|
// @version 0.7.0
|
||||||
// @description Copy structured ChatGPT song prompt versions into Suno fields.
|
// @description Copy structured ChatGPT song prompt versions into Suno fields.
|
||||||
// @author local
|
// @author local
|
||||||
// @match https://chatgpt.com/*
|
// @match https://chatgpt.com/*
|
||||||
@@ -975,34 +975,31 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const preparedTracks = [];
|
let enqueued = 0;
|
||||||
for (let index = 0; index < missingTracks.length; index += 1) {
|
for (let index = 0; index < missingTracks.length; index += 1) {
|
||||||
const track = { ...missingTracks[index] };
|
const track = { ...missingTracks[index] };
|
||||||
setStatus(statusTarget, `${index + 1}/${missingTracks.length} 없는 곡 WAV 준비 중: ${track.title || track.id}`);
|
setStatus(statusTarget, `${index + 1}/${missingTracks.length} 없는 곡 WAV 준비 중: ${track.title || track.id}`);
|
||||||
track.wavUrl = await requestSunoWavUrl(track);
|
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 = {
|
setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개. 로컬 프로그램이 순서대로 처리 중.`);
|
||||||
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}개`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function testCompanion(statusTarget) {
|
async function testCompanion(statusTarget) {
|
||||||
|
|||||||
+103
-1
@@ -12,6 +12,9 @@ $processor = Join-Path $scriptRoot "process-suno-library.ps1"
|
|||||||
$inbox = Join-Path $scriptRoot "companion-inbox"
|
$inbox = Join-Path $scriptRoot "companion-inbox"
|
||||||
$logs = Join-Path $scriptRoot "companion-logs"
|
$logs = Join-Path $scriptRoot "companion-logs"
|
||||||
New-Item -ItemType Directory -Force -Path $inbox, $logs | Out-Null
|
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 {
|
function Resolve-LibraryRoot {
|
||||||
param(
|
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 {
|
function Get-ExistingSunoIds {
|
||||||
param([string]$Root)
|
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 = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Parse("127.0.0.1"), $Port)
|
||||||
$listener.Start()
|
$listener.Start()
|
||||||
|
$listener.Server.ReceiveTimeout = 1000
|
||||||
|
|
||||||
Write-Host "SUNO companion listening on http://127.0.0.1:$Port"
|
Write-Host "SUNO companion listening on http://127.0.0.1:$Port"
|
||||||
Write-Host "OutputRoot: $OutputRoot"
|
Write-Host "OutputRoot: $OutputRoot"
|
||||||
@@ -190,6 +268,11 @@ Write-Host "Press Ctrl+C to stop."
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
while ($true) {
|
while ($true) {
|
||||||
|
Update-Queue
|
||||||
|
if (-not $listener.Pending()) {
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
continue
|
||||||
|
}
|
||||||
$client = $listener.AcceptTcpClient()
|
$client = $listener.AcceptTcpClient()
|
||||||
try {
|
try {
|
||||||
$stream = $client.GetStream()
|
$stream = $client.GetStream()
|
||||||
@@ -201,8 +284,9 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($request.Method -eq "GET" -and $request.Path -eq "/health") {
|
if ($request.Method -eq "GET" -and $request.Path -eq "/health") {
|
||||||
|
Update-Queue
|
||||||
Write-Host "Health check OK: $(Get-Date -Format 'HH:mm:ss')"
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,6 +342,24 @@ try {
|
|||||||
continue
|
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)
|
Write-Response $stream 404 "Not Found" (@{ ok = $false; error = "not found" } | ConvertTo-Json -Compress)
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user