Add ACE-Step music generation to the studio and library.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-02 23:58:18 -05:00
co-authored by Cursor
parent c79cca39ae
commit 5555155884
25 changed files with 2157 additions and 44 deletions
+322
View File
@@ -0,0 +1,322 @@
# Download ACE-Step / YuE weights into the local ComfyUI models tree via Hugging Face CLI.
# Example:
# powershell -ExecutionPolicy Bypass -File scripts\download_music_models.ps1
# powershell -File scripts\download_music_models.ps1 -ComfyRoot C:\ComfyUI -SkipYue
[CmdletBinding()]
param(
[string]$ComfyRoot = '',
[switch]$SkipAceStep,
[switch]$SkipAceStep15,
[switch]$SkipYue,
[switch]$Force
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'Continue'
function Write-Step([string]$Message) {
Write-Host ""
Write-Host "==> $Message" -ForegroundColor Cyan
}
function Write-Ok([string]$Message) {
Write-Host " $Message" -ForegroundColor Green
}
function Write-Skip([string]$Message) {
Write-Host " skip $Message" -ForegroundColor DarkYellow
}
function Write-Warn([string]$Message) {
Write-Host " warn $Message" -ForegroundColor Yellow
}
function Resolve-ComfyRoot([string]$Hint) {
$candidates = @(
$Hint,
$env:COMFY_ROOT,
$env:COMFYUI_ROOT,
'C:\ComfyUI',
'C:\ComfyUI_windows_portable\ComfyUI',
'D:\ComfyUI',
'C:\ComfyUI-MiniMax\ComfyUI',
(Join-Path $env:USERPROFILE 'ComfyUI'),
(Join-Path $env:LOCALAPPDATA 'Comfy-Desktop\ComfyUI-Installs\ComfyUI (1)\ComfyUI'),
(Join-Path $env:LOCALAPPDATA 'Comfy-Desktop\ComfyUI-Installs\ComfyUI\ComfyUI')
) | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\', '/') }
foreach ($path in $candidates) {
if (Test-Path (Join-Path $path 'main.py')) { return (Resolve-Path $path).Path }
if (Test-Path (Join-Path $path 'ComfyUI\main.py')) { return (Resolve-Path (Join-Path $path 'ComfyUI')).Path }
}
Write-Host "Could not find ComfyUI (looked for main.py)." -ForegroundColor Red
$typed = Read-Host "Enter ComfyUI root path (or blank to abort)"
if (-not $typed) { throw "COMFY_ROOT is required." }
$typed = $typed.Trim().Trim('"')
if (-not (Test-Path (Join-Path $typed 'main.py'))) {
throw "Not a ComfyUI root (missing main.py): $typed"
}
return (Resolve-Path $typed).Path
}
function Get-DirBytes([string]$Path) {
if (-not (Test-Path $Path)) { return [long]0 }
$item = Get-Item $Path
if (-not $item.PSIsContainer) { return [long]$item.Length }
[long]((Get-ChildItem $Path -Recurse -File -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum)
}
function Format-Bytes([long]$Bytes) {
if ($Bytes -ge 1GB) { return ('{0:N2} GB' -f ($Bytes / 1GB)) }
if ($Bytes -ge 1MB) { return ('{0:N1} MB' -f ($Bytes / 1MB)) }
if ($Bytes -ge 1KB) { return ('{0:N0} KB' -f ($Bytes / 1KB)) }
return "$Bytes B"
}
function Test-VerifiedTarget([string]$Path, [long]$MinBytes = 1MB) {
if (-not (Test-Path $Path)) { return $false }
$item = Get-Item $Path
if (-not $item.PSIsContainer) { return $item.Length -ge $MinBytes }
$files = Get-ChildItem $Path -Recurse -File -ErrorAction SilentlyContinue
if (-not $files) { return $false }
$total = [long]($files | Measure-Object Length -Sum).Sum
return $total -ge $MinBytes
}
function Ensure-Dir([string]$Path) {
if (-not (Test-Path $Path)) {
New-Item -ItemType Directory -Force -Path $Path | Out-Null
}
}
function Find-HfCli {
foreach ($name in @('hf', 'huggingface-cli')) {
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
}
return $null
}
function Ensure-HfCli {
$cli = Find-HfCli
if ($cli) {
Write-Ok "Hugging Face CLI: $cli"
return $cli
}
Write-Warn "Hugging Face CLI not found (hf / huggingface-cli)."
$answer = Read-Host "Install huggingface_hub now (Y/n)"
if ($answer -and $answer -notmatch '^(y|yes)$') {
throw "Hugging Face CLI is required. Install with: pip install -U huggingface_hub"
}
$pip = Get-Command pip -ErrorAction SilentlyContinue
$py = Get-Command python -ErrorAction SilentlyContinue
if ($pip) {
Write-Step "Installing huggingface_hub via pip"
& $pip.Source install -U huggingface_hub
} elseif ($py) {
Write-Step "Installing huggingface_hub via python -m pip"
& $py.Source -m pip install -U huggingface_hub
} else {
throw "pip/python not on PATH. Install Python, then: pip install -U huggingface_hub"
}
$cli = Find-HfCli
if (-not $cli) { throw "huggingface_hub installed but hf / huggingface-cli still not on PATH. Open a new terminal and retry." }
Write-Ok "Hugging Face CLI: $cli"
return $cli
}
function Invoke-HfDownload {
param(
[Parameter(Mandatory)] [string]$Cli,
[Parameter(Mandatory)] [string]$Repo,
[Parameter(Mandatory)] [string]$LocalDir,
[string[]]$Include,
[string]$File
)
Ensure-Dir $LocalDir
$exe = Split-Path $Cli -Leaf
$args = @()
if ($exe -eq 'huggingface-cli.exe' -or $exe -eq 'huggingface-cli') {
$args += @('download', $Repo)
} else {
$args += @('download', $Repo)
}
if ($File) { $args += $File }
$args += @('--local-dir', $LocalDir)
foreach ($pattern in $Include) {
$args += @('--include', $pattern)
}
Write-Host " hf $($args -join ' ')" -ForegroundColor DarkGray
& $Cli @args
if ($LASTEXITCODE -ne 0) {
throw "hf download failed for $Repo (exit $LASTEXITCODE)"
}
}
function Place-File([string]$Source, [string]$Dest) {
Ensure-Dir (Split-Path $Dest -Parent)
if ((Test-Path $Source) -and ((Resolve-Path $Source).Path -ne (Join-Path (Split-Path $Dest -Parent) (Split-Path $Dest -Leaf)))) {
Copy-Item -Force $Source $Dest
}
}
$root = Resolve-ComfyRoot $ComfyRoot
$cli = Ensure-HfCli
$checkpoints = Join-Path $root 'models\checkpoints'
$diffusion = Join-Path $root 'models\diffusion_models'
$textEncoders = Join-Path $root 'models\text_encoders'
$vae = Join-Path $root 'models\vae'
$ttsAce = Join-Path $root 'models\TTS\ACE-Step-v1-3.5B'
$customAce = Join-Path $root 'custom_nodes\ComfyUI-ACE-Step\models'
$yueRoot = Join-Path $root 'models\YuE'
$yueCustom = Join-Path $root 'custom_nodes\ComfyUI-YuE\models'
Write-Step "ComfyUI root"
Write-Host " $root"
Write-Host " checkpoints $checkpoints"
Write-Host " ACE-Step TTS $ttsAce"
Write-Host " ACE-Step custom $customAce"
Write-Host " YuE $yueRoot"
$before = Get-DirBytes (Join-Path $root 'models')
$downloaded = [System.Collections.Generic.List[string]]::new()
$skipped = [System.Collections.Generic.List[string]]::new()
function Pull-IfMissing {
param(
[string]$Label,
[string]$Target,
[long]$MinBytes,
[scriptblock]$Download
)
if (-not $Force -and (Test-VerifiedTarget $Target $MinBytes)) {
Write-Skip "$Label already present ($((Format-Bytes (Get-DirBytes $Target)))) — $Target"
$skipped.Add($Label) | Out-Null
return
}
Write-Step $Label
& $Download
if (-not (Test-VerifiedTarget $Target $MinBytes)) {
throw "Download finished but $Target is missing or too small."
}
Write-Ok "$Label ready ($((Format-Bytes (Get-DirBytes $Target))))"
$downloaded.Add($Label) | Out-Null
}
if (-not $SkipAceStep) {
$aio = Join-Path $checkpoints 'ace_step_v1_3.5b.safetensors'
Pull-IfMissing -Label 'ACE-Step v1 3.5B (native AIO checkpoint)' -Target $aio -MinBytes 1GB -Download {
Ensure-Dir $checkpoints
$tmp = Join-Path $env:TEMP 'aigen-ace-step-aio'
Ensure-Dir $tmp
Invoke-HfDownload -Cli $cli -Repo 'Comfy-Org/ACE-Step_ComfyUI_repackaged' -LocalDir $tmp -File 'all_in_one/ace_step_v1_3.5b.safetensors'
$found = Get-ChildItem $tmp -Recurse -Filter 'ace_step_v1_3.5b.safetensors' | Select-Object -First 1
if (-not $found) { throw 'AIO checkpoint not in Hugging Face download.' }
Place-File $found.FullName $aio
}
Pull-IfMissing -Label 'ACE-Step v1 3.5B (split transformer / vocoder / UMT5)' -Target $ttsAce -MinBytes 2GB -Download {
Invoke-HfDownload -Cli $cli -Repo 'ACE-Step/ACE-Step-v1-3.5B' -LocalDir $ttsAce -Include @('*.safetensors', '*.json')
}
if (Test-Path (Split-Path $customAce -Parent)) {
Pull-IfMissing -Label 'ACE-Step custom-node models copy' -Target $customAce -MinBytes 2GB -Download {
Ensure-Dir $customAce
Copy-Item -Recurse -Force (Join-Path $ttsAce '*') $customAce
}
} else {
Write-Skip "ComfyUI-ACE-Step custom node not installed — split weights stay in models\TTS"
}
}
if (-not $SkipAceStep15) {
$aio15 = Join-Path $checkpoints 'ace_step_1.5_turbo_aio.safetensors'
Pull-IfMissing -Label 'ACE-Step 1.5 Turbo AIO checkpoint' -Target $aio15 -MinBytes 500MB -Download {
Ensure-Dir $checkpoints
$tmp = Join-Path $env:TEMP 'aigen-ace-step-15-aio'
Ensure-Dir $tmp
Invoke-HfDownload -Cli $cli -Repo 'Comfy-Org/ace_step_1.5_ComfyUI_files' -LocalDir $tmp -Include @('*aio*.safetensors', '*turbo_aio*')
$found = Get-ChildItem $tmp -Recurse -Filter '*.safetensors' | Where-Object { $_.Name -match 'aio' } | Select-Object -First 1
if (-not $found) {
Invoke-HfDownload -Cli $cli -Repo 'Comfy-Org/ace_step_1.5_ComfyUI_files' -LocalDir $tmp
$found = Get-ChildItem $tmp -Recurse -Filter '*.safetensors' | Where-Object { $_.Name -match 'aio|1\.5' } | Sort-Object Length -Descending | Select-Object -First 1
}
if (-not $found) { throw 'ACE-Step 1.5 AIO safetensors not found in repo. Download split files instead, or update the repo layout.' }
Place-File $found.FullName $aio15
}
$diff15 = Join-Path $diffusion 'acestep_v1.5_turbo.safetensors'
$enc06 = Join-Path $textEncoders 'qwen_0.6b_ace15.safetensors'
$enc17 = Join-Path $textEncoders 'qwen_1.7b_ace15.safetensors'
$vae15 = Join-Path $vae 'ace_1.5_vae.safetensors'
$splitOk = (Test-VerifiedTarget $diff15 200MB) -and (Test-VerifiedTarget $enc06 50MB) -and (Test-VerifiedTarget $vae15 20MB)
if ($Force -or -not $splitOk) {
Write-Step 'ACE-Step 1.5 split files (diffusion / text encoder / VAE)'
Ensure-Dir $diffusion
Ensure-Dir $textEncoders
Ensure-Dir $vae
$tmp = Join-Path $env:TEMP 'aigen-ace-step-15-split'
Ensure-Dir $tmp
Invoke-HfDownload -Cli $cli -Repo 'Comfy-Org/ace_step_1.5_ComfyUI_files' -LocalDir $tmp -Include @('*.safetensors', '*.json')
foreach ($pair in @(
@{ Match = 'acestep_v1.5_turbo'; Dest = $diff15 },
@{ Match = 'qwen_0.6b_ace15'; Dest = $enc06 },
@{ Match = 'qwen_1.7b_ace15'; Dest = $enc17 },
@{ Match = 'ace_1.5_vae'; Dest = $vae15 }
)) {
$hit = Get-ChildItem $tmp -Recurse -Filter '*.safetensors' | Where-Object { $_.Name -like "*$($pair.Match)*" } | Select-Object -First 1
if ($hit) { Place-File $hit.FullName $pair.Dest; Write-Ok $hit.Name }
else { Write-Warn "Did not find $($pair.Match)*.safetensors in the 1.5 repo" }
}
$downloaded.Add('ACE-Step 1.5 split files') | Out-Null
} else {
Write-Skip "ACE-Step 1.5 split files already present"
$skipped.Add('ACE-Step 1.5 split files') | Out-Null
}
}
if (-not $SkipYue) {
$s1 = Join-Path $yueRoot 'YuE-s1-7B-anneal-en-cot'
$s2 = Join-Path $yueRoot 'YuE-s2-1B-general'
$codec = Join-Path $yueRoot 'xcodec_mini_infer'
Pull-IfMissing -Label 'YuE stage-1 (s1 7B anneal EN CoT)' -Target $s1 -MinBytes 1GB -Download {
Invoke-HfDownload -Cli $cli -Repo 'm-a-p/YuE-s1-7B-anneal-en-cot' -LocalDir $s1 -Include @('*.safetensors', '*.bin', '*.json', '*.model', '*.txt')
}
Pull-IfMissing -Label 'YuE stage-2 (s2 1B general)' -Target $s2 -MinBytes 200MB -Download {
Invoke-HfDownload -Cli $cli -Repo 'm-a-p/YuE-s2-1B-general' -LocalDir $s2 -Include @('*.safetensors', '*.bin', '*.json', '*.model', '*.txt')
}
Pull-IfMissing -Label 'YuE vocoder (xcodec_mini_infer)' -Target $codec -MinBytes 20MB -Download {
Invoke-HfDownload -Cli $cli -Repo 'm-a-p/xcodec_mini_infer' -LocalDir $codec -Include @('*.safetensors', '*.pth', '*.pt', '*.json', '*.yaml', '*.txt')
}
if (Test-Path (Split-Path $yueCustom -Parent)) {
Pull-IfMissing -Label 'YuE custom-node models copy' -Target $yueCustom -MinBytes 1GB -Download {
Ensure-Dir $yueCustom
Copy-Item -Recurse -Force (Join-Path $yueRoot '*') $yueCustom
}
} else {
Write-Skip "ComfyUI-YuE custom node not installed — YuE weights stay in models\YuE"
}
}
$after = Get-DirBytes (Join-Path $root 'models')
$delta = [Math]::Max(0, $after - $before)
Write-Host ""
Write-Host "------------------------------------------------------------" -ForegroundColor DarkGray
Write-Host "Downloaded: $($downloaded.Count)" -ForegroundColor Green
foreach ($item in $downloaded) { Write-Host " + $item" -ForegroundColor Green }
Write-Host "Skipped (already verified): $($skipped.Count)" -ForegroundColor DarkYellow
foreach ($item in $skipped) { Write-Host " = $item" -ForegroundColor DarkYellow }
Write-Host "Models tree size: $(Format-Bytes $after) (this run +$(Format-Bytes $delta))" -ForegroundColor Cyan
Write-Host "Restart ComfyUI if it was running so new checkpoints appear." -ForegroundColor DarkGray
Write-Host "------------------------------------------------------------" -ForegroundColor DarkGray