param( [string]$LibraryRoot = "\\VaultOfData\AKAMedia\음악\처리됨\SUNO", [switch]$Apply ) $ErrorActionPreference = "Stop" function ConvertTo-SafeName { param([string]$Value) $name = if ($Value) { $Value } else { "suno-track" } $name = $name -replace '[\\/:*?"<>|]+', ' ' $name = $name -replace '\s+', ' ' $name = $name.Trim() if (-not $name) { $name = "suno-track" } if ($name.Length -gt 120) { $name = $name.Substring(0, 120).Trim() } 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-NextTrackNumber { param([string]$Folder) if (-not (Test-Path -LiteralPath $Folder)) { return 1 } $numbers = Get-ChildItem -LiteralPath $Folder -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in ".flac", ".mp3", ".m4a" } | ForEach-Object { if ($_.BaseName -match '^(\d{2})\s') { [int]$Matches[1] } } if (-not $numbers) { return 1 } return (($numbers | Measure-Object -Maximum).Maximum + 1) } if (-not (Test-Path -LiteralPath $LibraryRoot)) { throw "SUNO library root not found: $LibraryRoot" } $candidateDirs = Get-ChildItem -LiteralPath $LibraryRoot -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^(?.+)\s\[[0-9a-fA-F-]{8,}\]$' } foreach ($dir in $candidateDirs) { $metadata = Get-ChildItem -LiteralPath $dir.FullName -File -Filter "*.json" -ErrorAction SilentlyContinue | ForEach-Object { try { $json = Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json if ($json.id) { [pscustomobject]@{ File = $_; Json = $json } } } catch {} } | Select-Object -First 1 if (-not $metadata) { Write-Host "SKIP no metadata id: $($dir.FullName)" continue } $id = "$($metadata.Json.id)" $title = ConvertTo-SafeName "$($metadata.Json.title)" if (-not $title -and $dir.Name -match '^(?<title>.+)\s\[[0-9a-fA-F-]{8,}\]$') { $title = ConvertTo-SafeName $Matches.title } $shortId = Get-ShortId $id $targetDir = Join-Path $LibraryRoot $title $trackNo = Get-NextTrackNumber $targetDir $audio = Get-ChildItem -LiteralPath $dir.FullName -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in ".flac", ".mp3", ".m4a" } | Select-Object -First 1 $cover = Get-ChildItem -LiteralPath $dir.FullName -File -Filter "cover.*" -ErrorAction SilentlyContinue | Select-Object -First 1 Write-Host "" Write-Host "FROM: $($dir.FullName)" Write-Host "TO: $targetDir" Write-Host "ID: $id" if (-not $Apply) { Write-Host "DRY: would move audio/metadata/cover" continue } New-Item -ItemType Directory -Force -Path $targetDir | Out-Null if ($audio) { $newAudio = Join-Path $targetDir ("{0:D2} {1}{2}" -f $trackNo, $title, $audio.Extension) Move-Item -LiteralPath $audio.FullName -Destination $newAudio -Force Write-Host "audio -> $newAudio" } $newMetadata = Join-Path $targetDir ("metadata.{0}.json" -f $shortId) Move-Item -LiteralPath $metadata.File.FullName -Destination $newMetadata -Force Write-Host "metadata -> $newMetadata" if ($cover) { $coverTarget = Join-Path $targetDir "cover.jpg" if (-not (Test-Path -LiteralPath $coverTarget)) { Move-Item -LiteralPath $cover.FullName -Destination $coverTarget -Force Write-Host "cover -> $coverTarget" } } $remaining = Get-ChildItem -LiteralPath $dir.FullName -Force -ErrorAction SilentlyContinue if (-not $remaining) { Remove-Item -LiteralPath $dir.FullName -Force Write-Host "removed empty old folder" } else { Write-Host "old folder kept; remaining files exist" } }