voxnorm
Text normalization for TTS: written forms into spoken forms, multilingual, pure Python. Currently speaks Traditional Chinese (zh-TW), Simplified Chinese (zh-CN), English, Japanese, and Korean.
Language models and databases write for the eye — 12:00, NT$1,200,
0912-345-678 — and a small TTS frontend handed that text verbatim has to
guess, and guesses badly. voxnorm rewrites the written forms into what a
speaker would actually say, and leaves everything else character-for-character
alone:
>>> import voxnorm
>>> voxnorm.normalize("會議在12:00開始")
'會議在十二點開始'
>>> voxnorm.normalize("The meeting starts at 12:00")
"The meeting starts at twelve o'clock"
>>> voxnorm.normalize("会議は12:00からです") # kana → Japanese
'会議は十二時からです'
>>> voxnorm.normalize("회의는 12:00에 시작합니다") # hangul → Korean (native-numeral hours)
'회의는 열두 시에 시작합니다'
>>> voxnorm.normalize("電話是0912-345-678。")
'電話是零九一二三四五六七八。'
>>> voxnorm.normalize("NT$1,200", lang="zh")
'新台幣一千兩百元'
>>> voxnorm.normalize("会议在12:00开始") # simplified glyphs → zh-CN
'会议在十二点开始'
Design
Two layers, split where languages stop sharing:
- Detection is language-neutral (
voxnorm/tokens.py): one scanner finds the spans — times, ISO dates, phone numbers, currency, percentages, room numbers, decimals, integers — and names their kind. - Verbalisation is per-language (
voxnorm/zh.py,cn.py,en.py,ja.py,ko.py): a new language is a new module and one registry entry. English, Japanese and Korean ride on num2words for their number words, each adding the readings the library cannot know — a Japanese year without the era default, Korean native-numeral hours (한 시, never 일 시), the irregular Korean months 유월/시월. Chinese is spelled out in-repo, Traditional first, because the 兩/二 reading choice (一千兩百, never 一千二百 in zh-TW speech) is exactly what general-purpose converters get wrong; the Simplified variant re-spells that one output vocabulary (兆→万亿, and¥reads as renminbi rather than yen).
Language is decided per call: an explicit hint wins
(normalize(text, lang="zh-TW")), otherwise script evidence decides — kana
means Japanese, hangul Korean, a Han character Chinese (Simplified when
distinctive simplified glyphs appear, Traditional otherwise), else English. (A
Japanese sentence written entirely in kanji is script-identical to Chinese
and lands on the Chinese reading — pass lang="ja" when you know better.)
The charter is structural only, never guess: a form whose reading is
ambiguous does not convert (1/2 the fraction and 8/17 the date are the
same slash string, so no slash form converts at all). What is not converted is
simply spoken as written — which is where it started.
Why not an FST toolkit
nemo_text_processing and WeTextProcessing were measured before this package
was written. Both depend on pynini, which publishes no aarch64 wheel — on an
ARM box they need OpenFst compiled from source and a permanently vendored
libfst.so — and both mis-read the very classes this package exists for:
phone numbers as quantities, 302號房 as a date, and (WeTextProcessing)
force-simplifying every Traditional character in the sentence it touches. The
classes a spoken product needs are a bounded set; implementing them directly
costs ~3 MB of pure Python and stays debuggable.
Install
uv add voxnorm # or: pip install voxnorm
Development
uv sync
uv run pytest
uv run ruff check .
Apache-2.0.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file voxnorm-0.1.0.tar.gz.
File metadata
- Download URL: voxnorm-0.1.0.tar.gz
- Upload date:
- Size: 34.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2df051959a3e36952ff75bb959e475f606587bf0740d15e3a207771c9fd7a824
|
|
| MD5 |
cbe4739c78aa39a76ae896d85df5079e
|
|
| BLAKE2b-256 |
57b754b500b736c7d02ffcb9e11aeb5c37a1a51a20bdfc2f22036c4bdf3e3229
|
Provenance
The following attestation bundles were made for voxnorm-0.1.0.tar.gz:
Publisher:
release.yml on allen2c/voxnorm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voxnorm-0.1.0.tar.gz -
Subject digest:
2df051959a3e36952ff75bb959e475f606587bf0740d15e3a207771c9fd7a824 - Sigstore transparency entry: 2496357619
- Sigstore integration time:
-
Permalink:
allen2c/voxnorm@4296592f5b4099bf1002ba26cbb07b83d885bf66 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/allen2c
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4296592f5b4099bf1002ba26cbb07b83d885bf66 -
Trigger Event:
push
-
Statement type:
File details
Details for the file voxnorm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: voxnorm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6ad1e68b79bdd6b1dff22923926ab8366ad3208b89a9291e1fc963406970430
|
|
| MD5 |
02c08b0e7457f4674bfe81cf64bd9a7b
|
|
| BLAKE2b-256 |
9c07235e2e46fbed33643530f920c0a885c4a827c2d93cb2a9daf0cc7eae5c27
|
Provenance
The following attestation bundles were made for voxnorm-0.1.0-py3-none-any.whl:
Publisher:
release.yml on allen2c/voxnorm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
voxnorm-0.1.0-py3-none-any.whl -
Subject digest:
b6ad1e68b79bdd6b1dff22923926ab8366ad3208b89a9291e1fc963406970430 - Sigstore transparency entry: 2496358264
- Sigstore integration time:
-
Permalink:
allen2c/voxnorm@4296592f5b4099bf1002ba26cbb07b83d885bf66 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/allen2c
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4296592f5b4099bf1002ba26cbb07b83d885bf66 -
Trigger Event:
push
-
Statement type: