| 1 | # Audio Narration & Video Export |
| 2 | |
| 3 | [English](./audio-narration.md) | [Chinese](./zh/audio-narration.md) |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | PPT Master can turn the speaker notes into per-slide narration via [`edge-tts`](https://github.com/rany2/edge-tts) (Microsoft Edge's online neural voices) by default, or via ElevenLabs, MiniMax, Qwen TTS, and CosyVoice when you need higher-quality cloud narration or a cloned voice. The edge path also writes a page-local SRT from the same TTS stream. It can then embed the audio back into the PPTX for PowerPoint's native video export. |
| 8 | |
| 9 | ## What you get |
| 10 | |
| 11 | - One audio file per slide under `<project_path>/audio/`, named to match the SVG (`01_cover.mp3`, `02_market_landscape.mp3`, …). |
| 12 | - With edge, one matching subtitle file per slide under `<project_path>/notes/subtitles/` (`01_cover.srt`, `02_market_landscape.srt`, …). Each file uses a page-local timeline with a `00:00:00,000` origin and edge's word-boundary timing. |
| 13 | - When canonical `animations.json` exists, an SVG-to-SRT timing plan derives `narration_animations.json` whose click-free object animations wait for the relevant subtitle cue. When neither animation sidecar exists, narrated export creates no sidecar and keeps the default `fade` page transition with no per-element builds. Both paths can produce a deck-wide `<project_path>/notes/subtitles/total.srt` aligned to the final PPTX timeline; after PowerPoint exports a video, the same command can calibrate page starts against its audio track for frame-accurate sidecar subtitles. |
| 14 | - Optional re-export: a new PPTX in `exports/` with each `m4a` / `mp3` / `wav` file embedded into the matching slide and slide auto-advance timings set to the audio length, so kiosk/auto-play and video export work without manual timing. |
| 15 | - Optional native video export on Windows: `powerpoint_video.py` delegates the final narrated PPTX to PowerPoint 2016+ and waits until its native MP4 encoder succeeds or fails. |
| 16 | - The original speaker notes are preserved. |
| 17 | |
| 18 | ## How it works |
| 19 | |
| 20 | 1. **Speaker notes are written as pure spoken narration.** PPT Master's notes spec deliberately produces TTS-friendly prose — no bracketed stage markers, no `Key points:` / `Duration:` meta-lines — so what is read aloud is exactly what's on the page. |
| 21 | 2. **AI picks the voice for you.** When you ask for narration, the AI checks the deck's primary language (`zh-CN` / `en-US` / `ja-JP` / `ko-KR` / …), pulls the selected provider's voice catalog, and recommends 3–6 candidates with a one-line tone description for each (e.g. "steady male voice for financial reporting"). It also recommends a speaking rate or provider defaults based on notes density. |
| 22 | 3. **One question, one answer.** You are asked once — provider, voice, rate, "embed audio back into PPTX", and "continue to video" — all with a recommended default. Reply "ok" to accept everything, or just call out the part you want to change. |
| 23 | 4. **Generation runs.** With edge, the script writes each page's MP3 and SRT from the same stream to `audio/` and `notes/subtitles/`; cloud providers currently write audio only. For Generate PPTX with canonical custom animation, the AI maps current SVG content groups to numbered SRT cues and derives click-free `narration_animations.json`; without animation sidecars, it skips that derivation and retains `fade` / no per-element builds. It then re-exports the deck with audio attached and merges the local SRT files using timing values read from that final PPTX. When automatic video export was selected and compatible Windows PowerPoint is available, it continues through PowerPoint's native encoder and waits for the MP4 before aligning the delivery SRT. Long-audio import and automatic long-audio splitting are not supported. |
| 24 | |
| 25 | Subtitles remain external artifacts: PPT Master does not embed them into the PPTX or burn them into the MP4. Automatic video export delegates to installed Windows PowerPoint; it is not a separate renderer. |
| 26 | |
| 27 | The shared stage is documented in [`workflows/stages/generate-audio.md`](../skills/ppt-master/workflows/stages/generate-audio.md). |
| 28 | |
| 29 | ## Two embedding paths |
| 30 | |
| 31 | | Command | Purpose | |
| 32 | |---|---| |
| 33 | | `--recorded-narration audio` | Prepare PowerPoint's recorded timings and narrations. Requires complete per-slide audio and writes page auto-advance timings. Use this for narrated/video export. The re-export is saved as `exports/<name>_<timestamp>_narrated.pptx`. | |
| 34 | | `--narration-audio-dir audio` | Lower-level audio embedding. Embeds matched files and allows partial coverage. Use this for testing or manual PowerPoint finishing. Exports get the same `_narrated` name suffix. | |
| 35 | |
| 36 | ## Triggering it |
| 37 | |
| 38 | Just say so in chat after the deck has been exported: |
| 39 | |
| 40 | ``` |
| 41 | You: Generate narration audio for this deck |
| 42 | You: Generate narration for this deck and re-export with audio embedded. |
| 43 | You: Add Japanese voice narration; pick a calm female voice. |
| 44 | ``` |
| 45 | |
| 46 | The Generate route also runs this stage when final Stage 2 resolves effective |
| 47 | Narration Audio to enabled. A later explicit request still wins over the |
| 48 | proactive default. The AI handles the rest. |
| 49 | |
| 50 | ## Languages |
| 51 | |
| 52 | Anything `edge-tts` supports — roughly 90 locales including all major Chinese variants (`zh-CN` / `zh-TW` / `zh-HK` Cantonese), English (US/UK/AU/IN), Japanese, Korean, French, German, Spanish, Portuguese, Russian, Arabic, etc. List voices for any locale yourself with: |
| 53 | |
| 54 | ```bash |
| 55 | python3 skills/ppt-master/scripts/notes_to_audio.py --list-voices --locale ja-JP |
| 56 | ``` |
| 57 | |
| 58 | ## Manual usage (advanced) |
| 59 | |
| 60 | If you want to skip the AI flow and call the script directly: |
| 61 | |
| 62 | ```bash |
| 63 | # 1. Make sure speaker notes are split (post-processing Step 7.1): |
| 64 | python3 skills/ppt-master/scripts/total_md_split.py <project_path> |
| 65 | |
| 66 | # 2A. Generate MP3/SRT pairs with edge-tts (default, no API key) |
| 67 | python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \ |
| 68 | --voice zh-CN-YunjianNeural --rate +0% |
| 69 | |
| 70 | # 2B. Or generate MP3s with ElevenLabs (requires ELEVENLABS_API_KEY) |
| 71 | export ELEVENLABS_API_KEY="your-elevenlabs-api-key" |
| 72 | python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \ |
| 73 | --provider elevenlabs \ |
| 74 | --voice-id <elevenlabs-voice-id> \ |
| 75 | --elevenlabs-model eleven_multilingual_v2 |
| 76 | |
| 77 | # 2C. Or generate MP3s with MiniMax (supports system and cloned voice_id) |
| 78 | export MINIMAX_API_KEY="your-minimax-api-key" |
| 79 | # Defaults to the China endpoint. For overseas access, set MINIMAX_TTS_BASE_URL=https://api.minimax.io/v1/t2a_v2. |
| 80 | python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \ |
| 81 | --provider minimax \ |
| 82 | --voice-id <minimax-voice-id> \ |
| 83 | --minimax-model speech-2.8-hd |
| 84 | |
| 85 | # 2D. Or generate audio with Qwen TTS (system voice or cloned voice) |
| 86 | export DASHSCOPE_API_KEY="your-dashscope-api-key" |
| 87 | python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \ |
| 88 | --provider qwen \ |
| 89 | --voice-id <qwen-voice> \ |
| 90 | --qwen-model qwen3-tts-flash \ |
| 91 | --qwen-language-type Chinese |
| 92 | |
| 93 | # 2E. Or generate MP3s with CosyVoice (system voice or cloned/designed voice_id) |
| 94 | export COSYVOICE_API_KEY="your-dashscope-api-key" |
| 95 | python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \ |
| 96 | --provider cosyvoice \ |
| 97 | --voice-id <cosyvoice-voice> \ |
| 98 | --cosyvoice-model cosyvoice-v3-flash |
| 99 | |
| 100 | # 3-4. Only when canonical animations.json exists, print the SRT-set |
| 101 | # fingerprint, then author |
| 102 | # <project_path>/narration_timing.json by comparing each current |
| 103 | # SVG content group with the numbered cues in that page's SRT. A missing |
| 104 | # cue means the group has no spoken counterpart and uses normal sequencing. |
| 105 | # When neither animation sidecar exists, skip directly to step 5. |
| 106 | python3 skills/ppt-master/scripts/narration_sync.py fingerprint <project_path> |
| 107 | |
| 108 | # 4. Derive click-free narration_animations.json from canonical animations.json |
| 109 | python3 skills/ppt-master/scripts/narration_sync.py animations <project_path> \ |
| 110 | --narration-padding 0.5 --force |
| 111 | |
| 112 | # 5. Re-export PPTX with audio embedded |
| 113 | python3 skills/ppt-master/scripts/svg_to_pptx.py <project_path> \ |
| 114 | -o <final_narrated_pptx> --recorded-narration audio \ |
| 115 | --narration-padding 0.5 |
| 116 | |
| 117 | # 6. Merge page-local SRT using the final PowerPoint timings |
| 118 | python3 skills/ppt-master/scripts/narration_sync.py subtitles <project_path> \ |
| 119 | --pptx <final_narrated_pptx> --force |
| 120 | |
| 121 | # 7. Optional on Windows: export through PowerPoint and wait for completion |
| 122 | python3 skills/ppt-master/scripts/powerpoint_video.py --check |
| 123 | python3 skills/ppt-master/scripts/powerpoint_video.py \ |
| 124 | <final_narrated_pptx> -o exports/<final_video>.mp4 |
| 125 | |
| 126 | # 8. Calibrate page starts against the exported audio track and write a |
| 127 | # same-stem sidecar SRT |
| 128 | python3 skills/ppt-master/scripts/narration_sync.py subtitles <project_path> \ |
| 129 | --pptx <final_narrated_pptx> --video <powerpoint_exported_video> \ |
| 130 | -o exports/<powerpoint_exported_video_stem>.srt --force |
| 131 | ``` |
| 132 | |
| 133 | Before sending any TTS request, `notes_to_audio.py` verifies that every |
| 134 | Generate SVG page or Native Enhance slide has a readable, non-empty per-slide |
| 135 | note. Missing or empty notes return exit code `2`; generate those notes first, |
| 136 | then rerun audio generation. |
| 137 | |
| 138 | For edge, `--voice` is required. Use `--list-voices --locale <locale>` to see what's available. |
| 139 | Edge generates up to three slide-level audio/SRT pairs concurrently by default. |
| 140 | Use `--concurrency <N>` to tune it or `--concurrency 1` for serial |
| 141 | troubleshooting. Cloud providers remain serial. |
| 142 | |
| 143 | The edge command creates `audio/<stem>.mp3` and `notes/subtitles/<stem>.srt` from the same streaming request. Sentence-ending punctuation closes a cue. A cue over 20 visible characters first splits at commas, semicolons, or colons, then at the nearest word boundary only if it is still too long. Use `--subtitle-max-chars` to change the limit. Adjacent timing overlap up to 100 ms is tolerated by moving the later cue start to the previous cue end; larger overlap fails. Each SRT uses a page-local timebase with a zero origin and preserves edge's `WordBoundary` timing, including any leading silence before the first cue. The cloud-provider commands currently create audio only. |
| 144 | |
| 145 | When canonical custom animation exists, `narration_timing.json` remains deliberately separate from read-only `animations.json`. It records the ordered SRT-set SHA-256, narration padding, ordered SVG group IDs, and optional 1-based cue numbers. `narration_sync.py animations` rejects a stale fingerprint, validates the group IDs against the current SVGs, and writes the derived `narration_animations.json` with only supported PowerPoint fields. A group with `effects[]` still maps to one cue: its first active row is anchored to that cue, while later rows retain their relative delay. With no animation sidecars, skip that derivation. `narration_sync.py subtitles` reads the final PPTX's actual presentation order plus millisecond slide-advance and transition values, so `total.srt` follows the native PPTX timeline. A relative `--pptx` path is resolved under `<project_path>`. |
| 146 | |
| 147 | PowerPoint's video encoder can quantize each slide/media segment to its output frame clock. Those small per-page differences may accumulate even when the PPTX timing values are correct. Passing the finished `.mp4` / `.wmv` / `.mov` with `--video` uses normalized audio correlation to locate each original page narration in the exported audio track. It changes only the page-level offsets: edge's cue text and page-local `WordBoundary` timing remain untouched. This is a post-export subtitle calibration step and does not rewrite the video. |
| 148 | |
| 149 | Use the default text-flow mode for the final narrated SVG export. It keeps authored line breaks in one editable, no-wrap text frame; narration does not require per-line text frames. |
| 150 | |
| 151 | ```json |
| 152 | { |
| 153 | "version": 1, |
| 154 | "srt_sha256": "<sha256 of the ordered page-local SRT set>", |
| 155 | "narration_padding": 0.5, |
| 156 | "slides": { |
| 157 | "01_title": { |
| 158 | "groups": [ |
| 159 | { "id": "page-title", "cue": 1 }, |
| 160 | { "id": "supporting-visual" } |
| 161 | ] |
| 162 | } |
| 163 | } |
| 164 | } |
| 165 | ``` |
| 166 | |
| 167 | For ElevenLabs, `--voice-id` is required. List voices from your ElevenLabs account with: |
| 168 | |
| 169 | ```bash |
| 170 | export ELEVENLABS_API_KEY="your-elevenlabs-api-key" |
| 171 | python3 skills/ppt-master/scripts/notes_to_audio.py --provider elevenlabs --list-voices |
| 172 | ``` |
| 173 | |
| 174 | For MiniMax, Qwen, and CosyVoice, pass the provider-specific system voice or cloned voice ID/name with `--voice-id`. Voice cloning itself is performed in the provider's console/API first; `notes_to_audio.py` uses the resulting voice ID to generate per-slide narration. |
| 175 | |
| 176 | Audio embedded into PPTX must use a PowerPoint-reliable format: `m4a` (AAC), `mp3`, or `wav`. Built-in generation defaults to `mp3`; transcode provider output such as `pcm`, `opus`, or `flac` before embedding. |
| 177 | |
| 178 | ## Use a cloned voice |
| 179 | |
| 180 | Four cloud providers — **ElevenLabs**, **MiniMax**, **Qwen**, **CosyVoice** — let you clone a voice from a short sample and then synthesize new speech in that voice. PPT Master narrates the entire deck in your cloned voice as long as you can hand it a `voice_id`. (`edge` does not support cloning.) |
| 181 | |
| 182 | **The split of responsibilities**: voice cloning itself happens in the provider's console or API — you upload a sample (typically 10 s – a few minutes of clean audio) and the provider returns a `voice_id`. PPT Master is on the *consumption* side: it takes that `voice_id` and reads every slide's notes in that voice. PPT Master never uploads your sample anywhere. |
| 183 | |
| 184 | | Provider | Where to clone | Sample length | |
| 185 | |---|---|---| |
| 186 | | ElevenLabs | [elevenlabs.io](https://elevenlabs.io) → Voices → Add Voice → Instant / Professional Voice Cloning | 1 min (Instant) / 30 min+ (Professional) | |
| 187 | | MiniMax | [platform.minimaxi.com](https://platform.minimaxi.com) → Voice Clone | ~10 s – 5 min | |
| 188 | | Qwen TTS | [DashScope console](https://dashscope.console.aliyun.com) → Speech Synthesis → Voice Replica | ~10 s – 5 min | |
| 189 | | CosyVoice | [DashScope console](https://dashscope.console.aliyun.com) → Speech Synthesis → Voice Replica | ~10 s – 5 min | |
| 190 | |
| 191 | **How to use it after cloning** — in chat, just say so. The AI will skip the voice-recommendation step and use your `voice_id` directly: |
| 192 | |
| 193 | ``` |
| 194 | You: Generate narration with my cloned MiniMax voice; voice_id is xxxxxxx |
| 195 | You: Generate the narration with my cloned ElevenLabs voice id abc123 |
| 196 | ``` |
| 197 | |
| 198 | Or call the script directly: |
| 199 | |
| 200 | ```bash |
| 201 | python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \ |
| 202 | --provider minimax --voice-id <your-cloned-voice-id> \ |
| 203 | --minimax-model speech-2.8-hd |
| 204 | ``` |
| 205 | |
| 206 | Replace `--provider minimax` with `elevenlabs` / `qwen` / `cosyvoice` as needed; `--voice-id` accepts the cloned voice the same way it accepts a system voice. |
| 207 | |
| 208 | **Notes**: |
| 209 | |
| 210 | - **Authorization** — only clone voices you own or have explicit permission to use. Each provider's terms forbid impersonation. |
| 211 | - **Language coverage** — the cloned voice inherits the speaker's accent. For multilingual decks (e.g. Chinese with English terms), pick a provider whose model handles your sample's language mix; ElevenLabs `eleven_multilingual_v2` and CosyVoice tend to be the most forgiving. |
| 212 | - **Provider retention** — reuse the `voice_id` while that voice remains available in your provider account. Retention, deletion, and expiration policies are provider-specific. |
| 213 | |
| 214 | ## Dependency |
| 215 | |
| 216 | ```bash |
| 217 | python3 -m pip install edge-tts |
| 218 | ``` |
| 219 | |
| 220 | Already listed in `skills/ppt-master/requirements.txt`. `edge-tts` calls Microsoft's online TTS service — an internet connection is required at generation time. The MP3s themselves are local files; nothing about playback or PowerPoint export depends on the network afterwards. |
| 221 | |
| 222 | Cloud TTS providers do not require extra Python packages; they use HTTPS directly. Configure the relevant API key in the current shell or in `.env` based on `.env.example`. |
| 223 | |
| 224 | Automatic MP4 export adds no Python package. It requires Windows PowerPoint 2016+ and Windows PowerShell; macOS and systems without compatible PowerPoint keep the narrated PPTX and use manual export. |
| 225 | |
| 226 | ## Tips |
| 227 | |
| 228 | - **Pacing**: On the Generate PPTX route, speaker notes scale with the independent information groups in the final SVG; 2–5 sentences is a typical rhythm, not a cap. Start with `+0%`; for a dense, deliberately detailed script, try `-5%`. |
| 229 | - **Mid-deck regeneration**: change a single slide's `notes/<page>.md`, re-run `notes_to_audio.py` (it overwrites all MP3s, so re-run for the whole deck — the cost is small). |
| 230 | - **Mixed-language decks** (Chinese with English technical terms etc.): `edge-tts` neural voices handle the embedded foreign words reasonably well in most locales — pick the dominant language voice and try one slide first. |
| 231 | |
| 232 | ## Export as video |
| 233 | |
| 234 | Once the narrated PPTX is in `exports/`, Windows PowerPoint 2016+ can export it automatically through: |
| 235 | |
| 236 | ```bash |
| 237 | python3 skills/ppt-master/scripts/powerpoint_video.py \ |
| 238 | <final_narrated_pptx> -o <final_video.mp4> |
| 239 | ``` |
| 240 | |
| 241 | The command uses recorded timings and narrations, defaults to 1080p/30 fps, and returns only after PowerPoint reports success or failure. The embedded audio plays as each slide's narration, while the per-slide auto-advance timings drive the video's pacing. `--recorded-narration` rejects `on-click` object animation because it does not generate object-level click timings. |
| 242 | |
| 243 | **Manual PowerPoint fallback (Windows / Mac, Office 2016+)**: |
| 244 | |
| 245 | 1. Open the narrated `.pptx` from `exports/`. |
| 246 | 2. **File → Export → Create a Video**. |
| 247 | 3. Pick a quality and "Use Recorded Timings and Narrations". |
| 248 | 4. Save as `.mp4` (`.wmv` is also available on Windows). |
| 249 | 5. Run the optional subtitle calibration command above and place its same-stem SRT beside the video. |
| 250 | |
| 251 | PowerPoint for Mac can export MP4/MOV manually, but Microsoft documents that |
| 252 | animation effects do not play in its movie export. Use the Windows automation |
| 253 | path when animation fidelity matters. |
| 254 | |
| 255 | **Keynote (Mac)**: open the deck → **File → Export To → Movie…** — Keynote also honors embedded audio and per-slide timings, output `.m4v` / `.mov`. |
| 256 | |
| 257 | **Tips**: |
| 258 | |
| 259 | - **No mic, no recording session needed** — the audio is generated, not recorded, so re-runs are deterministic. |
| 260 | - **Animation fidelity on Windows** — PowerPoint's Windows video export preserves PPT Master's native page transitions and click-free object animation. Mac movie export has the limitation noted above. See [Animations & Transitions](./animations.md). |
| 261 | - **Want to tweak just one slide's audio?** Edit `notes/<page>.md`, re-run `notes_to_audio.py` and the embedding step, then re-export the video — total turnaround is usually under a minute per slide. |
| 262 | - **File size**: a 20-page deck at Full HD typically lands at 30–80 MB depending on imagery. Drop to HD if you need a smaller file for sharing. |
| 263 |