Skip to main content

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

voxnorm-0.1.0.tar.gz (34.7 kB view details)

Uploaded Source

Built Distribution

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

voxnorm-0.1.0-py3-none-any.whl (22.4 kB view details)

Uploaded Python 3

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

Hashes for voxnorm-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2df051959a3e36952ff75bb959e475f606587bf0740d15e3a207771c9fd7a824
MD5 cbe4739c78aa39a76ae896d85df5079e
BLAKE2b-256 57b754b500b736c7d02ffcb9e11aeb5c37a1a51a20bdfc2f22036c4bdf3e3229

See more details on using hashes here.

Provenance

The following attestation bundles were made for voxnorm-0.1.0.tar.gz:

Publisher: release.yml on allen2c/voxnorm

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

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

Hashes for voxnorm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b6ad1e68b79bdd6b1dff22923926ab8366ad3208b89a9291e1fc963406970430
MD5 02c08b0e7457f4674bfe81cf64bd9a7b
BLAKE2b-256 9c07235e2e46fbed33643530f920c0a885c4a827c2d93cb2a9daf0cc7eae5c27

See more details on using hashes here.

Provenance

The following attestation bundles were made for voxnorm-0.1.0-py3-none-any.whl:

Publisher: release.yml on allen2c/voxnorm

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

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Supported by

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