Files
SoundGame/tools/stable-audio-3/optimized/tflite/scripts/install.py
T
SoundGame Dev f0f0a9b50e 初始提交:3 款纯音频游戏 + 共享引擎 + 完整文档与资产管线
- 共享引擎(app/src/main):GestureEngine 6 手势识别、AudioEngine 三层音频
  (ExoPlayer BGM 交叉淡入 / 对白 ducking / SoundPool 低延迟)、Game/GameEngine
  状态机接口、MainActivity 全屏封面 + TalkBack
- 《声命线》storyline:末世互动广播剧,3 幕 8 抉择点 4 结局
- 《声东击西》reflex:听声辨位反应,12 波战斗 + BGM 三档 BPM 变速
- 《暗室》escape:3x3 网格密室逃脱,钥匙→铁柜→保险丝→电箱→大门谜题链
- 文档:setup(从零搭建)/ architecture(架构)/ engine-api(引擎 API)/
  asset-pipeline(资产流水线)/ games/*(三款游戏设计文档)
- 资产管线:Qwen3-TTS 对白 + 程序合成音效/BGM + Mureka BGM 的 gen 脚本与台账,
  全部 ogg 资产入库可复现
- env.d 环境脚本:android/tts 入库;sfx/mureka 含密钥不入库,仅提交 .example.sh 模板
2026-08-29 13:00:34 +08:00

167 lines
6.3 KiB
Python

#!/usr/bin/env python3
"""Internal install plumbing — do NOT invoke directly. Use ../install.sh from the project root.
Called by install.sh after it has set up uv + .venv. Responsibilities:
1. (Optionally) pip-install requirements.txt — skipped when INSTALL_SKIP_PIP=1
is set (install.sh sets this since uv already handled deps).
2. Ask which DiT bundles to download, then fetch them from HuggingFace.
3. Print example commands the user can copy-paste.
Unlike the MLX release, this TFLite/LiteRT stack is portable CPU — it runs on
macOS/Linux, x86/ARM. No Apple-Silicon requirement.
"""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
# Windows consoles default to a legacy code page (cp1252/cp437) that can't encode
# the ✓/→ status glyphs. Force UTF-8 with replacement; no-op on macOS/Linux.
for _stream in (sys.stdout, sys.stderr):
try:
_stream.reconfigure(encoding="utf-8", errors="replace")
except AttributeError:
pass # exotic stream without reconfigure() — leave as-is
# Enable ANSI/VT escape processing in legacy Windows consoles (cmd.exe);
# harmless elsewhere and on modern Windows Terminal.
if os.name == "nt":
os.system("")
# This file lives in <project>/scripts/. SCRIPT_DIR points at the project
# root (where models/, requirements.txt, .venv/, sa3 wrapper live).
SCRIPT_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(Path(__file__).resolve().parent)) # `from weights import …`
MIN_PY = (3, 9)
SKIP_PIP = os.environ.get("INSTALL_SKIP_PIP") == "1"
def step(msg: str) -> None:
print(f"\n\033[1;36m→ {msg}\033[0m")
def check_environment() -> None:
"""Bail out early with a clear message if the interpreter is too old."""
py = sys.version_info
if (py.major, py.minor) < MIN_PY:
print(
f"\n\033[1;31merror\033[0m: this script is running under Python "
f"{py.major}.{py.minor}, but this stack requires Python "
f"{MIN_PY[0]}.{MIN_PY[1]}+.\n\n"
f"Re-run install.py with a Python {MIN_PY[0]}.{MIN_PY[1]}+ interpreter, e.g.:\n"
f" /path/to/python3.11 install.py\n\n"
f"On macOS: brew install python@3.11\n"
f"On Debian/Ubuntu: apt install python3.11\n"
f"On Windows: install from https://www.python.org/downloads/ "
f"(check 'Add python.exe to PATH')\n",
file=sys.stderr,
)
sys.exit(1)
# TFLite / LiteRT (ai_edge_litert) is portable across macOS/Linux, x86/ARM —
# no platform gate here (unlike the Metal-backed MLX release).
def pip_install_requirements() -> None:
if SKIP_PIP:
step("Dependencies already installed by install.sh (uv) — skipping pip step")
return
step("Installing Python dependencies")
req = SCRIPT_DIR / "requirements.txt"
# Prefer pip; fall back to uv if pip isn't available in this interpreter
# (e.g. user is running install.py directly inside a uv-created venv
# that wasn't seeded with pip).
pip_available = subprocess.run(
[sys.executable, "-m", "pip", "--version"],
capture_output=True,
).returncode == 0
if pip_available:
cmd = [sys.executable, "-m", "pip", "install", "-r", str(req)]
elif _have("uv"):
cmd = ["uv", "pip", "install", "--python", sys.executable, "-r", str(req)]
else:
print(
f"\n\033[1;31merror\033[0m: neither pip nor uv is available in this "
f"interpreter ({sys.executable}).\n"
f"Install pip with `python -m ensurepip --upgrade`, or use ./install.sh "
f"which sets everything up via uv.",
file=sys.stderr,
)
sys.exit(1)
print(f" $ {' '.join(cmd)}")
subprocess.check_call(cmd)
def _have(binary: str) -> bool:
"""True if the given binary is on PATH."""
from shutil import which
return which(binary) is not None
def main() -> None:
import argparse
ap = argparse.ArgumentParser(
description="SA3 TFLite install — non-interactive bundle downloader + post-install help.",
epilog="Normally invoked via ../install.sh, not directly.",
)
ap.add_argument("--download", default="",
metavar="BUNDLES",
help="Comma-separated list of bundles to pre-download "
"(sm-music, sm-sfx, medium). Without this flag, "
"nothing is downloaded — sa3_tflite.py will fetch any "
"missing weights from HuggingFace on first use.")
cli = ap.parse_args()
check_environment()
pip_install_requirements()
# Import after pip install so a fresh checkout works.
from weights import DIT_BUNDLES, SHARED, BUNDLE_SIZES, bundle_status, ensure_local
step("Current weights status")
for name in DIT_BUNDLES:
present, total = bundle_status(name)
mark = "✓" if present == total else " "
print(f" [{mark}] {name:9s} {present}/{total} files present ({BUNDLE_SIZES[name]})")
chosen: list[str] = []
if cli.download.strip():
chosen = [b.strip() for b in cli.download.split(",") if b.strip()]
unknown = [b for b in chosen if b not in DIT_BUNDLES]
if unknown:
print(f"\n\033[1;31merror\033[0m: unknown bundle(s): {', '.join(unknown)}. "
f"Choices: {', '.join(DIT_BUNDLES)}", file=sys.stderr)
sys.exit(1)
if chosen:
step(f"Downloading {len(chosen)} bundle(s): {', '.join(chosen)}")
seen: set[str] = set()
for name in chosen:
print(f"\n[{name}]")
items = DIT_BUNDLES[name] + SHARED
for rel, _hf in items:
if rel in seen:
continue
seen.add(rel)
ensure_local(rel)
else:
step("No --download set — weights will auto-download on first ./sa3 use")
print(f" To pre-download instead, pass: {Path(sys.executable).name} install.py --download sm-music")
print(f" or: ./install.sh --download sm-music,medium")
from examples import print_example_commands, BOLD, GREEN, RESET
print_example_commands(header=f"{BOLD}{GREEN}✓ Install complete.{RESET} Try these commands:")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nAborted.")
sys.exit(130)