File size: 3,623 Bytes
905b7fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/usr/bin/env python3
"""Render every spoken line of the pre-authored packs into the audio cache.

    python scripts/prebuild_audio.py                  # tất cả pack, tốc độ 1.0
    python scripts/prebuild_audio.py --pack bai-10-tu-giac
    python scripts/prebuild_audio.py --speeds 0.9 1.0 1.1
    python scripts/prebuild_audio.py --force          # dựng lại kể cả đã có

Run this once after editing a pack, or as a build step before deploying. The
lines are known in advance, so there is no reason for a student to wait on the
model: the first click should start talking immediately.

The cache key covers provider, voice, speed and format, so changing
SPEECH_PROVIDER or VIENEU_VOICE means re-running this — the old clips stay put
but will never be read again. Delete content/.audio-cache to reclaim the space.
"""

import argparse
import os
import sys
import time

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))

from app.config import settings  # noqa: E402
from app.services import audio_cache, packs, speech  # noqa: E402


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--pack", help="chỉ dựng một pack")
    parser.add_argument("--speeds", nargs="+", type=float, default=[1.0])
    parser.add_argument("--voice", help="ghi đè giọng mặc định")
    parser.add_argument("--force", action="store_true", help="dựng lại cả câu đã có")
    args = parser.parse_args()

    if settings.speech_provider == "browser":
        print(
            "SPEECH_PROVIDER=browser nên máy chủ không đọc hộ — trình duyệt tự tổng hợp\n"
            "bằng Web Speech API và không có gì để dựng sẵn cả.\n"
            "Đặt SPEECH_PROVIDER=vieneu (hoặc google) rồi chạy lại.",
            file=sys.stderr,
        )
        return 1

    chosen = [packs.PACKS[args.pack]] if args.pack else list(packs.PACKS.values())
    if args.pack and args.pack not in packs.PACKS:
        print(f"Không có pack '{args.pack}'. Hiện có: {', '.join(packs.PACKS)}", file=sys.stderr)
        return 1

    print(f"provider: {settings.speech_provider}")
    print(f"giọng:    {args.voice or settings.vieneu_voice}")
    speech.warm_up()

    total = built = skipped = failed = 0
    started = time.time()

    for pack in chosen:
        lines = pack.spoken_lines()
        print(f"\n{pack.title}{len(lines)} câu × {len(args.speeds)} tốc độ")
        for speed in args.speeds:
            for i, line in enumerate(lines, 1):
                total += 1
                if not args.force and audio_cache.get(line, speed, args.voice) is not None:
                    skipped += 1
                    continue
                try:
                    clip = speech.synthesize(line, speed=speed, voice=args.voice, use_cache=False)
                    audio_cache.put(line, speed, args.voice, clip)
                    built += 1
                    print(f"  [{i:3d}/{len(lines)}] {speed:.2f}× {line[:58]}…")
                except Exception as exc:  # noqa: BLE001 - một câu hỏng không dừng cả mẻ
                    failed += 1
                    print(f"  [{i:3d}/{len(lines)}] LỖI: {exc}", file=sys.stderr)

    stats = audio_cache.stats()
    print(
        f"\nXong sau {time.time() - started:.1f}s — dựng mới {built}, bỏ qua {skipped}, "
        f"lỗi {failed}, tổng {total}.\n"
        f"Cache hiện có {stats['clips']} clip, {stats['size_mb']} MB."
    )
    return 1 if failed else 0


if __name__ == "__main__":
    raise SystemExit(main())