Full Pipeline for Creating Explainer Video Series
Turn a "topic" into a "finished video": Planning → Page Layout (HTML Animation) → Script → Voiceover → Subtitles → 1080p MP4 → Douyin Covers (Horizontal 16:9 + Vertical 9:16). The default style archive is derived from the practical experience of the "Tao Te Ching" explainer video series (New Chinese Ink Wash / Yunjian Male Voice / White Text with Black Glow Subtitles). The entire set is reusable, and each item can be overridden.
Visual Route (HTML animation by default since v2.0; Graphic-Text Collaboration in v2.1):
- Default: HTML Animation Pages – Generate HTML presentation pages with entrance animations and cross-fade transitions between pages based on the script. Deterministic frame-by-frame rendering into a video; animations are 100% synced into the final video (see
references/html-animation.mdfor the scheme and layout rules). - v2.1 Graphic-Text Collaboration: Built-in ink wash SVG scene library (water waves / solitary boat / ripples / mountains / ink bamboo / moon gazing / koi fish) + item icons + external image support. Automatically matches or explicitly specifies scenes based on the script. Unified arrangement of staggered graphic elements and text animations.
- Fallback: Static PPT frames + xfade (used when native PPT tables/complex formatting is needed, or when the user explicitly requests a .pptx file).
v2.3 onwards, each episode includes standard Douyin covers: Horizontal 16:9 + Vertical 9:16. AI generates a set based on the topic (horizontal anchor point → user confirmation → vertical derived). Does not capture video frames; see Phase ⑥ and references/douyin-covers.md.
Five Startup Questions (Ask at the start of each episode; if the user says "same as last time" or "as usual", skip all and start directly)
- What is this episode about? (Topic / Chapter of scripture)
- Depth of content: Detailed explanation / Key points? (Default: Detailed explanation)
- Target video duration? (Default: 6-7 minutes)
- Keep the New Chinese Ink Wash PPT style? (Default: Yes)
- Keep Yunjian voice and white text with black glow subtitles? (Default: Yes)
Style Archive (Adopted directly if not specified by user)
| Item | Default Value |
|---|---|
| PPT Style | New Chinese Ink Wash: Rice paper off-white + Ink black + Dark cyan + Cinnabar |
| Font | Title: SimSun (Songti), Body: Microsoft YaHei (Use English font-family in SlideDSL) |
| Visual Elements | SVG ink wash illustrations (no AI image generation, 100% fidelity, editable repeatedly) |
| Animation | HTML animation pages: Title blur-in / Items staggered fade-up / Page cross-fade / Cue key point emphasis (The "element animation rejected" issue was for the ffmpeg overlay solution; the HTML deterministic rendering route has been validated and implemented since v2.0) |
| Voice | zh-CN-YunjianNeural (Yunjian · Male · Rich) |
| Speed | +0% (Uniform across the whole video; only change if explicitly requested by user, do not segment by content density) |
| Subtitles | White text + black outer glow (blur 0.8), transparent background, bottom center |
| Aspect Ratio | 1920×1080 (16:9) |
| Pronunciation Correction | "老子" → TTS reads as "Lǎo Zǐ" (classical third tone), subtitles still display "老子" |
| Douyin Cover | Horizontal 16:9 + Vertical 9:16; Episode name in four large characters; AI-generated set (anchor chain), no video frames |
Working Directory Convention
Create a video-working-directory/ in the workspace, with one sub-directory per episode (named after the topic):
video-working-directory/{episode-name}/
├── {BASE} script.json # Single source of truth: title/voice/pronunciation corrections/script per slide (+ page text)
├── {BASE} script_editable.txt # Exported by export_txt.py for user review in notepad
├── 01_deck/ # deck.html (animation preview page) + deck.json (timeline manifest)
├── 01_html_frames/ # f_XXXXX.png per frame + frames_manifest.json (HTML route)
├── 01_frames/ # frame_01.png ... (static PPT fallback route)
├── 02_audio/ # slide_01.mp3 ... slide_NN.mp3 (per-page audio)
├── voice_preview/ # Multiple voice preview mp3s + voice_preview.html
└── 03_output/ # Finished video {BASE}(with_subtitles).mp4
BASE = episode name (if it contains "script", it is automatically stripped). The script JSON is programmatically generated using Python json.dump; manually writing JSON with Chinese quotation marks is forbidden (it will cause escape errors).
The cover canvas {episode-name}-douyin-cover.miora is placed according to the generated media delivery specification in the workspace root, with double-size PNGs in its _assets/ directory.
Step-by-Step Process (Includes 4 confirmation gates; proceed only after receiving user's response for each gate)
Before starting (first time or if environment is abnormal), run the environment check:
python scripts/doctor.py. Fix any MISSING items before proceeding. Pacing baselines, gate list, BGM, and revision scope table are inreferences/pacing-and-gates.md. (Methodology integrated from liyupi/ai-promo-video-maker: strict pacing standards / data validation / dual gates / revision discipline).
Phase ① Content Planning (Gate 1: Five Startup Questions)
After determining the plan based on the five questions, produce a brief planning package (text structure / visual scheme / audio scheme). Once the user approves, proceed to ②.
Phase ② Page Layout (HTML Animation Route = Default)
- No longer making PPTs: Directly programmatically generate
{BASE} script.json, writing the scripttextper slide. Each slide may optionally includetitle(page title),points(screen item array),layout(title/bullets/quote/end),cues(key point emphasis, see below) — if missing, they are automatically derived from the script (rules in references/html-animation.md), but for formal production, it is recommended to specify them explicitly for better quality. - The number of pages depends on content depth (detailed explanation ≈ 8 pages); tables/data should still use the static PPT fallback route.
Phase ②′ PPT Production (Static Fallback Route)
- Use workbuddy-pptx (SlideDSL) to create slides. Number of pages depends on content depth (detailed explanation ≈ 8 pages).
- Speaker notes = per-slide script: Write once, reuse in multiple places (video voiceover / DOCX timeline / CapCut import).
- Use English font names (SimSun / Microsoft YaHei); Chinese names will not render.
- Colors and elements follow the style archive; tables/data pages should preferably use native PPT elements.
Phase ③ Script Finalization (Gate 2: Script Review)
-
Extract the script from PPT notes, programmatically generate
{BASE} script.json(structure below). -
Export editable text for review:
python scripts/export_txt.py "{BASE} script.json" -
Clearly instruct the user to open the txt file in Notepad and review page by page; after the user returns their edits, sync back to JSON:
python scripts/import_txt.py "{BASE} script.json" "{BASE} script_editable.txt"If the user only replies "the script is fine" without changes, skip the sync.
Phase ④ Voiceover (Gate 3: Voice Preview, Hard Gate voice_confirmed)
-
Generate a preview page for the user to select a voice:
python scripts/preview_voices.py voice_preview -
After the user selects, write to JSON and set
voice_confirmed=true(without this, TTS and synthesis will directly refuse to execute):python -c "import json;p='{BASE} script.json';d=json.load(open(p,encoding='utf-8'));d['voice']='zh-CN-YunjianNeural';d['rate']='+0%';d['voice_confirmed']=True;json.dump(d,open(p,'w',encoding='utf-8'),ensure_ascii=False,indent=2)"The default speed
+0%remains unchanged; only change if the user explicitly requests a speed change, and it must be uniform across the entire video.
Phase ⑤ Final Video Synthesis (Gate 4: Confirm with User Before Synthesis)
Order: Synthesize audio → Generate animation pages → Render frames → Compose video (HTML route); or Render frames → Audio → Video (static route).
HTML Animation Route (Default):
-
Synthesize per-page audio (Must be done first – page duration is taken from actual audio duration):
python scripts/tts_synth.py --narration "{BASE} script.json" --out 02_audio -
Generate animation preview page (deck.html can be previewed with
#previewin the browser):python scripts/gen_html_deck.py --narration "{BASE} script.json" --audio 02_audio --out 01_deck -
Render frames (only renders the animation window, completes in minutes):
node scripts/render_html_frames.mjs --deck 01_deck --out 01_html_frames -
Compose video (concat frame sequence + per-sentence subtitles, default white text with black glow style):
python scripts/compose_html_video.py --deck 01_deck --frames 01_html_frames --audio 02_audio --narration "{BASE} script.json" --out 03_outputDefault output:
{BASE}(with_subtitles).mp4;--also-plainadds a version without subtitles;--bgm <file>adds background music (auto ducking, lowers music volume when narration is active; music library from Mixkit, free for commercial use, must be selected after user preview).
Static PPT Fallback Route:
-
Render slide frames (Preferred: PowerPoint COM, requires local PowerPoint installation):
powershell -ExecutionPolicy Bypass -File scripts/export_frames.ps1 -Pptx "{BASE}.pptx" -OutDir 01_framesIf a PDF already exists, use the pymupdf path:
python scripts/pdf_to_frames.py "{BASE}.pdf" 01_frames 1920. After rendering, spot-check 3 pages (first page / a content-dense page / a table page) to confirm no font issues. -
Synthesize per-page audio: Same as tts_synth.py above.
-
Compose video (static pages + xfade + per-sentence subtitles):
python scripts/compose_video.py --frames 01_frames --audio 02_audio --narration "{BASE} script.json" --out 03_output -
Verify delivery: Check frames at entrance/transition points to confirm animations are normal, no black flashes; check frames mid-page to confirm subtitles; Run the output gate:
python scripts/verify_output.py --video "03_output/{BASE}(with_subtitles).mp4" --deck 01_deck(All specs/duration/loudness/black frames/silence must pass before delivery);present_filesdelivery.
Phase ⑥ Douyin Covers (Standard for each episode: Horizontal 16:9 + Vertical 9:16)
Deliver covers along with the final video for easy upload to Douyin. Do not capture video frames – AI generates a set based on the episode's topic; deliver .miora canvas + double-size PNGs;
See references/douyin-covers.md for prompt templates / image reference / quality checklist. Skip if the user explicitly says they don't need covers.
- Text: The episode name (four characters) is the only large text on the image (e.g., "有无相生"); do not add subtitles (will be unclear); zero text on the image besides the main text.
- Anchor Chain (Hard Rule): Horizontal anchor point (pure text-to-image 1920×1080) → User confirmation → Vertical version derived using the anchor point as reference image (1080×1920, vertical three-section layout); reuse style token from the original; forbid sending both images blindly.
- Visuals: Same style as the final video (rice paper off-white + ink black + dark cyan + cinnabar, ink wash diffusion, sparse white space); imagery taken from the episode's theme (e.g., "有无相生" → full moon + moon reflection in water).
- Exact Dimensions: The model may round to 1920×1072 / 1072×1920 (off by 8px); report this accurately; if the user needs exact dimensions, add padding to standard values.
- Delivery: Workspace root
{episode-name}-douyin-cover.miora+ double-size image cards; record cover text and dimensions indemand-confirmation.md.
(If 100% text control or cross-episode template reuse is needed, switch to editable design layout export; default is AI generation.)
Phase ⑦ Fine-Tuning and Iteration
- Maintain
demand-confirmation.mdper episode directory: Five question answers + record of each revision round (file user's original words); record feedback first, then edit; only redo necessary steps (seereferences/pacing-and-gates.mdfor change scope → redo steps table). - Subtitle style/color adjustment → See
references/subtitle-style.md(ASS color is BBGGRR reversed, must read). - Pronunciation issues → Only modify
tts_replacements, rerun TTS + synthesis; subtitles remain unchanged. - Subtitle sync → compose preferentially reads
02_audio/timing_XX.json(TTS sentence-level real timeline, auto-generated by tts_synth). - Visual/animation issues (HTML route) → Modify the script JSON's title/points/visual/cues or
gen_html_deck.pylayout → Rerun: generate deck → render → compose (reuse audio unless script also changed). - Visual issues (static route) → Modify PPT and re-export frames; reuse audio.
- Cover modifications → If text/imagery is incorrect, only regenerate the corresponding item; if the overall style is wrong, regenerate the anchor point and then derive (see
references/douyin-covers.md).
Phase ⑧ Delivery Acceptance (Self-Checklist)
- Visuals: No font/color anomalies; table pages (if any) rendered correctly
- Animation: Title/item entrances natural, page transitions smooth, cue emphasis aligned with narration (HTML route)
- Voiceover: Audible, no swallowed words, consistent speed throughout
- Pronunciation: Classical readings correct (e.g., "老子" = lǎo zǐ)
- Subtitles: Synced sentence-by-sentence with audio, uniform style, no overflow/cutting
- Transitions: Cross-fade smooth, no black flashes, no blank frames
- Duration: ≈ target duration (±10 seconds)
- Format: 1920×1080 / H.264 / AAC / .mp4
- Covers: Horizontal and vertical versions, four characters without missing strokes, consistent style, exact dimensions (checklist in
references/douyin-covers.md)
Script JSON Structure (Single Source of Truth)
{
"title": "Laozi Today-Understanding - Supreme Goodness Like Water",
"voice": "zh-CN-YunjianNeural",
"rate": "+0%",
"voice_confirmed": true,
"tts_replacements": { "老子": "老紫" },
"slides": [
{ "idx": 1, "text": "Script for page one..." },
{ "idx": 2, "text": "Script for page two...",
"title": "Three Virtues of Water", "points": ["Altruism, nourishing all things", "Non-contention, willingly taking the lower position"],
"cues": [ { "t": 4.5, "dur": 0.9, "target": 0 } ] }
]
}
idxis sequentially numbered from 1, strictly corresponding toframe_XX.png(static route) /slide_XX.mp3.- Optional fields per slide for HTML animation route:
title(page title),points(screen item array),layout(title/bullets/quote/end),cues(key point emphasis:t=relative seconds within page,target=item index starting from 0,duration=emphasis duration,action=pulse); Graphic-Text Collaboration (v2.1):visual(ink wash scene water/ripples/mountain/ bamboo/moon/fish, defaults to automatic matching based on script keywords),icons(item icons droplet/wave/mountain/moon/leaf),image(external image path, embedded in right column). If missing, they are automatically derived from the script; for formal production, it's recommended to specify them explicitly. tts_replacements: Pronunciation correction table, only affects TTS input, subtitles and editable text retain the original characters. Use empty{}for generic series.voice_confirmeddefaults to false; set to true at Gate ③, otherwise the script will refuse to synthesize.
Common Failure Quick Reference
| Symptom | Cause | Resolution |
|---|---|---|
| Subtitle box color doesn't match expectations | ASS color is BBGGRR reversed | Red=&H000000FF Blue=&H00FF0000 Black=&H00000000 |
| Classical reading pronounced as vernacular (e.g., 老子 lǎo zi) | TTS defaults to colloquial pronunciation | Add to tts_replacements and rerun TTS |
| PPT font doesn't render | Chinese font names not recognized | Use SimSun / Microsoft YaHei |
| One-step composition white screen / filter chain crash | Overlay after xfade conflicts | Split into two steps: render base layer first, then overlay; escape commas with \, |
| Frames "generated" but not found | pymupdf silently fails | Script uses abspath + post-write validation; or use export_frames.ps1 |
--rate -10% error "expected one argument" | argparse misinterprets option | Use --rate=-10% or just modify the JSON rate field |
| Composition error "Audio not yet confirmed" | Gate ③ was skipped | Set voice_confirmed=true and rerun |
| JSON error with Chinese quotation marks | Manual escape errors | Use Python json.dump to generate programmatically |
| Deck generation error "missing audio" | HTML route requires TTS first | Run tts_synth.py first, then gen_html_deck.py |
| Rendering error "cannot find Chromium" | Browser path changed | Specify with --browser pointing to chrome.exe / msedge.exe |
| Title page not centered | CSS class selector writing | Use .inner.layout-title (same-class elements), avoid descendant selectors |
| Items broken up (sequence number sentences become separate items) | Short sequence sentences not merged | gen_html_deck.py has built-in merging; defining explicit points can completely avoid this |
| Last frame duration incorrect | Concat quirk for the last file | compose_html_video.py has built-in repetition of the last file; do not delete |
| Cue emphasis not appearing | Cue exceeds page duration | Check if t in slides[].cues is less than the audio duration for that page |
| Cover large characters missing strokes/incorrect characters | AI unstable with long Chinese text | Keep text within four characters and regenerate; if anchor point characters are wrong, regenerate anchor point first, then derive |
| Cover short side 1072≠1080 | Model rounding | Deliver as-is (aspect ratio still ≈16:9/9:16); if precise, pad to 1920×1080 / 1080×1920 |
| Two covers don't look like a set | Both generated independently from text | Must use anchor chain: derive vertical version using horizontal anchor point as reference image |
Complete pitfalls and architecture experience see references/pitfalls.md; HTML animation route details see references/html-animation.md.
Resources
scripts/doctor.py— Environment health check (run before starting each episode, fix MISSING items before proceeding)scripts/verify_output.py— Output gate (specs/duration/loudness/black frames/silence, all pass before delivery)scripts/gen_html_deck.py— Script → graphic-text collaborative animation HTML presentation page + deck.json manifest (HTML route entry)scripts/render_html_frames.mjs— Playwright deterministic frame-by-frame rendering (only captures animation window, reuses last frame for static segments)scripts/compose_html_video.py— Frame sequence + audio + real-time subtitle + BGM ducking composition (HTML route)scripts/tts_synth.py— edge-tts batch synthesis (pronunciation correction + sentence-level real timeline + voice_confirmed gate)scripts/compose_video.py— Static pages + xfade + per-sentence subtitle composition (static fallback route)scripts/export_txt.py/scripts/import_txt.py— Bidirectional sync between script and editable txtscripts/preview_voices.py— Five-voice comparison preview page (playable offline)scripts/export_frames.ps1— PowerPoint COM export 1920×1080 frame images (preferred static route)scripts/pdf_to_frames.py— pymupdf render PDF to frames (static route fallback)references/douyin-covers.md— Full Douyin cover process: text rules / prompt templates / anchor chain / quality checklist (standard horizontal + vertical for each episode)references/html-animation.md— HTML animation route: feasibility conclusion / layout rules / pitfalls (must read)references/pacing-and-gates.md— Pacing baselines / data validation / dual gates / BGM / revision scope table (must read)references/playbook.md— Full process manual: confirmation items table / tech stack / optimization roadmapreferences/pitfalls.md— Pitfall checklist and architecture experiencereferences/subtitle-style.md— ASS subtitle style manual (color reversal / glow effects / style presets)assets/narration-template.json— Script JSON template
Dependencies: playwright-core (in skill root node_modules, already installed); Chromium detection order
ms-playwright cache → system Edge; if both are missing, specify with --browser.