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 }