{"slug":"agents365-ai-video-podcast-maker-lite-6c459c","title":"\u7cbe\u7b80\u53e3\u64ad\u89c6\u9891\u6d41\u6c34\u7ebf","description":"\u4ece\u9009\u9898\u5230\u5e26\u65c1\u767d\u7684\u8bb2\u89e3\u89c6\u9891\uff1a\u5199\u53e3\u64ad\u7a3f\uff08\u542b\u53cd AI \u8154\u4e0e\u6570\u5b57\u663e\u793a\u89c4\u5219\uff09\u2192 Azure TTS \u751f\u6210\u97f3\u9891\u4e0e\u5b57\u5e55 \u2192 Remotion \u5408\u6210 1080p/4K MP4 \u2192 \u53ef\u9009 BGM \u6df7\u97f3\u3002\u8bbe\u6709\u4eba\u5de5\u5ba1\u7a3f\u4e0e Studio \u9884\u89c8\u4e24\u9053\u786e\u8ba4\u5173\u5361\uff0c\u5f3a\u8c03\u97f3\u9891\u4e3a\u4e3b\u65f6\u949f\u3001\u65f6\u957f\u9700\u6821\u9a8c\u3002","category":"\u526a\u8f91\u4e0e\u5305\u88c5","tags":["Agents365-ai","\u526a\u8f91\u4e0e\u5305\u88c5"],"author":"Agents365-ai","source_url":"https://github.com/Agents365-ai/video-podcast-maker/blob/33b8078e3e87c9626d39d144b4c901b4ebf16f74/skills/video-podcast-maker-lite/SKILL.md","license":"MIT","status":"published","verification":"source_reviewed","verification_notes":"\u5df2\u6838\u5bf9\u539f\u59cb\u4ed3\u5e93\u3001\u56fa\u5b9a\u63d0\u4ea4\u3001\u8bb8\u53ef\u3001\u6280\u80fd\u6b63\u6587\u4e0e\u5305\u7ed3\u6784\uff0c\u5e76\u8865\u5145\u4e2d\u6587\u6267\u884c\u9a8c\u6536\u8bf4\u660e\uff1b\u672a\u8fd0\u884c\u5305\u5185\u811a\u672c\u3002","revision":"17a8552922694cb58f4b0d02dd0f6027","created_at":"2026-11-02T14:47:08+00:00","updated_at":"2026-11-02T14:47:08+00:00","source_repo":"Agents365-ai/video-podcast-maker","source_path":"skills/video-podcast-maker-lite/SKILL.md","source_commit":"33b8078e3e87c9626d39d144b4c901b4ebf16f74","source_updated_at":"2026-11-02T14:46:04+00:00","stars":0,"proxy_score":null,"score":null,"grade":null,"dimensions":{},"evaluation":{},"installs":0,"platforms":[],"formats":[],"category_slug":"packaging","category_name":"\u526a\u8f91\u4e0e\u5305\u88c5","quality":{"review_type":"text_and_package_review","readiness":"requires_setup","reviewed_at":"2026-11-02T14:46:04+00:00","review_summary":"\u6d41\u7a0b\u4e0e\u4eba\u5de5\u5173\u5361\u8bbe\u8ba1\u5177\u4f53\uff0c\u4ea7\u7269\u53ef\u9010\u9879\u9a8c\u6536\uff1b\u4f46\u9700\u81ea\u5907 Azure \u51ed\u8bc1\u4e0e Remotion \u9879\u76ee\uff0c\u4e14\u90e8\u5206\u5f15\u7528\u6587\u4ef6\u7f3a\u5931\uff0c\u811a\u672c\u672a\u9a8c\u8bc1\u3002","cost_notes":"Azure TTS \u6309\u5b57\u7b26\u8ba1\u8d39\uff0c\u9700\u7528\u6237\u81ea\u884c\u786e\u8ba4\u989d\u5ea6\u4e0e\u4ef7\u683c\uff1bPixabay \u97f3\u4e50\u4e3a\u514d\u8d39\u8bb8\u53ef\u4f46\u9700\u8bb0\u5f55\u6765\u6e90\u3002","source_sha256":"493aaf481a5e631b4931adae621dc4137d51cc59ddcfb6cad1b36f3c666cdf5c","prerequisites":["Azure Speech \u8ba2\u9605\u7684 key \u4e0e region","Python 3 \u4e0e azure-cognitiveservices-speech","ffmpeg\u3001Node 18+ \u4e0e\u4e00\u4e2a\u5df2\u5b89\u88c5\u4f9d\u8d56\u7684 Remotion \u9879\u76ee","\u53ef\u9009\uff1aPlaywright MCP \u4f1a\u8bdd\u7528\u4e8e\u6293\u53d6 Pixabay \u97f3\u4e50"],"checks":["podcast.txt \u6bcf\u6bb5\u6709 [SECTION:xxx] \u6807\u8bb0\uff0c\u65e0 markdown \u8bed\u6cd5\uff0c\u6570\u5b57\u4e3a\u963f\u62c9\u4f2f\u6570\u5b57\u4e14\u53ef\u8ffd\u6eaf\u5230\u6765\u6e90","tts.py \u4ea7\u51fa\u7684 timing.json \u4e2d sections \u65f6\u957f\u4e4b\u548c\u4e0e wav \u603b\u65f6\u957f\u4e00\u81f4\uff0ccues.json \u7684 section_frame \u53ef\u7528\u4e8e\u5bf9\u9f50\u753b\u9762","output.mp4 \u4e0e podcast_audio.wav \u65f6\u957f\u5dee\u5728 \u00b10.5 \u79d2\u5185\uff08ffprobe \u6838\u5bf9\uff09","\u5b57\u5e55\u4f4d\u4e8e\u8fdb\u5ea6\u6761\u4e0a\u65b9\u4e14\u4e0d\u906e\u6321\uff0c\u6b63\u6587 \u226532px\u3001\u6807\u9898 \u226564px","final_video.mp4 \u7ecf volumedetect \u68c0\u67e5\uff0c\u5747\u503c\u4e0e\u5cf0\u503c\u843d\u5728\u6587\u6863\u7ed9\u51fa\u7684\u533a\u95f4\u9644\u8fd1"],"limitations":["\u9996\u6b21\u642d\u5efa Remotion \u9879\u76ee\u9700\u7ea6 2GB \u4f9d\u8d56\u5b89\u88c5\uff0c\u8017\u65f6\u4e0e\u78c1\u76d8\u5360\u7528\u672a\u5728\u672c\u8f6e\u9a8c\u8bc1","BGM \u6293\u53d6\u4f9d\u8d56 Pixabay \u9875\u9762\u7ed3\u6784\uff0c\u6587\u6863\u81ea\u8ff0 2026-08-25 \u6709\u6548\uff0c\u540e\u7eed\u53ef\u80fd\u5931\u6548","\u672a\u63d0\u4f9b\u793a\u4f8b Remotion \u7ec4\u4ef6\uff0c\u753b\u9762\u8d28\u91cf\u53d6\u51b3\u4e8e\u4f7f\u7528\u8005\u81ea\u884c\u7f16\u5199","\u5305\u5185 tts.py \u672a\u5728\u672c\u8f6e\u5ba1\u9605\u4e2d\u8fd0\u884c\uff0c\u591a\u97f3\u5b57\u4e0e\u522b\u540d\u884c\u4e3a\u4ee5\u5b9e\u9645\u8f93\u51fa\u4e3a\u51c6"],"issues":["SKILL.md \u5f15\u7528\u5b8c\u6574\u7248\u6280\u80fd\u7684 natural-narration.md \u4e0e script-polish.md \u4f5c\u4e3a\u300c\u89c4\u8303\u6765\u6e90\u300d\uff0c\u4f46\u8fd9\u4e24\u4e2a\u6587\u4ef6\u672a\u968f\u5305\u63d0\u4f9b\uff0c\u89c4\u5219\u53ea\u80fd\u4ee5\u672c\u6587\u4ef6\u4e3a\u51c6","Step 6 \u63d0\u5230\u4ed3\u5e93\u7ea7\u7d22\u5f15/\u5206\u7c7b\u811a\u672c\uff0c\u4f46\u672a\u968f\u5305\u63d0\u4f9b\uff0c\u5c5e\u5916\u90e8\u4f9d\u8d56","BGM \u6293\u53d6\u6b65\u9aa4\u4f9d\u8d56\u6d4f\u89c8\u5668\u4f1a\u8bdd\u4e0e\u9875\u9762 JSON-LD\uff0c\u5c5e\u6613\u53d8\u5916\u90e8\u6761\u4ef6"],"missing_files":[]},"quality_url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/SHILIU-QUALITY.md","usage_guide":{"task":"\u628a\u4e00\u4e2a\u4e3b\u9898\u505a\u6210\u65e0\u4eba\u51fa\u955c\u7684\u65c1\u767d\u8bb2\u89e3\u89c6\u9891\uff0c\u5305\u542b\u53e3\u64ad\u7a3f\u3001\u914d\u97f3\u3001\u5b57\u5e55\u3001\u753b\u9762\u5408\u6210\u4e0e\u53ef\u9009\u80cc\u666f\u97f3\u4e50\u3002","inputs":"\u4e3b\u9898\u6216\u5df2\u6709\u53e3\u64ad\u7a3f\uff1bAzure Speech \u7684 key \u4e0e region\uff08\u73af\u5883\u53d8\u91cf\uff09\uff1bffmpeg\u3001Node 18+\uff1b\u4e00\u4e2a\u5df2\u5b8c\u6210 npm install \u7684 Remotion \u9879\u76ee\uff08\u9996\u6b21\u9700\u81ea\u884c\u642d\u5efa\uff09\uff1b\u53ef\u9009 Playwright \u6d4f\u89c8\u5668\u4f1a\u8bdd\u7528\u4e8e\u53d6 BGM\u3002","outputs":"videos/{name}/ \u4e0b\u7684 podcast.txt\u3001podcast_audio.wav\u3001podcast_audio.srt\u3001timing.json\u3001cues.json\u3001output.mp4\uff0c\u4ee5\u53ca\u6df7\u97f3\u540e\u7684 final_video.mp4\uff1b\u6309\u9700\u751f\u6210\u5c01\u9762\u9759\u5e27\u4e0e publish_info.md\u3002","example_input":"\u7528\u8fd9\u4e2a\u6280\u80fd\u505a\u4e00\u671f 3 \u5206\u949f\u89c6\u9891\uff0c\u4e3b\u9898\u662f\u300c\u4e3a\u4ec0\u4e48\u56e2\u961f\u5e94\u8be5\u628a\u5468\u62a5\u6539\u6210\u5f02\u6b65\u66f4\u65b0\u300d\uff0c\u5148\u53ea\u5199\u53e3\u64ad\u7a3f\u7ed9\u6211\u5ba1\u3002","example_output":"\u793a\u610f\uff1a\u5148\u4ea4\u4ed8 videos/async-weekly/podcast.txt\uff08\u5206 hero / content-1 / content-2 / outro \u56db\u6bb5\uff0c\u7eaf\u53e3\u8bed\u3001\u6570\u5b57\u7528\u963f\u62c9\u4f2f\u6570\u5b57\uff09\uff1b\u7528\u6237\u786e\u8ba4\u540e\u518d\u4ea7\u51fa wav\u3001srt\u3001timing.json \u4e0e output.mp4\uff0c\u5e76\u7528 ffprobe \u62a5\u544a\u97f3\u89c6\u9891\u65f6\u957f\u5dee\u3002\u4ee5\u4e0a\u4e3a\u7ed3\u6784\u793a\u610f\uff0c\u975e\u771f\u5b9e\u751f\u6210\u7ed3\u679c\u3002"},"read_url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c","skill_url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/SKILL.md","download_url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/download","install_url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/install","files":[{"path":"LICENSE","bytes":1069,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/LICENSE"},{"path":"SHILIU-QUALITY.md","bytes":2161,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/SHILIU-QUALITY.md"},{"path":"SKILL.md","bytes":18853,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/SKILL.md"},{"path":"references/pronunciation.md","bytes":3086,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/references/pronunciation.md"},{"path":"scripts/tts.py","bytes":27618,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/scripts/tts.py"},{"path":"shiliu-quality.json","bytes":2015,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/shiliu-quality.json"},{"path":"shiliu-source.json","bytes":293,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/shiliu-source.json"},{"path":"usage.json","bytes":1075,"url":"https://shiliuai.cn/api/v2/skills/agents365-ai-video-podcast-maker-lite-6c459c/files/usage.json"}],"content":"---\nname: video-podcast-maker-lite\ndescription: Minimal personal narrated-video pipeline \u2014 a topic becomes a talking-head-free explainer MP4 (1080p or 4K) via script \u2192 Azure TTS (SSML) \u2192 Remotion. Use when the user wants a quick narrated video from a topic without the full video-podcast-maker machinery (no extra skills, no thumbnails/shorts/publish matrix). Do NOT trigger for heavy production needs \u2014 use video-podcast-maker for those.\nargument-hint: \"[topic]\"\nauthor: Agents365-ai\ncategory: Content Creation\nversion: 1.1.0\n---\n\n# Video Podcast Maker Lite\n\nA single-purpose pipeline for personal use: **topic \u2192 narration script \u2192 Azure TTS (SSML) \u2192 Remotion \u2192 1080p/4K MP4**. No external skills, no config files, no bundled templates \u2014 the Remotion composition is generated once against the [contract below](#composition-contract), then reused across videos.\n\n## Prerequisites\n\n```bash\npip3 install azure-cognitiveservices-speech   # the only Python dependency\nexport AZURE_SPEECH_KEY=\"...\"      # Azure Speech resource key\nexport AZURE_SPEECH_REGION=\"...\"   # e.g. eastasia\n# ffmpeg + node 18+ required; one Remotion project with npm install done\n# Playwright MCP (session browser) needed only for Step 5 BGM fetching\n```\n\n## Project discovery (run before Step 1)\n\n1. **Locate the Remotion project** \u2014 the directory containing both `src/remotion/index.ts` and `node_modules/remotion/`. Check the current working directory first; if it is not there, ask the user for the project path. Do NOT scan the filesystem, and do NOT create a new project if one exists elsewhere.\n2. **Inventory existing videos** \u2014 `ls videos/` and `ls src/remotion/*Video.tsx`. The most recent `*Video.tsx` is the component to copy in Step 3; `videos/` shows which videos already exist.\n3. **Existing `videos/{name}/`?** Then this is an iteration on that video, not a new one \u2014 reuse the directory and re-run only what changed (see [Iterating](#iterating)).\n4. **No project anywhere** (first run only) \u2192 scaffold once in the working directory: `npm init -y && npm i remotion @remotion/cli @remotion/transitions react react-dom`, create `src/remotion/`, and warn the user about the one-time ~2 GB install. All later videos skip this.\n\n## Workflow (6 steps)\n\nRun all commands from the project root. All artifacts for one video live in `videos/{name}/` inside the project. `{name}` is lowercase English, hyphen-separated.\n\n### Step 1 \u2014 Write the script: `videos/{name}/podcast.txt`\n\nOne `[SECTION:xxx]` marker per video segment; section names are lowercase English (`hero`, `content-1`, `outro`). An optional display title goes after a `|` \u2014 `[SECTION:outro|thanks]` \u2014 it labels the progress-bar pill and default layout (without it, the label derives from the first sentence, which can be awkward). Lines starting with `#` are ignored (and may safely mention markers). Spoken text only \u2014 no markdown \u2014 and follow the [script style rules](#script-style-anti-ai-flavor-zh-cn) below. Example:\n\n```text\n# comment lines are not spoken\n[SECTION:hero|intro]\n\u6b22\u8fce\u6765\u5230\u672c\u671f\u89c6\u9891\uff01\u4eca\u5929\u6211\u4eec\u8981\u804a\u4e00\u4e2a\u5927\u5bb6\u90fd\u5173\u5fc3\u7684\u8bdd\u9898\u3002\n\n[SECTION:content-1|point 1]\n\u9996\u5148\uff0c\u6211\u4eec\u6765\u770b\u7b2c\u4e00\u4e2a\u8981\u70b9\u3002\u8fd9\u91cc\u6709\u51e0\u4e2a\u5173\u952e\u4fe1\u606f\u9700\u8981\u4f60\u77e5\u9053\u3002\n\n[SECTION:outro|thanks]\n\u597d\u4e86\uff0c\u4eca\u5929\u7684\u5185\u5bb9\u5c31\u5230\u8fd9\u91cc\u3002\u5982\u679c\u89c9\u5f97\u6709\u5e2e\u52a9\uff0c\u6b22\u8fce\u70b9\u8d5e\u5173\u6ce8\uff0c\u6211\u4eec\u4e0b\u671f\u518d\u89c1\uff01\n```\n\n### Script style (anti-AI-flavor, zh-CN)\n\nApply while writing, then self-check before TTS. Goal: everyday spoken Chinese, not written prose with commas. (Distilled from the full skill's `natural-narration.md` + `script-polish.md` \u2014 those are the canonical sources; edit rules there first, then mirror here.)\n\n**Connector swap** (written \u2192 spoken): \u6b64\u5916\u2192\u8fd8\u6709 \u00b7 \u7136\u800c\u2192\u4f46\u662f \u00b7 \u56e0\u6b64\u2192\u6240\u4ee5 \u00b7 \u4e0e\u6b64\u540c\u65f6\u2192\u8fd9\u65f6\u5019 \u00b7 \u603b\u7684\u6765\u8bf4/\u7efc\u4e0a\u6240\u8ff0\u2192\u5220\u6389 \u00b7 \u9996\u5148/\u5176\u6b21/\u6700\u540e\u2192\u76f4\u63a5\u8bb2\u4e0b\u4e00\u4ef6\u4e8b\u3002\n\n**Kill list** (rewrite or delete): \u8d4b\u80fd\u3001\u6253\u9020\u3001\u6df1\u5165\u63a2\u8ba8\u3001\u503c\u5f97\u4e00\u63d0/\u503c\u5f97\u6ce8\u610f\u7684\u662f\u3001\u4f17\u6240\u5468\u77e5\u3001\u81f3\u5173\u91cd\u8981\u3001\u9769\u547d\u6027\u3001\u98a0\u8986\u3001\u5929\u82b1\u677f\u3001\u65e0\u7f1d\u3001\u95ed\u73af\u3001\u6293\u624b\u3001\u91cc\u7a0b\u7891\u3001\u6807\u5fd7\u7740\u3001\u672a\u6765\u53ef\u671f\u3001\u8ba9\u6211\u4eec\u62ed\u76ee\u4ee5\u5f85\u3002\n\n**Structural tells** \u2014 the fix patterns:\n\n| Pattern | Fix |\n| ------ | ------ |\n| Verb-noun shells: \u8fdb\u884c\u4f18\u5316/\u5b9e\u73b0\u589e\u957f/\u505a\u51fa\u9009\u62e9 | concrete action + result: \"\u628a\u5ba1\u6279\u4ece\u4e09\u6b65\u6539\u6210\u4e00\u6b65\" |\n| Negation contrast: \u4e0d\u662f X,\u800c\u662f Y | state Y directly |\n| Three-part parallelism: \u65e2\u662f\u2026\u53c8\u662f\u2026\u66f4\u662f\u2026 | keep the most informative item; two beat three |\n| Empty intensifiers: \u663e\u8457/\u5927\u5e45/\u975e\u5e38 | a number or a perceivable consequence |\n| Vague attribution: \u4e1a\u5185\u666e\u904d\u8ba4\u4e3a/\u6709\u4e13\u5bb6\u6307\u51fa | named source + date, else delete the sentence |\n| Slogan endings: \u672a\u6765\u53ef\u671f/\u6ce8\u5165\u65b0\u7684\u6d3b\u529b | land on a concrete fact: number, date, next action |\n\n**Write for the ear**: one idea per sentence, subject first, vary sentence length, no nested clauses, no \u2014 or \u00b7 as connectives (they don't get spoken and clutter subtitles). A light first person is fine (\"\u6211\u5b9e\u6d4b\u4e0b\u6765\").\n\n**Subtitles are the script, verbatim** \u2014 so write numbers the way they should LOOK on screen: Arabic digits (`3.8`, `63K`, `98.8%`, `128G`), never Chinese numerals (`\u516d\u5341\u4e94\u70b9\u4e00` / `\u4e09\u767e\u4e5d\u5341\u4e03`). The same digit rule applies to on-screen text in the Remotion components (cards, headlines). Do NOT write the spoken form into the script to fix pronunciation \u2014 it leaks into subtitles. `tts.py` derives the spoken layer itself: every number-bearing token (`86.1`, `9B`, `5600`, `Qwen3.5`) is converted to its Chinese reading (`\u516b\u5341\u516d\u70b9\u4e00`, `\u4e5dB`, `\u4e94\u5343\u516d\u767e`, `\u5343\u95ee\u4e09\u70b9\u4e94`) before synthesis, then word boundaries are mapped back so subtitles keep the display text. (Multilingual voices like `zh-CN-XiaoxiaoMultilingualNeural` read bare digits in English in mixed context \u2014 that is exactly what this layer prevents. SSML `<sub alias>` was tried and abandoned: Azure's word-boundary events for `<sub>` are buggy and corrupt the SRT.)\n\n**Numbers must be traceable** \u2014 a precise number without a source is fabricated; drop it or attribute it.\n\n**Self-check before Step 2**: no kill-list words? no \"\u4e0d\u662f\u2026\u800c\u662f\u2026\"? no slogan ending? Read each section aloud \u2014 if you stumble, split the sentence.\n\n**STOP \u2014 script review gate (mandatory)**: when `podcast.txt` is written, halt the pipeline and hand the script to the user for review. Do NOT run TTS (Step 2) until the user explicitly approves the script. This gate comes before everything else downstream \u2014 audio, timings, and visual entrances all derive from the script, so a late script change costs a full re-run.\n\n### Step 2 \u2014 TTS\n\n```bash\npython3 \"${SKILL_DIR}/scripts/tts.py\" videos/{name}/podcast.txt videos/{name}/\n```\n\nRun `--check` first (lint-only, no synthesis, free) to catch polyphone and alias gaps before paying for synthesis.\n\nProduces `podcast_audio.wav` + `podcast_audio.srt` + `timing.json` + `cues.json` (per-cue text, global frame, and `section_frame` \u2014 use it to align visual entrances in the component instead of hand-parsing the srt). Each section is synthesized separately via SSML and concatenated, so section timings are exact by construction. Subtitle cues are phrase-first: a sentence within 30 visible chars is shown whole; longer sentences are packed from comma/semicolon clauses (~22 per cue); an over-long clause is cut at the word boundary nearest its middle, never mid-word; tiny trailing cues merge into the previous one **within the same section only** (a short first sentence of the next section must never bleed into the previous section's last cue). Re-running over an existing `timing.json` prints a per-section duration diff; any section that moved >0.3s means the component's hardcoded entrance delays need re-aligning from the new `cues.json`.\n\nKnobs: `--voice` (default `zh-CN-XiaoxiaoNeural`), `--style` (`mstts:express-as`, e.g. `gentle` / `cheerful` \u2014 stick to these two; others can sound strained), `--rate` (prosody, e.g. `-4%`), `--phonemes` (whole-word pinyin dict for polyphones like \u547d\u4ee4\u884c/\u540c\u884c; by default `~/.video-podcast-maker/phonemes.json` and `phonemes.json` next to the input are merged \u2014 per-video entries win), `--aliases` (pronunciation aliases display\u2192spoken, e.g. `\"Ornith-1.5\": \"Ornith \u4e00\u70b9\u4e94\"`; same merge order with `aliases.json`). Env fallbacks: `TTS_VOICE` / `TTS_STYLE` / `TTS_RATE`. For a consistent channel voice across videos, set them once in your shell profile (e.g. `TTS_VOICE=zh-CN-XiaoxiaoMultilingualNeural TTS_RATE=+5%`) instead of passing flags every time.\n\n**Pronunciation**: polyphone pre-flight, known misreading shapes, alias-dict mechanics, and built-in spoken-layer behaviors are documented in [`references/pronunciation.md`](references/pronunciation.md) \u2014 read it before the first TTS run of a new topic.\n\n### Step 3 \u2014 Compose visuals\n\n- **First video ever**: generate the composition (`index.ts` + `Root.tsx` + `Video.tsx` under `src/remotion/`) against the [composition contract](#composition-contract).\n- **Every later video**: copy the previous video's component and edit \u2014 never start from the contract again.\n\nOne Remotion project hosts all videos; a new video adds exactly one component file and one `<Composition>` registration:\n\n```text\nproject-root/                    # ONE project, npm install once\n\u251c\u2500\u2500 src/remotion/\n\u2502   \u251c\u2500\u2500 index.ts                 # registerRoot \u2014 shared, never changes\n\u2502   \u251c\u2500\u2500 Root.tsx                 # one <Composition id=\"\u2026\"> per video\n\u2502   \u251c\u2500\u2500 DemoVideo.tsx            # per-video component (copy of the last one, edited)\n\u2502   \u2514\u2500\u2500 NextTopicVideo.tsx\n\u2514\u2500\u2500 videos/{name}/               # per-video artifacts: podcast.txt, wav, srt, timing.json, mp4\n```\n\nPer video: pick a unique PascalCase component/composition id (e.g. `ReferenceManagerComparison`), set title / colors, and give each section name a layout (a `switch (section.name)` over `hero` / `content-N` / `outro` works well).\n\n**Visual richness (default style \u2014 plain text-in-a-box is not the target look)**: every info card, flow box, pill and stat tile carries ONE leading emoji (or an `@lobehub/icons` brand component when it depicts a real product); at most one per element, and never inside a MONO value line (emoji break monospace alignment \u2014 put it on the label/title instead). Keep the mapping one concept = one emoji consistent across the whole video and pick it tastefully for the topic (dates/parameters/speed/cost each get an obvious match; the agent chooses). Every section also gets at least one visual anchor (an illustration): official material first (product banner, spec card, screenshot), else a free illustration or icon set (unDraw SVG, Pixabay/Pexels images, OpenMoji / Microsoft Fluent Emoji / Google Noto Emoji, `@lobehub/icons` for brand logos) \u2014 note the source + license in the publish_info asset-sources section (attribution-required sets like Flaticon's free tier must be credited in the video description). Emoji decorate the UI cards, illustrations anchor the section; neither replaces the other.\n\n### Step 4 \u2014 Preview (mandatory human gate)\n\n```bash\nnpx remotion studio src/remotion/index.ts --public-dir videos/{name}/\n```\n\nMUST launch Studio and wait for the user to review in person. NEVER render until the user explicitly confirms (\"\u6e32\u67d3\" / \"render\"). An adjustment request is not confirmation \u2014 apply the change, let Studio hot-reload, and ask again. Every round of visual changes needs its own fresh confirmation; confirmation never carries over to Step 5.\n\n### Step 5 \u2014 Render + BGM\n\nRender:\n\n```bash\nnpx remotion render src/remotion/index.ts MyVideo videos/{name}/output.mp4 --public-dir videos/{name}/\n```\n\n**BGM (default; skip only if the user says no music)**: fetch one **random track from Pixabay Music** and mix it at low volume (narration stays dominant). Pixabay License: royalty-free, commercial use OK, no attribution required \u2014 still log title/author into the publish_info asset-sources section.\n\nHow to fetch (verified 2026-08-25): Pixabay's search/list pages are client-rendered and Cloudflare-gated for non-browser clients, but a single-track page opened in a real browser embeds the full-track download URL in its JSON-LD, and that `cdn.pixabay.com` URL then downloads fine with plain curl.\n\n1. With the session's browser (Playwright MCP), open a search page \u2014 `https://pixabay.com/music/search/cinematic/` (or `relaxing` / `ambient` if a calmer bed is wanted).\n2. Collect result links matching `/music/<slug>-<id>/` (exclude `/music/search/` and locale-prefixed ones like `/de/music/...`), pick one at random, open it.\n3. Read the track's JSON-LD (`script[type=application/ld+json\"]` \u2192 the `AudioObject`): `contentUrl` (full track MP3), `name`, `creator.name`, `duration`. Prefer ~1.5\u20138 min; if out of range, open another link. If the page's JSON-LD has no `contentUrl` (layout changed), stop retrying \u2014 ask the user to pick a track and provide the download URL.\n4. Download with curl (browser UA + `Referer: https://pixabay.com/` \u2014 verified to work):\n\n   ```bash\n   curl -sL -A \"<browser UA>\" -H \"Referer: https://pixabay.com/\" \"<contentUrl>\" -o videos/{name}/bgm.mp3\n   ```\n\nMix (bgm low; the loudnorm stage is required \u2014 without it the mix lands \u2248 -32 dB mean, ~10 dB too quiet):\n\n```bash\nffmpeg -y -i videos/{name}/output.mp4 -i videos/{name}/bgm.mp3 -filter_complex \"[1:a]volume=0.08[bg];[0:a][bg]amix=inputs=2:duration=first[a];[a]loudnorm=I=-16:TP=-1.5:LRA=11[out]\" -map 0:v -map \"[out]\" -c:v copy -shortest videos/{name}/final_video.mp4\n```\n\nStop the Studio server once the render is confirmed \u2014 a leftover Studio holds its port and keeps watching files.\n\n### Step 6 \u2014 Delivery extras (only when the user's pipeline includes them)\n\nRendered after Step 5, in this order:\n\n- **Cover stills**: 16x9 + 4x3 Thumbnail stills via `npx remotion still` \u2014 delete the old PNG first, stills skip existing files.\n- **BGM loudness check**: `ffmpeg -i final_video.mp4 -af volumedetect -f null -`, mean \u2248 -19 to -22 dB, max \u2248 -1.5 dB.\n- **`publish_info.md`**: title / description / tags / chapter timestamps (chapters derive from `timing.json`).\n- **`assets/manifest.json`**: asset provenance.\n- **Repo-level bookkeeping**: index/theme generators (e.g. a `VIDEO_INDEX.md` builder) typically key off `publish_info.md` titles, and classification scripts may refuse to run until the new video dir is added to their assignment map \u2014 run them after the publishing artifacts are in place.\n\n## Composition contract\n\nThe composition consumes three files from `--public-dir videos/{name}/` via `staticFile()`: `podcast_audio.wav`, `podcast_audio.srt`, and `timing.json`:\n\n```json\n{\n  \"total_duration\": 32.1,\n  \"fps\": 30,\n  \"total_frames\": 964,\n  \"sections\": [{ \"name\": \"hero\", \"label\": \"\u2026\", \"start_time\": 0, \"duration\": 11.8, \"start_frame\": 0, \"duration_frames\": 355 }]\n}\n```\n\nNon-negotiables when generating a composition (each one is a real failure mode if missed):\n\n1. **Audio is the master clock** \u2014 `calculateMetadata` returns `durationInFrames = timing.total_frames`, loaded at runtime. Never hardcode a duration. Resolution per project convention: 1920\u00d71080 @ 30fps, or 4K (3840\u00d72160) via a wrapper that scales a 1920\u00d71080 design \u00d72 (keep the inner design at 1080p coordinates).\n2. **Async assets gate rendering** \u2014 load `timing.json`/SRT with `fetch(staticFile(...))` wrapped in `delayRender`/`continueRender`, or the first frames render without data/subtitles. When copying an existing component that instead bundles `timing.json` via a direct `import`, that convention is equally valid (the JSON is inlined at bundle time) \u2014 follow the copied component.\n3. **Compensate TransitionSeries overlap** \u2014 rendered length is `sum(sections) \u2212 (N\u22121)\u00d7transitionFrames`. Scale every section proportionally so the total lands exactly on `total_frames` (absorb rounding on the last section); do not pad the first section.\n4. **Subtitles** \u2014 parse the SRT, show the cue active at the current frame, positioned bottom-center *above the progress bar* (bottom \u2248 70px). Body text \u2265 32px, titles \u2265 64px.\n5. **Fail loud** \u2014 `cancelRender` with the real error if `timing.json` fails to load; a silent hang costs a render-timeout to diagnose.\n6. **Props type must be a `type` alias, not an `interface`** \u2014 Remotion constrains props to `Record<string, unknown>`, which interfaces fail (no implicit index signature). `type VideoProps = { ... }` passes.\n7. **Chapter progress bar** \u2014 pinned to the very bottom (height ~55px at 1080p): one pill per section with `flex` proportional to `duration_frames` and the section `label` as text (~24px); the active pill is filled with `primaryColor` and gets a translucent white overlay whose width is the intra-chapter progress, past pills gray, future pills outlined; plus a ~3px global progress line along the bottom edge. Section layouts keep the bottom ~200px clear in total (bar + subtitle zone).\n\n## Iterating\n\n- Script changed \u2192 re-run Step 2 (timestamps all shift \u2014 never hand-edit `timing.json`), then re-render.\n- Audio re-synthesized (same script, different voice/rate) \u2192 re-run Step 2, then visuals may stay if timings didn't shift; re-enter the Step 4 gate before rendering.\n- Visuals only \u2192 edit the video's component, re-render (audio untouched). This re-enters the Step 4 gate: fresh in-person confirmation required before rendering.\n- Reuse the same `videos/{name}/` directory; never start a new project per video.\n\n## Rules\n\n1. **Studio before render.** Never render without a fresh, explicit in-person confirmation in the current Studio session (see Step 4).\n2. After rendering, `output.mp4` duration must match `podcast_audio.wav` within \u00b10.5s (`ffprobe` both). If not, the composition contract (items 1/3) is violated \u2014 fix the composition, not the timing file.\n3. **Always `--public-dir videos/{name}/`** on every Remotion command \u2014 it is how the composition finds `timing.json`, the WAV, and the SRT.\n4. One Remotion project for all videos (see the layout in Step 3); only `videos/{name}/` and the per-video component change.\n\n## Troubleshooting\n\n- `Azure Speech SDK is not installed` \u2192 `pip3 install azure-cognitiveservices-speech`.\n- `Set AZURE_SPEECH_KEY and AZURE_SPEECH_REGION first` \u2192 export both env vars (see Prerequisites).\n- TTS network/auth failure \u2192 the script retries once per section; persistent `CancellationReason.Error` usually means a bad key/region or an unsupported voice/style combo.\n- `ffmpeg: command not found` \u2192 `brew install ffmpeg`.\n- Video/audio duration mismatch > 0.5s \u2192 re-run Step 2; if it persists, the composition is violating contract item 1 or 3.\n- `timing.json` not found in Studio/render \u2192 missing `--public-dir videos/{name}/`.\n","usage":"\u8bfb\u53d6 SKILL.md\uff0c\u5e76\u6309\u9700\u8bfb\u53d6\u914d\u5957\u6587\u4ef6\uff1b\u4e0b\u8f7d ZIP \u83b7\u53d6\u5b8c\u6574\u5305\u3002\u6280\u80fd\u662f\u4efb\u52a1\u65b9\u6cd5\uff0c\u4e0d\u6388\u4e88\u65b0\u589e\u8d26\u6237\u3001\u4ed8\u8d39\u6216\u7cfb\u7edf\u6743\u9650\u3002"}
