Files
blind/audio/gen_music_acestep.py
T
2026-05-18 18:39:49 +08:00

252 lines
8.1 KiB
Python

#!/usr/bin/env python3
"""
《林夏》BGM + 角色铃声 批量生成脚本
使用 ACE-Step 1.5 本地模型
用法:
conda activate acestep
python audio/gen_music_acestep.py [--category bgm|ringtone|all] [--dry-run]
"""
import os, sys, time, argparse
# ── ACE-Step 修复(RTX 5090 cuBLAS)──────────────────────────
import torch
torch.backends.cuda.preferred_blas_library("cublaslt")
REPO_DIR = "/home/xsl/tools/ACE-Step-1.5"
OUT_DIR = "/home/xsl/blind/audio/00_raw/music"
os.makedirs(OUT_DIR, exist_ok=True)
sys.path.insert(0, REPO_DIR)
# ── BGM 四段 ─────────────────────────────────────────────────
BGM_TRACKS = [
{
"filename": "lv11_bgm_m1_main_loop",
"duration": 90,
"caption": (
"lo-fi midnight bedroom, soft piano, light vinyl crackle, "
"muted piano, subtle synth pad, occasional distant rain, "
"melancholy but warm, alone but not lonely, "
"ambient loop, no drums, no clear melody peak, 70 bpm"
),
"lyrics": "",
"steps": 20,
"seed": 2103,
"note": "M1 · 21:00 主题 Loop",
},
{
"filename": "lv11_bgm_m2_suspense",
"duration": 30,
"caption": (
"subtle ambient suspense, cinematic tension, "
"low drone, single hanging piano note high register, sparse strings, "
"unsettling but not horror, like a question mark, "
"builds slowly, ends on unresolved chord, 60 bpm"
),
"lyrics": "",
"steps": 20,
"seed": 2230,
"note": "M2 · 22:30 悬疑过门",
},
{
"filename": "lv11_bgm_m3_zhounan",
"duration": 120,
"caption": (
"gentle indie folk, intimate acoustic, "
"fingerpicked nylon guitar, wordless soft humming no lyrics just mmm, "
"light shaker, hopeful nostalgic like reading an old letter, "
"gradual build soft climax at 80 seconds gentle outro, 75 bpm"
),
"lyrics": "",
"steps": 25,
"seed": 2345,
"note": "M3 · 23:45 周南段落",
},
{
"filename": "lv11_bgm_m4_ending",
"duration": 90,
"caption": (
"warm cinematic piano, dawn moment, solo piano, "
"soft string pad entering at 30 seconds, "
"like the moment you realize you survived a hard night, "
"single melody line, no drums, fade out, 50 bpm"
),
"lyrics": "",
"steps": 20,
"seed": 200,
"note": "M4 · 02:00 结尾",
},
]
# ── 角色铃声 9 个 ──────────────────────────────────────────────
RINGTONES = [
{
"filename": "lv11_ring_mom",
"duration": 6,
"caption": "warm marimba ringtone, simple 4-note motif, old Nokia style, 70 bpm, loop",
"note": "妈妈铃声",
"seed": 1001,
},
{
"filename": "lv11_ring_azhe",
"duration": 5,
"caption": "cold minimal electronic ringtone, 2 notes only, distant, 80 bpm, loop",
"note": "阿哲铃声",
"seed": 1002,
},
{
"filename": "lv11_ring_xiaomei",
"duration": 5,
"caption": "cheerful pop ringtone, ukulele plucks, 4-bar phrase, energetic, 100 bpm, loop",
"note": "小美铃声",
"seed": 1003,
},
{
"filename": "lv11_ring_zhounan",
"duration": 5,
"caption": "nostalgic 90s chime, soft marimba, gentle, 70 bpm, loop",
"note": "周南铃声",
"seed": 1004,
},
{
"filename": "lv11_ring_hr",
"duration": 5,
"caption": "corporate professional ringtone, neutral marimba, clean, 90 bpm, loop",
"note": "HR王姐铃声",
"seed": 1005,
},
{
"filename": "lv11_ring_anan",
"duration": 4,
"caption": "bouncy J-pop intro ringtone, bright, energetic, 110 bpm, loop",
"note": "安安铃声",
"seed": 1006,
},
{
"filename": "lv11_ring_dorm_group",
"duration": 5,
"caption": "soft group notification chime, friendly, warm bells, 80 bpm, loop",
"note": "寝室群铃声",
"seed": 1007,
},
{
"filename": "lv11_ring_unknown",
"duration": 6,
"caption": "eerie low drone ringtone, no melody, unsettling, 40 bpm, dark ambient, loop",
"note": "未知号码铃声",
"seed": 1008,
},
{
"filename": "lv11_ring_delivery",
"duration": 3,
"caption": "short delivery notification beep, simple two-tone, neutral, 90 bpm",
"note": "外卖铃声",
"seed": 1009,
},
]
def load_handler():
"""初始化 ACE-Step handler(带 cublaslt 修复)"""
from acestep.handler import AceStepHandler
print("正在加载 ACE-Step 模型...", end=" ", flush=True)
t0 = time.time()
handler = AceStepHandler()
handler.initialize_service(
project_root=REPO_DIR,
config_path="acestep-v15-turbo",
device="cuda",
)
print(f"就绪 ({time.time()-t0:.1f}s) dtype={handler.dtype}")
return handler
def generate_one(handler, track, is_ringtone=False):
"""生成单条音频,返回输出路径或 None"""
from acestep.inference import GenerationParams, GenerationConfig, generate_music
filename = track["filename"]
out_path = os.path.join(OUT_DIR, f"{filename}_v1.wav")
if os.path.exists(out_path):
print(f" [跳过] {filename}(已存在)")
return out_path
note = track.get("note", "")
print(f" 生成: {filename} [{note}] {track['duration']}s ...", end=" ", flush=True)
params = GenerationParams(
caption=track["caption"],
lyrics=track.get("lyrics", ""),
duration=track["duration"],
instrumental=True,
inference_steps=track.get("steps", 20),
seed=track.get("seed", 42),
)
from acestep.inference import GenerationConfig
config = GenerationConfig(batch_size=1, audio_format="wav")
t0 = time.time()
try:
result = generate_music(handler, None, params, config, save_dir=OUT_DIR)
elapsed = time.time() - t0
if result.success:
gen_path = result.audios[0]["path"]
# 重命名为规范名称
if gen_path != out_path:
os.rename(gen_path, out_path)
print(f"✓ {elapsed:.1f}s → {os.path.basename(out_path)}")
return out_path
else:
print(f"✗ {result.error}")
return None
except Exception as e:
print(f"✗ {e}")
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--category", choices=["bgm", "ringtone", "all"], default="all")
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
all_tracks = []
if args.category in ("bgm", "all"):
all_tracks += [("bgm", t) for t in BGM_TRACKS]
if args.category in ("ringtone", "all"):
all_tracks += [("ringtone", t) for t in RINGTONES]
print(f"\n{'='*60}")
print(f"《林夏》音乐生成 共 {len(all_tracks)} 条")
print(f"输出目录: {OUT_DIR}")
print(f"{'='*60}")
if args.dry_run:
for cat, t in all_tracks:
exists = "✓" if os.path.exists(os.path.join(OUT_DIR, f"{t['filename']}_v1.wav")) else "○"
print(f" [{exists}][{cat}] {t['filename']} {t['duration']}s {t.get('note','')}")
return
handler = load_handler()
ok, skip, fail = 0, 0, 0
for cat, track in all_tracks:
result = generate_one(handler, track, is_ringtone=(cat == "ringtone"))
if result is None:
fail += 1
elif "跳过" in str(result):
skip += 1
else:
ok += 1
print(f"\n{'='*60}")
print(f"完成 {ok} 跳过 {skip} 失败 {fail}")
print(f"输出: {OUT_DIR}")
print(f"{'='*60}")
if __name__ == "__main__":
main()