Author: Produced by 43 COLLEGE 凯寓 (KAIYU) Version: v1.0
Throw any text in → MP3 voiceover audio file.Automatic routing by language:
- Chinese → MiniMax (better Chinese quality)
- English → ElevenLabs
Trigger scenarios
- "Use TTS to generate a voiceover / narration / dubbing / read-aloud from this passage"
- "Turn this text into speech / read it out / record it as audio"
- "Make a video narration MP3"
- "voice this / narrate this / read this aloud"
- "Record this WeChat official account article as audio"
Do not trigger: the user is only asking "what does TTS mean" — this is a general-knowledge question. The Skill is only used togenerate MP3 files。
First-time use
You need to configure the API key of at least one provider first (recommended to configure both, for automatic routing by language). See SETUP.md。
Cross-platform compatibility
| Project | macOS / Linux | Windows |
|---|---|---|
| Python | python3 |
python |
| Path separator | / |
\ |
| Script path | ${CLAUDE_SKILL_DIR}/scripts/tts.py |
${CLAUDE_SKILL_DIR}\scripts\tts.py |
All examples below use the python3 style (Mac/Linux). Windows users should replace python3 with python, and / with \ .
About
${CLAUDE_SKILL_DIR}: this is a documentation placeholder representing the installation directory of this skill (usually~/.claude/skills/voiceover-maker/). Claude will automatically replace it with the actual path when executing commands in bash,users should not copy this string directly into the terminal— copying and pasting directly will fail because the variable is undefined. Either hand the command to the AI to execute, or replace it yourself with the specific directory.
Main script
scripts/tts.py, zero dependencies (only the Python 3 standard library).
Preflight (recommended for first-time use)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --check
Does not call the real synthesis API, only checks: whether config.json exists and can be parsed, whether the ElevenLabs / MiniMax keys are filled in, whether the silent asset is in place, and the network reachability of the two endpoints. After configuring the keys, run the preflight once to confirm there are no problems before doing the actual synthesis.
Basic usage
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "你好世界,这是一段中文测试。" # auto → MiniMax
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "Hello world, this is a test." # auto → ElevenLabs
Output defaults to ~/Desktop, filename tts-{provider}-{voice}-{timestamp}.mp3。
Specify a voice (case-insensitive)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "中文" --voice lyrical # MiniMax 抒情男声
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "中文" --voice news_anchor # MiniMax 新闻女声
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "English" --voice rachel # ElevenLabs Rachel
--voice accepts both preset names and raw voice_id pass-through.
Force a provider
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "文本" --provider minimax
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "文本" --provider elevenlabs
MiniMax emotion / speed / pitch / volume
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "好消息!" --emotion happy --speed 1.1
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "深夜独白" --voice radio_host --emotion sad --speed 0.9
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "正式播报" --voice executive --pitch -2 --vol 1.2
--emotion:neutral/happy/sad/angry/fearful/disgusted/surprised--speed:0.5–2.0--pitch:-12–12--vol:0.5–2.0
ElevenLabs does not support these parameters; if passed, they will be ignored and a warning will be logged.
Long text (read from a file + automatic segmentation)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --file 旁白稿.txt
- MiniMax single-call limit is 9500 characters (buffer kept; original 10000)
- ElevenLabs single-call limit is 4500 characters
- Overly long text is automatically segmented in the order paragraph → sentence-ending punctuation → hard cut, and the multiple MP3 frames are concatenated directly
Dry-run (estimate character count and price, without calling the API)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --file 长稿.txt --dry-run
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "测试文本" --provider minimax --dry-run
Prints: text character count / estimated billable characters / estimated cost (CNY/USD) / number of segments + character count of each segment.
Dry-run long scripts first— MiniMax bills Chinese at CJK ×2, so for long Chinese scripts the billable characters are about twice the character count.
Voice preview (5-second sample)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --preview lyrical
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --preview rachel
Quickly generate a 5s sample to ~/Desktop/preview-{provider}-{voice}.mp3using a fixed example sentence, for easy side-by-side voice comparison. Files with the same name will be overwritten automatically.
Silence between long-form paragraphs
When synthesizing multiple paragraphs, by default insert 300ms of silencebetween paragraphs (to avoid them sounding too rushed); the asset comes from assets/silence-300ms-32k-128k-mono.mp3(aligned with MiniMax's default audio_setting).
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --file 长稿.txt --gap-ms 600
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --file 长稿.txt --gap-ms 0
--gap-ms Rounded to 300ms units (the asset is one 300ms unit, repeated an integer number of times).
Output control
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "文本" -o ~/Desktop/旁白.mp3
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "文本" -o - # stdout(管道)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "文本" --force # 覆盖已存在文件
By default, existing files are not overwritten (to protect against accidents).
List local presets
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --list-voices
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --list-voices minimax
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --list-voices elevenlabs
List MiniMax remote system voices (about 300)
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --list-remote-voices
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --list-remote-voices --filter Lyrical
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --list-remote-voices --filter Female
Call /v1/get_voice to get the full system_voice list. Pick the ones you want to use, add the voice_id to config.json 's providers.minimax.voices and give it a short name.
Local cache
The same (provider + voice_id + model + text + voice_settings + audio_setting) combination hits the cache and is reused directly, without calling the API again. On a cache hit, print ✓ cache hit [hash]。
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py "文本" --no-cache # 跳过缓存
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --clear-cache # 清空缓存
Cache location:cache/{16位哈希}.mp3 + cache/{16位哈希}.json(meta)。
Usage statistics
python3 ${CLAUDE_SKILL_DIR}/scripts/tts.py --usage
Print: today's / this month's / cumulative request count + billed character count; broken down by key; cache size.
Data comes from usage.jsonl(a JSON line is appended on every real API call; cache hits are not counted).
Unit tests (pure logic, no network calls)
cd ${CLAUDE_SKILL_DIR}
python3 -m unittest tests.test_logic -v
26 tests cover language detection, text segmentation, billed characters, cache key, and price estimation. Run them before and after changing core functions to avoid regressions.
Routing rules
config.json 's language_routing:
{ "zh": "minimax", "en": "elevenlabs" }
Language detection: a Chinese character ratio ≥ 15% is considered Chinese, otherwise English. When Chinese and English are mixed and the ratio ∈ [10%, 90%], print amixed warning, suggesting you can use --provider to force a switch.
Priority(high to low):
--providerexplicitly specified--voiceis a preset name of some provider → infer backwards (a name conflict is an error, requiring--providerto be explicit)- auto + language detection
Built-in voice presets + scenario suggestions
MiniMax (Chinese, default model speech-2.8-hd)
| Preset | Description | Suitable scenarios |
|---|---|---|
| lyrical(default) | Lyrical male voice, youthful and magnetic, smooth and expressive | Rational narration, emotional passages |
| gentleman | Warm male voice, youthful and magnetic, standard Mandarin | Product introductions, customer video openings |
| radio_host | Radio male host, youthful and poetic, late-night radio quality | Artsy narration, brand mood films |
| executive | Steady executive, middle-aged male, business depth | Business PPT narration, investor roadshows |
| announcer | Announcer male voice, middle-aged and magnetic, clear and authoritative | Formal launches, annual report data |
| sincere | Sincere youthful male voice, encouraging and highly approachable | User thank-you notes, community announcements |
| news_anchor | News female voice, middle-aged professional broadcasting tone | News reports, serious popular science |
| sweet_lady | Sweet female voice, youthful and gentle | Lightweight tutorials, consumer goods |
| warm_bestie | Warm bestie female voice, youthful, crisp and friendly | Lifestyle vlogs, dialogue scenes |
| wise_women | Experienced older-sister female voice, middle-aged lyrical | Long-form article excerpts, reminiscing-style narration |
MiniMax has about 300 system voices in total. To add other voices: use --list-remote-voices to look up the voice_id, and add it to config.json 's providers.minimax.voices。
ElevenLabs (English, default model eleven_multilingual_v2)
| Preset | Description |
|---|---|
| rachel(default) | English female voice, steady, science-explainer narration |
| adam | English male voice, deep |
| bella | English female voice, young and lively |
| antoni | English male voice, gentle |
| domi | English female voice, powerful |
| josh | English male voice, young |
Configuration / Credentials
config.json(Do not commit, contains API keys) copied from config.json.template , structure:
{
"default_output_dir": "~/Desktop",
"default_provider": "auto",
"language_routing": { "zh": "minimax", "en": "elevenlabs" },
"providers": {
"elevenlabs": { api_key, endpoint, default_voice, default_model, voices, voice_settings },
"minimax": { api_key, fallback_keys, endpoint, default_voice, default_model, voices, voice_settings, audio_setting }
}
}
It can run with only one provider configured—if only ElevenLabs is filled in, Chinese text will also be routed to ElevenLabs (slightly lower quality, but usable). It's recommended to configure both.
Environment variable overrides
Priority: environment variables > config.json. When switching machines or in CI, use:
| Variable | Purpose |
|---|---|
ELEVENLABS_API_KEY |
Override ElevenLabs key |
MINIMAX_API_KEY |
Override MiniMax primary key |
MINIMAX_FALLBACK_API_KEY |
Append as the highest-priority fallback key |
Fault tolerance mechanism
Network layer (retry with the same key)
- HTTP 5xx → exponential backoff (2s → 4s), retry 2 times
- URLError (DNS, timeout, connection refused) → same as above
- 4xx → no retry, hand over to the business layer for judgment
MiniMax dual-key fallback (switch key and retry)
Trigger conditions (strict whitelist, no longer substring keyword matching to avoid misjudgment):
- HTTP status codes: 401 / 402 / 403 / 429
- Business status codes: 1004 / 1008 / 1029 / 1039 / 2013 / 2049
If any is met → switch to fallback_keys and retry. Other errors (such as 2039 text moderation) fail directly, without wasting fallback quota.
api_key is used first (recommended to put the cheaper key, such as a Token Plan key),fallback_keys as the fallback (pay-as-you-go key). The two are not interchangeable, and both must be configured to use fallback. It can also run with only one configured.
Notes
- ElevenLabs bills by characters; before long text, first
--dry-run - MiniMax has a text limit of 10000 characters per request (the script uses 9500 to leave a buffer, and automatically segments)
- MiniMax bills by UTF-8 bytes, 1 Chinese character ≈ 2 characters
- Cross-platform: runs on Mac/Windows/Linux, zero third-party dependencies
- Default output
~/Desktop, can be changed inconfig.jsontodefault_output_dir - Existing output files are not overwritten by default; use
--force
Source references
- ElevenLabs documentation:https://elevenlabs.io/docs/api-reference/text-to-speech
- MiniMax documentation:https://platform.minimaxi.com/docs/api-reference/speech-t2a-http.md
- MiniMax voice list:https://platform.minimaxi.com/docs/faq/system-voice-id.md