Communitygithub.com

EMzhe/explainer-video-series

Full pipeline for creating explainer video series: planning → HTML animation pages / script → voiceover → subtitles → 1080p final video → Douyin covers (horizontal + vertical). Includes four user confirmation gates (startup questions, script review, voice demo, synthesis confirmation) and pronunciation correction.

What is explainer-video-series?

This skill automates the complete production workflow for creating series of explainer videos, turning a topic into a finished video with narration, subtitles, and social media covers. It supports two rendering pipelines: an HTML animation route (default) that generates animated presentation pages with cross-fade transitions, and a static PPT fallback route. The workflow includes four mandatory user confirmation checkpoints to ensure quality and alignment: startup questions, script review, voice preview, and final synthesis approval. It handles pronunciation corrections (e.g., forcing 'Laozi' to be read with the classical third tone) and generates both horizontal (16:9) and vertical (9:16) Douyin covers for each episode. The skill is designed for platforms like Claude Code and Cursor, and is intended for users creating educational video series, scripture explanations, courseware walkthroughs, or any topic requiring narrated slides with synchronized subtitles.

Works with~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/EMzhe/explainer-video-series/tree/HEAD

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

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.md for 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)

  1. What is this episode about? (Topic / Chapter of scripture)
  2. Depth of content: Detailed explanation / Key points? (Default: Detailed explanation)
  3. Target video duration? (Default: 6-7 minutes)
  4. Keep the New Chinese Ink Wash PPT style? (Default: Yes)
  5. Keep Yunjian voice and white text with black glow subtitles? (Default: Yes)

Style Archive (Adopted directly if not specified by user)

ItemDefault Value
PPT StyleNew Chinese Ink Wash: Rice paper off-white + Ink black + Dark cyan + Cinnabar
FontTitle: SimSun (Songti), Body: Microsoft YaHei (Use English font-family in SlideDSL)
Visual ElementsSVG ink wash illustrations (no AI image generation, 100% fidelity, editable repeatedly)
AnimationHTML 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)
Voicezh-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)
SubtitlesWhite text + black outer glow (blur 0.8), transparent background, bottom center
Aspect Ratio1920×1080 (16:9)
Pronunciation Correction"老子" → TTS reads as "Lǎo Zǐ" (classical third tone), subtitles still display "老子"
Douyin CoverHorizontal 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 in references/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 script text per slide. Each slide may optionally include title (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)

  1. Extract the script from PPT notes, programmatically generate {BASE} script.json (structure below).

  2. Export editable text for review:

    python scripts/export_txt.py "{BASE} script.json"
    
  3. 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)

  1. Generate a preview page for the user to select a voice:

    python scripts/preview_voices.py voice_preview
    
  2. 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):

  1. 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
    
  2. Generate animation preview page (deck.html can be previewed with #preview in the browser):

    python scripts/gen_html_deck.py --narration "{BASE} script.json" --audio 02_audio --out 01_deck
    
  3. Render frames (only renders the animation window, completes in minutes):

    node scripts/render_html_frames.mjs --deck 01_deck --out 01_html_frames
    
  4. 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_output
    

    Default output: {BASE}(with_subtitles).mp4; --also-plain adds 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:

  1. Render slide frames (Preferred: PowerPoint COM, requires local PowerPoint installation):

    powershell -ExecutionPolicy Bypass -File scripts/export_frames.ps1 -Pptx "{BASE}.pptx" -OutDir 01_frames
    

    If 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.

  2. Synthesize per-page audio: Same as tts_synth.py above.

  3. 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
    
  4. 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_files delivery.

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.

  1. 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.
  2. 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.
  3. 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).
  4. 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.
  5. Delivery: Workspace root {episode-name}-douyin-cover.miora + double-size image cards; record cover text and dimensions in demand-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.md per episode directory: Five question answers + record of each revision round (file user's original words); record feedback first, then edit; only redo necessary steps (see references/pacing-and-gates.md for 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.py layout → 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 } ] }
  ]
}
  • idx is sequentially numbered from 1, strictly corresponding to frame_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_confirmed defaults to false; set to true at Gate ③, otherwise the script will refuse to synthesize.

Common Failure Quick Reference

SymptomCauseResolution
Subtitle box color doesn't match expectationsASS color is BBGGRR reversedRed=&H000000FF Blue=&H00FF0000 Black=&H00000000
Classical reading pronounced as vernacular (e.g., 老子 lǎo zi)TTS defaults to colloquial pronunciationAdd to tts_replacements and rerun TTS
PPT font doesn't renderChinese font names not recognizedUse SimSun / Microsoft YaHei
One-step composition white screen / filter chain crashOverlay after xfade conflictsSplit into two steps: render base layer first, then overlay; escape commas with \,
Frames "generated" but not foundpymupdf silently failsScript uses abspath + post-write validation; or use export_frames.ps1
--rate -10% error "expected one argument"argparse misinterprets optionUse --rate=-10% or just modify the JSON rate field
Composition error "Audio not yet confirmed"Gate ③ was skippedSet voice_confirmed=true and rerun
JSON error with Chinese quotation marksManual escape errorsUse Python json.dump to generate programmatically
Deck generation error "missing audio"HTML route requires TTS firstRun tts_synth.py first, then gen_html_deck.py
Rendering error "cannot find Chromium"Browser path changedSpecify with --browser pointing to chrome.exe / msedge.exe
Title page not centeredCSS class selector writingUse .inner.layout-title (same-class elements), avoid descendant selectors
Items broken up (sequence number sentences become separate items)Short sequence sentences not mergedgen_html_deck.py has built-in merging; defining explicit points can completely avoid this
Last frame duration incorrectConcat quirk for the last filecompose_html_video.py has built-in repetition of the last file; do not delete
Cue emphasis not appearingCue exceeds page durationCheck if t in slides[].cues is less than the audio duration for that page
Cover large characters missing strokes/incorrect charactersAI unstable with long Chinese textKeep text within four characters and regenerate; if anchor point characters are wrong, regenerate anchor point first, then derive
Cover short side 1072≠1080Model roundingDeliver as-is (aspect ratio still ≈16:9/9:16); if precise, pad to 1920×1080 / 1080×1920
Two covers don't look like a setBoth generated independently from textMust 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 txt
  • scripts/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 roadmap
  • references/pitfalls.md — Pitfall checklist and architecture experience
  • references/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.

Related Skills