Show sync counts and progress bars
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// ==UserScript==
|
||||
// @name ChatGPT to Suno Prompt Copier
|
||||
// @namespace local.suno-helper
|
||||
// @version 0.9.1
|
||||
// @version 0.9.3
|
||||
// @description Copy structured ChatGPT song prompt versions into Suno fields.
|
||||
// @author local
|
||||
// @match https://chatgpt.com/*
|
||||
@@ -30,7 +30,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const SCRIPT_VERSION = "0.9.1";
|
||||
const SCRIPT_VERSION = "0.9.3";
|
||||
const STORAGE_KEY = "chatgptSunoPromptSlots";
|
||||
const SLOT_COUNT = 2;
|
||||
const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com");
|
||||
@@ -246,6 +246,28 @@
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.cgs-progress-wrap {
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
.cgs-progress-label {
|
||||
color: #b7c9ff;
|
||||
font-size: 12px;
|
||||
}
|
||||
.cgs-progress {
|
||||
height: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
.cgs-progress-fill {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, #2f7cf6, #5db6ff);
|
||||
transition: width 180ms ease;
|
||||
}
|
||||
`);
|
||||
|
||||
function createPanel(title, mode = "") {
|
||||
@@ -1412,16 +1434,20 @@
|
||||
throw new Error(syncText || `${syncResponse.status} ${syncResponse.statusText}`);
|
||||
}
|
||||
const syncData = tryParseJson(syncText) || {};
|
||||
const syncTextSummary = showSyncSummary(syncData, tracks.length);
|
||||
const missingTracks = Array.isArray(syncData.tracks) ? syncData.tracks : [];
|
||||
if (!missingTracks.length) {
|
||||
setStatus(statusTarget, `동기화 완료 상태: ${tracks.length}곡 모두 로컬에 있음`);
|
||||
setStatus(statusTarget, `동기화 완료. ${syncTextSummary}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let enqueued = 0;
|
||||
let wavPending = 0;
|
||||
const syncExisting = Number(syncData.existingRequested ?? syncData.existing ?? 0);
|
||||
const syncTotal = Number(syncData.requested ?? syncData.received ?? tracks.length);
|
||||
for (let index = 0; index < missingTracks.length; index += 1) {
|
||||
const track = { ...missingTracks[index] };
|
||||
updateProgress(syncExisting + enqueued, syncTotal, "WAV 큐 준비");
|
||||
setStatus(statusTarget, `${index + 1}/${missingTracks.length} 없는 곡 WAV 변환 요청 중: ${track.title || track.id}`);
|
||||
track.wavUrl = await prepareSunoWavCandidate(track);
|
||||
if (!track.wavUrl) {
|
||||
@@ -1445,6 +1471,7 @@
|
||||
throw new Error(text || `${response.status} ${response.statusText}`);
|
||||
}
|
||||
enqueued += 1;
|
||||
updateProgress(syncExisting + enqueued, syncTotal, "WAV 큐 준비");
|
||||
const data = tryParseJson(text) || {};
|
||||
setStatus(statusTarget, `${enqueued}/${missingTracks.length}곡 큐 추가: ${track.title || track.id} / 대기 ${data.queued ?? "?"}개`);
|
||||
}
|
||||
@@ -1452,7 +1479,7 @@
|
||||
setStatus(statusTarget, `큐에 넣은 곡 없음. 새 곡 ${missingTracks.length}개 중 ${wavPending}개가 WAV 후보 URL 생성 실패.`);
|
||||
return;
|
||||
}
|
||||
setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개, 보류 ${wavPending}개. 로컬 프로그램이 WAV 준비를 기다리며 처리 중.`);
|
||||
setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개, 보류 ${wavPending}개. ${syncTextSummary}`);
|
||||
}
|
||||
|
||||
async function uploadTrackToCompanion(track, statusTarget, format = "mp3") {
|
||||
@@ -1471,9 +1498,10 @@
|
||||
const syncText = await syncResponse.text();
|
||||
if (!syncResponse.ok) throw new Error(syncText || `${syncResponse.status} ${syncResponse.statusText}`);
|
||||
const syncData = tryParseJson(syncText) || {};
|
||||
const syncTextSummary = showSyncSummary(syncData, 1);
|
||||
const missingTracks = Array.isArray(syncData.tracks) ? syncData.tracks : [];
|
||||
if (!missingTracks.length) {
|
||||
setStatus(statusTarget, `이미 로컬에 있음, SUNO 다운로드 생략: ${track.title || track.id}`);
|
||||
setStatus(statusTarget, `이미 로컬에 있음, SUNO 다운로드 생략: ${track.title || track.id}. ${syncTextSummary}`);
|
||||
return {
|
||||
ok: true,
|
||||
skipped: true,
|
||||
@@ -1529,17 +1557,21 @@
|
||||
const syncText = await syncResponse.text();
|
||||
if (!syncResponse.ok) throw new Error(syncText || `${syncResponse.status} ${syncResponse.statusText}`);
|
||||
const syncData = tryParseJson(syncText) || {};
|
||||
const syncTextSummary = showSyncSummary(syncData, tracks.length);
|
||||
const missingTracks = Array.isArray(syncData.tracks) ? syncData.tracks : [];
|
||||
if (!missingTracks.length) {
|
||||
setStatus(statusTarget, `동기화 완료 상태: ${tracks.length}곡 모두 로컬에 있음`);
|
||||
setStatus(statusTarget, `동기화 완료. ${syncTextSummary}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let uploaded = 0;
|
||||
let failed = 0;
|
||||
const syncExisting = Number(syncData.existingRequested ?? syncData.existing ?? 0);
|
||||
const syncTotal = Number(syncData.requested ?? syncData.received ?? tracks.length);
|
||||
for (let index = 0; index < missingTracks.length; index += 1) {
|
||||
const track = { ...missingTracks[index] };
|
||||
try {
|
||||
updateProgress(syncExisting + uploaded + failed, syncTotal, `${format.toUpperCase()} 다운로드`);
|
||||
setStatus(statusTarget, `${index + 1}/${missingTracks.length} ${format.toUpperCase()} 처리 중: ${track.title || track.id}`);
|
||||
await uploadTrackToCompanion(track, statusTarget, format);
|
||||
uploaded += 1;
|
||||
@@ -1548,13 +1580,14 @@
|
||||
console.warn("[ChatGPT to Suno] upload failed", track, error);
|
||||
setStatus(statusTarget, `${track.title || track.id} 실패: ${error.message || error}`);
|
||||
}
|
||||
updateProgress(syncExisting + uploaded + failed, syncTotal, `${format.toUpperCase()} 다운로드`);
|
||||
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||
}
|
||||
let healthText = "";
|
||||
try {
|
||||
healthText = ` / ${formatCompanionStatus(await readCompanionHealth())}`;
|
||||
} catch {}
|
||||
setStatus(statusTarget, `${format.toUpperCase()} 일괄 업로드 완료: ${uploaded}곡 성공, ${failed}곡 실패. 로컬에서 메타데이터 후처리 중${healthText}`);
|
||||
setStatus(statusTarget, `${format.toUpperCase()} 일괄 업로드 완료: ${uploaded}곡 성공, ${failed}곡 실패. ${syncTextSummary}. 로컬에서 메타데이터 후처리 중${healthText}`);
|
||||
}
|
||||
|
||||
function trackOptionLabel(track, index) {
|
||||
@@ -1643,6 +1676,11 @@
|
||||
<button class="cgs-btn cgs-wide" type="button" data-cgs-scan-library>곡 감지</button>
|
||||
</div>
|
||||
<div class="cgs-muted" data-cgs-selection-summary></div>
|
||||
<div class="cgs-muted" data-cgs-sync-summary></div>
|
||||
<div class="cgs-progress-wrap">
|
||||
<div class="cgs-progress-label" data-cgs-progress-label>진행 0/0</div>
|
||||
<div class="cgs-progress"><div class="cgs-progress-fill" data-cgs-progress-fill></div></div>
|
||||
</div>
|
||||
<div class="cgs-track-list" data-cgs-track-list></div>
|
||||
<div class="cgs-row">
|
||||
<button class="cgs-btn cgs-wide" type="button" data-cgs-process-one-mp3>선택 1곡 MP3</button>
|
||||
@@ -1670,6 +1708,9 @@
|
||||
|
||||
const summary = body.querySelector("[data-cgs-summary]");
|
||||
const selectionSummary = body.querySelector("[data-cgs-selection-summary]");
|
||||
const syncSummary = body.querySelector("[data-cgs-sync-summary]");
|
||||
const progressLabel = body.querySelector("[data-cgs-progress-label]");
|
||||
const progressFill = body.querySelector("[data-cgs-progress-fill]");
|
||||
const slotSelect = body.querySelector("[data-cgs-slot]");
|
||||
const trackList = body.querySelector("[data-cgs-track-list]");
|
||||
let libraryTracks = [];
|
||||
@@ -1677,8 +1718,32 @@
|
||||
const selectedTrackIds = new Set();
|
||||
let lastSelectedIndex = -1;
|
||||
let autoScanTimer = 0;
|
||||
let progressState = { total: 0, done: 0, label: "진행" };
|
||||
|
||||
const getSelectedTracks = () => libraryTracks.filter((track) => selectedTrackIds.has(track.id));
|
||||
const updateProgress = (done, total, label = "진행") => {
|
||||
const safeTotal = Math.max(0, Number(total || 0));
|
||||
const safeDone = Math.max(0, Math.min(Number(done || 0), safeTotal || Number(done || 0)));
|
||||
progressState = { total: safeTotal, done: safeDone, label };
|
||||
const percent = safeTotal ? Math.round((safeDone / safeTotal) * 1000) / 10 : 0;
|
||||
progressLabel.textContent = `${label} ${safeDone}/${safeTotal}${safeTotal ? ` (${percent}%)` : ""}`;
|
||||
progressFill.style.width = `${safeTotal ? percent : 0}%`;
|
||||
};
|
||||
const formatSyncSummary = (data, fallbackRequested = 0) => {
|
||||
const requested = Number(data.requested ?? data.received ?? fallbackRequested ?? 0);
|
||||
const existing = Number(data.existingRequested ?? data.existing ?? 0);
|
||||
const missing = Number(data.missingRequested ?? data.missing ?? 0);
|
||||
const invalid = Number(data.invalidRequested ?? 0);
|
||||
return `로컬 확인: 요청 ${requested}곡 / 이미 있음 ${existing}곡 / 새로 필요 ${missing}곡${invalid ? ` / ID 없음 ${invalid}곡` : ""}`;
|
||||
};
|
||||
const showSyncSummary = (data, fallbackRequested = 0) => {
|
||||
const text = formatSyncSummary(data, fallbackRequested);
|
||||
const requested = Number(data.requested ?? data.received ?? fallbackRequested ?? 0);
|
||||
const existing = Number(data.existingRequested ?? data.existing ?? 0);
|
||||
updateProgress(existing, requested, "로컬 확인");
|
||||
syncSummary.textContent = text;
|
||||
return text;
|
||||
};
|
||||
const mergeLibraryTracks = (tracks) => {
|
||||
for (const track of tracks) {
|
||||
if (!track.id) continue;
|
||||
|
||||
+31
-3
@@ -251,6 +251,21 @@ function Get-QueueSnapshot {
|
||||
}
|
||||
}
|
||||
|
||||
function Format-ProgressBar {
|
||||
param(
|
||||
[int]$Done,
|
||||
[int]$Total,
|
||||
[int]$Width = 28
|
||||
)
|
||||
|
||||
if ($Total -le 0) {
|
||||
return "[" + ("-" * $Width) + "]"
|
||||
}
|
||||
$ratio = [Math]::Max(0, [Math]::Min(1, $Done / $Total))
|
||||
$filled = [int][Math]::Round($ratio * $Width)
|
||||
return "[" + ("#" * $filled) + ("-" * ($Width - $filled)) + "]"
|
||||
}
|
||||
|
||||
function Stop-AllWork {
|
||||
$stopped = 0
|
||||
foreach ($job in @($script:activeJobs)) {
|
||||
@@ -288,6 +303,7 @@ function Show-Dashboard {
|
||||
Write-Host "Logs: $logs"
|
||||
Write-Host ""
|
||||
Write-Host ("Workers: {0}/{1} Queue: {2} Done: {3}/{4} Failed: {5} Progress: {6}%" -f $snapshot.activeCount, $snapshot.maxWorkers, $snapshot.queued, $snapshot.done, $snapshot.total, $snapshot.failed, $snapshot.percent)
|
||||
Write-Host ("{0} {1}/{2}" -f (Format-ProgressBar $snapshot.done $snapshot.total), $snapshot.done, $snapshot.total) -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
if ($snapshot.activeJobs.Count) {
|
||||
@@ -381,7 +397,8 @@ function Add-TracksToQueue {
|
||||
[void]$queueIds.Add($id)
|
||||
$added += $track
|
||||
}
|
||||
$script:stats.total += @($added).Count
|
||||
$script:stats.total += (@($added).Count + $skipped)
|
||||
$script:stats.completed += $skipped
|
||||
$script:stats.skipped += $skipped
|
||||
Update-Queue
|
||||
$snapshot = Get-QueueSnapshot
|
||||
@@ -446,17 +463,23 @@ function Get-MissingTracks {
|
||||
|
||||
$existingIds = Get-ExistingSunoIds $libraryRoot
|
||||
$missing = @()
|
||||
$existingRequested = @()
|
||||
$invalid = 0
|
||||
foreach ($track in @($Tracks)) {
|
||||
$id = "$($track.id)".Trim()
|
||||
if (-not $id) { continue }
|
||||
if (-not $id) { $invalid++; continue }
|
||||
if (-not $existingIds.Contains($id)) {
|
||||
$missing += $track
|
||||
} else {
|
||||
$existingRequested += $track
|
||||
}
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
existingIds = @($existingIds)
|
||||
existingRequested = $existingRequested
|
||||
missing = $missing
|
||||
invalid = $invalid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,8 +568,13 @@ try {
|
||||
outputRoot = $OutputRoot
|
||||
libraryRoot = $libraryRoot
|
||||
artist = $ArtistName
|
||||
requested = @($manifest.tracks).Count
|
||||
received = @($manifest.tracks).Count
|
||||
existing = @($result.existingIds).Count
|
||||
libraryExistingTotal = @($result.existingIds).Count
|
||||
existingRequested = @($result.existingRequested).Count
|
||||
missingRequested = @($result.missing).Count
|
||||
invalidRequested = $result.invalid
|
||||
existing = @($result.existingRequested).Count
|
||||
missing = @($result.missing).Count
|
||||
tracks = @($result.missing)
|
||||
} | ConvertTo-Json -Depth 50 -Compress)
|
||||
|
||||
Reference in New Issue
Block a user