252 lines
8.1 KiB
Python
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()
|