music3-mac16 / cli.py
aaaholics's picture
Initial public release of Music3-Mac16
dd63733 verified
Raw History Blame Contribute Delete
5.4 kB
"""cli.py — 项目入口 (PM v0.2 §10)。
命令:
python cli.py baseline --seconds 5 # Milestone 1: 跑通并测量
python cli.py generate "描述" --seconds 30 # 直接生成 (阶段驻留管线)
python cli.py idea "想法" --seconds 30 # 完整 Idea→Music (重写 caption + 生成)
python cli.py webui # 启动 WebUI (浏览器测试入口)
"""
from __future__ import annotations
import sys
from pathlib import Path
import click
ROOT = Path(__file__).parent
@click.group()
def main() -> None:
"""Music3-Mac16: MiniMax Music3 on 16GB Apple Silicon Mac."""
@main.command()
@click.option("--host", default="127.0.0.1", help="监听地址")
@click.option("--port", default=8642, type=int, help="端口")
def webui(host: str, port: int) -> None:
"""启动 WebUI (浏览器测试入口)。
用法: music-minimax webui → 打开 http://127.0.0.1:8642
"""
import webbrowser
from webui.app import start
url = f"http://{host}:{port}"
click.echo(f"启动 WebUI: {url}")
webbrowser.open(url)
start(host=host, port=port)
@main.command()
@click.option("--seconds", default=5, type=click.IntRange(1, 60), help="目标生成秒数")
@click.option("--label", default="baseline", help="本次运行的标签(写进 profile json)")
def baseline(seconds: int, label: str) -> None:
"""Milestone 1: Baseline — 跑通现有 MLX Music3 并记录峰值内存/速度/正确性。"""
from generation.pipeline import BaselineRunner
runner = BaselineRunner(label=label)
runner.run(target_seconds=seconds)
@main.command()
@click.argument("prompt")
@click.option("--seconds", default=15, type=click.IntRange(1, 240), help="目标生成秒数")
@click.option("--lyrics", default=None, help="结构化歌词 (可选, 缺省为 [instrumental])")
@click.option("--steps", default=30, type=click.IntRange(1, 50), help="Flow-matching 步数")
@click.option("--seed", default=0, type=int, help="随机种子")
@click.option("--label", default="idea", help="输出目录标签")
@click.option("--model", default="models/runtime/MiniMax-Music3-4bit",
help="模型路径 (4bit / mxfp8 / 8bit 等 mlx-community 变体)")
@click.option("--experimental-long-form", is_flag=True,
help="显式开启 120/180/240 秒实验路径,固定使用 flush=32")
def generate(prompt: str, seconds: int, lyrics: str | None, steps: int, seed: int,
label: str, model: str, experimental_long_form: bool) -> None:
"""Idea→Music: 从自然语言描述生成 WAV(阶段驻留管线)。
用法: music-minimax generate "dark melodic techno, 128 BPM" --seconds 30
music-minimax generate "pop ballad" --model models/runtime/MiniMax-Music3-mxfp8
"""
from generation.phased_pipeline import PhasedPipeline
pipeline = PhasedPipeline(label=label, model_path=model)
output = None
if experimental_long_form:
long_form_dir = ROOT / "outputs" / "long_form_test" / f"{seconds}s"
output = str(long_form_dir / "output.wav")
report = pipeline.generate(
caption=prompt,
lyrics=lyrics or "[instrumental]",
duration=seconds,
steps=steps,
seed=seed,
output=output,
experimental_long_form=experimental_long_form,
)
click.echo(f"✓ 生成完成: {report['output_wav']}")
click.echo(f" 峰值内存: {report['max_stage_peak_gb']} GB (预算 11.5GB)")
click.echo(f" AR 阶段 : {report['ar_peak_gb']} GB | Flow 阶段: {report['flow_peak_gb']} GB")
@main.command()
@click.argument("idea")
@click.option("--lyrics", default=None, help="可选歌词 (带段落标签)")
@click.option("--seconds", default=30, type=click.IntRange(1, 60), help="目标生成秒数")
@click.option("--rewriter-model", default=None,
help="本地重写模型 (默认 mlx-community/Qwen3-8B-4bit, 首次自动下载)")
@click.option("--label", default="idea_to_music", help="输出目录标签")
@click.option("--dry-run", is_flag=True, help="只重写 caption 不生成音乐")
def idea(idea: str, lyrics: str | None, seconds: int, rewriter_model: str | None,
label: str, dry_run: bool) -> None:
"""Idea→Music 完整工作流: 想法 → caption 重写 → 本地生成 WAV。
用法: music-minimax idea "dark melodic techno with female airy vocal, lonely"
"""
from caption.rewriter import rewrite_caption
from generation.phased_pipeline import PhasedPipeline
click.echo("① 重写想法 → 结构化 caption (本地模型)...")
caption = rewrite_caption(
idea=idea,
lyrics=lyrics or "",
model=rewriter_model or "mlx-community/Qwen3-8B-4bit",
)
click.echo(f"\n── 生成的 caption ──\n{caption}\n─────────────────────")
if dry_run:
click.echo("(--dry-run: 只输出 caption, 不生成音乐)")
return
click.echo(f"② 生成 {seconds}s 音乐 (阶段驻留管线)...")
pipeline = PhasedPipeline(label=label)
report = pipeline.generate(
caption=caption,
lyrics=lyrics or "[instrumental]",
duration=seconds,
)
click.echo(f"✓ 完整工作流完成: {report['output_wav']}")
click.echo(f" 峰值内存: {report['max_stage_peak_gb']} GB (预算 11.5GB)")
if __name__ == "__main__":
main()