Popiai-skill仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

108 lines
3.5 KiB

#!/usr/bin/env python3
"""Phase 3 Step 4: Build preview_data.json from preview_plan.json (with blob_path).
Usage: python3 scripts/build_preview_data.py [--lang zh|en]
If --lang is omitted, auto-detects from .audiobook-state.json or book content.
"""
import json, re, sys, os
from pathlib import Path
PROJECT_DIR = Path(os.getcwd())
AUDIOBOOK_ROOT = PROJECT_DIR / ".audiobook"
BOOK_DIR = next(d for d in sorted(AUDIOBOOK_ROOT.iterdir()) if d.is_dir() and (d / ".audiobook-state.json").exists())
# Parse --lang argument
LANG = None
for i, arg in enumerate(sys.argv[1:], 1):
if arg == "--lang" and i < len(sys.argv) - 1:
LANG = sys.argv[i + 1]
state = json.loads((BOOK_DIR / ".audiobook-state.json").read_text(encoding="utf-8"))
BOOK_NAME = state.get("bookName", BOOK_DIR.name)
# Auto-detect language if not specified
if not LANG:
LANG = state.get("lang", "")
if not LANG:
if re.search(r'[\u4e00-\u9fff]', BOOK_NAME):
LANG = "zh"
else:
LANG = "en"
plan_path = BOOK_DIR / "voice_samples" / "preview_plan.json"
plan = json.loads(plan_path.read_text(encoding="utf-8"))
# Read characters.md for descriptions (best-effort)
chars_path = BOOK_DIR / "analysis" / "characters.md"
char_descriptions = {}
if chars_path.exists():
current_name = None
for line in chars_path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if line.startswith("### ") or line.startswith("## "):
current_name = line.lstrip("#").strip()
elif current_name and line and not line.startswith("-"):
char_descriptions.setdefault(current_name, line)
# Build preview_data from plan
preview = {
"bookName": BOOK_NAME,
"lang": LANG,
"pauseDensity": "medium",
"narrator": None,
"characters": [],
}
for role in plan:
role_name = role["role"]
candidates = []
for c in role["candidates"]:
cand = {
"voiceId": c["voice_id"],
"voiceName": c["voice_name"],
"description": c.get("description", ""),
"samplePath": c.get("blob_path") or "",
}
candidates.append(cand)
if role_name == "narrator":
preview["narrator"] = {
"voiceId": candidates[0]["voiceId"] if candidates else "",
"sampleText": role.get("sample_text", ""),
"speed": role.get("speed", 0.9),
"volume": 1.0,
"candidates": candidates,
}
else:
preview["characters"].append({
"name": role_name,
"label": role_name.lower().replace(" ", "_"),
"description": char_descriptions.get(role_name, ""),
"voiceId": candidates[0]["voiceId"] if candidates else "",
"sampleText": role.get("sample_text", ""),
"speed": role.get("speed", 1.0),
"volume": 1.0,
"candidates": candidates,
})
out_path = BOOK_DIR / "voice_samples" / "preview_data.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(preview, f, ensure_ascii=False, indent=2)
# Validation
missing = []
for role in plan:
for c in role["candidates"]:
bp = c.get("blob_path")
if not bp or not Path(bp).exists():
missing.append(f" {role['role']}/{c['voice_name']}: {bp or 'None'}")
if missing:
print(f"WARNING: {len(missing)} candidates have missing audio:")
for m in missing:
print(m)
print("Audio preview will be broken for these candidates. Re-run Step 3.2/3.3.")
else:
print(f"preview_data.json written: {out_path}")
print(f" {len(plan)} roles, all audio files verified.")