Skip to main content

Fast Cantonese text-to-phoneme (G2P) converter — Jyutping with tone numbers

Project description

canto-hk-g2p

Fast Cantonese text-to-phoneme (G2P) converter — converts Cantonese (and English code-switched) text to Jyutping with tone numbers (LSHK standard, all-ASCII). Rust core with Python bindings via PyO3/maturin.

CI PyPI License: Apache-2.0

from canto_hk_g2p import Pipeline

p = Pipeline()
p.convert("你好嘅,I love Hong Kong")
# → "nei5 hou2 ge3 , I love Hong Kong"

Why this exists

Cantonese TTS systems struggle with two fundamental problems. First, colloquial Cantonese characters (嘅, 喺, 噉, 㗎, …) are rare or absent from standard Chinese vocabulary tables, causing tokenizers to split them into byte fragments that the model never learns to pronounce reliably. Second, Hong Kong Cantonese freely mixes English — a sentence like 佢 send 咗 email 俾我 is everyday speech — and no existing Cantonese G2P tool handles this cleanly.

canto-hk-g2p solves both: it converts the entire input, including colloquial particles, numbers, dates, and inline English, to all-ASCII Jyutping before the text ever reaches a TTS encoder. The result is stable, tone-accurate phoneme sequences with 100% coverage.

The library is a standalone open-source deliverable — Cantonese TTS pre-processing is the primary motivation, but the tool is useful anywhere Cantonese phonemization is needed.


Features

  • All-ASCII Jyutping output with LSHK tone numbers (nei5 hou2 ge3)
  • English code-switching — the only Cantonese G2P tool that handles mixed HK text; English tokens pass through unchanged (佢 send 咗 email 俾我keoi5 send zo2 email bei2 ngo5)
  • Punctuation normalisation (punc_norm=True by default) — converts exotic punctuation to TTS-friendly equivalents before G2P: 「」《》 removed, , ——, · → space,
  • Text normalization — Arabic and full-width digits expanded to Cantonese spoken form:
    • Year: 2026年ji6 ling4 ji6 luk6 nin4
    • Date: 6月13日luk6 jyut6 sap6 saam1 jat6
    • Percentage: 50%baak3 fan6 zi1 ng5 sap6; decimal 50.5%baak3 fan6 zi1 ng5 sap6 dim2 ng5
    • Currency symbols: HK$100jat1 baak3 jyun4; ¥500ng5 baak3 jat6 jyun4; €200ji6 baak3 au1 jyun4
    • Currency codes: USD100jat1 baak3 mei5 jyun4; EUR 200ji6 baak3 au1 jyun4
    • Measurement units: 120km/h → 一百二十公里每小時; 36.5°C → 三十六點五攝氏度; 75kg → 七十五公斤
    • Fractions: 1/2 → 二分之一; 3/4 → 四分之三
    • Scores: 3:1 → 三比一; 10:0 → 十比零
    • Ordinal/floor/episode: 第3名 → 第三名; 3樓 → 三樓; 第3集 → 第三集
    • Time: 下午3時15分 → Cantonese spoken time
    • Phone numbers: digit-by-digit expansion
  • Polyphone disambiguation via longest-match word-level segmentation (~85% accuracy)
  • Rayon parallel batch processing — scales to large corpora
  • Zero runtime Python dependencies — pronunciation dictionaries bundled in the wheel
  • convert_detailed() — structured (token, jyutping, lang) output with language tags (yue / en / punct)
  • Apache-2.0 license, permissive data sources only — safe to redistribute

Installation

# pip
pip install canto-hk-g2p

# uv (recommended — faster resolver)
uv pip install canto-hk-g2p

# uv project
uv add canto-hk-g2p

Pre-built wheels are available for Linux (x86_64, aarch64), macOS (x86_64, Apple Silicon), and Windows (x86_64) for Python 3.8+. No Rust toolchain required for end users.


Quick start

from canto_hk_g2p import Pipeline

p = Pipeline()

# Basic Cantonese
p.convert("你好嘅")
# → "nei5 hou2 ge3"

# Full sentence with punctuation and English
p.convert("你好嘅,I love Hong Kong")
# → "nei5 hou2 ge3 , I love Hong Kong"

# Punctuation normalisation (default on)
p.convert("《天氣之子》——一個故事")
# → "tin1 hei3 zi1 zi2 , jat1 go3 gu3 si6"

# Number and date normalization
p.convert("2026年6月13日")
# → "ji6 ling4 ji6 luk6 nin4 luk6 jyut6 sap6 saam1 jat6"

p.convert("50%")
# → "baak3 fan6 zi1 ng5 sap6"

# English code-switching — Latin tokens pass through unchanged
p.convert("佢send咗email俾我")
# → "keoi5 send zo2 email bei2 ngo5"

# Batch conversion (Rayon parallel)
p.convert_batch(["香港", "銀行", "你好嘅"])
# → ["hoeng1 gong2", "ngan4 hong4", "nei5 hou2 ge3"]

# Structured output with language tags
p.convert_detailed("香港 hello")
# → [("香港", "hoeng1 gong2", "yue"), ("hello", "hello", "en")]

IPA output

from canto_hk_g2p import Pipeline
from canto_hk_g2p.ipa import jyutping_to_ipa

p = Pipeline()

# Cantonese → IPA with tone diacritics (default)
p.convert_ipa("你好嘅")
# → "nei̯˩˧ hɐu̯˧˥ kɛː˧"

# IPA with tone numbers
p.convert_ipa("你好嘅", tone="number")
# → "nei̯5 hɐu̯2 kɛː3"

# English code-switching → English tokens use CMU dict
p.convert_ipa("佢 send 咗 email 俾我")
# → "kʰɵy̯˨ sɛnd tsɔː˧ iːmeɪl pei̯˧˥ ŋɔː˩˧"

# Standalone utility — convert existing Jyutping strings
jyutping_to_ipa("hoeng1 gong2")
# → "hœːŋ˥ kɔːŋ˧˥"

IPA tone marks: ˥ high level (1), ˧˥ high rising (2), ˧ mid level (3), ˨˩ low falling (4), ˩˧ low rising (5), ˨ low level (6).


API reference

Pipeline(*, punc_norm=True)

Loads the binary pronunciation dictionaries from the bundled data/ directory. The pipeline object is reusable and thread-safe for batch calls.

Parameter Type Default Description
punc_norm bool True Enable punctuation normalisation. Converts 「」《》 (removed), , ——, ·→space, before G2P lookup. Set to False to disable.
p = Pipeline()                   # punc_norm on (default)
p2 = Pipeline(punc_norm=False)   # raw punctuation passthrough

convert(text: str) -> str

Converts a string of Cantonese (or mixed Cantonese/English) text to space-separated Jyutping syllables.

Parameter Type Description
text str Input text (UTF-8). May contain Cantonese characters, English words, digits, punctuation.

Returns: str — space-separated Jyutping syllables. Punctuation tokens are preserved as-is. Latin tokens (English words, acronyms) are passed through unchanged.

p.convert("銀行")          # → "ngan4 hong4"
p.convert("2026年")        # → "ji6 ling4 ji6 luk6 nin4"
p.convert("I love you")   # → "I love you"
p.convert("send")          # → "send"
p.convert("")              # → ""

convert_batch(texts: list[str]) -> list[str]

Converts a list of strings in parallel using Rayon. Output order matches input order.

Parameter Type Description
texts list[str] List of input strings.

Returns: list[str] — one Jyutping string per input, in the same order.

p.convert_batch(["香港", "銀行", "你好嘅"])
# → ["hoeng1 gong2", "ngan4 hong4", "nei5 hou2 ge3"]

p.convert_batch([])   # → []

convert_detailed(text: str) -> list[tuple[str, str, str]]

Returns a structured token-level breakdown. Each element is a 3-tuple (token, jyutping, lang).

Field Type Values
token str The original text span for this token
jyutping str Space-separated Jyutping syllables for the token; equals token for Latin passthrough
lang str "yue" (Cantonese), "en" (English / Latin), "punct" (punctuation)

The joined jyutping from convert_detailed() is identical to the output of convert() for the same input.

p.convert_detailed("香港 hello")
# → [("香港", "hoeng1 gong2", "yue"), ("hello", "hello", "en")]

p.convert_detailed("你好,")
# → [("你好", "nei5 hou2", "yue"), (",", ",", "punct")]

p.convert_detailed("2026年")
# → all tokens tagged "yue" (normalizer expands digits to Chinese characters first)

p.convert_detailed("")
# → []

Accuracy

Verified examples

Input Output Notes
你好嘅 nei5 hou2 ge3 Oral particle 嘅 from hand-curated list
香港 hoeng1 gong2 Word-level dict lookup
銀行 ngan4 hong4 Polyphone resolved by word context (not haang4)
m4 Cantonese negation particle
hai2 Locative particle
2026年 ji6 ling4 ji6 luk6 nin4 Year normalizer
6月13日 luk6 jyut6 sap6 saam1 jat6 Date normalizer
50% baak3 fan6 zi1 ng5 sap6 Percentage normalizer
HK$100 jat1 baak3 jyun4 Currency normalizer
USD100 jat1 baak3 mei5 jyun4 Currency code normalizer
¥500 ng5 baak3 jat6 jyun4 Currency symbol normalizer (¥ → 日圓)
120km/h jat1 baak3 ji6 sap6 gung1 lei5 mui5 siu2 si4 Unit normalizer
36.5°C saam1 sap6 luk6 dim2 ng5 sip3 si6 dou6 Decimal + unit normalizer
hello hello Latin passthrough — not in Cantonese phoneme space
send send Latin passthrough — English token in HK code-switching
佢send咗email俾我 keoi5 send zo2 email bei2 ngo5 Full code-switch sentence

Known limitations

  • Residual polyphones: Word-boundary segmentation resolves approximately 85% of polyphone cases. Single-character polyphones that cannot be disambiguated by context (e.g. 好 as hou2 greeting vs. hou3 adverb) fall back to the most-frequent reading in the dictionary.
  • Fraction 十分之: Fractions with denominator 十 (e.g. 1/10) output fan1 instead of fan6 because 十分之 is also a common adverb (十分之好 = "extremely good") — the adverb entry in rime-cantonese takes longest-match priority. Other denominators (1/2, 1/3, 3/4, 1/12 …) produce the correct fan6 reading.
  • Tone sandhi (變調): Citation tones only — tone sandhi is deferred to a future release.
  • No neural polyphone tier: The current segmentation-based approach covers most cases. A BERT-based polyphone layer (trainable on HKCanCor CC-BY) is on the roadmap but not included in v1.

Comparison with other tools

Tool Implementation English code-switch License Notes
canto-hk-g2p Rust + Python Yes (passthrough) Apache-2.0 First Rust-native Cantonese G2P; mmap binary dict; text normalizer
ToJyutping (CanCLID) Python / JS No (letter-by-letter) BSD-2 De-facto standard; rime-cantonese data; no normalizer
PyCantonese Python (Rust internal) No MIT Most complete toolkit; dict-heuristic polyphone
g2pW-Cantonese Python (BERT) No mixed SOTA neural polyphone; trained on words.hk + CantoDict — license-tainted, not cleanly redistributable

canto-hk-g2p is the only tool that handles English code-switching cleanly — standard in Hong Kong Cantonese (e.g. 佢 send 咗 email 俾我) — and the only one with a Cantonese text normalizer for numbers, dates, and currency.


Data sources

Bundled in the wheel (all permissive)

Source License Usage
rime-cantonese jyut6ping3.dict/.chars/.words CC-BY-4.0 Primary lexicon (~100k entries); attribution required — see NOTICE
Unihan kCantonese Unicode License v3 (MIT-equivalent) Rare-character fallback (~20k chars)
data/oral_hk.tsv (hand-curated) Apache-2.0 ~60 HK colloquial characters: 嘅 喺 咗 哋 噉 㗎 囉 喎 …

Excluded (license-incompatible)

Source Reason
rime-cantonese jyut6ping3.maps.dict.yaml ODbL v1.0 (share-alike — would infect data files)
CC-Canto CC-BY-SA 3.0 (copyleft — same problem)
words.hk Proprietary — no redistribution
CantoDict Proprietary

Build from source

Prerequisites

pip install maturin

Steps

# 1. Clone the repository
git clone https://github.com/typangaa/canto-hk-g2p.git
cd canto-hk-g2p

# 2. Fetch data sources (downloads ~16 MB from rime-cantonese and Unicode)
python3 scripts/fetch_data.py

# 3. Build binary pronunciation dictionaries
python3 scripts/build_dict.py

# 4. Build the Rust extension and install into the current environment
maturin develop --release
# Or build a wheel:
maturin build --release
pip install target/wheels/*.whl

Run tests

# Rust unit tests
cargo test

# Python integration tests (requires built extension + data/)
python3 -m pytest tests/ -v

All 228 tests should pass. The test suite covers basic G2P correctness, polyphone disambiguation, English passthrough, code-switching, punctuation normalisation, number/date/unit/currency/fraction/score normalization, batch processing, and convert_detailed() output structure.


Contributing

Contributions are welcome. Please open an issue before starting significant work so we can align on direction. See CONTRIBUTING.md for coding guidelines, how to add entries to the hand-curated oral list, and the pull-request process.

A few areas where help is particularly valuable:

  • Expanding data/oral_hk.tsv with additional colloquial characters
  • Unit abbreviation expansion (km, °C, kg → Cantonese spoken form)
  • Improving number normalization edge cases
  • Adding tone sandhi rules (future release)
  • Packaging and PyPI release CI

License

Apache-2.0 — see LICENSE.

Attribution (CC-BY-4.0 requirement)

This library bundles data derived from rime-cantonese (© CanCLID contributors), licensed under Creative Commons Attribution 4.0 International. See NOTICE for full attribution details.


Related projects

  • SEA-G2P — upstream inspiration (Rust G2P for Vietnamese)
  • ToJyutping — de-facto Python/JS Cantonese G2P front-end
  • PyCantonese — comprehensive Cantonese NLP toolkit
  • rime-cantonese — primary lexicon data source

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

canto_hk_g2p-1.5.0.tar.gz (3.1 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

canto_hk_g2p-1.5.0-cp38-abi3-win_amd64.whl (3.2 MB view details)

Uploaded CPython 3.8+Windows x86-64

canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

canto_hk_g2p-1.5.0-cp38-abi3-macosx_11_0_arm64.whl (3.3 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

canto_hk_g2p-1.5.0-cp38-abi3-macosx_10_12_x86_64.whl (3.3 MB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file canto_hk_g2p-1.5.0.tar.gz.

File metadata

  • Download URL: canto_hk_g2p-1.5.0.tar.gz
  • Upload date:
  • Size: 3.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for canto_hk_g2p-1.5.0.tar.gz
Algorithm Hash digest
SHA256 573e510d464beb6545e959df548c4e92eeff1a4e53e866783aa913bc8167b0b7
MD5 9577a2b435277f39153f1b809f8d3d59
BLAKE2b-256 7d2f54d38ea2fb2ad4fa6d70b6be281aece8fee77b638bd0d25a202ad60f3c08

See more details on using hashes here.

Provenance

The following attestation bundles were made for canto_hk_g2p-1.5.0.tar.gz:

Publisher: release.yml on typangaa/canto-hk-g2p

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file canto_hk_g2p-1.5.0-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: canto_hk_g2p-1.5.0-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.2 MB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for canto_hk_g2p-1.5.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 2fa5d5013d6d78b15efa3b60b5f5d569b4b517582bfb058b0e0be63ca7472c17
MD5 6f82128c265d02c62337d72c6c80359e
BLAKE2b-256 47d2fb1cc44adb64a553b450b152101e97ecac5e06b0ee28396333357b7ac3fa

See more details on using hashes here.

Provenance

The following attestation bundles were made for canto_hk_g2p-1.5.0-cp38-abi3-win_amd64.whl:

Publisher: release.yml on typangaa/canto-hk-g2p

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d37b9d77b9402f3b1d518eb79a87fe0d6c30f76c48614516c4aff5064e917a15
MD5 4ac5597819df56fc1cf99fbdb8b343b8
BLAKE2b-256 8c53c9e33d59803d7e999671193d613d0e82f3a11f715548f119ea6443cf2447

See more details on using hashes here.

Provenance

The following attestation bundles were made for canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on typangaa/canto-hk-g2p

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 a395f8f88f4d46fa1b9b29af4a1021ef83a017a98fc3880cecd298311e79403d
MD5 5b52070c2839cc6bdf68efe03c077772
BLAKE2b-256 9ca8e393b938dd2cce599a3ea8a5eff77276693f6fe734f438b7fcda9f359ab5

See more details on using hashes here.

Provenance

The following attestation bundles were made for canto_hk_g2p-1.5.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on typangaa/canto-hk-g2p

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file canto_hk_g2p-1.5.0-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for canto_hk_g2p-1.5.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ea7a4a1d77e7cdcca78fb38c50f079dbfb2c9ec3a053fbf8d8ccd2fed6340392
MD5 6ac03d09f22f907cae6ce557c3d3abab
BLAKE2b-256 77cb44c264d544c6b81b6b25049bf6168d9596cb04034ff06d4173b0a8a7d9e1

See more details on using hashes here.

Provenance

The following attestation bundles were made for canto_hk_g2p-1.5.0-cp38-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on typangaa/canto-hk-g2p

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file canto_hk_g2p-1.5.0-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for canto_hk_g2p-1.5.0-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 5d1d731afa0e0af9f6e5dbdb7b198c31fc9c46681baba8d09df0d50c7a1f42c1
MD5 27426a7cd963619792b1a124717e3329
BLAKE2b-256 3a9f69dfa267628fc7e1668afd84da58d980d828d55e434796a1863df25a777f

See more details on using hashes here.

Provenance

The following attestation bundles were made for canto_hk_g2p-1.5.0-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on typangaa/canto-hk-g2p

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page