Flatten skill category directory structure
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
#!/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.")
|
||||
Reference in New Issue
Block a user