Add clip-id based Suno library sync

This commit is contained in:
DESKTOP-KSVGT20\shkim
2026-05-01 23:10:57 +09:00
parent cbcb37ddb5
commit 1414c38bd1
7 changed files with 254 additions and 30 deletions
+108 -16
View File
@@ -1,8 +1,10 @@
param(
[string]$ManifestPath = "",
[string]$OutputRoot = "\\VaultOfData\AKAMedia\음악\처리됨\SUNO",
[string]$OutputRoot = "\\VaultOfData\AKAMedia\음악\처리됨",
[string]$ArtistName = "SUNO",
[string]$FfmpegPath = "",
[switch]$KeepWorkFiles
[switch]$KeepWorkFiles,
[switch]$Force
)
$ErrorActionPreference = "Stop"
@@ -64,6 +66,70 @@ function ConvertTo-SafeName {
return $name
}
function Get-ShortId {
param([string]$Id)
if (-not $Id) { return "unknown-id" }
if ($Id.Length -le 12) { return $Id }
return $Id.Substring(0, 12)
}
function Get-TrackFolder {
param(
[string]$Root,
[string]$Title,
[string]$Id
)
$safeTitle = ConvertTo-SafeName $Title $Id
$shortId = Get-ShortId $Id
return Join-Path $Root ("{0} [{1}]" -f $safeTitle, $shortId)
}
function Get-AlbumTitle {
param(
[string]$Title,
[string]$Id
)
return "{0} [{1}]" -f (ConvertTo-SafeName $Title $Id), (Get-ShortId $Id)
}
function Resolve-LibraryRoot {
param(
[string]$Root,
[string]$Artist
)
$trimmed = $Root.TrimEnd("\", "/")
if ((Split-Path -Leaf $trimmed) -ieq $Artist) {
return $trimmed
}
return Join-Path $trimmed $Artist
}
function Test-ExistingTrack {
param(
[string]$Root,
[string]$Id
)
if (-not $Id -or -not (Test-Path -LiteralPath $Root)) { return $false }
$metadataHit = Get-ChildItem -LiteralPath $Root -Recurse -Filter "*metadata.json" -File -ErrorAction SilentlyContinue |
Where-Object {
try {
$json = Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json
"$($json.id)" -eq $Id
} catch {
$false
}
} |
Select-Object -First 1
return [bool]$metadataHit
}
function Get-ObjectValues {
param($Value)
@@ -167,13 +233,13 @@ if (-not $tracks.Count) {
throw "manifest에 tracks 배열이 없어."
}
$dateFolder = Get-Date -Format "yyyy-MM-dd"
$targetRoot = Join-Path $OutputRoot $dateFolder
$workRoot = Join-Path $targetRoot "_work"
New-Item -ItemType Directory -Force -Path $targetRoot, $workRoot | Out-Null
$libraryRoot = Resolve-LibraryRoot $OutputRoot $ArtistName
New-Item -ItemType Directory -Force -Path $libraryRoot | Out-Null
$workRoot = Join-Path $libraryRoot "_work"
New-Item -ItemType Directory -Force -Path $workRoot | Out-Null
Write-Host "Manifest: $manifestFile"
Write-Host "Output: $targetRoot"
Write-Host "Output: $libraryRoot"
Write-Host "ffmpeg: $ffmpeg"
Write-Host "Tracks: $($tracks.Count)"
@@ -183,8 +249,18 @@ foreach ($track in $tracks) {
$id = if ($track.id) { "$($track.id)" } else { "" }
$title = if ($track.title) { "$($track.title)" } else { $id }
if (-not $Force -and (Test-ExistingTrack $libraryRoot $id)) {
Write-Host ""
Write-Host "[$index/$($tracks.Count)] 이미 있음, 건너뜀: $title [$id]"
continue
}
$albumTitle = Get-AlbumTitle $title $id
$trackFolder = Get-TrackFolder $libraryRoot $title $id
New-Item -ItemType Directory -Force -Path $trackFolder | Out-Null
$safeTitle = ConvertTo-SafeName $title $id
$prefix = "{0:D3} - {1}" -f $index, $safeTitle
$shortId = Get-ShortId $id
$prefix = "{0} [{1}]" -f $safeTitle, $shortId
$lyrics = Get-DeepValue $track @("prompt", "lyrics", "lyric", "text")
$style = Get-DeepValue $track @("gpt_description_prompt", "style", "tags", "description")
@@ -199,9 +275,10 @@ 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"
$coverFile = Join-Path $workRoot "$prefix.cover.$(Get-UrlExtension $imageUrl 'jpg')"
$metadataFile = Join-Path $targetRoot "$prefix.metadata.json"
$flacFile = Join-Path $targetRoot "$prefix.flac"
$rawCoverFile = Join-Path $workRoot "$prefix.cover.$(Get-UrlExtension $imageUrl 'jpg')"
$coverFile = Join-Path $trackFolder "cover.jpg"
$metadataFile = Join-Path $trackFolder "metadata.json"
$flacFile = Join-Path $trackFolder ("01 {0}.flac" -f $safeTitle)
Write-Host ""
Write-Host "[$index/$($tracks.Count)] $title"
@@ -219,7 +296,15 @@ foreach ($track in $tracks) {
$coverOk = $false
if ($imageUrl) {
$coverOk = Invoke-Download $imageUrl $coverFile
$coverDownloaded = Invoke-Download $imageUrl $rawCoverFile
if ($coverDownloaded) {
& $ffmpeg -hide_banner -y -i $rawCoverFile -frames:v 1 $coverFile
$coverOk = ($LASTEXITCODE -eq 0) -and (Test-Path -LiteralPath $coverFile)
if (-not $coverOk) {
Copy-Item -LiteralPath $rawCoverFile -Destination $coverFile -Force
$coverOk = Test-Path -LiteralPath $coverFile
}
}
}
$track | ConvertTo-Json -Depth 50 | Set-Content -LiteralPath $metadataFile -Encoding UTF8
@@ -240,21 +325,28 @@ foreach ($track in $tracks) {
$argsList.Add("attached_pic")
$argsList.Add("-c:v")
$argsList.Add("copy")
$argsList.Add("-metadata:s:v")
$argsList.Add("comment=Cover (front)")
}
$argsList.Add("-c:a")
$argsList.Add("flac")
$argsList.Add("-compression_level")
$argsList.Add("8")
Add-MetadataArg $argsList "title" $title
Add-MetadataArg $argsList "artist" "SUNO"
Add-MetadataArg $argsList "album" "SUNO"
Add-MetadataArg $argsList "artist" $ArtistName
Add-MetadataArg $argsList "album" $albumTitle
Add-MetadataArg $argsList "album_artist" $ArtistName
Add-MetadataArg $argsList "ARTISTS" $ArtistName
Add-MetadataArg $argsList "track" "1/1"
Add-MetadataArg $argsList "disc" "1/1"
Add-MetadataArg $argsList "genre" $style
Add-MetadataArg $argsList "lyrics" $lyrics
Add-MetadataArg $argsList "lyrics-XXX" $lyrics
Add-MetadataArg $argsList "description" $style
Add-MetadataArg $argsList "comment" "Suno clip id: $id"
Add-MetadataArg $argsList "date" $createdAt
Add-MetadataArg $argsList "encoder" "Codex SUNO helper"
Add-MetadataArg $argsList "suno_model" $model
Add-MetadataArg $argsList "TMED" "Digital Media"
$argsList.Add($flacFile)
& $ffmpeg @argsList
@@ -271,4 +363,4 @@ if (-not $KeepWorkFiles -and (Test-Path -LiteralPath $workRoot)) {
}
Write-Host ""
Write-Host "처리 완료: $targetRoot"
Write-Host "처리 완료: $libraryRoot"