diff --git a/README.md b/README.md
index 060292d..476836f 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ SUNO 화면 구조가 바뀌어 자동 입력이 실패하면, 패널의 `Title`
SUNO 곡 상세/플레이어 화면에서 `WAV + 커버 + 메타 다운로드`를 누르면 현재 페이지에서 감지된 곡 정보를 기준으로 아래 파일을 다운로드합니다.
-SUNO 라이브러리 목록 화면에서는 `라이브러리 곡 감지`를 누른 뒤 `처리용 manifest 저장`을 누릅니다. 현재 브라우저에 로드된 곡 목록을 기준으로 처리하므로, 무한 스크롤 목록에서는 먼저 아래쪽까지 스크롤해서 곡을 로드해야 합니다.
+SUNO 라이브러리 목록 화면에서는 `곡 감지`를 누른 뒤 `WAV + 메타데이터 다운`을 누릅니다. 현재 브라우저에 로드된 곡 목록을 기준으로 처리하므로, 무한 스크롤 목록에서는 먼저 아래쪽까지 스크롤해서 곡을 로드해야 합니다.
- `{title}.wav`: clip id가 감지되면 `https://cdn1.suno.ai/{clipId}.wav` 후보를 내려받습니다.
- `{title}.cover.*`: 페이지에서 감지된 앨범아트 URL을 내려받습니다.
@@ -48,15 +48,15 @@ WAV 다운로드는 SUNO 계정 권한, 변환 준비 상태, CDN 접근 정책
브라우저는 `\\VaultOfData\AKAMedia\음악\처리됨\SUNO` 같은 로컬/NAS 경로에 직접 파일을 정리하거나 WAV를 FLAC으로 인코딩할 수 없습니다. 그래서 Tampermonkey는 manifest를 만들고, 로컬 PowerShell 스크립트가 실제 다운로드/변환/정리를 담당합니다.
-1. SUNO 라이브러리에서 `라이브러리 곡 감지`를 누릅니다.
-2. `처리용 manifest 저장`을 눌러 Downloads 폴더에 `suno-library-YYYY-MM-DD.manifest.json`을 저장합니다. 이 단계에서 브라우저가 SUNO의 `convert_wav/`와 `wav_file/` API를 호출해 WAV URL을 준비합니다.
+1. SUNO 라이브러리에서 `곡 감지`를 누릅니다.
+2. `WAV + 메타데이터 다운`을 누릅니다. 이 단계에서 브라우저가 SUNO의 `convert_wav/`와 `wav_file/` API를 호출해 WAV URL을 준비합니다.
3. `run_process_suno_library.ps1`을 실행합니다.
더 편한 흐름:
1. 먼저 `run_suno_companion.ps1`을 실행해서 로컬 수신 프로그램을 켜둡니다.
-2. SUNO 라이브러리에서 `라이브러리 곡 감지`를 누릅니다.
-3. `로컬 프로그램으로 처리`를 누릅니다.
+2. SUNO 라이브러리에서 `곡 감지`를 누릅니다.
+3. `WAV + 메타데이터 다운`을 누릅니다.
이 경우 Tampermonkey가 manifest를 `http://127.0.0.1:17873/process`로 보내고, 로컬 프로그램이 자동으로 다운로드/FLAC 변환/정리를 시작합니다.
@@ -66,6 +66,6 @@ WAV 다운로드는 SUNO 계정 권한, 변환 준비 상태, CDN 접근 정책
\\VaultOfData\AKAMedia\음악\처리됨\SUNO\YYYY-MM-DD
```
-로컬 처리기는 manifest에 저장된 WAV URL을 다운로드하고, 실패하면 manifest의 audio URL로 fallback합니다. 이후 ffmpeg로 FLAC 변환하면서 title, artist, album, genre/style, lyrics, Suno clip id, cover art를 메타데이터로 넣습니다.
+로컬 처리기는 manifest에 저장된 WAV URL만 다운로드합니다. MP3/audio fallback은 사용하지 않습니다. 이후 ffmpeg로 FLAC 변환하면서 title, artist, album, genre/style, lyrics, Suno clip id, cover art를 메타데이터로 넣습니다.
로그는 `companion-logs` 폴더에 저장됩니다.
diff --git a/chatgpt-suno-tampermonkey.user.js b/chatgpt-suno-tampermonkey.user.js
index 9baa6a7..d70a80b 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.5.0
+// @version 0.5.1
// @description Copy structured ChatGPT song prompt versions into Suno fields.
// @author local
// @match https://chatgpt.com/*
@@ -895,16 +895,8 @@
if (preparedWavUrl) {
downloadUrl(preparedWavUrl, `${baseName}.wav`, (reason) => {
fail("wav")(reason);
- if (track.audioUrl && track.audioUrl !== preparedWavUrl) {
- downloadUrl(track.audioUrl, `${baseName}.${extensionFromUrl(track.audioUrl, "audio")}`, fail("audio-fallback"));
- messages.push("audio fallback 요청");
- setStatus(statusTarget, messages.join(" / "));
- }
});
messages.push("wav 요청");
- } else if (track.audioUrl) {
- downloadUrl(track.audioUrl, `${baseName}.${extensionFromUrl(track.audioUrl, "audio")}`, fail("audio"));
- messages.push("audio 요청");
}
setStatus(statusTarget, messages.join(" / "));
}
@@ -935,7 +927,7 @@
async function exportSunoManifest(tracks, statusTarget) {
if (!tracks.length) {
- setStatus(statusTarget, "내보낼 곡을 못 찾았어. 라이브러리 곡 감지를 먼저 해줘.");
+ setStatus(statusTarget, "내보낼 곡을 못 찾았어. 곡 감지를 먼저 해줘.");
return;
}
const preparedTracks = [];
@@ -957,7 +949,7 @@
async function postToCompanion(tracks, statusTarget) {
if (!tracks.length) {
- setStatus(statusTarget, "보낼 곡을 못 찾았어. 라이브러리 곡 감지를 먼저 해줘.");
+ setStatus(statusTarget, "보낼 곡을 못 찾았어. 곡 감지를 먼저 해줘.");
return;
}
const preparedTracks = [];
@@ -1001,22 +993,10 @@
${FIELD_DEFS.map((def) => ``).join("")}
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
`;
@@ -1059,21 +1039,6 @@
});
}
- body.querySelector("[data-cgs-refresh-track]").addEventListener("click", () => {
- updateSummary();
- const track = findCurrentSunoTrack();
- setStatus(body, track.id || track.audioUrl ? `감지됨: ${track.title || track.id}` : "현재 화면에서 곡 URL을 못 찾았어.");
- });
-
- body.querySelector("[data-cgs-download-track]").addEventListener("click", async () => {
- const track = findCurrentSunoTrack();
- if (!track.id && !track.audioUrl && !track.imageUrl) {
- setStatus(body, "다운로드할 곡 정보를 못 찾았어. 곡 상세/플레이어 화면에서 다시 눌러봐.");
- return;
- }
- await downloadSunoPackage(track, body);
- });
-
body.querySelector("[data-cgs-scan-library]").addEventListener("click", () => {
libraryTracks = scanSunoLibraryTracks();
updateSummary();
@@ -1088,25 +1053,14 @@
setStatus(body, `${networkHint} 목록 새로고침이나 스크롤 후 다시 눌러봐. 화면 텍스트: ${visibleHints.join(", ")}`);
});
- body.querySelector("[data-cgs-download-library]").addEventListener("click", async () => {
- if (!libraryTracks.length) libraryTracks = scanSunoLibraryTracks();
- updateSummary();
- await downloadSunoBatch(libraryTracks, body);
- });
-
- body.querySelector("[data-cgs-export-library]").addEventListener("click", async () => {
- if (!libraryTracks.length) libraryTracks = scanSunoLibraryTracks();
- updateSummary();
- await exportSunoManifest(libraryTracks, body);
- });
-
- body.querySelector("[data-cgs-send-companion]").addEventListener("click", async () => {
+ body.querySelector("[data-cgs-process-library]").addEventListener("click", async () => {
if (!libraryTracks.length) libraryTracks = scanSunoLibraryTracks();
updateSummary();
try {
await postToCompanion(libraryTracks, body);
} catch (error) {
- setStatus(body, `로컬 프로그램 연결 실패: ${error.message || error}`);
+ setStatus(body, `로컬 프로그램 연결 실패. manifest로 저장할게: ${error.message || error}`);
+ await exportSunoManifest(libraryTracks, body);
}
});
}
diff --git a/process-suno-library.ps1 b/process-suno-library.ps1
index 97bc3b1..e60528c 100644
--- a/process-suno-library.ps1
+++ b/process-suno-library.ps1
@@ -191,7 +191,6 @@ foreach ($track in $tracks) {
$model = Get-DeepValue $track @("model_name", "model", "major_model_version")
$createdAt = Get-DeepValue $track @("created_at", "createdAt")
- $audioUrl = Resolve-SunoMediaUrl $(if ($track.audioUrl) { "$($track.audioUrl)" } else { Get-DeepValue $track @("audio_url", "audioUrl", "stream_audio_url", "streamAudioUrl", "play_url", "playUrl", "play_path", "playPath", "mp3_url", "mp3Url", "video_url", "videoUrl") })
$wavUrl = Resolve-SunoMediaUrl $(if ($track.wavUrl) { "$($track.wavUrl)" } else { Get-DeepValue $track @("wav_url", "wavUrl", "download_url", "downloadUrl") })
$wavCandidates = @()
if ($wavUrl) { $wavCandidates += $wavUrl }
@@ -200,7 +199,6 @@ foreach ($track in $tracks) {
$imageUrl = if ($track.imageUrl) { "$($track.imageUrl)" } else { Get-DeepValue $track @("image_url", "imageUrl", "image_large_url", "cover_url") }
$inputAudio = Join-Path $workRoot "$prefix.input.wav"
- $fallbackAudio = Join-Path $workRoot "$prefix.input.$(Get-UrlExtension $audioUrl 'audio')"
$coverFile = Join-Path $workRoot "$prefix.cover.$(Get-UrlExtension $imageUrl 'jpg')"
$metadataFile = Join-Path $targetRoot "$prefix.metadata.json"
$flacFile = Join-Path $targetRoot "$prefix.flac"
@@ -214,14 +212,8 @@ foreach ($track in $tracks) {
$audioOk = Invoke-Download $candidate $inputAudio
if ($audioOk) { break }
}
- if (-not $audioOk -and $audioUrl) {
- Write-Host "WAV 후보 실패. audioUrl로 fallback 시도."
- $audioOk = Invoke-Download $audioUrl $fallbackAudio
- if ($audioOk) { $inputAudio = $fallbackAudio }
- }
-
if (-not $audioOk) {
- Write-Warning "오디오를 받지 못해서 건너뜀: $title"
+ Write-Warning "WAV를 받지 못해서 건너뜀: $title"
continue
}