#!/usr/bin/env python3 """ 《林夏》SFX 批量生成脚本(ACE-Step 1.5) 生成环境氛围类、过渡类、结局类音效 非 ACE-Step 的短促 UI 音效(叮、咚、震动)需另行处理, 本脚本专注可由 ACE-Step 生成的 ≥5s 氛围类音效。 用法: conda activate acestep python audio/gen_sfx_acestep.py [--dry-run] """ import os, sys, time, argparse 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/sfx" AMB_DIR = "/home/xsl/blind/audio/00_raw/ambience" os.makedirs(OUT_DIR, exist_ok=True) os.makedirs(AMB_DIR, exist_ok=True) sys.path.insert(0, REPO_DIR) # ── 氛围类音效(ACE-Step 可生成)───────────────────────────── SFX_TRACKS = [ # 过渡 / 转场 { "filename": "sfx_transition_chapter", "dir": OUT_DIR, "duration": 8, "caption": "cinematic transition swell, soft orchestral, gentle rise and fall, cinematic fade", "note": "章节过渡轻 swell", "seed": 2001, }, { "filename": "sfx_heartbeat_fast", "dir": OUT_DIR, "duration": 8, "caption": "human heartbeat accelerating, tension building, thumping close mic, anxiety", "note": "心跳加速", "seed": 2002, }, { "filename": "sfx_deep_breath", "dir": OUT_DIR, "duration": 6, "caption": "single deep breath, calm, close mic, soft exhale, meditative", "note": "深呼吸", "seed": 2003, }, { "filename": "sfx_rain_heavier", "dir": OUT_DIR, "duration": 8, "caption": "rain suddenly intensifying, window glass, urban rain, dramatic shift", "note": "雨势忽大", "seed": 2004, }, { "filename": "sfx_rain_lighter", "dir": OUT_DIR, "duration": 8, "caption": "rain gradually softening, distant window, fading urban rain, peaceful", "note": "雨势忽小", "seed": 2005, }, { "filename": "sfx_glass_shatter_short", "dir": OUT_DIR, "duration": 5, "caption": "glass breaking, single crash, sharp impact, quiet aftermath", "note": "玻璃碎一秒", "seed": 2006, }, # 结局专用 { "filename": "sfx_ending_chime", "dir": OUT_DIR, "duration": 5, "caption": "soft chime, title card reveal, delicate bells, warm resonance, fade out", "note": "字幕浮现 chime", "seed": 2007, }, { "filename": "sfx_night_bus", "dir": OUT_DIR, "duration": 10, "caption": "late night bus passing on empty street, distant rumble, urban quiet", "note": "夜班车驶过", "seed": 2008, }, { "filename": "sfx_cat_yowl", "dir": OUT_DIR, "duration": 5, "caption": "single cat yowl distant alley, late night, brief, natural", "note": "野猫叫一声", "seed": 2009, }, { "filename": "sfx_breathing_unknown", "dir": OUT_DIR, "duration": 8, "caption": "quiet human breathing through phone, slightly muffled, anonymous, eerie stillness", "note": "未知号码呼吸", "seed": 2010, }, # 拟音 { "filename": "sfx_kettle_whistle", "dir": OUT_DIR, "duration": 6, "caption": "kettle whistling, close mic, small kitchen, vintage, building steam", "note": "烧水壶哨响", "seed": 2011, }, { "filename": "sfx_water_boiling", "dir": OUT_DIR, "duration": 8, "caption": "water boiling in kettle, bubbling, close mic, kitchen ambience", "note": "水沸腾", "seed": 2012, }, { "filename": "sfx_keyboard_slow", "dir": OUT_DIR, "duration": 6, "caption": "slow thoughtful keyboard typing, soft mechanical, contemplative rhythm", "note": "键盘慢打", "seed": 2013, }, { "filename": "sfx_keyboard_fast", "dir": OUT_DIR, "duration": 5, "caption": "fast keyboard typing, mechanical, busy, rapid rhythm, office", "note": "键盘快打", "seed": 2014, }, { "filename": "sfx_door_knock_gentle", "dir": OUT_DIR, "duration": 5, "caption": "gentle three knocks on apartment door, polite delivery, hallway echo", "note": "外卖敲门(轻)", "seed": 2015, }, { "filename": "sfx_door_knock_firm", "dir": OUT_DIR, "duration": 5, "caption": "firm knocking on apartment door, impatient, louder, hallway", "note": "外卖敲门(响)", "seed": 2016, }, { "filename": "sfx_phone_vibrate", "dir": OUT_DIR, "duration": 5, "caption": "phone vibrating on wooden bedside table, buzzing, close mic, 2 bursts", "note": "手机震动桌面", "seed": 2017, }, { "filename": "sfx_paper_turning", "dir": OUT_DIR, "duration": 5, "caption": "paper pages turning, quiet desk, gentle rustling, close mic", "note": "纸张翻动", "seed": 2018, }, ] # ── 环境底噪(单独输出到 ambience/)──────────────────────────── AMBIENCE_TRACKS = [ { "filename": "amb_apartment_night_01", "dir": AMB_DIR, "duration": 120, "caption": ( "late night apartment ambience, very quiet, distant traffic, " "refrigerator hum, occasional water pipe, urban residential, " "stereo, immersive, Beijing night" ), "note": "合租屋夜晚底噪 (2min loop段)", "seed": 3001, }, { "filename": "amb_apartment_night_02", "dir": AMB_DIR, "duration": 120, "caption": ( "quiet Beijing apartment at night, minimal sounds, " "faint city noise, soft hum, occasional distant car, " "late 2020s urban residential night ambience" ), "note": "合租屋夜晚底噪备用", "seed": 3002, }, { "filename": "amb_bar_loud_01", "dir": AMB_DIR, "duration": 60, "caption": ( "crowded bar ambience, people talking laughing, " "background electronic music, glasses clinking, " "busy nightclub atmosphere, lively" ), "note": "酒吧嘈杂环境", "seed": 3003, }, { "filename": "amb_rain_window_01", "dir": AMB_DIR, "duration": 120, "caption": ( "gentle rain on window, soft steady patter, " "cozy indoor feeling, night rain, Beijing apartment, " "stereo, immersive" ), "note": "窗外细雨(BGM M1 衬底)", "seed": 3004, }, { "filename": "amb_kitchen_morning", "dir": AMB_DIR, "duration": 60, "caption": ( "morning kitchen sounds, water dripping tap, " "refrigerator hum, quiet domestic morning, " "natural, realistic" ), "note": "厨房晨间(结局A)", "seed": 3005, }, { "filename": "amb_dawn_birds", "dir": AMB_DIR, "duration": 30, "caption": ( "dawn birds chirping, urban morning, early light, " "sparse birdsong, peaceful city dawn" ), "note": "清晨鸟叫(结局D)", "seed": 3006, }, ] ALL_TRACKS = SFX_TRACKS + AMBIENCE_TRACKS def load_handler(): 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): from acestep.inference import GenerationParams, GenerationConfig, generate_music out_dir = track["dir"] filename = track["filename"] out_path = os.path.join(out_dir, f"{filename}_v1.wav") if os.path.exists(out_path): print(f" [跳过] {filename}") return "skip" note = track.get("note", "") print(f" {filename} [{note}] {track['duration']}s ...", end=" ", flush=True) params = GenerationParams( caption=track["caption"], lyrics="", duration=track["duration"], instrumental=True, inference_steps=20, seed=track.get("seed", 42), ) config = GenerationConfig(batch_size=1, audio_format="wav") t0 = time.time() try: result = generate_music(handler, None, params, config, save_dir=out_dir) if result.success: gen_path = result.audios[0]["path"] if gen_path != out_path: os.rename(gen_path, out_path) print(f"✓ {time.time()-t0:.1f}s") return "ok" else: print(f"✗ {result.error}") return "fail" except Exception as e: print(f"✗ {e}") return "fail" def main(): parser = argparse.ArgumentParser() parser.add_argument("--dry-run", action="store_true") parser.add_argument("--category", choices=["sfx", "ambience", "all"], default="all") args = parser.parse_args() tracks = [] if args.category in ("sfx", "all"): tracks += SFX_TRACKS if args.category in ("ambience", "all"): tracks += AMBIENCE_TRACKS print(f"\n{'='*60}") print(f"《林夏》SFX + 环境底噪生成 共 {len(tracks)} 条") print(f"SFX 输出: {OUT_DIR}") print(f"环境 输出: {AMB_DIR}") print(f"{'='*60}") if args.dry_run: for t in tracks: out = os.path.join(t["dir"], f"{t['filename']}_v1.wav") flag = "✓" if os.path.exists(out) else "○" print(f" [{flag}] {t['filename']} {t['duration']}s {t.get('note','')}") return handler = load_handler() ok = skip = fail = 0 for track in tracks: r = generate_one(handler, track) if r == "ok": ok += 1 elif r == "skip": skip += 1 else: fail += 1 print(f"\n{'='*60}") print(f"完成 {ok} 跳过 {skip} 失败 {fail}") print(f"{'='*60}") if __name__ == "__main__": main()