Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,49 @@ print(eng("project")) # Output: /proː˧.t͡ɕek̚˨˩
```


### Custom Word Pronunciation Dictionary

PyThaiTTS supports custom word dictionaries to define custom Thai pronunciation (as Thai text reading or IPA phonemes) for specific words, acronyms, or names.

Dictionaries can be passed as Python dicts, JSON files, or delimited text files (TSV, CSV, or colon-separated):

```python
from pythaitts import TTS

# 1. Provide custom Thai pronunciation (hyphenated or plain text)
custom_dict = {
"เพชรบุรี": "เพ็ด-ชะ-บุ-รี",
"สถิติ": "สะ-ถิ-ติ",
"ChatGPT": "แชตจีพีที",
}

tts = TTS(custom_dict=custom_dict)
tts.tts("ยินดีต้อนรับสู่เพชรบุรี", filename="custom.wav")

# 2. Or pass per-call or with IPA phonemes directly
tts.tts(
"ผมไปเพชรบุรี",
custom_dict={"เพชรบุรี": "/pʰet̚˨˩.t͡ɕʰa˦˥.bu˨˩.riː˧/"},
filename="ipa.wav",
)

# 3. Add or update custom words dynamically
tts.add_custom_dict({"ก.พ.": "กอ-พอ"})

# 4. Load from a JSON or TSV file
tts.set_custom_dict("my_words.json")
```

You can also use custom dictionaries with G2P directly:

```python
from pythaitts.pretrained.fastthaig2p import G2P

g2p = G2P(custom_dict={"เพชรบุรี": "เพ็ด-ชะ-บุ-รี"})
print(g2p.convert("ผมไปเพชรบุรี"))
```


### Text Preprocessing

PyThaiTTS includes automatic text preprocessing to improve TTS quality:
Expand Down
7 changes: 7 additions & 0 deletions demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ def main():
print(f"✓ Waveform generated successfully!")
print(f"Waveform shape: {waveform.shape}")
print()

# Demonstrate custom word pronunciation dictionary
print("Demonstrating custom word pronunciation dictionary...")
custom_dict = {"พายไทยทีทีเอส": "พาย-ไทย-ที-ที-เอส"}
waveform_custom = tts.tts(text, custom_dict=custom_dict, return_type="waveform")
print(f"✓ Custom pronunciation waveform generated! Shape: {waveform_custom.shape}")
print()

print("=" * 60)
print("Demo completed successfully!")
Expand Down
116 changes: 106 additions & 10 deletions pythaitts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@
"""
__version__ = "0.8.0"

from typing import Union, Iterable, Iterator
from typing import Union, Iterable, Iterator, Optional, Dict, Any
from pathlib import Path

from pythaitts.preprocess import preprocess_text, num_to_thai, expand_maiyamok
from pythaitts.preprocess import (
preprocess_text,
num_to_thai,
expand_maiyamok,
load_custom_dict,
)
from pythaitts.pretrained.fastthaig2p.en_g2p import (
EnglishG2P,
en_g2p,
Expand All @@ -24,12 +30,21 @@


class TTS:
def __init__(self, pretrained="fastthaig2p", mode="last_checkpoint", version="1.0", device:str="cpu", **kwargs) -> None:
def __init__(
self,
pretrained="fastthaig2p",
mode="last_checkpoint",
version="1.0",
device: str = "cpu",
custom_dict: Optional[Union[Dict[str, str], str, Path, Iterable[Any]]] = None,
**kwargs,
) -> None:
"""
:param str pretrained: TTS pretrained (fastthaig2p, lunarlist_onnx, khanomtan, lunarlist, vachana)
:param str mode: pretrained mode (fastthaig2p, lunarlist_onnx, and vachana don't support)
:param str version: model version (default is 1.0 or 1.1)
:param str device: device for running model. (fastthaig2p, lunarlist_onnx, and vachana support CPU only.)
:param custom_dict: Optional custom word dictionary for custom Thai pronunciation in words

**Options for mode**
* *last_checkpoint* (default) - last checkpoint of model
Expand All @@ -54,6 +69,9 @@ def __init__(self, pretrained="fastthaig2p", mode="last_checkpoint", version="1.
self.pretrained = pretrained
self.mode = mode
self.device = device
self.custom_dict = custom_dict
if custom_dict is not None:
kwargs["custom_dict"] = custom_dict
self.load_pretrained(version=version, **kwargs)

def load_pretrained(self, version="1.0", **kwargs):
Expand All @@ -80,7 +98,40 @@ def load_pretrained(self, version="1.0", **kwargs):
"PyThaiTTS doesn't support %s pretrained." % self.pretrained
)

def tts(self, text: str, speaker_idx: str = "thai_som", language_idx: str = "th-th", return_type: str = "file", filename: str = None, preprocess: bool = True, **kwargs):
def add_custom_dict(
self, custom_dict: Union[Dict[str, str], str, Path, Iterable[Any]]
) -> None:
"""Add custom word pronunciations to the TTS model."""
loaded = load_custom_dict(custom_dict)
if self.custom_dict is None:
self.custom_dict = {}
elif isinstance(self.custom_dict, (str, Path)):
self.custom_dict = load_custom_dict(self.custom_dict)
elif not isinstance(self.custom_dict, dict):
self.custom_dict = load_custom_dict(self.custom_dict)
self.custom_dict.update(loaded)
if hasattr(self.model, "add_custom_dict"):
self.model.add_custom_dict(loaded)

def set_custom_dict(
self, custom_dict: Union[Dict[str, str], str, Path, Iterable[Any]]
) -> None:
"""Replace custom word pronunciations with new dictionary."""
self.custom_dict = custom_dict
if hasattr(self.model, "set_custom_dict"):
self.model.set_custom_dict(custom_dict)

def tts(
self,
text: str,
speaker_idx: str = "thai_som",
language_idx: str = "th-th",
return_type: str = "file",
filename: str = None,
preprocess: bool = True,
custom_dict: Optional[Union[Dict[str, str], str, Path, Iterable[Any]]] = None,
**kwargs,
):
"""
speech synthesis

Expand All @@ -90,31 +141,64 @@ def tts(self, text: str, speaker_idx: str = "thai_som", language_idx: str = "th-
:param str return_type: return type (default is file)
:param str filename: path filename for save wav file if return_type is file.
:param bool preprocess: whether to preprocess text (convert numbers to Thai text and expand ๆ). Default is True.
:param custom_dict: Optional custom word dictionary for custom Thai pronunciation in words
:param kwargs: Additional parameters passed to the underlying model.
"""
effective_custom_dict = (
custom_dict if custom_dict is not None else getattr(self, "custom_dict", None)
)

# Preprocess text if requested
if preprocess:
from pythaitts.preprocess import preprocess_text
text = preprocess_text(text)

if self.pretrained not in ("fastthaig2p", "FastThaiG2P") and effective_custom_dict:
text = preprocess_text(text, custom_dict=effective_custom_dict)
else:
text = preprocess_text(text)
elif self.pretrained not in ("fastthaig2p", "FastThaiG2P") and effective_custom_dict:
from pythaitts.preprocess import preprocess_text

text = preprocess_text(
text,
expand_numbers=False,
expand_maiyamok_char=False,
custom_dict=effective_custom_dict,
)

if self.pretrained == "lunarlist" or self.pretrained == "lunarlist_onnx":
return self.model(text=text,return_type=return_type,filename=filename)
return self.model(text=text, return_type=return_type, filename=filename)
elif self.pretrained == "vachana":
if speaker_idx in ("thai_som", "Linda", None):
speaker_idx = "th_f_1"
return self.model(text=text,speaker_idx=speaker_idx,return_type=return_type,filename=filename, **kwargs)
return self.model(
text=text,
speaker_idx=speaker_idx,
return_type=return_type,
filename=filename,
**kwargs,
)
elif self.pretrained in ("fastthaig2p", "FastThaiG2P"):
if speaker_idx in ("Linda", None):
speaker_idx = "thai_som"
return self.model(text=text,speaker_idx=speaker_idx,return_type=return_type,filename=filename, **kwargs)
call_kwargs = dict(kwargs)
if effective_custom_dict is not None:
call_kwargs["custom_dict"] = effective_custom_dict
return self.model(
text=text,
speaker_idx=speaker_idx,
return_type=return_type,
filename=filename,
**call_kwargs,
)
if speaker_idx in ("thai_som", None):
speaker_idx = "Linda"
return self.model(
text=text,
speaker_idx=speaker_idx,
language_idx=language_idx,
return_type=return_type,
filename=filename
filename=filename,
)

def stream(
Expand All @@ -125,6 +209,7 @@ def stream(
play: bool = False,
preprocess: bool = True,
max_phonemes: int = 400,
custom_dict: Optional[Union[Dict[str, str], str, Path, Iterable[Any]]] = None,
**kwargs,
):
"""
Expand All @@ -136,9 +221,14 @@ def stream(
:param bool play: Whether to play audio chunks in real-time to speakers
:param bool preprocess: Whether to preprocess text (numbers to words, ๆ)
:param int max_phonemes: Maximum phonemes per synthesized chunk
:param custom_dict: Optional custom word dictionary for custom Thai pronunciation in words
:param kwargs: Additional parameters passed to the model
:return: Generator yielding audio chunks
"""
effective_custom_dict = (
custom_dict if custom_dict is not None else getattr(self, "custom_dict", None)
)

if self.pretrained in ("fastthaig2p", "FastThaiG2P"):
if speaker_idx in ("Linda", None):
speaker_idx = "thai_som"
Expand All @@ -149,20 +239,25 @@ def stream(
play=play,
preprocess=preprocess,
max_phonemes=max_phonemes,
custom_dict=effective_custom_dict,
**kwargs,
)
else:
from pythaitts.realtime import stream_text_to_chunks

def _gen():
for chunk in stream_text_to_chunks(
text, max_phonemes=max_phonemes, preprocess=preprocess
text,
max_phonemes=max_phonemes,
preprocess=preprocess,
custom_dict=effective_custom_dict,
):
yield self.tts(
text=chunk,
speaker_idx=speaker_idx,
return_type=return_type,
preprocess=False,
custom_dict=effective_custom_dict,
**kwargs,
)

Expand All @@ -182,6 +277,7 @@ def _gen():
"en_to_ipa",
"en_ipa_to_thai_ipa",
"preprocess_text",
"load_custom_dict",
"num_to_thai",
"expand_maiyamok",
"chunk_text",
Expand Down
Loading
Loading