diff --git a/chatgpt-suno-tampermonkey.user.js b/chatgpt-suno-tampermonkey.user.js index a13c672..79cf358 100644 --- a/chatgpt-suno-tampermonkey.user.js +++ b/chatgpt-suno-tampermonkey.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ChatGPT to Suno Prompt Copier // @namespace local.suno-helper -// @version 0.7.7 +// @version 0.7.8 // @description Copy structured ChatGPT song prompt versions into Suno fields. // @author local // @match https://chatgpt.com/* @@ -28,7 +28,7 @@ (function () { "use strict"; - const SCRIPT_VERSION = "0.7.7"; + const SCRIPT_VERSION = "0.7.8"; const STORAGE_KEY = "chatgptSunoPromptSlots"; const SLOT_COUNT = 2; const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com"); @@ -992,6 +992,25 @@ return extractWavUrl(track.wavUrl) || ((await canReadUrl(cdnWavUrl)) ? cdnWavUrl : ""); } + async function prepareSunoWavCandidate(track) { + if (!track.id) return extractWavUrl(track.wavUrl); + const base = `https://studio-api-prod.suno.com/api/gen/${encodeURIComponent(track.id)}`; + const cdnWavUrl = `https://cdn1.suno.ai/${encodeURIComponent(track.id)}.wav`; + try { + await callSunoJson(`${base}/convert_wav/`, { method: "POST" }); + } catch (error) { + console.warn("[ChatGPT to Suno] convert_wav failed; local retry will continue", error); + } + try { + const data = await callSunoJson(`${base}/wav_file/`, { method: "GET" }); + const direct = extractWavUrl(data); + if (direct) return direct; + } catch (error) { + console.warn("[ChatGPT to Suno] wav_file probe failed; local retry will continue", error); + } + return extractWavUrl(track.wavUrl) || cdnWavUrl; + } + function downloadTextFile(text, name) { const blob = new Blob([text], { type: "application/json;charset=utf-8" }); const url = URL.createObjectURL(blob); @@ -1111,11 +1130,11 @@ let wavPending = 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); + setStatus(statusTarget, `${index + 1}/${missingTracks.length} 없는 곡 WAV 변환 요청 중: ${track.title || track.id}`); + track.wavUrl = await prepareSunoWavCandidate(track); if (!track.wavUrl) { wavPending += 1; - setStatus(statusTarget, `${track.title || track.id} WAV 변환 URL이 아직 안 나와서 보류 (${wavPending}곡)`); + setStatus(statusTarget, `${track.title || track.id} WAV 후보 URL을 못 만들어서 보류 (${wavPending}곡)`); continue; } const enqueueManifest = { @@ -1138,10 +1157,10 @@ setStatus(statusTarget, `${enqueued}/${missingTracks.length}곡 큐 추가: ${track.title || track.id} / 대기 ${data.queued ?? "?"}개`); } if (!enqueued) { - setStatus(statusTarget, `큐에 넣은 곡 없음. 새 곡 ${missingTracks.length}개 중 ${wavPending}개가 아직 SUNO WAV 변환 URL을 안 줬어.`); + setStatus(statusTarget, `큐에 넣은 곡 없음. 새 곡 ${missingTracks.length}개 중 ${wavPending}개가 WAV 후보 URL 생성 실패.`); return; } - setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개, WAV 보류 ${wavPending}개. 로컬 프로그램이 순서대로 처리 중.`); + setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개, 보류 ${wavPending}개. 로컬 프로그램이 WAV 준비를 기다리며 처리 중.`); } function trackOptionLabel(track, index) { diff --git a/process-suno-library.ps1 b/process-suno-library.ps1 index 105e27d..fb1f896 100644 --- a/process-suno-library.ps1 +++ b/process-suno-library.ps1 @@ -3,6 +3,8 @@ param( [string]$OutputRoot = "\\VaultOfData\AKAMedia\음악\처리됨", [string]$ArtistName = "SUNO", [string]$FfmpegPath = "", + [int]$WavRetrySeconds = 300, + [int]$WavRetryIntervalSeconds = 8, [switch]$KeepWorkFiles, [switch]$Force ) @@ -233,6 +235,37 @@ function Invoke-Download { } } +function Invoke-DownloadWithRetry { + param( + [string[]]$Urls, + [string]$OutFile, + [int]$RetrySeconds, + [int]$IntervalSeconds + ) + + $candidates = @($Urls | Where-Object { $_ } | Select-Object -Unique) + if (-not $candidates.Count) { return $false } + + $deadline = (Get-Date).AddSeconds([Math]::Max(1, $RetrySeconds)) + $attempt = 0 + do { + $attempt++ + foreach ($candidate in $candidates) { + Write-Host "WAV 후보 시도 #$attempt`: $candidate" + if (Invoke-Download $candidate $OutFile) { + return $true + } + } + + if ((Get-Date) -lt $deadline) { + Write-Host "WAV 아직 준비 안 됨. $IntervalSeconds초 후 재시도..." + Start-Sleep -Seconds ([Math]::Max(1, $IntervalSeconds)) + } + } while ((Get-Date) -lt $deadline) + + return $false +} + function Add-MetadataArg { param( [System.Collections.Generic.List[string]]$Args, @@ -392,12 +425,7 @@ foreach ($track in $tracks) { continue } - $audioOk = $false - foreach ($candidate in $wavCandidates) { - Write-Host "WAV 후보 시도: $candidate" - $audioOk = Invoke-Download $candidate $inputAudio - if ($audioOk) { break } - } + $audioOk = Invoke-DownloadWithRetry $wavCandidates $inputAudio $WavRetrySeconds $WavRetryIntervalSeconds if (-not $audioOk) { Write-Warning "WAV를 받지 못해서 건너뜀: $title" continue