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.
 
 
 
 

81 lines
3.1 KiB

#!/usr/bin/env python3
"""Phase 1 Step C: Validate segments.json for attribution errors.
Usage: python3 scripts/validate_segments.py <chapter_number>
Checks:
- No unknown speakers
- No missing voice/character fields
- Flags runs of 3+ consecutive same-speaker dialogue (for review)
Run after every segments.json is written. Fix any ERRORs before proceeding to Phase 2.
"""
import json, sys, os
from pathlib import Path
from collections import Counter
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())
CHAPTER_N = int(sys.argv[1]) if len(sys.argv) > 1 else 1
segments_path = BOOK_DIR / "analysis" / f"chapter_{CHAPTER_N}" / "segments.json"
segments = json.loads(segments_path.read_text(encoding="utf-8"))
# --- 1. Count speakers ---
voice_counts = Counter(s["voice"] for s in segments)
dialogue_count = sum(1 for s in segments if s["type"] == "dialogue")
unknown_count = sum(1 for s in segments if s.get("voice") == "unknown" or s.get("character") == "unknown")
print(f"Total segments: {len(segments)}")
print(f"Dialogue segments: {dialogue_count}")
print(f"Speaker distribution: {dict(voice_counts)}")
# --- 2. Check for unknown speakers ---
if unknown_count > 0:
pct = unknown_count / max(dialogue_count, 1) * 100
print(f"\nWARNING: {unknown_count} unknown speakers ({pct:.0f}% of dialogue)")
for i, s in enumerate(segments):
if s.get("voice") == "unknown":
print(f" segment {i}: {s['text'][:50]}...")
# --- 3. Check for suspicious consecutive same-speaker dialogue ---
consecutive_runs = []
prev_voice = None
run_start = 0
run_len = 0
for i, s in enumerate(segments):
if s["type"] != "dialogue":
if run_len >= 3:
consecutive_runs.append((prev_voice, run_start, run_len))
prev_voice = None
run_len = 0
continue
if s["voice"] == prev_voice:
run_len += 1
else:
if run_len >= 3:
consecutive_runs.append((prev_voice, run_start, run_len))
prev_voice = s["voice"]
run_start = i
run_len = 1
if run_len >= 3:
consecutive_runs.append((prev_voice, run_start, run_len))
if consecutive_runs:
print(f"\nNOTE: {len(consecutive_runs)} runs of 3+ consecutive dialogue lines from same speaker (may be correct, review if unexpected):")
for voice, start, length in consecutive_runs:
print(f" '{voice}' speaks {length}x in a row starting at segment {start}")
# --- 4. Check voice field consistency ---
missing_voice = [i for i, s in enumerate(segments) if "voice" not in s]
if missing_voice:
print(f"\nERROR: {len(missing_voice)} segments missing 'voice' field: {missing_voice}")
dialogue_without_char = [i for i, s in enumerate(segments) if s["type"] == "dialogue" and "character" not in s]
if dialogue_without_char:
print(f"ERROR: {len(dialogue_without_char)} dialogue segments missing 'character' field: {dialogue_without_char}")
if not unknown_count and not missing_voice and not dialogue_without_char:
print("\nAll checks passed.")