File size: 2,585 Bytes
5eee449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""VITS text frontend: text -> phoneme token ids.

Faithful port of origin/inflect_vits_frontend.py + the token mapping from
origin/runtime/text/__init__.py + commons.intersperse. Text normalization and
eSpeak configuration live in text_normalize.py (verbatim copy of
origin/inflect_nano_v2_frontend.py).

Pipeline (mirrors origin/inference.py:InflectTTS._tokens):
    text -> normalize_text -> eSpeak phonemize (en-us, with_stress,
    preserve_punctuation) -> phoneme overrides -> per-character symbol ids
    -> intersperse(0)  (add_blank)
"""

from __future__ import annotations

from .symbols import symbols
from .text_normalize import _configure_espeak, normalize_text

__all__ = ["normalize_text", "phonemize_text", "text_to_token_ids", "intersperse"]

# eSpeak is the general fallback. This table contains verified exceptions only
# (verbatim from origin/inflect_vits_frontend.py).
PHONEME_OVERRIDES = {
    "sˈæskɐtʃˌuːən": "sɐskˈætʃəwən",
    "flʊɹɹˈɛsənt": "flʊˈɹɛsənt",
}

_SYMBOL_TO_ID = {s: i for i, s in enumerate(symbols)}
_BLANK_ID = 0  # symbols[0] == '_'


def intersperse(seq: list[int], item: int = _BLANK_ID) -> list[int]:
    """commons.intersperse: insert `item` around every element -> 2N+1."""
    result = [item] * (len(seq) * 2 + 1)
    result[1::2] = seq
    return result


def phonemize_text(text: str) -> str:
    """normalize + eSpeak phonemize + overrides (run_vits_frontend equivalent)."""
    import re

    normalized = normalize_text(text)
    _configure_espeak()
    from phonemizer import phonemize

    phoneme_text = phonemize(
        [normalized],
        language="en-us",
        backend="espeak",
        strip=True,
        preserve_punctuation=True,
        with_stress=True,
        njobs=1,
    )[0]
    for source, replacement in PHONEME_OVERRIDES.items():
        phoneme_text = phoneme_text.replace(source, replacement)
    return re.sub(r"\s+", " ", phoneme_text).strip()


def text_to_token_ids(text: str, add_blank: bool = True) -> list[int]:
    """Full frontend: text -> interspersed phoneme ids (2N+1 with add_blank)."""
    phoneme_text = phonemize_text(text)
    try:
        sequence = [_SYMBOL_TO_ID[ch] for ch in phoneme_text]
    except KeyError as exc:
        raise ValueError(
            f"phoneme {exc.args[0]!r} from {phoneme_text!r} is not in the "
            "model symbol table"
        ) from exc
    if add_blank:
        sequence = intersperse(sequence, _BLANK_ID)
    if not sequence:
        raise ValueError("The text frontend produced no speakable tokens.")
    return sequence