返回 ppt-master
generate-audio.md
根目录 / skills / ppt-master / workflows / stages / generate-audio.md
1 ---
2 description: Shared post-processing stage for narration audio, PPTX embedding, PowerPoint video delivery, and triggered sound mixing.
3 ---
4
5 # Generate Audio Stage
6
7 > Shared narration stage. Run after the owning route's notes step. Edge, ElevenLabs, MiniMax, and timestamp-capable CosyVoice produce per-slide audio/SRT from synthesis timing. Qwen is audio-only because its TTS API exposes no timing. The caller owns final PPTX integration.
8
9 This stage is **context-independent**: it reads `notes/*.md` and queries the selected TTS voice catalog, so either owning route may invoke it in a fresh session. It does not choose the top-level route and does not patch slide design.
10
11 **Trigger**: In Generate PPTX, run when the effective `Narration Audio` outcome
12 in `design_spec.md` §I is `enabled`; a later explicit request first updates that
13 outcome and its provenance. Quick Generate instead runs when the request or
14 current agent's active-context decision selects narration. In Enhance Native
15 PPTX, run when its confirmed enhancement plan has `audio.enabled: true`.
16
17 **Hard dependency — speaker notes**: Audio requires complete per-slide speaker
18 notes. Generate PPTX additionally requires its effective `Speaker Notes`
19 outcome to be enabled; Enhance Native PPTX follows its confirmed notes/audio
20 plan, where enabling audio also enables notes. Quick records the same dependency
21 in active context. Do not enter audio generation while the owning route's notes
22 are missing or incomplete; generate and validate those notes first, then resume
23 this stage.
24
25 ## When to Run
26
27 - Per-page narration files exist at `notes/*.md`. In Generate PPTX, split `notes/total.md` during Step 7.1. In Enhance Native PPTX, the notes module writes numeric files such as `001.md`.
28 - Default mode: `edge-tts` is installed (`python3 -m pip install edge-tts`).
29 - The stage is page-level only: one note becomes `audio/<stem>.<audio-ext>` plus `audio/<stem>.srt` on provider-timed paths, or one audio file with Qwen / explicit CosyVoice audio-only mode. Never substitute one long track or automatic splitting.
30 - Final/literal script notes are synthesized verbatim. Source SRT timecodes are pacing evidence only; new provider timing owns the generated audio/SRT set.
31 - SRT bound to an authoritative existing recording does not enter TTS. Recorded narration requires page-level audio or an explicit page/time map; automatic long-track splitting is unsupported.
32 - A fully successful run writes a compact `audio/manifest.json` with only provider/model, audio/subtitle format, relevant voice settings, and a SHA-256 fingerprint instead of the raw cloud voice ID. It has no per-slide inventory, artifact hashes, or API keys and is not a normal generation input. The flat `audio/` directory is the single active narration set; do not create provider subdirectories unless the user explicitly asks to preserve multiple variants.
33 - PPT narration assets must be PowerPoint-reliable audio: `m4a` (AAC), `mp3`, or `wav`. The built-in TTS path defaults to `mp3`; provider formats such as `pcm`, `opus`, or `flac` must be transcoded before embedding.
34 - PowerPoint recorded narration export requires `ffprobe` so slide timings can be written from actual audio duration.
35 - Optional automatic video export requires Windows PowerPoint 2016+ and runs
36 through `powerpoint_video.py`; the command waits for PowerPoint's native
37 encoder to finish before returning.
38 - Optional slideshow capture is an explicit manual Windows PowerPoint handoff;
39 it is never an automatic fallback or project dependency.
40 - macOS PowerPoint may export MP4/MOV manually, but it has no equivalent
41 `CreateVideo` automation contract and its movie export does not preserve
42 animation effects. Do not replace the missing API with UI scripting.
43 - Optional post-export video calibration requires `ffmpeg` plus `numpy`; it runs only after a finished PowerPoint video is supplied or created.
44 - Direct MP4 delivery with resolved transition/object-animation sound cues also
45 requires `ffmpeg` plus `numpy`. It renders an independent SFX stem, mixes it
46 after native PowerPoint export, and validates the actual mixed audio track.
47 - High-quality cloud mode: provider API key is set before use:
48 - ElevenLabs: `ELEVENLABS_API_KEY`
49 - MiniMax: `MINIMAX_API_KEY`
50 - Qwen: `QWEN_API_KEY` or `DASHSCOPE_API_KEY`
51 - CosyVoice: `COSYVOICE_API_KEY` or `DASHSCOPE_API_KEY`
52 - Keys may live in the current process environment or the first `.env` found in this order: current working directory, skill directory (e.g. `~/.agents/skills/ppt-master/.env`), clone repo root, `~/.ppt-master/.env`
53 - The deck is in a single dominant language (mixed-language decks: pick the dominant one — the AI uses judgment, not a heuristic).
54
55 If per-slide notes are missing, recover through the owning route. Generate
56 PPTX returns to its enabled notes branch and then runs
57 `total_md_split.py <project_path>`; Enhance Native PPTX returns to
58 `native-enhance-pptx` Step 6 and writes numeric notes directly. Never run the
59 Generate splitter against a Native Enhance project.
60
61 ---
62
63 ## Step 1: Determine the deck's language
64
65 The AI already knows the deck's language from writing the notes. No detection script needed.
66
67 - Identify the primary language from the notes content: `zh` / `en` / `ja` / `ko` / etc.
68 - For mixed-language decks (e.g. Chinese with English technical terms), pick the language the audience will hear most of.
69 - For Chinese specifically: pick the locale based on context — `zh-CN` (mainland mandarin, default), `zh-TW` (Taiwanese mandarin), or `zh-HK` (Cantonese). Default Generate may ask when context is unclear; Quick chooses the best supported default and continues.
70
71 ---
72
73 ## Step 2: Choose audio backend and pull the voice catalog
74
75 Default to **edge** unless the user explicitly asks for a cloud provider / higher-quality cloud narration / a cloned voice.
76
77 **edge backend**:
78
79 ```bash
80 python3 skills/ppt-master/scripts/notes_to_audio.py --list-voices --locale <locale>
81 ```
82
83 **ElevenLabs backend**:
84
85 ```bash
86 python3 skills/ppt-master/scripts/notes_to_audio.py --provider elevenlabs --list-voices
87 ```
88
89 **Cloud providers using explicit voice IDs/names**:
90
91 ```bash
92 python3 skills/ppt-master/scripts/notes_to_audio.py --provider minimax --list-voices
93 python3 skills/ppt-master/scripts/notes_to_audio.py --provider qwen --list-voices
94 python3 skills/ppt-master/scripts/notes_to_audio.py --provider cosyvoice --list-voices
95 ```
96
97 The output is a flat list of all available voices for the selected provider. From this list, the AI picks **3–6 candidates** to recommend, applying these rules:
98
99 - **Cover both genders** when both exist for the locale.
100 - **For edge**: prefer `COMMON_VOICES`-listed voices (curated set inside `notes_to_audio.py`) when the locale has them — they are battle-tested.
101 - **For ElevenLabs**: prefer voices already present in the user's account; if the user provides a specific `voice_id`, do not override it.
102 - **For MiniMax / Qwen / CosyVoice**: if the user provides a cloned `voice_id`, use it directly. Do not attempt voice cloning inside this narration stage.
103 - **For CosyVoice subtitles**: use a cloned voice from a supported v3.5/v3/v2 model or a system voice marked timestamp-supported. Model and voice families must match. Use `--cosyvoice-audio-only` only when the user accepts no page-local SRT.
104 - **Match the deck's tone** — pick the strongest recommendation based on style:
105 - Chinese consultant / data-driven / financial-report deck → a steady male voice (e.g. `zh-CN-YunjianNeural`) or a clear female voice (e.g. `zh-CN-XiaoxiaoNeural`)
106 - Chinese general / teaching / product-introduction deck → a bright female or young male voice (e.g. `zh-CN-XiaoyiNeural` / `zh-CN-YunxiNeural`)
107 - Chinese launch event / broadcast deck → a broadcast-toned male voice (e.g. `zh-CN-YunyangNeural`)
108 - English consultant deck → `en-US-GuyNeural` (steady) or `en-US-JennyNeural` (clear)
109 - Japanese / Korean → pick from `ja-JP-*` / `ko-KR-*` neural voices, mark gender + tone
110
111 For each candidate, write a **one-line description in the user's chat language** covering: gender · tone · best-fit scenario. For cloud providers, include the voice name/ID exactly as it must be passed to `--voice-id`.
112
113 ---
114
115 ## Step 3: Resolve generation settings
116
117 **Quick exception**: do not pause. Apply explicit user values, then resolve
118 unspecified provider, voice, rate, and embed choices from the recommended-value
119 rules below. Keep video off unless the caller selected direct video; then embed
120 the narrated PPTX and continue to native video only when
121 `powerpoint_video.py --check` succeeds. If final resolved motion contains sound
122 cues, continue automatically through the post-export mix without another
123 question. An explicit slideshow-capture request instead stops at the
124 capture-ready narrated PPTX until the user supplies the recorded MP4; it never
125 silently switches to native export. Require a timestamp-capable provider only
126 when narration-cue sync or subtitle delivery needs page-local SRT; on the
127 native-export branch, audio-only narration can still calibrate the sound mix
128 from its complete per-page tracks.
129
130 **Default / Enhance Native — one-shot interaction (mandatory)**:
131
132 For Default or Enhance Native, send one message that resolves all five configuration decisions and recommends each value. Before offering automatic video export, run `python3 skills/ppt-master/scripts/powerpoint_video.py --check`; do not present an unavailable local capability as executable. Do NOT split into multiple rounds.
133 An explicit slideshow-capture choice does not run this availability check; it
134 uses the manual Windows playback handoff below.
135
136 **Cloned-voice fast path**: if the user mentioned a cloned voice / 克隆音色 / 复刻音色 / "my own voice" along with a `voice_id`, skip the voice-recommendation list — set the named provider (`elevenlabs` / `minimax` / `qwen` / `cosyvoice`) and pin that `voice_id`. Quick applies its exception above; Default and Enhance Native confirm only rate + embed + video.
137
138 **Message template** (Chinese; translate to user's chat language if different). “Embed” means caller-specific integration: SVG re-export for Generate PPTX, or native OOXML application for Enhance Native PPTX.
139
140 > 检测到 notes 主语言为 **<语言>**(locale: `<locale>`)。基于 deck 调性(<风格>),我推荐以下配置:
141 >
142 > **生成模式**:⭐ 推荐 `<edge|elevenlabs|minimax|qwen|cosyvoice>`(理由:<一句话,如"无需配置,稳定生成"或"用户要求高质量云端音色">)。
143 >
144 > **音色**:
145 > - **[1] <ShortName>** — <性别·调性·适用场景> ⭐ **推荐**
146 > - [2] <ShortName> — <性别·调性·适用场景>
147 > - [3] <ShortName> — <性别·调性·适用场景>
148 > - [4] <ShortName> — <性别·调性·适用场景>
149 > - [5] <ShortName> — <性别·调性·适用场景>
150 > - 也可直接输入清单中的其他 ShortName。
151 >
152 > **语速/风格参数**:⭐ 推荐 `<rate or provider defaults>`(理由:<一句话,如"页均 2–3 句,正常语速听感最稳"或"ElevenLabs 默认 voice settings 保留音色原始表现最稳">)。
153 >
154 > **生成完是否重新导出嵌入音频的 PPTX**:⭐ 推荐 **是**(一次到位,自动按音频时长设页面停留)。
155 >
156 > **带音频 PPTX 完成后是否继续导出视频**:⭐ 推荐 **原生编码**(本机 Windows PowerPoint 2016+ 可用时)。需要录下实际放映声音时可选 **实时放映录制**。
157 >
158 > 直接回"好"用全部推荐值,或告诉我想改的部分(如"音色 2,语速 -5%"或"用 MiniMax 的 voice_id xxx")。
159
160 **Recommended-value rules**:
161 - **Generation mode**: default `edge`; follow the user's choice when they name a cloud provider / voice ID. Do not recommend Qwen when page-local SRT, subtitle animation, or video subtitles are needed; if the user insists, state that only audio is delivered and skip the SRT step.
162 - **Voice**: pick the Step 2 candidate that fits the deck's tone best.
163 - **Rate**: edge defaults to `+0%`; recommend `-5%` for dense notes (>4 long sentences per page) and `+5%` for short, tight notes; going outside this range needs a stated reason. Cloud providers keep provider defaults unless the user explicitly asks to change speed or style.
164 - **Embed**: recommend yes by default, unless the user already has a customized PPTX they do not want overwritten.
165 - **Video**: recommend native encoding when `powerpoint_video.py --check` succeeds; use slideshow capture only on an explicit user choice. When automation is unavailable, deliver the narrated PPTX; never silently switch to screen recording or a third-party renderer.
166
167 ---
168
169 ## Step 4: Execute (no further interaction)
170
171 **Blocking notes preflight**: `notes_to_audio.py` resolves the complete notes
172 roster from `svg_output/*.svg` on Generate projects or
173 `analysis/slide_index.json` on Native Enhance projects. Before any TTS request,
174 every expected note must exist, be readable, and contain spoken text. Exit code
175 `2` returns the caller to its notes-generation step; never continue with partial
176 audio generation.
177
178 Run sequentially — do NOT bundle:
179
180 ```bash
181 # 1A. Generate audio with edge (default)
182 python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \
183 --voice <chosen-ShortName> --rate <chosen-rate>
184
185 # 1B. Or generate audio/SRT pairs with ElevenLabs
186 python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \
187 --provider elevenlabs --voice-id <chosen-voice-id> \
188 --elevenlabs-model eleven_multilingual_v2
189
190 # 1C. Or generate audio with MiniMax
191 # Defaults to the China endpoint; set MINIMAX_TTS_BASE_URL=https://api.minimax.io/v1/t2a_v2 for overseas access.
192 python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \
193 --provider minimax --voice-id <chosen-voice-id> \
194 --minimax-model speech-2.8-hd
195
196 # 1D. Or generate audio only with Qwen TTS (the API returns no timestamps)
197 python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \
198 --provider qwen --voice-id <chosen-voice> \
199 --qwen-model qwen3-tts-flash --qwen-language-type Chinese
200
201 # 1E. Or generate audio/SRT pairs with a timestamp-capable CosyVoice voice
202 python3 skills/ppt-master/scripts/notes_to_audio.py <project_path> \
203 --provider cosyvoice --voice-id <chosen-voice> \
204 --cosyvoice-model cosyvoice-v3-flash
205
206 # 2A. Only when narration-cue sync is selected and page SRT + animations.json
207 # exist, author or refresh narration_timing.json
208 # by matching SVG group semantics to SRT topics, then derive the narrated
209 # sidecar. Reuse current SVG semantics when complete; otherwise read only
210 # the missing or stale svg_output pages.
211 python3 skills/ppt-master/scripts/narration_sync.py animations <project_path> \
212 --narration-start-floor 0.8 --narration-padding 0.5 --force
213
214 # 2B. Re-export with audio embedded
215 # Use the base export's [REPORT] path to preserve source-bound deck motion.
216 # Quick Generate adds --quick-generate --with-notes to every re-export below.
217 # For the native-export mix branch when final motion has sound cues, also
218 # pass --conversion-trace <final_narrated_trace>. Explicit slideshow capture
219 # does not require that trace for sound delivery.
220 python3 skills/ppt-master/scripts/svg_to_pptx.py <project_path> \
221 --recorded-narration audio \
222 --narration-start-floor 0.8 --narration-padding 0.5 \
223 --inherit-motion-from "<base_postflight_report>"
224
225 # Optional: use the canonical presentation animation instead
226 python3 skills/ppt-master/scripts/svg_to_pptx.py <project_path> \
227 --recorded-narration audio \
228 --narration-start-floor 0.8 --narration-padding 0.5 \
229 --animation-config animations.json \
230 --inherit-motion-from "<base_postflight_report>"
231
232 # Optional: export narration with no object or page-transition animation
233 python3 skills/ppt-master/scripts/svg_to_pptx.py <project_path> \
234 --recorded-narration audio \
235 --narration-start-floor 0.8 --narration-padding 0.5 \
236 --no-animations
237
238 # 2C. Only when page-local SRT exists, merge it against timing values read
239 # from the final PPTX
240 python3 skills/ppt-master/scripts/narration_sync.py subtitles <project_path> \
241 --pptx <final_narrated_pptx> --force
242
243 # 2D. Optional: export the raw video through installed Windows PowerPoint
244 # and wait for completion
245 python3 skills/ppt-master/scripts/powerpoint_video.py \
246 <final_narrated_pptx> -o <raw_powerpoint_video.mp4>
247
248 # 2E. Only when final resolved motion has sound cues and direct MP4 delivery is
249 # selected, derive the exact embedded sounds from the final narrated PPTX,
250 # calibrate cue times against raw video narration, and publish the verified
251 # SFX stem, mixed MP4, and report. Defaults are about 35% for transitions,
252 # 25% for object cues, and a -1 dBFS limiter.
253 python3 skills/ppt-master/scripts/video_sound_mix.py <project_path> \
254 --pptx <final_narrated_pptx> \
255 --trace <final_narrated_trace> \
256 --video <raw_powerpoint_video.mp4> \
257 -o <final_mixed_video.mp4> \
258 --stem-output <final_sfx_stem.wav> \
259 --report-output <sound_mix_report.json> --force
260
261 # 2F. Only when page-local SRT exists, align the frozen narration text against
262 # the final delivery video: mixed when 2E ran, captured when the explicit
263 # slideshow-capture handoff returned an MP4, otherwise the raw video.
264 python3 skills/ppt-master/scripts/video_subtitles.py <project_path> \
265 --video <final_delivery_video.mp4> --language <language> --force
266 ```
267
268 **Explicit slideshow capture**: desktop Windows PowerPoint plays the final
269 narrated PPTX full-screen from the beginning with automatic, click-free timing;
270 capture only the deck frame and one application/system-audio source, with mic,
271 UI, pointer, and notifications absent. Trim short head/tail handles. Human-check
272 streams, narration, every cue once, complete motion, and no dropped frames. The
273 capture has no machine cue receipt and must never enter `video_sound_mix.py`.
274 If the host cannot capture, report only the capture-ready PPTX handoff. Align
275 page SRT against an accepted capture and append one compact `workflow_log.py`
276 note.
277
278 **Default — bounded Edge concurrency (may override)**: Generate up to three
279 slide-level audio/SRT pairs concurrently. Use `--concurrency <N>` to tune the
280 Edge path or `--concurrency 1` for serial troubleshooting. Cloud providers
281 remain serial.
282
283 If `notes_to_audio.py` errors with a missing dependency or missing provider API key, fix the prerequisite and re-run — do NOT swallow the error.
284
285 The edge command writes each MP3 and its internal page SRT from the same `edge-tts` stream. SRT cues use the service's `WordBoundary` timing: sentence-ending punctuation always closes a cue; text over the default 20-visible-character limit first splits at commas, semicolons, or colons, then at the nearest word boundary. Override the limit with `--subtitle-max-chars`. Adjacent timing overlap up to 100 ms is tolerated by moving the later cue start to the previous cue end; larger overlap fails instead of silently distorting timing. Each SRT uses a page-local timeline whose origin is `00:00:00,000`, including any leading silence before the first cue.
286
287 MiniMax reads word timing from its synchronous subtitle file. ElevenLabs uses `/with-timestamps` and original-text character alignment. CosyVoice enables HTTP streaming plus `word_timestamp_enabled`, then uses the final audio URL and word timing from that synthesis; unsupported model/voice pairs fail without replacing the prior pair unless `--cosyvoice-audio-only` was explicit. Qwen exposes no timing, so it remains audio-only and this stage never estimates SRT timing.
288
289 Provider-timed paths share punctuation-first, `--subtitle-max-chars`-bounded regrouping, exact-text validation, and rollback-safe pair publication. See [`docs/audio-narration.md`](../../../../docs/audio-narration.md) for current model and audio-parameter recommendations.
290
291 Before generation starts, `notes_to_audio.py` removes stale `audio/manifest.json` and `audio/total.srt`; an incomplete run therefore cannot claim the previous set's provenance or merged timeline. A successful audio-only provider run also removes same-stem stale SRT files. The new manifest is published atomically only after the complete page roster succeeds.
292
293 **Mandatory when narration-cue sync is selected — semantic animation context**: Before writing or refreshing `<project_path>/narration_timing.json`, determine whether the active context already contains the current top-level SVG group IDs and visible group-content semantics for every affected page. Reuse that context without rereading SVG when it is complete and still matches the current `svg_output/`. If any page is missing, stale, or represented only by group IDs/order without content meaning, read only that page's SVG as a read-only source and extract the missing group semantics. Always combine those semantics with the page SRT topics/timestamps and `animations.json`; group order alone is not a semantic narration mapping.
294
295 > Narration-cue sync with `animations.json` requires `narration_timing.json`.
296 > Narration-independent custom motion instead passes `--animation-config animations.json`
297 > and makes no object-sync claim. Explicit `--no-animations`
298 > bypasses both. Without a timing sidecar, `narration_sync.py animations` maps
299 > groups **positionally** (group N → cue N) and warns when later objects may
300 > reveal during an earlier topic. Treat that warning as required repair: author
301 > the semantic plan and re-derive.
302
303 **Narration animation ownership**: When narration-cue sync is selected, `animations.json` remains read-only. The audio stage deep-copies it to `narration_animations.json`, preserves transitions, effects, durations, order, and explicit `effect: none`, then changes only the derived trigger/delay values needed for click-free narration playback. The authored `narration_timing.json` maps each animated content group—not each effect row—to the SRT cue that speaks about that content. For `effects[]`, the cue anchors the group's first active row; later rows keep global order and their relative delay. The command may still read an affected SVG page to resolve structural group order when a sparse sidecar cannot identify every effective group; this structural fallback does not replace the semantic-context step and never edits SVG, notes, or `animations.json`. Unmatched groups keep their canonical relative delay.
304
305 **Title timing handoff when canonical animation exists**: preserve the title reveal decision already made by the custom-animation pass. Assign a title group to an SRT cue only when the user's request or the active motion plan explicitly chose `narration-cued`; otherwise leave its `cue` omitted in `narration_timing.json` so it keeps the canonical relative delay from `animations.json`. Do not infer `narration-cued` merely because speaker notes mention the title.
306
307 **Narrated export animation selection**:
308
309 | Sidecar state | Behavior |
310 |---|---|
311 | `narration_animations.json` exists and narration-cue sync is selected | Use it |
312 | Only canonical `animations.json` exists and narration-cue sync is selected | Block until narration synchronization creates the derived sidecar |
313 | Canonical `animations.json` exists and motion is narration-independent, whether or not a derived sidecar also exists | Pass `--animation-config animations.json`; do not claim object sync |
314 | Both are absent | Create no sidecar; inherit the base report's deck motion |
315
316 Generate passes the base report through `--inherit-motion-from`: inherited
317 `-a none` preserves explicit objects-off, while final Stage-2 `false` does not.
318 Only explicit all-motion-off uses `--no-animations`. Invalid reports block;
319 page-start lead-in, audio duration, and page-tail padding own final advance.
320
321 **Narration pacing controls**: page-front and page-tail timing are independent,
322 optional parameters. Unless the user supplies values, use
323 `narration_start_floor=0.8` seconds and `narration_padding=0.5` seconds without
324 adding a confirmation question. For a destination-page transition of `T`
325 seconds, the post-transition lead-in is
326 `max(0, narration_start_floor - T)`: narration never begins during the
327 transition, while a longer transition is not stretched. Apply the same
328 lead-in to embedded narration, cue-bound object animation, subtitle offsets,
329 and slide advance. Uncued title or decorative animation keeps its canonical
330 relative timing. Setting the start floor to `0` means narration begins as soon
331 as the transition completes; it does not bypass the transition.
332
333 When canonical custom animation is synchronized,
334 `<project_path>/narration_timing.json` is the explicit semantic mapping for
335 narrated object animation. It is fingerprinted to the ordered SRT set; `cue`
336 is the 1-based subtitle cue, and omitted `cue` keeps that group's canonical
337 relative delay. Reuse a complete current mapping when its fingerprint and SVG
338 group semantics remain valid; rebuild only affected pages when either input
339 changed.
340
341 Get the exact fingerprint value with:
342
343 ```bash
344 python3 skills/ppt-master/scripts/narration_sync.py fingerprint <project_path>
345 ```
346
347 ```json
348 {
349 "version": 1,
350 "srt_sha256": "<sha256 of the ordered page-local SRT set>",
351 "narration_start_floor": 0.8,
352 "narration_padding": 0.5,
353 "slides": {
354 "01_title": {
355 "groups": [
356 { "id": "page-title", "cue": 1 },
357 { "id": "supporting-visual" }
358 ]
359 }
360 }
361 }
362 ```
363
364 `narration_sync.py subtitles` may still write `<project_path>/audio/total.srt` as a PPTX-timeline diagnostic. It is not the delivery subtitle for a finished video.
365
366 When video export was selected, `powerpoint_video.py` opens the final narrated
367 PPTX through local Windows PowerPoint, requests its native video encoder with
368 recorded timings and narrations enabled, and polls `CreateVideoStatus` until the
369 MP4 succeeds, fails, or times out. The interface is synchronous to its caller
370 even though PowerPoint performs encoding asynchronously. It preserves the
371 native visual-animation and narration path rather than re-rendering the deck,
372 but does not reliably write transition or object-animation sounds into the MP4
373 audio track. This is the default automated video path; an explicitly selected
374 slideshow capture bypasses `CreateVideo` but still uses desktop PowerPoint as
375 the real-time renderer and audio player.
376
377 If native video export fails, keep the narrated PPTX as a successful upstream
378 artifact and report the video failure separately. Do not regenerate audio or
379 the PPTX unless their own validation failed.
380
381 On the native-export path, when the final narrated trace and PPTX contain sound
382 cues, treat the PowerPoint MP4 as a raw intermediate. `video_sound_mix.py`
383 cross-checks that trace against the final PPTX read-back, extracts the exact
384 embedded sound relationships, calibrates every page against the raw video's
385 narration, renders a float SFX stem, and mixes it with narration at unity gain.
386 Transition cues default to about 35%, object cues to about 25%; `amix`
387 normalization and ducking remain off, and a -1 dBFS peak limiter follows the
388 mix. The receipt must prove a non-silent stem, preserved video-stream hash,
389 changed and present final audio, duration parity, non-clipping true peak, and
390 correlation between the added final-audio component and the stem. A valid
391 `animations.json` or OOXML package alone is not MP4 audio acceptance.
392
393 After the final delivery MP4 exists, `video_subtitles.py` takes the exact
394 narration text frozen in the page SRT set and force-aligns it against that
395 finished video's actual audio track with `stable-ts`. Use the mixed MP4 when
396 sound mixing ran, the accepted capture when slideshow recording ran, otherwise
397 the raw PowerPoint MP4. Long delivery cues may be split for display at this
398 final stage. This writes a same-stem external SRT without changing the MP4,
399 notes, page SRT, or animation files.
400
401 This stage keeps subtitles as external SRT files and never burns them in.
402 Automatic export is an optional Windows PowerPoint integration. When it is
403 unavailable, stop after the narrated PPTX unless explicit capture is selected;
404 that handoff remains incomplete until a real capture is accepted.
405
406 **Caller integration**:
407
408 | Caller | After audio generation |
409 |---|---|
410 | Generate PPTX | Derive narration-cued motion when selected; otherwise pass canonical motion, inherit base motion, or use explicit all-motion-off. Export with `--recorded-narration audio`; Quick also passes `--quick-generate --with-notes`. Native video uses conversion trace plus raw export and cue mix as required. Explicit capture returns the narrated PPTX for the handoff above, skips trace-only sound work and mixing, then aligns subtitles against the accepted capture. |
411 | Enhance Native PPTX | Return to [`native-enhance-pptx`](../native-enhance-pptx.md) Step 9. Native video passes its final PPTX to `powerpoint_video.py`; explicit capture uses the same handoff above and skips mixing. |
412
413 For Qwen or explicit CosyVoice audio-only mode, embed/export the audio normally
414 but skip `narration_timing.json`, `narration_sync.py animations`, SRT merge, and
415 final-video subtitle alignment. Pass canonical narration-independent custom
416 motion explicitly when present. On the native-export branch, a direct-MP4 sound
417 mix may still run because page audio, not SRT, supplies its correlation
418 template. Never present missing subtitle artifacts or object sync as generated.
419
420 For Generate PPTX, `--recorded-narration audio` prepares PowerPoint's recorded timings and narrations: every slide must have a matching supported audio file, every duration must be readable by `ffprobe`, and object animations must not use `--animation-trigger on-click`. Use `after-previous` or `with-previous` for narrated/video export. Narration changes the slide-advance layer only: the resolved page-transition effect remains unchanged, `-t none` remains visually transition-free, and narration advance disables click while using page-start lead-in plus audio duration plus page-tail padding. The re-export is saved as `exports/<project_name>_<timestamp>_narrated.pptx`, telling it apart from silent exports.
421
422 **Narrated SVG export**: use the default text-flow mode. It keeps authored line breaks in one editable, no-wrap text frame; narration does not require per-line text frames.
423
424 ---
425
426 ## Step 5: Completion report
427
428 Output one summary block listing:
429
430 - Number of audio files generated and their location (`<project_path>/audio/*`).
431 - For provider-timed subtitles, number of matching page-local SRT files and their location (`<project_path>/audio/*`); for Qwen or explicit CosyVoice audio-only mode, report that no page-local SRT was generated.
432 - Narration provider/model plus the `<project_path>/audio/manifest.json` provenance path.
433 - For narrated object animation, whether current SVG semantics were reused or which missing/stale pages were reread, plus semantic mapping coverage and fallback count.
434 - For Generate PPTX, report derived narration animation coverage/path when cue sync ran, the canonical config path for narration-independent custom motion, or inherited/all-motion-off state.
435 - When native video export was selected, the raw PowerPoint MP4 path/status.
436 When resolved cues triggered sound mixing, also report the final mixed MP4,
437 SFX stem, cue count, and `video_sound_mix.py` receipt; otherwise identify the
438 raw MP4 as final.
439 - For slideshow capture, report the capture-ready PPTX handoff or accepted MP4
440 plus system-audio and human picture/narration/all-cue status; never report a
441 mix receipt.
442 - When page-local SRT was merged, the PPTX-timeline `audio/total.srt` path.
443 - When final-video subtitle alignment ran, the aligned delivery SRT path and
444 whether its source was the mixed, captured, or raw final video;
445 otherwise do not claim a video-aligned subtitle.
446 - The provider, voice, and rate/settings actually used.
447 - The caller-owned integration result: narrated SVG export path, enhanced native PPTX path, or “audio only”.
448 - For Generate PPTX when embedding was skipped, one-line hint: `python3 skills/ppt-master/scripts/svg_to_pptx.py <project_path> --recorded-narration audio`.
449
449 lines MARKDOWN