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
- crawl_find platform and kind: which Sume social route each calls
In Sume's MCP, crawl_find takes platform plus kind; tiktok videos means keyword search and tiktok top means top search. Full mapping to the REST routes.
- Sume SDK backoff: 500 ms to 8 s, retry-after capped at 60 s
The Sume TypeScript client waits min(500 x 2^attempt, 8000) ms, honors retry-after up to 60 s, and adds 20 percent jitter. The full table by attempt.
- Unit test Sume SDK retries with a fake fetch
createSumeClient takes a fetch option for tests. Feed it a 503 then a 200 to prove the SDK retries a GET and sends one x-api-key, with no network.
- 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.
Written by Sume