Count TTS characters like Sume: JS string length, emoji and Hangul
Sume TTS counts characters as JavaScript string length, so an emoji counts as 2. A short Python function counts UTF-16 units to predict the limit and cost.

The counting rule
The API reference for Sume TTS 1.0 says the transcript can be at most 20,000 characters, that spaces and punctuation count, and that the count matches JavaScript string length. JavaScript string length counts UTF-16 code units, not what a person sees as characters.
Price follows the same count at $0.0475 per 1,000 characters, so the count is the invoice.
Where Python disagrees
Python's len() counts Unicode code points. They match for most text, but differ for characters outside the Basic Multilingual Plane, which JavaScript stores as two units. Most emoji are one code point in Python and two in JavaScript.
Precomposed Hangul syllables are in the BMP and count as one. A syllable written as separate jamo counts for each piece, so normalise text if you want the shortest count.
| Text | Python len() | UTF-16 units (JavaScript length) |
|---|---|---|
| hello | 5 | 5 |
| 한국어 | 3 | 3 |
| a😀b | 3 | 4 |
The function
import unicodedata
def tts_chars(text, normalize=True):
if normalize:
text = unicodedata.normalize("NFC", text)
return len(text.encode("utf-16-le")) // 2
for s in ["hello", "한국어", "a\U0001F600b"]:
print(repr(s), len(s), tts_chars(s))Using it
- Check
tts_chars(script) <= 20000before submitting. - Estimate cost as
tts_chars(script) * 0.0475 / 1000. - Split on paragraph breaks when a script is over the limit.
- Strip markup and stage directions first, since they count.
Test strings worth keeping
Keep a few fixtures in your tests: plain ASCII, Korean text, a string with an emoji, and a string with a newline. Check that your counter agrees with the length you get in JavaScript for each. If your service is written in JavaScript, use text.length directly and skip this function.
Counting before submit is cheap and prevents a refused request on a script that sits near the limit.
Normalisation caveat
NFC normalisation changes the text you send, so decide whether you want the shorter count. If you send the original string, count the original. The API counts what you actually send. See the cost per minute post for dollar examples.
Sources
Related posts
More in Developers
- Cursor mcp.json ${env:NAME} for Sume's API key: no secret in the repo
Cursor's mcp.json interpolates ${env:NAME} in headers. Keep Sume's API key in an environment variable, send one credential, and know the fixed OAuth redirects.
- Cut a voiceover into sentence clips with TTS segmentation
Sume TTS returns gapless sentence segments, cutting 70 ms after each last word by default. Per-segment audio needs wav or raw; mp3 returns timings only.
- DBOS Python durable workflow for a Sume job: resume after a crash
Submit and poll a Sume image job in a DBOS workflow: step retries, order-derived Idempotency-Key and workflow id, tested with DBOS 3.2.0 on SQLite.
- Deno 2.9 Deno.test.each: a case table for a Sume webhook verifier
Deno 2.9 adds Deno.test.each. Table-test a sume-v1 verifier: valid, rotated, empty secret, stale timestamp and tampered body, with WebCrypto only.
Written by Sume