Skip to main content

poto-tts

English text-to-speech that pronounces Ghanaian words properly. Kwabena, Achimota, the Okuapenhene, Nyankpani, Gyasi — names, places and titles that every general TTS mangles.

To be clear about what this is not: the voices do not have a Ghanaian accent. They are Kokoro's speakers — British and American — and this library changes what they say, not who they sound like. A Ghanaian-sounding voice needs a model trained on Ghanaian speech, which is a different problem. What you get here is an English voice that no longer stumbles over Ghanaian words.

                 general TTS                    poto-tts
Kwabena          kwˈeɪbnə                       kwabˈina
Achimota         ɐtʃɪmˈoʊɾə                     atʃimˈota
Okuapenhene      ˈoʊkjuːˌeɪpənhˌiːn             okwapenhˈene
Nyankpani        nˌaɪɐŋkpˈɑːni                  njankpˈani
Gyasi            dʒaɪʲˈɑːsi                     dʒˈasi
pip install poto-tts
poto-tts "Kwabena went to Achimota" -o out.wav
from poto_tts import load

tts = load()                               # downloads the voice on first use
tts.save("The Okuapenhene met Nana Bawumia", "out.wav")

How it works

text, then a per-word lookup in the Ghanaian lexicon
with an espeak fallback, then respelling into lfn letters, then espeak reads it back,
then Kokoro speaks it

The middle step is the one that needs explaining. sherpa-onnx hands Kokoro text and runs espeak over it — there is no way to pass phonemes in — so a pronunciation can only reach the model as spelling espeak will read correctly. Lingua Franca Nova is used as that notation because its spelling is strictly phonemic, one letter per sound, where English spelling is not (through, though, tough). So /kwabɪna/ is written kwabina, espeak reads it back as /kwabˈina/, and Kokoro says it. lfn is a codec, not a language.

Because every word goes through the lexicon, the Ghanaian pronunciation reaches ordinary English too — convention is /kɔnvɛnʃən/ rather than /kənvˈɛnʃən/ — and that is most of what makes the result sound local. On name-heavy news text the lexicon covers about nine words in ten; the rest fall back to espeak's English, respelled the same way.

Roughly six times more Ghanaian words come out right than with stock Kokoro. That is measurable — tools/measure_coverage.py — but the honest summary is simpler: the names work now.

Voices

Twenty-eight English speakers, named so you can choose one without decoding a prefix:

female male
British Grace, Comfort, Mercy, Patience Emmanuel, Isaac, Ebenezer, Bright
American Gifty, Beatrice, Esther, Vida, Felicia, Priscilla, Charity, Regina, Cynthia, Georgina, Adelaide Samuel, Prince, Godfred, Wisdom, Justice, Solomon, Nathaniel, Cephas, Desmond

★ British voices sit closest to educated Ghanaian English, so they come first. A listening judgement, not a measurement.

tts = load(voice="Emmanuel")      # "bm_george" and 26 work too
poto-tts --voices                 # the full list with genders and ids

The names are aliases and do not change a voice's timbre. Kokoro's 25 other-language speakers exist in the model but are not offered here — a Ghanaian English library cannot vouch for a Japanese speaker.

Cross-platform

sherpa-onnx runs on Android, iOS, WebAssembly, C++, C, Go, C#, Java, Kotlin, Rust, Dart and Swift. Those runtimes have no Python, so they cannot run the respeller — and they still get the names right, because the voice ships an espeak-ng-data directory with 69,198 Ghanaian pronunciations compiled into espeak's own English dictionary.

pronunciation comes from reaches
Python the respeller (lfn) every word in the sentence
Android, iOS, WASM, C++ the compiled espeak dictionary Ghanaian names, places, titles

Ship these four files and send plain text:

onnx/model.onnx      the generator
voices.bin           speaker embeddings
tokens.txt           phoneme → id
espeak-ng-data/      the Ghanaian part — ship this one

Swap in a stock espeak-ng-data and you get a working voice that mispronounces every Ghanaian name, with no error. That directory is the deliverable.

Want the full respelling on device? It is a lookup and a join, and the voice repo carries the table: lfn-lexicon.tsv.gz, 104,623 words mapped to their lfn spelling. Look each word up, join with spaces, synthesise with lang=lfn. What a port must decide is what to do with a word the table lacks — under lang=lfn an unrespelled English word is read with Latin letter values, so either fall back to the dictionary route for that utterance or accept the odd word.

sherpa-onnx docs · the voice on the Hub

Web interface and REST API

pip install 'poto-tts[api]'
poto-tts serve                    # then open http://localhost:8080

Paste text and hear it, or upload a CSV for batch work and get back a ZIP of WAVs with a manifest. One HTML file, no build step, no CDN — it renders on a laptop or a Pi with no internet.

text,voice,filename
"Kwabena went to Achimota",Grace,kwabena
"The Okuapenhene met Nana Bawumia",Emmanuel,durbar

text is the only required column, and a single-column file with no header works too. Batches are capped at 500 rows and refused rather than trimmed.

endpoint
GET / the web interface
GET /speak?text=… · POST /speak WAV
POST /batch CSV → ZIP of WAVs plus a manifest
GET /voices · GET /backends · GET /health
GET /platforms how to run the same voice off-server

Changing how a word is said

The lexicon will still miss your grandmother's name.

tts = load(lexicon={"Owusu": "o w u s u", "Tetteh": "t ɛ t ɛ"})

Values are Ghanaian IPA. That fixes the Python path immediately. To fix it for Android and iOS as well, rebuild the dictionary they read — needs the espeak-ng binary and the lexicon extra:

pip install 'poto-tts[lexicon]'
poto-tts dict --out my-espeak-data --extra my_words.tsv
Owusu	o w u s u
Kufuor	=kufu'or

A leading = passes raw espeak mnemonics through, and is checked — an invalid mnemonic makes espeak silently discard the rest of an entry, so the build rejects it rather than shipping half a word. Your entries override the packaged lexicon, so the file corrects as well as adds.

To see what will be sent to espeak, without loading a model:

poto-tts --phonemes "Nana Addo met Kwame Nkrumah"

Models

Fetched from the Hub through huggingface_hub, so downloads are counted: ghananlpcommunity/poto-tts-kokoro-gh. Each repo's config.json names its own files, speakers and licence, so a new voice needs no release of this library:

tts = load(repo_id="your-org/your-voice")

POTO_TTS_CACHE moves model files off ~/.cache.

Training a voice

tools/ holds a full Piper training pipeline — dataset fetch, filtering, speaker clustering, forced alignment, punctuation from measured pauses, training, export. It was used to train a Ghanaian voice on 85 hours of broadcast speech, and that voice is not shipped: at 45,000 steps it sounded worse than Kokoro with a good front-end, and its corpus is broadcast recordings whose speakers never consented to being modelled. The pipeline is kept because it works on any dataset you hold rights to.

Thresholds in it are measured rather than chosen, and the comments record the distributions they came from — including the ones that were wrong first.

Credits

sherpa-onnx (k2-fsa) · Kokoro-82M (hexgrad, Apache-2.0) · ghana-english-g2p (GhanaNLP) · espeak-ng · Lingua Franca Nova, whose orthography is doing work its designers did not plan for

MIT, except the bundled espeak-ng-data, which is GPL-3.0.

Download files

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

Source Distribution

poto_tts-0.5.0.tar.gz (62.7 kB view details)

Uploaded Source

Built Distribution

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

poto_tts-0.5.0-py3-none-any.whl (58.7 kB view details)

Uploaded Python 3

File details

Details for the file poto_tts-0.5.0.tar.gz.

File metadata

  • Download URL: poto_tts-0.5.0.tar.gz
  • Upload date:
  • Size: 62.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.10.12

File hashes

Hashes for poto_tts-0.5.0.tar.gz
Algorithm Hash digest
SHA256 95594928e72d580ee7034fe3a197a292a6f534de45224b124ddee1af3422fc11
MD5 3c92fad364e22ea653fca5816037bd46
BLAKE2b-256 4f656066791205a2e3e207e5363e6150da4aa2ccde2f70622742587d04ec8ce2

See more details on using hashes here.

File details

Details for the file poto_tts-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: poto_tts-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 58.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.10.12

File hashes

Hashes for poto_tts-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 033b04f0847c9fe395ada13124b3aa49979145e3e7770ffaed055a0b45bccd54
MD5 22a2e25a9b48e449f2be78cdb0bc7241
BLAKE2b-256 6769d58f9233505cb7c2c56430256bea8e97e9836e31f0041d55a51bdb872020

See more details on using hashes here.

Release history Release notifications | RSS feed

0.7.0

2 files

0.6.1

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page