[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M79AART0512NBSCGAHKP675Q":3,"skill-same:01M79AART0512NBSCGAHKP675Q":138},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":16,"sourceHtmlUrl":17,"categories":18,"keywords":19,"publishedAt":20,"linkOnly":21,"markdownContent":22,"markdownBody":23,"frontMatter":24,"refName":25,"commitSha":26,"skillDirectory":27,"forksCount":28,"repositoryHtmlUrl":29,"repositoryHomepageUrl":6,"ownerHtmlUrl":30,"ownerBlogUrl":31,"ownerDescription":32,"updatedAt":33,"install":34,"resources":47},"01M79AART0512NBSCGAHKP675Q","video-production",null,"AI educational video production pipeline. Use when producing lecture videos, tutorial videos, or educational content. Covers script writing, slide generation (gpt-image-2 hand-drawn style or HTML), TTS narration (ElevenLabs) with ASR verification, subtitle alignment, and FFmpeg video assembly.","yhyu13\u002FAgentSkillsVault","yhyu13","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F19365678?v=4","01M79A3XCF5M26GEWS60XQQRWW","俞航","01M79A3XD7YZ779GGWS41K367S",3,"MIT","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002FSKILL.md",[],[],"2026-11-15 14:19:17",false,"---\nname: video-production\ndescription: \"AI educational video production pipeline. Use when producing lecture videos, tutorial videos, or educational content. Covers script writing, slide generation (gpt-image-2 hand-drawn style or HTML), TTS narration (ElevenLabs) with ASR verification, subtitle alignment, and FFmpeg video assembly.\"\n---\n\n# Video Production Skill\n\nEnd-to-end pipeline for producing AI-narrated educational videos with slides.\nDistilled from producing 40+ real videos for an AI-run YouTube channel (蝦說 AI), including every mistake we only made once.\n\n**Who this is for:** an AI agent (Claude, Codex, Gemini, or any coding agent) asked to produce a narrated slide video. A human can follow it too.\n\n## Quick Start\n\n```\n0. Read references\u002Fteaching-style.md + references\u002Fnarration-style.md (content quality rules)\n1. Create project directory and cd into it; copy config.json from references\u002Fconfig-example.json and fill it in\n2. Write narration.json (the script — one string per slide)\n3. Generate slides (Path A: gpt-image-2 hand-drawn \u002F Path B: HTML + Playwright screenshot)\n4. Visually inspect every slide PNG (wrong characters \u002F clipping \u002F unreadable fonts)\n5. TTS narration → MP3 (with built-in ASR verification)   → node scripts\u002Ftts_with_asr.js\n6. FFmpeg assemble → video.mp4                            → node scripts\u002Fassemble.js\n7. Quality check (bitrate + frame extraction + visual)\n8. Subtitles → SRT (+ optional burn-in)                   → node scripts\u002Fgen_subtitles.js\n9. Cover image → thumbnail                                → python scripts\u002Fcover_gen.py\n10. Upload wherever you publish; verify the thumbnail and visibility after upload\n```\n\n## Requirements\n\n- **Node.js** ≥ 18 (scripts use only built-ins + `playwright` for HTML screenshots)\n- **Python** ≥ 3.9 (only for gpt-image-2 slide\u002Fcover generation and optional rescore)\n- **FFmpeg + FFprobe** on PATH (or set explicit paths in `config.json`)\n- **API keys** (environment variables, or a `.env` file in the project directory):\n  - `ELEVENLABS_API_KEY` — TTS\n  - `OPENAI_API_KEY` — Whisper ASR verification + gpt-image-2 slides\u002Fcover\n- A **TTS voice**: set `tts.voiceId` in `config.json` (any voice from your ElevenLabs\n  voice library — a premade voice works out of the box; a cloned voice makes it yours).\n\n> ⚠️ Never hardcode API keys in scripts and never commit them to git. One of our keys\n> was auto-revoked from a public repo push and 32 videos rendered silent. Environment\n> variables only.\n\n---\n\n## Configuration\n\nCreate `config.json` in your project directory (start from `references\u002Fconfig-example.json`):\n\n```json\n{\n  \"tts\": {\n    \"provider\": \"elevenlabs\",\n    \"voiceId\": \"YOUR_ELEVENLABS_VOICE_ID\",\n    \"model\": \"eleven_multilingual_v2\",\n    \"maxRetries\": 5\n  },\n  \"asr\": {\n    \"provider\": \"openai\",\n    \"model\": \"whisper-1\",\n    \"language\": \"zh\",\n    \"passThreshold\": 0.85\n  },\n  \"video\": {\n    \"width\": 1920,\n    \"height\": 1080,\n    \"audioBitrate\": \"192k\",\n    \"slidePadding\": 1.0\n  },\n  \"branding\": {\n    \"watermark\": \"🎬 Your Channel Name\"\n  },\n  \"ffmpeg\": \"ffmpeg\",\n  \"ffprobe\": \"ffprobe\"\n}\n```\n\n---\n\n## Checklist (mandatory, do not skip steps)\n\n```\n□ 0. Read references\u002Fteaching-style.md — content\u002Fteaching rules (pre-flight constitution)\n□ 1. Write narration.json\n□ 1.5 ⭐ ALIGNMENT CHECK: narration entries count == slide count. MUST be equal!\n     If narration splits a topic across 2 entries, there must be 2 separate slides.\n     Mismatch = audio\u002Fvisual desync for every slide after the mismatch point.\n□ 2. Generate slides (Path A or B below)\n□ 3. ⭐ Visual check: open every slide PNG with an image tool\n     (wrong\u002Fgarbled characters? clipped edges? readable on a phone?)\n□ 4. TTS synthesis + ⭐ ASR verification (similarity ≥ 0.85, never skip)\n□ 5. FFmpeg assemble → video.mp4\n□ 6. ⭐ Quality check: ffprobe audio bitrate + extract a frame + visual verify\n□ 7. Subtitles → subtitles_aligned.srt (external track and\u002For burn-in)\n□ 8. ⭐ Cover image → thumbnail (16:9; pad, don't crop)\n□ 9. Upload (start unlisted\u002Fprivate, review, then publish)\n□ 10. ⭐ After upload: verify the thumbnail actually shows YOUR cover, not an auto frame\n```\n\n**The ⭐ steps are non-negotiable.** Each one exists because skipping it once ruined an\nentire batch of videos.\n\n> 🔴 **The cover is part of the video, not an optional extra.** We once shipped a video\n> where every step was done except the cover — it went live with a default gray frame.\n> \"No cover = not shipped.\"\n\n---\n\n## Step 1: Script (`narration.json`)\n\nA JSON array of strings, **one per slide** (CRITICAL: array length MUST equal slide count):\n\n```json\n[\n  \"開場白，吸引注意力...\",\n  \"第一個重點，配合比喻...\",\n  \"第二個重點...\",\n  \"結尾，call to action...\"\n]\n```\n\n### Writing guidelines\n\n- **80–150 characters per slide** (Chinese). Too short = rushed; too long = TTS breaks.\n- **Conversational tone**: metaphors, rhetorical questions, humor.\n- **One concept per slide.** Don't cram.\n- **Story > Information.** Viewers remember stories, not bullet lists.\n- Full teaching methodology (audience empathy, glossing jargon, honest attribution,\n  first-person endings…): `references\u002Fteaching-style.md`. Voice & TTS-safe writing:\n  `references\u002Fnarration-style.md`.\n\n### TTS-friendly writing (important!)\n\nTTS engines mispronounce things. Prevent it at the script stage:\n\n| Problem | Fix |\n|---------|-----|\n| English abbreviations (LLM, NLP) | Spell out in Chinese, or letters with periods (P.U.A.) |\n| Raw numbers (135,000) | Chinese numerals (十三萬五千) |\n| Version numbers (4.5) | Display text keeps `4.5`, TTS text gets 四點五 |\n| Long sentences (>50 chars) | Split at natural breath points |\n| Parenthetical content | Rewrite as natural speech |\n| Chinese heteronyms 破音字 (還\u002F重\u002F長\u002F得…) | Scan with references\u002Fheteronyms.json, rewrite |\n\n**Punctuation and pacing:** our TTS script strips punctuation before synthesis (each\npunctuation mark becomes a pause in Chinese TTS — dense commas produce machine-gun\nnarration). Write narration that flows in breath-groups of ~8–22 characters; put commas\nonly at real breath points. Subtitles still use the original punctuated text.\n\n---\n\n## Step 2: Slides\n\nTwo first-class paths. Pick ONE per video (Path A is our channel default; Path B has no\nimage-API dependency).\n\n### Path A — gpt-image-2 hand-drawn teaching style (`scripts\u002Fslides_gen.py`)\n\nFull-bleed AI-generated slides in a \"professor's hand-drawn lecture notes\" style:\nwhite background, bold black CJK title top-left with underline, thin black arrows,\nlots of whitespace, a small mascot in the corner, stick figures only (no real faces).\n\n1. Write one Chinese prompt per slide into `slides_prompts.json` (array of strings).\n   Wrap every string that must appear on the slide in 「」. Always end the shared style\n   block with: 「所有中文字必須完全正確、清楚可讀、不可有亂碼或錯字。數字要正確。」\n2. `python scripts\u002Fslides_gen.py` → generates `slides_raw\u002Fslide_NN.png` (1536×1024).\n   Batch ≤4–5 concurrent lanes (~120s per image). **Do not run Whisper at the same\n   time** — concurrent heavy API calls starve each other.\n3. **Visually inspect every image** (garbled characters \u002F wrong or invented numbers \u002F\n   typos → regenerate just that slide; HTTP 502 → just retry that slide).\n   gpt-image-2 WILL invent numbers to fill tables unless your prompt explicitly says\n   「畫面只能出現 X 這幾個數字，其他留空」.\n4. `node scripts\u002Fpad_and_burn.js pad` → 1536×1024 scaled to 1410×940, padded onto a\n   1920×1080 white canvas with a 140px bottom band reserved for subtitles.\n\n### Path B — HTML slides + Playwright screenshot (`scripts\u002Fscreenshot.js`)\n\nCreate one HTML file per slide: `slides\u002Fslide_01.html`, `slide_02.html`, …\n(template: `references\u002Fslide-template.html`), then `node scripts\u002Fscreenshot.js`.\n\nDesign rules (hard-won; mobile viewers are the majority):\n\n| Element | Minimum font size |\n|---------|-------------------|\n| Main title | **≥72px** |\n| Subtitle \u002F section header | ≥36px |\n| Body text | **≥32px** (absolute floor) |\n| Key numbers | ≥48px |\n| Watermark | ≥20px (only element allowed smaller) |\n\n- Resolution = `video.width × video.height` (default 1920×1080).\n- **Fill 80%+ of the canvas** — `padding: 60-80px`, flexbox centering, no floating cards.\n- Every element on the slide must be mentioned in the narration (and vice versa).\n- 🔴 Never use \u003C24px body text. Fewer points in bigger font > more points in tiny font.\n\n### Path C — Node Canvas fallback (`scripts\u002Fgenerate_slides.js`)\n\nNo browser, no image API: renders simple title+bullets slides from `slides.json`.\nUse only when Playwright and the image API are both unavailable.\n\n---\n\n## Step 3: TTS + ASR Verification (`scripts\u002Ftts_with_asr.js`)\n\n```bash\nnode scripts\u002Ftts_with_asr.js [project_dir]\n```\n\nReads `narration.json`, synthesizes each entry via ElevenLabs, saves `audio\u002Fslide_XX.mp3`,\nthen **verifies every clip with Whisper ASR**:\n\n1. Transcribe the generated audio\n2. Compute character-overlap similarity vs the original text\n3. **≥ 0.85 = PASS** (target ≥0.90 for important videos)\n4. \u003C 0.85 = FAIL → adjust wording and retry (up to `tts.maxRetries`)\n\n**Adjustment rules:** swap synonyms \u002F split long sentences \u002F write numbers in Chinese —\nbut never change the meaning, never drop information.\n\n**Don't chase 0.85 forever — verify the words, ship on redundancy.** Whisper produces\nfalse alarms on Chinese: same-sound different-tone homophones, digits vs 中文數字,\nSimplified output vs Traditional script. If the ASR \"errors\" are homophones and every\nkey word\u002Fnumber comes through, the audio is correct — keep the best attempt. The slide\nshows the number visually and the subtitle uses the original text, so the viewer has\ntriple redundancy. For digit-heavy narration, run `python scripts\u002Frescore.py` — it\nstrips numerals and compares toneless-pinyin multisets (≥0.90 = pass), which kills\nmost false failures. A real defect = ASR gets the SAME wrong word consistently across\nmultiple synth attempts (that's the TTS mispronouncing, not Whisper mishearing → rewrite\nthat word).\n\n---\n\n## Step 4: FFmpeg Assembly (`scripts\u002Fassemble.js`)\n\n```bash\nnode scripts\u002Fassemble.js [project_dir]\n```\n\nPairs each `slides\u002Fslide_XX.png` with `audio\u002Fslide_XX.mp3`, creates per-slide clips,\nconcatenates into `video.mp4`.\n\nKey flags (all already in the script):\n- `-b:a 192k` — without it audio can silently render at 2kbps (present but inaudible)\n- `-tune stillimage` — much smaller files for slide video\n- `-pix_fmt yuv420p` — plays everywhere\n- `-movflags +faststart` — video streams\u002Fplays inline instead of \"link won't open\"\n\n> 🔴 **Mixing clips from different sources** (e.g. your narrated slides + a real screen\n> recording)? Do NOT use the concat demuxer with `-c copy` — strict players (Windows\n> Media Player) refuse to play the result. Re-encode through the concat FILTER into one\n> continuous stream, normalize every input (`scale`, `fps=30`, `format=yuv420p`,\n> 44100 stereo audio), re-encode audio, add faststart, and align loudness\n> (`loudnorm=I=-16:TP=-1.5:LRA=11`). Details in references\u002Flessons-learned.md.\n\n> 🔴 **Mixing two TTS providers in one video?** Their sample rates differ (ElevenLabs\n> 44100 Hz vs OpenAI TTS 24000 Hz). Resample everything to 44100 BEFORE assembly or\n> some players choke exactly at the voice-switch point.\n\n---\n\n## Step 5: Quality Check (three checks, all required)\n\n### 5a. Audio bitrate\n```bash\nffprobe -v error -show_entries stream=codec_type,codec_name,bit_rate -of default video.mp4\n```\n✅ ~130–192 kbps AAC ❌ ~2 kbps or N\u002FA = TTS failed silently\n\n### 5b. Visual verification\n```bash\nffmpeg -ss 3 -i video.mp4 -frames:v 1 -update 1 verify.png\n```\nCompare `verify.png` against `slides\u002Fslide_01.png` with an image tool. Content must match.\n\n### 5c. Font size check\nInspect `verify.png`: is every text element readable at mobile size?\n\n---\n\n## Step 6: Subtitles (`scripts\u002Fgen_subtitles.js`)\n\n```bash\nnode scripts\u002Fgen_subtitles.js [project_dir]\n```\n\nProduces `subtitles_aligned.srt`: **Whisper word timestamps for timing, original\nnarration text for display** (never use ASR output as subtitle text — it mishears).\nLine breaks are width-aware (CJK=1, Latin=0.5, ≤16 full-width per line) and never cut\ninside an English word.\n\n> ⚠️ **The #1 subtitle bug: drift from assuming clip duration = audio + padding.**\n> FFmpeg's `-shortest` truncates the padding, so real clip duration ≈ audio duration.\n> The script therefore reads ACTUAL durations from `temp\u002Fclip_XX.mp4` with ffprobe.\n> If you ever hand-roll offsets: never `offset += audioDur + padding` — that drifts\n> +1s per slide and by slide 16 subtitles are 15 seconds late.\n\nShip options:\n- **External SRT track** (recommended when your platform supports it — viewers can\n  toggle, nothing covers the slides)\n- **Burn-in**: `node scripts\u002Fpad_and_burn.js burn` (Path A white-band layout: dark text\n  FontSize 30 \u002F MarginV 30 sits inside the reserved 140px band)\n  For full-bleed dark HTML slides use FontSize=14–18, MarginV=6–12, BorderStyle=3 instead.\n\nFor rough\u002Fcloned voices where Whisper timestamps collapse, there is a geometry-based\nfallback (ffmpeg `silencedetect` + character-width proportional alignment) — see\nreferences\u002Flessons-learned.md § subtitle alignment.\n\n---\n\n## Step 7: Cover \u002F Thumbnail (`scripts\u002Fcover_gen.py`)\n\n```bash\npython scripts\u002Fcover_gen.py \"一張 YouTube 影片封面，橫式 16:9，白底手繪教學風…主標題用超大粗黑體繁體中文寫「你的標題」…\"\n```\n\n- Generate at 1536×1024, then **pad — do not crop — to 1280×720**:\n  crop cuts off the top of your title. `scale=1080:720` + `pad=1280:720:100:0:color=white`\n  (white side bars are invisible on a white cover).\n- Target ≤2MB for YouTube.\n- Same prompt hygiene as slides: quote exact text in 「」, forbid invented numbers,\n  visually verify before shipping.\n\n---\n\n## Step 8: Upload\n\nPlatform-specific; do it however you normally operate (browser automation, manual, CLI).\nRegardless of method, these rules survived contact with reality:\n\n1. Upload as **unlisted\u002Fprivate first**, review the actual playback, then publish.\n2. **Verify the thumbnail after upload** — the preview must show YOUR cover, not an\n   auto-selected frame.\n3. Re-check title\u002Fdescription\u002Flanguage settings after any dialog reopens (some upload\n   wizards silently reset radio buttons).\n4. If you used an external SRT: upload it as a caption track (\"with timing\"), publish\n   the track, then verify captions actually appear on the watch page.\n5. Disclose AI authorship in the description if the narration\u002Fproduction is AI-made.\n\n---\n\n## Output Structure\n\n```\nmy-video-project\u002F\n├── config.json              ← project config (from references\u002Fconfig-example.json)\n├── narration.json           ← script text per slide\n├── slides_prompts.json      ← (Path A) one gpt-image-2 prompt per slide\n├── slides_raw\u002F              ← (Path A) raw 1536×1024 generations\n├── slides\u002F                  ← final 1920×1080 PNGs (padded or screenshotted)\n│   ├── slide_01.png\n│   └── ...\n├── audio\u002F\n│   ├── slide_01.mp3\n│   └── ...\n├── temp\u002F                    ← per-slide clips + whisper word caches\n├── video.mp4                ← assembled (no subtitles)\n├── subtitles_aligned.srt    ← aligned subtitle track\n├── video_sub.mp4            ← (optional) burned-in version\n└── thumbnail.jpg            ← cover, 1280×720\n```\n\n---\n\n## Scripts Reference\n\nAll scripts take the project directory as an optional first argument (default: CWD).\n\n| Script | Purpose |\n|--------|---------|\n| `scripts\u002Fslides_gen.py` | gpt-image-2 slide generation from slides_prompts.json |\n| `scripts\u002Fpad_and_burn.js` | pad 3:2 images to 16:9 + subtitle band \u002F burn SRT |\n| `scripts\u002Fscreenshot.js` | Playwright HTML→PNG screenshots |\n| `scripts\u002Fgenerate_slides.js` | Node Canvas fallback slide renderer |\n| `scripts\u002Ftts_with_asr.js` | ElevenLabs TTS + Whisper ASR verification loop |\n| `scripts\u002Fassemble.js` | FFmpeg per-slide clips + concat |\n| `scripts\u002Fgen_subtitles.js` | aligned SRT (whisper timing + original text) |\n| `scripts\u002Frescore.py` | homophone\u002Fdigit-tolerant second-chance ASR scoring |\n| `scripts\u002Fcover_gen.py` | gpt-image-2 cover generation |\n\n---\n\n## Sub-agents\n\nIf you spawn a sub-agent to produce a video, the task prompt must explicitly say\n**\"read the video-production SKILL.md first\"**. Sub-agents that aren't told skip the\npipeline and reinvent (worse) wheels. Re-read the skill fresh each session — it evolves.\n\n## When things break\n\n`references\u002Flessons-learned.md` is the accident report archive: path bugs, silent audio,\nsubtitle drift, moderation blocks on image generation, players that refuse concat-copied\nfiles, Whisper false alarms, batch API contention, and more. Read it before your first\nvideo; grep it when something looks weird — the odds are good we already hit it.\n","# Video Production Skill\n\nEnd-to-end pipeline for producing AI-narrated educational videos with slides.\nDistilled from producing 40+ real videos for an AI-run YouTube channel (蝦說 AI), including every mistake we only made once.\n\n**Who this is for:** an AI agent (Claude, Codex, Gemini, or any coding agent) asked to produce a narrated slide video. A human can follow it too.\n\n## Quick Start\n\n```\n0. Read references\u002Fteaching-style.md + references\u002Fnarration-style.md (content quality rules)\n1. Create project directory and cd into it; copy config.json from references\u002Fconfig-example.json and fill it in\n2. Write narration.json (the script — one string per slide)\n3. Generate slides (Path A: gpt-image-2 hand-drawn \u002F Path B: HTML + Playwright screenshot)\n4. Visually inspect every slide PNG (wrong characters \u002F clipping \u002F unreadable fonts)\n5. TTS narration → MP3 (with built-in ASR verification)   → node scripts\u002Ftts_with_asr.js\n6. FFmpeg assemble → video.mp4                            → node scripts\u002Fassemble.js\n7. Quality check (bitrate + frame extraction + visual)\n8. Subtitles → SRT (+ optional burn-in)                   → node scripts\u002Fgen_subtitles.js\n9. Cover image → thumbnail                                → python scripts\u002Fcover_gen.py\n10. Upload wherever you publish; verify the thumbnail and visibility after upload\n```\n\n## Requirements\n\n- **Node.js** ≥ 18 (scripts use only built-ins + `playwright` for HTML screenshots)\n- **Python** ≥ 3.9 (only for gpt-image-2 slide\u002Fcover generation and optional rescore)\n- **FFmpeg + FFprobe** on PATH (or set explicit paths in `config.json`)\n- **API keys** (environment variables, or a `.env` file in the project directory):\n  - `ELEVENLABS_API_KEY` — TTS\n  - `OPENAI_API_KEY` — Whisper ASR verification + gpt-image-2 slides\u002Fcover\n- A **TTS voice**: set `tts.voiceId` in `config.json` (any voice from your ElevenLabs\n  voice library — a premade voice works out of the box; a cloned voice makes it yours).\n\n> ⚠️ Never hardcode API keys in scripts and never commit them to git. One of our keys\n> was auto-revoked from a public repo push and 32 videos rendered silent. Environment\n> variables only.\n\n---\n\n## Configuration\n\nCreate `config.json` in your project directory (start from `references\u002Fconfig-example.json`):\n\n```json\n{\n  \"tts\": {\n    \"provider\": \"elevenlabs\",\n    \"voiceId\": \"YOUR_ELEVENLABS_VOICE_ID\",\n    \"model\": \"eleven_multilingual_v2\",\n    \"maxRetries\": 5\n  },\n  \"asr\": {\n    \"provider\": \"openai\",\n    \"model\": \"whisper-1\",\n    \"language\": \"zh\",\n    \"passThreshold\": 0.85\n  },\n  \"video\": {\n    \"width\": 1920,\n    \"height\": 1080,\n    \"audioBitrate\": \"192k\",\n    \"slidePadding\": 1.0\n  },\n  \"branding\": {\n    \"watermark\": \"🎬 Your Channel Name\"\n  },\n  \"ffmpeg\": \"ffmpeg\",\n  \"ffprobe\": \"ffprobe\"\n}\n```\n\n---\n\n## Checklist (mandatory, do not skip steps)\n\n```\n□ 0. Read references\u002Fteaching-style.md — content\u002Fteaching rules (pre-flight constitution)\n□ 1. Write narration.json\n□ 1.5 ⭐ ALIGNMENT CHECK: narration entries count == slide count. MUST be equal!\n     If narration splits a topic across 2 entries, there must be 2 separate slides.\n     Mismatch = audio\u002Fvisual desync for every slide after the mismatch point.\n□ 2. Generate slides (Path A or B below)\n□ 3. ⭐ Visual check: open every slide PNG with an image tool\n     (wrong\u002Fgarbled characters? clipped edges? readable on a phone?)\n□ 4. TTS synthesis + ⭐ ASR verification (similarity ≥ 0.85, never skip)\n□ 5. FFmpeg assemble → video.mp4\n□ 6. ⭐ Quality check: ffprobe audio bitrate + extract a frame + visual verify\n□ 7. Subtitles → subtitles_aligned.srt (external track and\u002For burn-in)\n□ 8. ⭐ Cover image → thumbnail (16:9; pad, don't crop)\n□ 9. Upload (start unlisted\u002Fprivate, review, then publish)\n□ 10. ⭐ After upload: verify the thumbnail actually shows YOUR cover, not an auto frame\n```\n\n**The ⭐ steps are non-negotiable.** Each one exists because skipping it once ruined an\nentire batch of videos.\n\n> 🔴 **The cover is part of the video, not an optional extra.** We once shipped a video\n> where every step was done except the cover — it went live with a default gray frame.\n> \"No cover = not shipped.\"\n\n---\n\n## Step 1: Script (`narration.json`)\n\nA JSON array of strings, **one per slide** (CRITICAL: array length MUST equal slide count):\n\n```json\n[\n  \"開場白，吸引注意力...\",\n  \"第一個重點，配合比喻...\",\n  \"第二個重點...\",\n  \"結尾，call to action...\"\n]\n```\n\n### Writing guidelines\n\n- **80–150 characters per slide** (Chinese). Too short = rushed; too long = TTS breaks.\n- **Conversational tone**: metaphors, rhetorical questions, humor.\n- **One concept per slide.** Don't cram.\n- **Story > Information.** Viewers remember stories, not bullet lists.\n- Full teaching methodology (audience empathy, glossing jargon, honest attribution,\n  first-person endings…): `references\u002Fteaching-style.md`. Voice & TTS-safe writing:\n  `references\u002Fnarration-style.md`.\n\n### TTS-friendly writing (important!)\n\nTTS engines mispronounce things. Prevent it at the script stage:\n\n| Problem | Fix |\n|---------|-----|\n| English abbreviations (LLM, NLP) | Spell out in Chinese, or letters with periods (P.U.A.) |\n| Raw numbers (135,000) | Chinese numerals (十三萬五千) |\n| Version numbers (4.5) | Display text keeps `4.5`, TTS text gets 四點五 |\n| Long sentences (>50 chars) | Split at natural breath points |\n| Parenthetical content | Rewrite as natural speech |\n| Chinese heteronyms 破音字 (還\u002F重\u002F長\u002F得…) | Scan with references\u002Fheteronyms.json, rewrite |\n\n**Punctuation and pacing:** our TTS script strips punctuation before synthesis (each\npunctuation mark becomes a pause in Chinese TTS — dense commas produce machine-gun\nnarration). Write narration that flows in breath-groups of ~8–22 characters; put commas\nonly at real breath points. Subtitles still use the original punctuated text.\n\n---\n\n## Step 2: Slides\n\nTwo first-class paths. Pick ONE per video (Path A is our channel default; Path B has no\nimage-API dependency).\n\n### Path A — gpt-image-2 hand-drawn teaching style (`scripts\u002Fslides_gen.py`)\n\nFull-bleed AI-generated slides in a \"professor's hand-drawn lecture notes\" style:\nwhite background, bold black CJK title top-left with underline, thin black arrows,\nlots of whitespace, a small mascot in the corner, stick figures only (no real faces).\n\n1. Write one Chinese prompt per slide into `slides_prompts.json` (array of strings).\n   Wrap every string that must appear on the slide in 「」. Always end the shared style\n   block with: 「所有中文字必須完全正確、清楚可讀、不可有亂碼或錯字。數字要正確。」\n2. `python scripts\u002Fslides_gen.py` → generates `slides_raw\u002Fslide_NN.png` (1536×1024).\n   Batch ≤4–5 concurrent lanes (~120s per image). **Do not run Whisper at the same\n   time** — concurrent heavy API calls starve each other.\n3. **Visually inspect every image** (garbled characters \u002F wrong or invented numbers \u002F\n   typos → regenerate just that slide; HTTP 502 → just retry that slide).\n   gpt-image-2 WILL invent numbers to fill tables unless your prompt explicitly says\n   「畫面只能出現 X 這幾個數字，其他留空」.\n4. `node scripts\u002Fpad_and_burn.js pad` → 1536×1024 scaled to 1410×940, padded onto a\n   1920×1080 white canvas with a 140px bottom band reserved for subtitles.\n\n### Path B — HTML slides + Playwright screenshot (`scripts\u002Fscreenshot.js`)\n\nCreate one HTML file per slide: `slides\u002Fslide_01.html`, `slide_02.html`, …\n(template: `references\u002Fslide-template.html`), then `node scripts\u002Fscreenshot.js`.\n\nDesign rules (hard-won; mobile viewers are the majority):\n\n| Element | Minimum font size |\n|---------|-------------------|\n| Main title | **≥72px** |\n| Subtitle \u002F section header | ≥36px |\n| Body text | **≥32px** (absolute floor) |\n| Key numbers | ≥48px |\n| Watermark | ≥20px (only element allowed smaller) |\n\n- Resolution = `video.width × video.height` (default 1920×1080).\n- **Fill 80%+ of the canvas** — `padding: 60-80px`, flexbox centering, no floating cards.\n- Every element on the slide must be mentioned in the narration (and vice versa).\n- 🔴 Never use \u003C24px body text. Fewer points in bigger font > more points in tiny font.\n\n### Path C — Node Canvas fallback (`scripts\u002Fgenerate_slides.js`)\n\nNo browser, no image API: renders simple title+bullets slides from `slides.json`.\nUse only when Playwright and the image API are both unavailable.\n\n---\n\n## Step 3: TTS + ASR Verification (`scripts\u002Ftts_with_asr.js`)\n\n```bash\nnode scripts\u002Ftts_with_asr.js [project_dir]\n```\n\nReads `narration.json`, synthesizes each entry via ElevenLabs, saves `audio\u002Fslide_XX.mp3`,\nthen **verifies every clip with Whisper ASR**:\n\n1. Transcribe the generated audio\n2. Compute character-overlap similarity vs the original text\n3. **≥ 0.85 = PASS** (target ≥0.90 for important videos)\n4. \u003C 0.85 = FAIL → adjust wording and retry (up to `tts.maxRetries`)\n\n**Adjustment rules:** swap synonyms \u002F split long sentences \u002F write numbers in Chinese —\nbut never change the meaning, never drop information.\n\n**Don't chase 0.85 forever — verify the words, ship on redundancy.** Whisper produces\nfalse alarms on Chinese: same-sound different-tone homophones, digits vs 中文數字,\nSimplified output vs Traditional script. If the ASR \"errors\" are homophones and every\nkey word\u002Fnumber comes through, the audio is correct — keep the best attempt. The slide\nshows the number visually and the subtitle uses the original text, so the viewer has\ntriple redundancy. For digit-heavy narration, run `python scripts\u002Frescore.py` — it\nstrips numerals and compares toneless-pinyin multisets (≥0.90 = pass), which kills\nmost false failures. A real defect = ASR gets the SAME wrong word consistently across\nmultiple synth attempts (that's the TTS mispronouncing, not Whisper mishearing → rewrite\nthat word).\n\n---\n\n## Step 4: FFmpeg Assembly (`scripts\u002Fassemble.js`)\n\n```bash\nnode scripts\u002Fassemble.js [project_dir]\n```\n\nPairs each `slides\u002Fslide_XX.png` with `audio\u002Fslide_XX.mp3`, creates per-slide clips,\nconcatenates into `video.mp4`.\n\nKey flags (all already in the script):\n- `-b:a 192k` — without it audio can silently render at 2kbps (present but inaudible)\n- `-tune stillimage` — much smaller files for slide video\n- `-pix_fmt yuv420p` — plays everywhere\n- `-movflags +faststart` — video streams\u002Fplays inline instead of \"link won't open\"\n\n> 🔴 **Mixing clips from different sources** (e.g. your narrated slides + a real screen\n> recording)? Do NOT use the concat demuxer with `-c copy` — strict players (Windows\n> Media Player) refuse to play the result. Re-encode through the concat FILTER into one\n> continuous stream, normalize every input (`scale`, `fps=30`, `format=yuv420p`,\n> 44100 stereo audio), re-encode audio, add faststart, and align loudness\n> (`loudnorm=I=-16:TP=-1.5:LRA=11`). Details in references\u002Flessons-learned.md.\n\n> 🔴 **Mixing two TTS providers in one video?** Their sample rates differ (ElevenLabs\n> 44100 Hz vs OpenAI TTS 24000 Hz). Resample everything to 44100 BEFORE assembly or\n> some players choke exactly at the voice-switch point.\n\n---\n\n## Step 5: Quality Check (three checks, all required)\n\n### 5a. Audio bitrate\n```bash\nffprobe -v error -show_entries stream=codec_type,codec_name,bit_rate -of default video.mp4\n```\n✅ ~130–192 kbps AAC ❌ ~2 kbps or N\u002FA = TTS failed silently\n\n### 5b. Visual verification\n```bash\nffmpeg -ss 3 -i video.mp4 -frames:v 1 -update 1 verify.png\n```\nCompare `verify.png` against `slides\u002Fslide_01.png` with an image tool. Content must match.\n\n### 5c. Font size check\nInspect `verify.png`: is every text element readable at mobile size?\n\n---\n\n## Step 6: Subtitles (`scripts\u002Fgen_subtitles.js`)\n\n```bash\nnode scripts\u002Fgen_subtitles.js [project_dir]\n```\n\nProduces `subtitles_aligned.srt`: **Whisper word timestamps for timing, original\nnarration text for display** (never use ASR output as subtitle text — it mishears).\nLine breaks are width-aware (CJK=1, Latin=0.5, ≤16 full-width per line) and never cut\ninside an English word.\n\n> ⚠️ **The #1 subtitle bug: drift from assuming clip duration = audio + padding.**\n> FFmpeg's `-shortest` truncates the padding, so real clip duration ≈ audio duration.\n> The script therefore reads ACTUAL durations from `temp\u002Fclip_XX.mp4` with ffprobe.\n> If you ever hand-roll offsets: never `offset += audioDur + padding` — that drifts\n> +1s per slide and by slide 16 subtitles are 15 seconds late.\n\nShip options:\n- **External SRT track** (recommended when your platform supports it — viewers can\n  toggle, nothing covers the slides)\n- **Burn-in**: `node scripts\u002Fpad_and_burn.js burn` (Path A white-band layout: dark text\n  FontSize 30 \u002F MarginV 30 sits inside the reserved 140px band)\n  For full-bleed dark HTML slides use FontSize=14–18, MarginV=6–12, BorderStyle=3 instead.\n\nFor rough\u002Fcloned voices where Whisper timestamps collapse, there is a geometry-based\nfallback (ffmpeg `silencedetect` + character-width proportional alignment) — see\nreferences\u002Flessons-learned.md § subtitle alignment.\n\n---\n\n## Step 7: Cover \u002F Thumbnail (`scripts\u002Fcover_gen.py`)\n\n```bash\npython scripts\u002Fcover_gen.py \"一張 YouTube 影片封面，橫式 16:9，白底手繪教學風…主標題用超大粗黑體繁體中文寫「你的標題」…\"\n```\n\n- Generate at 1536×1024, then **pad — do not crop — to 1280×720**:\n  crop cuts off the top of your title. `scale=1080:720` + `pad=1280:720:100:0:color=white`\n  (white side bars are invisible on a white cover).\n- Target ≤2MB for YouTube.\n- Same prompt hygiene as slides: quote exact text in 「」, forbid invented numbers,\n  visually verify before shipping.\n\n---\n\n## Step 8: Upload\n\nPlatform-specific; do it however you normally operate (browser automation, manual, CLI).\nRegardless of method, these rules survived contact with reality:\n\n1. Upload as **unlisted\u002Fprivate first**, review the actual playback, then publish.\n2. **Verify the thumbnail after upload** — the preview must show YOUR cover, not an\n   auto-selected frame.\n3. Re-check title\u002Fdescription\u002Flanguage settings after any dialog reopens (some upload\n   wizards silently reset radio buttons).\n4. If you used an external SRT: upload it as a caption track (\"with timing\"), publish\n   the track, then verify captions actually appear on the watch page.\n5. Disclose AI authorship in the description if the narration\u002Fproduction is AI-made.\n\n---\n\n## Output Structure\n\n```\nmy-video-project\u002F\n├── config.json              ← project config (from references\u002Fconfig-example.json)\n├── narration.json           ← script text per slide\n├── slides_prompts.json      ← (Path A) one gpt-image-2 prompt per slide\n├── slides_raw\u002F              ← (Path A) raw 1536×1024 generations\n├── slides\u002F                  ← final 1920×1080 PNGs (padded or screenshotted)\n│   ├── slide_01.png\n│   └── ...\n├── audio\u002F\n│   ├── slide_01.mp3\n│   └── ...\n├── temp\u002F                    ← per-slide clips + whisper word caches\n├── video.mp4                ← assembled (no subtitles)\n├── subtitles_aligned.srt    ← aligned subtitle track\n├── video_sub.mp4            ← (optional) burned-in version\n└── thumbnail.jpg            ← cover, 1280×720\n```\n\n---\n\n## Scripts Reference\n\nAll scripts take the project directory as an optional first argument (default: CWD).\n\n| Script | Purpose |\n|--------|---------|\n| `scripts\u002Fslides_gen.py` | gpt-image-2 slide generation from slides_prompts.json |\n| `scripts\u002Fpad_and_burn.js` | pad 3:2 images to 16:9 + subtitle band \u002F burn SRT |\n| `scripts\u002Fscreenshot.js` | Playwright HTML→PNG screenshots |\n| `scripts\u002Fgenerate_slides.js` | Node Canvas fallback slide renderer |\n| `scripts\u002Ftts_with_asr.js` | ElevenLabs TTS + Whisper ASR verification loop |\n| `scripts\u002Fassemble.js` | FFmpeg per-slide clips + concat |\n| `scripts\u002Fgen_subtitles.js` | aligned SRT (whisper timing + original text) |\n| `scripts\u002Frescore.py` | homophone\u002Fdigit-tolerant second-chance ASR scoring |\n| `scripts\u002Fcover_gen.py` | gpt-image-2 cover generation |\n\n---\n\n## Sub-agents\n\nIf you spawn a sub-agent to produce a video, the task prompt must explicitly say\n**\"read the video-production SKILL.md first\"**. Sub-agents that aren't told skip the\npipeline and reinvent (worse) wheels. Re-read the skill fresh each session — it evolves.\n\n## When things break\n\n`references\u002Flessons-learned.md` is the accident report archive: path bugs, silent audio,\nsubtitle drift, moderation blocks on image generation, players that refuse concat-copied\nfiles, Whisper false alarms, batch API contention, and more. Read it before your first\nvideo; grep it when something looks weird — the odds are good we already hit it.\n",{"name":5,"description":7},"main","3b63fa056f5de7956766fca020e9c71fdf2b0fab","SKILL\u002Fdesign\u002Ftier0\u002Fvideo-production",0,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault","https:\u002F\u002Fgithub.com\u002Fyhyu13","https:\u002F\u002Fyhyu13.github.io","大丈夫自立于天地之间，岂可郁郁久居于人下\r\nA zealot of AI.","2026-11-14 20:53:07",{"source":8,"skillName":5,"commands":35,"prompt":45,"zipAvailable":46},[36,39,42],{"tool":37,"command":38},"npx","npx skills add yhyu13\u002FAgentSkillsVault --skill video-production",{"tool":40,"command":41},"bunx","bunx skills add yhyu13\u002FAgentSkillsVault --skill video-production",{"tool":43,"command":44},"pnpm","pnpm dlx skills add yhyu13\u002FAgentSkillsVault --skill video-production","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002FSKILL.md",true,[48,54,60,64,68,72,76,80,84,88,92,96,100,106,110,114,118,122,126,130,134],{"path":49,"resourceType":50,"resourceTypeDesc":51,"sizeBytes":52,"sourceHtmlUrl":53},".gitignore","OTHER","其他",113,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002F.gitignore",{"path":55,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":58,"sourceHtmlUrl":59},"examples\u002Fdemo\u002Fnarration.json","REFERENCE","参考文档",555,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fexamples\u002Fdemo\u002Fnarration.json",{"path":61,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":62,"sourceHtmlUrl":63},"examples\u002Fdemo\u002FREADME.md",1164,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fexamples\u002Fdemo\u002FREADME.md",{"path":65,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":66,"sourceHtmlUrl":67},"examples\u002Fdemo\u002Fslides_prompts.json",1603,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fexamples\u002Fdemo\u002Fslides_prompts.json",{"path":69,"resourceType":69,"resourceTypeDesc":70,"sizeBytes":71,"sourceHtmlUrl":16},"LICENSE","许可证",1091,{"path":73,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":74,"sourceHtmlUrl":75},"README.md",5276,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002FREADME.md",{"path":77,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":78,"sourceHtmlUrl":79},"references\u002Fconfig-example.json",720,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Freferences\u002Fconfig-example.json",{"path":81,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":82,"sourceHtmlUrl":83},"references\u002Fheteronyms.json",5240,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Freferences\u002Fheteronyms.json",{"path":85,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":86,"sourceHtmlUrl":87},"references\u002Flessons-learned.md",19456,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Freferences\u002Flessons-learned.md",{"path":89,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":90,"sourceHtmlUrl":91},"references\u002Fnarration-style.md",2720,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Freferences\u002Fnarration-style.md",{"path":93,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":94,"sourceHtmlUrl":95},"references\u002Fslide-template.html",1318,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Freferences\u002Fslide-template.html",{"path":97,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":98,"sourceHtmlUrl":99},"references\u002Fteaching-style.md",10360,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Freferences\u002Fteaching-style.md",{"path":101,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":104,"sourceHtmlUrl":105},"scripts\u002Fassemble.js","SCRIPT","脚本",4868,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fassemble.js",{"path":107,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":108,"sourceHtmlUrl":109},"scripts\u002Fcover_gen.py",2468,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fcover_gen.py",{"path":111,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":112,"sourceHtmlUrl":113},"scripts\u002Fgen_subtitles.js",7277,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fgen_subtitles.js",{"path":115,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":116,"sourceHtmlUrl":117},"scripts\u002Fgenerate_slides.js",4354,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fgenerate_slides.js",{"path":119,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":120,"sourceHtmlUrl":121},"scripts\u002Fpad_and_burn.js",2846,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fpad_and_burn.js",{"path":123,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":124,"sourceHtmlUrl":125},"scripts\u002Frescore.py",3742,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Frescore.py",{"path":127,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":128,"sourceHtmlUrl":129},"scripts\u002Fscreenshot.js",2151,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fscreenshot.js",{"path":131,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":132,"sourceHtmlUrl":133},"scripts\u002Fslides_gen.py",3175,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Fslides_gen.py",{"path":135,"resourceType":102,"resourceTypeDesc":103,"sizeBytes":136,"sourceHtmlUrl":137},"scripts\u002Ftts_with_asr.js",8280,"https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdesign\u002Ftier0\u002Fvideo-production\u002Fscripts\u002Ftts_with_asr.js",[139,146,153,160,167,174],{"skillNo":140,"name":141,"nameZh":6,"description":142,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":145,"publishedAt":20},"01M7DZJW3GMCBHNCC1J37K73T7","feishu-kdocs-cli","Office cloud CLIs for agents: install, authenticate, verify and troubleshoot Feishu `lark-cli` and Kingsoft\u002FWPS Docs `kdocs-cli`, plus the three China office MCP endpoints (Feishu, WPS SkillHub, Tencent Docs). Use when setting up or re-logging-in 飞书 CLI \u002F 金山文档 CLI, when a command fails with missing scope \u002F PATH \u002F token \u002F device-code errors, when a kdocs or lark command returns 400001 or unknown action, or when wiring 飞书 \u002F 金山文档 \u002F 腾讯文档 MCP into Cindy, Claude or another host. Not a per-command reference: domain commands live in the lark-* Skills and the kdocs Skill.","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fdevops\u002Ftier0\u002Ffeishu-kdocs-cli\u002FSKILL.md",[],[],{"skillNo":147,"name":148,"nameZh":6,"description":149,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":150,"categories":151,"keywords":152,"publishedAt":20},"01M7BE0VQF0T7ACMY3W9V27ZYG","vfx-fx","Cinematic and game VFX in Blender including smoke, fire, particles, trails, and Geometry Nodes FX setups via MCP.","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fgame-dev\u002Ftier2\u002Fvfx-fx\u002FSKILL.md",[],[],{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":159,"publishedAt":20},"01M7BE0Q23SY6QAW24XEADEH0N","uv-workflow","Production UV unwrapping for seam placement, UV packing, texel density, UDIM, lightmap UVs, and modular UV layouts. Use when preparing meshes for texturing and engine export in Blender via MCP.","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fgame-dev\u002Ftier2\u002Fuv-workflow\u002FSKILL.md",[],[],{"skillNo":161,"name":162,"nameZh":6,"description":163,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":164,"categories":165,"keywords":166,"publishedAt":20},"01M7BE0JAKENVJB4Z0SV3Q7QGR","texture-workflow","Production texture workflow for atlases, baking AO\u002Fcurvature\u002Fnormal\u002Froughness maps, decals, and texture memory optimization. Use for game-ready texture pipelines in Blender via MCP.","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fgame-dev\u002Ftier2\u002Ftexture-workflow\u002FSKILL.md",[],[],{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":173,"publishedAt":20},"01M7BE0CYB7BDJP7HD5WTS2PJB","sculpting","Blender sculpting for organic and hard surface detail using Dyntopo, multiresolution, brushes, alpha workflow, wrinkles, and damage. Use when high-frequency surface detail is needed before retopology in Blender via MCP.","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fgame-dev\u002Ftier2\u002Fsculpting\u002FSKILL.md",[],[],{"skillNo":175,"name":176,"nameZh":6,"description":177,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":178,"categories":179,"keywords":180,"publishedAt":20},"01M7BE09TBX9NQ5EBHN4JZ9VXM","scene-assembly","Large scene layout in Blender with collections, linking, overrides, streaming-friendly organization, and set assembly via MCP.","https:\u002F\u002Fgithub.com\u002Fyhyu13\u002FAgentSkillsVault\u002Fblob\u002Fmain\u002FSKILL\u002Fgame-dev\u002Ftier2\u002Fscene-assembly\u002FSKILL.md",[],[]]