Blog Audio: Gemini TTS Narration for Blog Posts

Generate audio narration of blog posts using Google Gemini TTS.

How to use it

  1. Hit Copy SKILL.md — or use the Claude Code line below to get every file.
  2. Claude: ⋯ → Download .md, then Customize → Skills → Add → Upload skill.
    ChatGPT: make a Project and paste it into Instructions.
    Neither? Paste it at the top of a new chat — it works for that chat.
  3. Describe your job in plain words. The AI follows the skill from there.
Claude Code — installs the whole folder, not just SKILL.md
npx degit AgriciDaniel/claude-blog/skills/blog-audio#main ~/.claude/skills/blog-audio-2

For one project only, change the path to .claude/skills/blog-audio-2. This skill also uses run.py, generate_audio.py, blog_audio_prepared.txt, blog_audio_dialogue.txt, Next.js — copying SKILL.md alone won't be enough. See the folder on GitHub.

Not working?
  • Check which app you pasted it into — the steps above name the right one.
  • Some skills need the paid tier of Claude or ChatGPT.
Step-by-step guide with screenshots · Ask in the forum

Paste into Claude, ChatGPT or Cursor.

Show the full text252 lines
blog-audio-2/SKILL.md252 lines8.6 KBpushed 27d agoRawView on GitHub

Blog Audio: Gemini TTS Narration for Blog Posts

Generate professional audio narration of blog content using Google's Gemini TTS. Three modes: summary (200-300 word spoken overview), full article read-aloud, or two-speaker podcast dialogue. 30 voices, 80+ languages, HTML5 embed output.

Quick Reference

Command What it does
/blog audio generate <file> Generate audio narration of a blog post
/blog audio voices Show available voices with characteristics
/blog audio setup Check/configure API key for Gemini TTS

Prerequisites

  • Python 3.11+ (venv managed automatically by run.py)
  • GOOGLE_AI_API_KEY environment variable (same key used by blog-image)
  • FFmpeg (for WAV-to-MP3 conversion; falls back to WAV if missing)

Always Use run.py Wrapper

# CORRECT:
python3 scripts/run.py generate_audio.py --text "..." --voice Charon --json

# WRONG:
python3 scripts/generate_audio.py --text "..."  # Fails without venv

API Key Check (Gate Pattern)

Before generating audio, check for the API key:

test -n "${GOOGLE_AI_API_KEY:-}" && echo "GOOGLE_AI_API_KEY is set" || echo "GOOGLE_AI_API_KEY is not set"
  • If set: proceed with generation
  • If not set: guide the user: "Audio generation requires a Google AI API key. Get one free at https://aistudio.google.com/apikey Then set it: export GOOGLE_AI_API_KEY=your-key This can be the same key used by /blog image, but it must be exported in the shell."
  • When called internally (from blog-write): return silently if key is missing. Never block the writing workflow.

Setup

For /blog audio setup:

  1. Check if GOOGLE_AI_API_KEY is set in environment
  2. If blog-image uses project .mcp.json, confirm the referenced env var is exported
  3. If not, guide user to https://aistudio.google.com/apikey
  4. Verify with a dry run: python3 scripts/run.py generate_audio.py --text "Test" --dry-run --json

Voice Selection

For /blog audio voices:

Load references/voices.md and present the voice catalog to the user.

Ask the user which voice they prefer, or recommend based on content type:

  • Article narration: Charon (Informative) or Sadaltager (Knowledgeable)
  • Tutorial/how-to: Achird (Friendly) or Sulafat (Warm)
  • News/analysis: Rasalgethi (Informative) or Schedar (Even)
  • Lifestyle/wellness: Aoede (Breezy) or Vindemiatrix (Gentle)
  • Dialogue host: Puck (Upbeat) or Laomedeia (Upbeat)
  • Dialogue expert: Kore (Firm) or Charon (Informative)

Generation Workflow

For /blog audio generate <file>:

Step 1: Read the Blog Post

Read the file and extract:

  • Title (from H1 or frontmatter)
  • Full content (markdown body)
  • Approximate word count

Step 2: Choose Mode

Ask the user (or auto-select if they specified --mode):

Mode When to use Output
Summary Quick audio overview (1-2 min) 200-300 word spoken summary
Full Complete read-aloud (5-15 min) Full article as natural speech
Dialogue Podcast-style (3-8 min) Two-person conversation about the article

Step 3: Prepare Text

Claude prepares the text; the script does TTS only.

Summary mode: Write a 200-300 word spoken summary of the article. Rules:

  • Write as natural speech, not written text
  • Open with the article's key finding or answer
  • Cover 3-5 main takeaways
  • Close with actionable advice
  • No markdown, no "In this article...", no meta-commentary
  • Use conversational transitions ("Here's what matters...", "The key finding is...")

Full mode: Strip the markdown content to clean spoken text:

  • Headings become natural transitions ("Next, let's look at...")
  • Links become plain text (remove URLs, keep anchor text)
  • Images and charts: omit or briefly describe ("As the data shows...")
  • Code blocks: describe verbally ("The code uses a for-loop to...")
  • Lists: convert to natural sentences
  • Remove frontmatter, schema markup, HTML tags
  • Add brief intro: "This is [title], published on [date]."

Dialogue mode: Write a 2-person conversation script about the article:

  • Speaker1 = Host (curious, asks good questions)
  • Speaker2 = Expert (knowledgeable, gives clear answers)
  • Format each line as: Speaker1: What's the key takeaway here?
  • Cover the article's main points conversationally
  • 15-25 exchanges (produces ~3-8 minutes)
  • Natural, not stilted ("That's a great point" over "Indeed, as the research indicates")

Step 4: Select Voice

If the user chose a voice, use it. Otherwise, recommend based on mode:

  • Summary/Full: default to Charon (Informative)
  • Dialogue: default to Puck (Host) + Kore (Expert)

Step 5: Generate Audio

Write the prepared text to a file under the working directory, then call:

# Single voice (summary or full mode)
python3 scripts/run.py generate_audio.py \
  --text-file blog_audio_prepared.txt \
  --voice Charon \
  --model flash \
  --output audio/post-slug.mp3 \
  --json

# Two voices (dialogue mode)
python3 scripts/run.py generate_audio.py \
  --text-file blog_audio_dialogue.txt \
  --voice Puck \
  --voice2 Kore \
  --model pro \
  --output audio/post-slug-dialogue.mp3 \
  --json

Model selection:

  • flash (default): maps to gemini-3.1-flash-tts-preview, good for summaries and standard narration.
  • flash31: explicit alias for gemini-3.1-flash-tts-preview.
  • legacy-flash25: retained only for older compatibility.
  • pro or legacy-pro25: maps to gemini-2.5-pro-preview-tts, use only when needed.

Step 6: Deliver

Present the result to the user:

  1. File path: where the audio was saved
  2. Duration: human-readable (e.g., "3:42")
  3. Embed code: ready-to-paste HTML5 audio tag
  4. Cost: estimated API cost
  5. Placement suggestion: where to insert the embed in the blog post

Embedding Guide

Standard HTML (Hugo, Jekyll, static sites)

<audio controls preload="metadata">
  <source src="audio/post-slug.mp3" type="audio/mpeg">
  Your browser does not support the audio element.
</audio>

MDX (Next.js, Gatsby)

<audio controls preload="metadata">
  <source src="/audio/post-slug.mp3" type="audio/mpeg" />
</audio>

WordPress

[audio src="audio/post-slug.mp3"]

Placement

Insert the audio player after the introduction (below the first H2) or at the very top of the article with a label: "Listen to this article" or "Audio version".

Internal API (for blog-write)

When invoked internally from blog-write:

Input:

  • text: Prepared text (already cleaned by Claude)
  • voice: Voice name (default: Charon)
  • voice2: Second voice for dialogue (optional)
  • model: flash or pro
  • output_path: Where to save the file

Output:

### Audio Narration
- **Path:** /path/to/audio/post-slug.mp3
- **Duration:** 3:42
- **Voice:** Charon
- **Embed:** `<audio controls preload="metadata"><source src="audio/post-slug.mp3" type="audio/mpeg"></audio>`

Graceful fallback: If GOOGLE_AI_API_KEY is not set, return immediately with no error. The writing workflow continues without audio. Never block blog-write because audio generation is unavailable.

Error Handling

Error Resolution
GOOGLE_AI_API_KEY not set Get key at https://aistudio.google.com/apikey
FFmpeg not found Install: sudo apt install ffmpeg. Falls back to WAV output.
Rate limited Wait and retry. Check limits at https://aistudio.google.com/rate-limit
Text too long (>8,192 input tokens) Split into sections around 7,800 tokens; the script chunks and stitches prepared text
Unknown voice name Run /blog audio voices to see valid options
API error Check key validity and model availability
API key missing (internal call) Return silently: writing workflow continues

Reference Documentation

Load on-demand: do NOT load all at startup:

  • references/voices.md: Full 30-voice catalog, recommendations by content type, dialogue pairings
1---
2name: blog-audio
3description: >
4 Generate audio narration of blog posts using Google Gemini TTS.
5 Supports summary narration, full article read-aloud, and two-speaker
6 podcast/dialogue mode with 30 voice options. Outputs MP3 with HTML5
7 audio embed code. Works standalone via /blog audio or internally from
8 blog-write. Falls back gracefully when API key is not configured.
9 Use when user says "blog audio", "narrate blog", "audio version",
10 "text to speech", "tts", "podcast mode", "read aloud", "audio narration",
11 "voice", "narration", "generate audio".
12user-invokable: true
13argument-hint: "[generate|voices|setup] [file-or-text] [--mode summary|full|dialogue] [--voice name]"
14license: MIT
15metadata:
16 author: AgriciDaniel
17 version: "2.2.0"
18---
19 
20# Blog Audio: Gemini TTS Narration for Blog Posts
21 
22Generate professional audio narration of blog content using Google's Gemini TTS.
23Three modes: summary (200-300 word spoken overview), full article read-aloud,
24or two-speaker podcast dialogue. 30 voices, 80+ languages, HTML5 embed output.
25 
26## Quick Reference
27 
28| Command | What it does |
29|---------|-------------|
30| `/blog audio generate <file>` | Generate audio narration of a blog post |
31| `/blog audio voices` | Show available voices with characteristics |
32| `/blog audio setup` | Check/configure API key for Gemini TTS |
33 
34## Prerequisites
35 
36- Python 3.11+ (venv managed automatically by `run.py`)
37- `GOOGLE_AI_API_KEY` environment variable (same key used by blog-image)
38- FFmpeg (for WAV-to-MP3 conversion; falls back to WAV if missing)
39 
40## Always Use run.py Wrapper
41 
42```bash
43# CORRECT:
44python3 scripts/run.py generate_audio.py --text "..." --voice Charon --json
45 
46# WRONG:
47python3 scripts/generate_audio.py --text "..." # Fails without venv
48```
49 
50## API Key Check (Gate Pattern)
51 
52Before generating audio, check for the API key:
53 
54```bash
55test -n "${GOOGLE_AI_API_KEY:-}" && echo "GOOGLE_AI_API_KEY is set" || echo "GOOGLE_AI_API_KEY is not set"
56```
57 
58- If set: proceed with generation
59- If not set: guide the user:
60 "Audio generation requires a Google AI API key. Get one free at https://aistudio.google.com/apikey
61 Then set it: `export GOOGLE_AI_API_KEY=your-key`
62 This can be the same key used by `/blog image`, but it must be exported in the shell."
63- **When called internally** (from blog-write): return silently if key is missing.
64 Never block the writing workflow.
65 
66## Setup
67 
68For `/blog audio setup`:
69 
701. Check if `GOOGLE_AI_API_KEY` is set in environment
712. If blog-image uses project `.mcp.json`, confirm the referenced env var is exported
723. If not, guide user to https://aistudio.google.com/apikey
734. Verify with a dry run: `python3 scripts/run.py generate_audio.py --text "Test" --dry-run --json`
74 
75## Voice Selection
76 
77For `/blog audio voices`:
78 
79Load `references/voices.md` and present the voice catalog to the user.
80 
81Ask the user which voice they prefer, or recommend based on content type:
82- **Article narration**: Charon (Informative) or Sadaltager (Knowledgeable)
83- **Tutorial/how-to**: Achird (Friendly) or Sulafat (Warm)
84- **News/analysis**: Rasalgethi (Informative) or Schedar (Even)
85- **Lifestyle/wellness**: Aoede (Breezy) or Vindemiatrix (Gentle)
86- **Dialogue host**: Puck (Upbeat) or Laomedeia (Upbeat)
87- **Dialogue expert**: Kore (Firm) or Charon (Informative)
88 
89## Generation Workflow
90 
91For `/blog audio generate <file>`:
92 
93### Step 1: Read the Blog Post
94 
95Read the file and extract:
96- Title (from H1 or frontmatter)
97- Full content (markdown body)
98- Approximate word count
99 
100### Step 2: Choose Mode
101 
102Ask the user (or auto-select if they specified `--mode`):
103 
104| Mode | When to use | Output |
105|------|-------------|--------|
106| **Summary** | Quick audio overview (1-2 min) | 200-300 word spoken summary |
107| **Full** | Complete read-aloud (5-15 min) | Full article as natural speech |
108| **Dialogue** | Podcast-style (3-8 min) | Two-person conversation about the article |
109 
110### Step 3: Prepare Text
111 
112Claude prepares the text; the script does TTS only.
113 
114**Summary mode:**
115Write a 200-300 word spoken summary of the article. Rules:
116- Write as natural speech, not written text
117- Open with the article's key finding or answer
118- Cover 3-5 main takeaways
119- Close with actionable advice
120- No markdown, no "In this article...", no meta-commentary
121- Use conversational transitions ("Here's what matters...", "The key finding is...")
122 
123**Full mode:**
124Strip the markdown content to clean spoken text:
125- Headings become natural transitions ("Next, let's look at...")
126- Links become plain text (remove URLs, keep anchor text)
127- Images and charts: omit or briefly describe ("As the data shows...")
128- Code blocks: describe verbally ("The code uses a for-loop to...")
129- Lists: convert to natural sentences
130- Remove frontmatter, schema markup, HTML tags
131- Add brief intro: "This is [title], published on [date]."
132 
133**Dialogue mode:**
134Write a 2-person conversation script about the article:
135- Speaker1 = Host (curious, asks good questions)
136- Speaker2 = Expert (knowledgeable, gives clear answers)
137- Format each line as: `Speaker1: What's the key takeaway here?`
138- Cover the article's main points conversationally
139- 15-25 exchanges (produces ~3-8 minutes)
140- Natural, not stilted ("That's a great point" over "Indeed, as the research indicates")
141 
142### Step 4: Select Voice
143 
144If the user chose a voice, use it. Otherwise, recommend based on mode:
145- Summary/Full: default to Charon (Informative)
146- Dialogue: default to Puck (Host) + Kore (Expert)
147 
148### Step 5: Generate Audio
149 
150Write the prepared text to a file under the working directory, then call:
151 
152```bash
153# Single voice (summary or full mode)
154python3 scripts/run.py generate_audio.py \
155 --text-file blog_audio_prepared.txt \
156 --voice Charon \
157 --model flash \
158 --output audio/post-slug.mp3 \
159 --json
160 
161# Two voices (dialogue mode)
162python3 scripts/run.py generate_audio.py \
163 --text-file blog_audio_dialogue.txt \
164 --voice Puck \
165 --voice2 Kore \
166 --model pro \
167 --output audio/post-slug-dialogue.mp3 \
168 --json
169```
170 
171**Model selection:**
172- `flash` (default): maps to `gemini-3.1-flash-tts-preview`, good for summaries and standard narration.
173- `flash31`: explicit alias for `gemini-3.1-flash-tts-preview`.
174- `legacy-flash25`: retained only for older compatibility.
175- `pro` or `legacy-pro25`: maps to `gemini-2.5-pro-preview-tts`, use only when needed.
176 
177### Step 6: Deliver
178 
179Present the result to the user:
1801. **File path**: where the audio was saved
1812. **Duration**: human-readable (e.g., "3:42")
1823. **Embed code**: ready-to-paste HTML5 audio tag
1834. **Cost**: estimated API cost
1845. **Placement suggestion**: where to insert the embed in the blog post
185 
186## Embedding Guide
187 
188### Standard HTML (Hugo, Jekyll, static sites)
189```html
190<audio controls preload="metadata">
191 <source src="audio/post-slug.mp3" type="audio/mpeg">
192 Your browser does not support the audio element.
193</audio>
194```
195 
196### MDX (Next.js, Gatsby)
197```jsx
198<audio controls preload="metadata">
199 <source src="/audio/post-slug.mp3" type="audio/mpeg" />
200</audio>
201```
202 
203### WordPress
204```
205[audio src="audio/post-slug.mp3"]
206```
207 
208### Placement
209Insert the audio player after the introduction (below the first H2) or at the
210very top of the article with a label: "Listen to this article" or "Audio version".
211 
212## Internal API (for blog-write)
213 
214When invoked internally from blog-write:
215 
216**Input:**
217- `text`: Prepared text (already cleaned by Claude)
218- `voice`: Voice name (default: Charon)
219- `voice2`: Second voice for dialogue (optional)
220- `model`: flash or pro
221- `output_path`: Where to save the file
222 
223**Output:**
224```markdown
225### Audio Narration
226- **Path:** /path/to/audio/post-slug.mp3
227- **Duration:** 3:42
228- **Voice:** Charon
229- **Embed:** `<audio controls preload="metadata"><source src="audio/post-slug.mp3" type="audio/mpeg"></audio>`
230```
231 
232**Graceful fallback:** If `GOOGLE_AI_API_KEY` is not set, return immediately
233with no error. The writing workflow continues without audio. Never block
234blog-write because audio generation is unavailable.
235 
236## Error Handling
237 
238| Error | Resolution |
239|-------|-----------|
240| GOOGLE_AI_API_KEY not set | Get key at https://aistudio.google.com/apikey |
241| FFmpeg not found | Install: `sudo apt install ffmpeg`. Falls back to WAV output. |
242| Rate limited | Wait and retry. Check limits at https://aistudio.google.com/rate-limit |
243| Text too long (>8,192 input tokens) | Split into sections around 7,800 tokens; the script chunks and stitches prepared text |
244| Unknown voice name | Run `/blog audio voices` to see valid options |
245| API error | Check key validity and model availability |
246| API key missing (internal call) | Return silently: writing workflow continues |
247 
248## Reference Documentation
249 
250Load on-demand: do NOT load all at startup:
251- `references/voices.md`: Full 30-voice catalog, recommendations by content type, dialogue pairings
252 

Discussion

Alternatives

Also in Podcast & newsletter