Image + audio video compositing tool.
⛔⛔⛔ Highest iron rule: audio-visual sync (violation means scrap the video and redo it!)
Every time a video is generated, duration must be precisely calculated from the timestamps in narration.json; it is absolutely forbidden to fill it in manually by feel!
Mandatory workflow (not a single step may be missing)
1. TTS 生成配音 → 得到 narration.mp3 + narration.json(时间戳)
2. 读取 narration.json,逐句分析内容语义
3. 确定每张图对应哪些句子(按内容语义匹配,不是平均分!)
4. 每张图的 duration = 对应最后一句的 end - 对应第一句的 start
5. 校验:所有 duration 之和 ≈ 音频总时长(误差 < 1s)
6. ⛔ 运行 verify_alignment.py 校验(必须通过才能合成!)
7. 校验通过才能写 video_config.yaml,否则停止!
Example (using classical poetry teaching as an example)
narration.json 时间戳:
句0 [0.1-2.6] "静夜思,唐,李白。"
句1 [2.6-5.7] "床前明月光,疑是地上霜。"
句2 [5.7-8.6] "举头望明月,低头思故乡。"
句3 [8.6-16.4] "这首诗是唐代大诗人李白..."
句4 [16.4-20.9] "短短二十个字..."
句5 [20.9-22.6] "床前明月光。"
...
图片分配:
01_title.png → 句0-2(全诗朗诵)→ duration = 8.6 - 0.1 = 8.5s
02_poet.png → 句3-4(诗人介绍)→ duration = 20.9 - 8.6 = 12.3s
03_moonlight.png → 句5-8(第一句解读)→ duration = 38.6 - 20.9 = 17.7s
...
校验:8.5 + 12.3 + 17.7 + ... = 114.4s ≈ 音频 114.5s ✅
Forbidden actions
- ❌ Assigning 10s, 15s, 20s to each image by feel
- ❌ Equal distribution (total duration / number of images)
- ❌ Writing duration without reading narration.json
- ❌ Forcing compositing when the total image duration differs from the audio by more than 5 seconds
- ❌ Letting video_maker.py auto-stretch by more than 1 second
- ❌ duration: auto (already completely removed from the code!)
Image generation iron rules (violation means redo)
0. Default size (most important!)
Default ratio: 16:9 (1920×1080 landscape)
Unless in the following cases, always use 16:9:
- The user explicitly specifies another ratio
- When coordinating with other workflows, the other workflow has explicit requirements (e.g., Xiaohongshu requires 3:4, Douyin requires 9:16)
# ✅ 默认:不指定 -r 参数,或明确写 -r 16:9
python text_to_image.py "提示词" -o output.png
python text_to_image.py "提示词" -r 16:9 -o output.png
# ❌ 禁止:随意切换比例,一会儿竖版一会儿横版
# 同一个视频项目,所有图片必须是同一比例!
1. Image density requirements
| Video duration | Minimum number of images | Duration per image |
|---|---|---|
| 30 seconds | 8 images | 3-4 seconds |
| 60 seconds | 15 images | 3-5 seconds |
| 90 seconds | 22 images | 3-5 seconds |
| 120 seconds | 30 images | 3-5 seconds |
Iron rule: each image lasts at most 7 seconds; if exceeded, it must be split!
Calculation formula:图片数量 = ceil(音频时长 / 4)
2. Language requirements for image generation prompts
All image generation prompts must be written in Chinese! English prompts are forbidden!
# ❌ 错误:用英文写 prompt
python text_to_image.py "modern tech illustration, AI robot, blue gradient background"
# ❌ 错误:英文 prompt 里夹中文
python text_to_image.py "tech style, Chinese text '对决', blue theme"
# ✅ 正确:纯中文 prompt
python text_to_image.py "现代科技插画风格,可爱AI机器人坐在电脑前,蓝紫色渐变背景,霓虹灯光效,多个悬浮的全息UI面板,信息密度高,专业信息图风格"
Iron rules:
- prompts must be purely in Chinese
- If there is text in the generated image, it must also be in Chinese
- No English may appear
3. Information density requirements
Information density = many text points + rich visual elements
Text content must be rich:
# ❌ 错误:文字太少
图片里只写 "AI对比"
# ✅ 正确:文字要点多
图片里包含:
- 标题:QoderWork vs OpenClaw
- 副标题:桌面AI助手对比
- 要点1:开箱即用 vs 自由定制
- 要点2:$19/月 vs 免费开源
- 要点3:普通用户 vs 技术极客
Visual elements must be rich:
# ❌ 错误:太空洞
只有文字,没有图标、图表、装饰
# ✅ 正确:可视化丰富
- 配合图标(对勾、叉、箭头、星星)
- 配合图表(柱状图、饼图、对比条)
- 配合插画(机器人、电脑、用户形象)
- 配合装饰(光效、渐变、边框)
Information density principles:
- Each image must have a clear text title and key points
- The text content must correspond to the voiceover content
- More importantlyvisualize: icons, charts, illustrations, decorations
- Pure text images are forbidden, and pure decorative images are also forbidden
4. Image generation descriptions must be detailed and specific
Each image must have rich, specific visual elements; vague and empty descriptions are forbidden!
# ❌ 错误:太笼统
"一个机器人"
"科技风格的图"
"对比图"
# ✅ 正确:细致具体
"可爱的蓝色AI机器人吉祥物,圆润金属质感,坐在现代简约办公桌前,
桌上有27寸曲面显示器显示代码,旁边放着咖啡杯和多肉植物,
机器人头顶悬浮三个全息面板分别显示折线图、饼图、进度条,
深蓝色科技感背景,地面有蓝色光带,整体赛博朋克风格,柔和的体积光"
Prompts must have 6 essential elements(none may be missing):
| Element | Description | Example |
|---|---|---|
| Subject | Who/what it is, must be specific | "A blue, rounded, metallic-textured AI robot" rather than "robot" |
| Action | What is it doing, what is its posture | "Both hands on the keyboard typing, head slightly tilted" |
| Environment | Where is it, what is the background | "Modern minimalist office, city night view outside the floor-to-ceiling window" |
| Details | What objects/elements are around it | "On the desk there is a coffee cup, a succulent plant, and sticky notes" |
| Style | What art style/lighting effect | "Cyberpunk style, neon lighting, volumetric light effect" |
| Color | What is the main color tone | "Blue-purple gradient as the main color tone, with orange accents" |
5. Visual style consistency
All images in the same video must maintain a consistent style:
- Use the same style prefix
- Use the same color tone
- Use the same aspect ratio (mixing landscape and portrait is prohibited!)
# 定义统一风格前缀(中文!)
STYLE="现代科技插画风格,干净矢量设计,蓝紫渐变配色,专业信息图美感,高信息密度,霓虹发光效果,深色背景"
# 所有图片都用这个前缀 + 相同比例
python text_to_image.py "$STYLE,[具体场景内容]" -r 16:9 -o scene01.png
python text_to_image.py "$STYLE,[具体场景内容]" -r 16:9 -o scene02.png
6. Aspect ratio selection guide
| Scenario | Aspect ratio | Description |
|---|---|---|
| Default/general | 16:9 | Bilibili, YouTube, WeChat Official Account videos, PPT illustrations |
| Douyin/WeChat Channels/Kuaishou | 9:16 | Vertical short video platforms, requires user specification or process requirements |
| Xiaohongshu | 3:4 | Xiaohongshu note illustrations, requires user specification or process requirements |
| WeChat Moments | 1:1 | Square, requires user specification |
Iron rule: When unsure which aspect ratio to use, always use 16:9
Core process (iron rule)
Story-based video generation process (nested process)
When the user provides a story/plot/script,the following nested process must be strictly followed:
┌─────────────────────────────────────────────────────────────┐
│ 第一层:故事 → 拆分场景 → 并发生成场景主图(文生图) │
│ │
│ 大闹天宫 → 场景1:弼马温受辱 │
│ 场景2:筋斗云回花果山 │
│ 场景3:玉帝派兵 │
│ ... │
│ → 并发调用 text_to_image.py 生成每个场景主图 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第二层:每个场景主图 → 图生图拆出细镜头(保持角色一致) │
│ │
│ 场景1主图 → 细镜头1:悟空看官印疑惑 │
│ 细镜头2:悟空踢翻马槽 │
│ 场景2主图 → 细镜头1:踏筋斗云腾空 │
│ 细镜头2:花果山自封大圣 │
│ → 并发调用 image_to_image.py,以主图为参考 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 第三层:生成配音 + 字幕 + 校验 + 合成视频 │
│ │
│ 1. tts_generator.py 生成配音 + 时间戳 │
│ 2. 【铁律】根据时间戳精确计算每张图的duration │
│ 3. 生成 SRT 字幕 │
│ 4. 生成 video_config.yaml │
│ 5. ⛔ verify_alignment.py 校验(必须通过!) │
│ 6. video_maker.py 合成: │
│ → 图片合成(带转场) │
│ → 合并音频 │
│ → 烧录字幕(ASS格式,底部居中固定) │
│ → 自动拼接片尾(二维码+"点关注不迷路") │
│ → 添加BGM │
└─────────────────────────────────────────────────────────────┘
**铁律:所有视频必须自动拼接片尾!**
Directory structure specification
assets/generated/{project_name}/
├── scene1/
│ ├── main.png # 场景1主图(文生图)
│ ├── shot_01.png # 细镜头1(图生图)
│ └── shot_02.png # 细镜头2(图生图)
├── scene2/
│ ├── main.png
│ ├── shot_01.png
│ └── shot_02.png
├── ...
├── narration.mp3 # 配音
├── narration.json # 时间戳
├── subtitles.srt # 字幕
├── video_config.yaml # 视频配置
└── {project_name}.mp4 # 最终视频
Example execution command
# 第一层:并发生成场景主图(默认 16:9)
python .opencode/skills/image-service/scripts/text_to_image.py "风格描述,场景1内容" -r 16:9 -o scene1/main.png &
python .opencode/skills/image-service/scripts/text_to_image.py "风格描述,场景2内容" -r 16:9 -o scene2/main.png &
wait
# 第二层:并发图生图生成细镜头(保持相同比例)
python .opencode/skills/image-service/scripts/image_to_image.py scene1/main.png "保持角色风格,细镜头描述" -r 16:9 -o scene1/shot_01.png &
python .opencode/skills/image-service/scripts/image_to_image.py scene1/main.png "保持角色风格,细镜头描述" -r 16:9 -o scene1/shot_02.png &
wait
# 第三层:生成配音+校验+合成视频
python .opencode/skills/video-creator/scripts/tts_generator.py --text "完整旁白" --output narration.mp3 --timestamps
# ⛔ 强制校验(必须通过才能合成!)
python .opencode/skills/video-creator/scripts/verify_alignment.py video_config.yaml
# 校验通过后才能合成
python .opencode/skills/video-creator/scripts/video_maker.py video_config.yaml --srt subtitles.srt --bgm epic
Video configuration file format
# video_config.yaml
ratio: "16:9" # 默认横版!必须加引号避免YAML解析错误
bgm_volume: 0.12
outro: true
scenes:
- audio: narration.mp3
images:
# 按场景顺序排列所有细镜头
- file: scene1/shot_01.png
duration: 4.34
- file: scene1/shot_02.png
duration: 4.88
- file: scene2/shot_01.png
duration: 2.15
# ...
Note:ratio must be wrapped in quotation marks, such as "16:9", otherwise YAML will parse it as a time format.
Duration allocation specification (iron rule!)
Before generating video_config.yaml, duration must be calculated strictly according to the following process:
Step 1: Read the timestamp file
import json
with open("narration.json", "r") as f:
timestamps = json.load(f)
audio_duration = timestamps[-1]["end"]
print(f"音频总时长: {audio_duration:.1f}s")
Step 2: Divide scenes by content semantics
Based on the narration content, determine the time period corresponding to each image:
# 示例:根据解说词内容划分
# 找到每个主题切换点的时间戳
scenes = [
("cover.png", 0, 12.5), # 开场到第一个主题切换
("scene01.png", 12.5, 26), # 第二段内容
# ...根据 narration.json 中的句子边界精确划分
]
Step 3: Calculate the duration of each image
for file, start, end in scenes:
duration = end - start
print(f"{file}: {duration:.1f}s")
Step 4: Verify the total duration
total_duration = sum(duration for _, _, duration in scenes)
assert abs(total_duration - audio_duration) < 1.0, \
f"时长不匹配!图片总时长{total_duration}s vs 音频{audio_duration}s"
Iron rule
- narration.json timestamps must be read first, do not estimate by feeling
- Divide by sentence semantic boundaries, do not distribute evenly
- Verify before generating the configuration, ensure the total image duration = total audio duration (error < 0.5 seconds)
- Do not let the script automatically stretch, videos with out-of-sync audio and visuals are unqualified
- duration=0 is prohibited, each image must be at least 1 second
- After generating the yaml, it must be verified with verify_alignment.py before compositing
Verification script (mandatory execution! Must pass before compositing!)
# ⛔ 合成视频前必须先运行校验脚本,不通过禁止合成!
python .opencode/skills/video-creator/scripts/verify_alignment.py video_config.yaml
# 校验内容:
# 1. 所有图片文件是否存在
# 2. duration 是否为精确数值(非数字直接拒绝!)
# 3. 图片总时长 vs 音频总时长(误差 < 1s)
# 4. 每张图时长是否在合理范围(1-7s)
# 5. 图片文件名关键词 vs 语音内容关键词 语义交叉比对
# 6. 输出完整对照表(图片 + 时长 + 语义✅/❌ + 对应语音文字)
# 退出码 0 = 通过,1 = 失败
# 失败时禁止合成,必须修复后重新校验!
Note: video_maker.py also has a built-in hard validation—duration must be an exact positive floating-point number; if it is missing or non-numeric, execution is directly rejected and exit(1) is called! duration: auto has been completely removed!
Duration allocation table template
Before generating the configuration, first output the allocation table for confirmation:
| 场景图 | 对应内容 | 开始 | 结束 | 时长 |
|--------|----------|------|------|------|
| cover.png | 开场引入 | 0s | 12.5s | 12.5s |
| scene01.png | AI Agent时代 | 12.5s | 26s | 13.5s |
| ... | ... | ... | ... | ... |
| **合计** | | | | **{total}s** |
音频总时长:{audio_duration}s
差值:{diff}s ✅/❌
Subtitle specifications
Subtitles use the ASS format,forced to a fixed position at bottom center:
- Position: bottom center (Alignment=2)
- Font: PingFang SC
- Size: screen height / 40
- Outline: 2px black outline + 1px shadow
- Bottom margin: screen height / 20
Prohibited: subtitles jumping around, inconsistent sizes, unfixed positions
Script parameter description
video_maker.py
python video_maker.py config.yaml [options]
| Parameter | Description | Default value |
|---|---|---|
--no-outro |
Do not add an ending | Add |
--no-bgm |
Do not add BGM | Add |
--fade |
Transition duration (seconds) | 0.5 |
--bgm-volume |
BGM volume | 0.08 |
--bgm |
Custom BGM (optional: epic) | Default tech style |
--ratio |
Video aspect ratio | 16:9 (will be overridden by the configuration file) |
--srt |
Subtitle file path | None |
verify_alignment.py
python verify_alignment.py video_config.yaml
| Validation item | Description |
|---|---|
| Image existence | All image files must exist |
| duration precision | Must be a positive floating-point number; auto/empty/non-numeric are prohibited |
| Total duration match | Total image duration vs total audio duration, error < 1s |
| Single-image duration range | Each image 1-7 seconds; exceeding the range triggers a warning |
| Semantic cross-comparison | Summary section image filename keywords vs speech content keywords match |
tts_generator.py
python tts_generator.py --text "文本" --output audio.mp3 [options]
| Parameter | Description | Default value |
|---|---|---|
--voice |
Voice | zh-CN-YunxiNeural |
--rate |
Speech rate | +0% |
--timestamps |
Output timestamp JSON | No |
Supported video aspect ratios
Consistent with the image-service image generation service, supporting 10 aspect ratios:
| Aspect ratio | Resolution | Use case |
|---|---|---|
| 1:1 | 1024×1024 | Square, WeChat Moments |
| 2:3 | 832×1248 | Vertical poster |
| 3:2 | 1248×832 | Horizontal poster |
| 3:4 | 1080×1440 | Xiaohongshu, WeChat Moments |
| 4:3 | 1440×1080 | Traditional monitor |
| 4:5 | 864×1080 | |
| 5:4 | 1080×864 | Horizontal photo |
| 9:16 | 1080×1920 | Douyin, WeChat Channels, vertical screen |
| 16:9 | 1920×1080 | Bilibili, YouTube, horizontal screen |
| 21:9 | 1536×672 | Ultra-wide screen movie |
Ending specifications
Iron rule: all videos must automatically stitch an ending of the corresponding size!
Ending matching order:
- Exact match:
outro_{ratio}.mp4 - Orientation match: vertical →
outro_9x16.mp4, horizontal →outro_16x9.mp4 - Fallback:
outro.mp4
BGM resources
Classified by style
| Style | File | Shortcut parameter | Use case |
|---|---|---|---|
| Ancient style/Chinese style | bgm_ancient_tale.mp3 |
ancient |
Water Margin, Three Kingdoms, historical stories |
bgm_asian_drums.mp3 |
asian |
Wuxia, action, combat | |
bgm_meditation.mp3 |
meditation |
Zen, meditation, traditional Chinese studies | |
bgm_garden.mp3 |
garden |
Pastoral, leisurely, scenery | |
| Healing/relaxing | bgm_carefree.mp3 |
carefree |
Vlog, daily, life |
bgm_dreamy.mp3 |
dreamy |
Dreamy, memories, warm | |
bgm_happybee.mp3 |
happybee |
Cheerful, bright, sunny | |
| Passionate/epic | bgm_epic.mp3 |
epic |
Inspirational, combat, high-energy |
bgm_heroic.mp3 |
heroic |
Hero, victory, glory | |
bgm_crusade.mp3 |
crusade |
War, epic, grand | |
bgm_allthis.mp3 |
allthis |
Cinematic, narrative, emotional | |
| Technology/future | bgm_technology.mp3 |
tech |
AI, product, tutorial |
bgm_digital.mp3 |
digital |
Digital, network, internet | |
bgm_chasm.mp3 |
chasm |
Sci-fi, space, mysterious | |
| Suspense/tense | bgm_anxiety.mp3 |
anxiety |
Mystery, tense, crisis |
bgm_darkfog.mp3 |
darkfog |
Horror, dark, suspense | |
| Cheerful/lively | bgm_funky.mp3 |
funky |
Funny, relaxed, rhythmic |
bgm_happyboy.mp3 |
happyboy |
Cute, children, animation | |
bgm_doh.mp3 |
doh |
Playful, fun, short video | |
| Electronic/rhythmic | bgm_edm.mp3 |
edm |
Dynamic, editing, beat-synced |
bgm_electro.mp3 |
electro |
Electronic, modern, trendy | |
bgm_bitshift.mp3 |
bitshift |
Game, 8-bit, retro electronic | |
bgm_hiphop.mp3 |
hiphop |
Rap, street, cool |
Usage
# 方式1:快捷参数(推荐)
python video_maker.py config.yaml --bgm epic
python video_maker.py config.yaml --bgm ancient
python video_maker.py config.yaml --bgm edm
# 方式2:完整文件名
python video_maker.py config.yaml --bgm bgm_ancient_tale.mp3
# 方式3:自定义路径
python video_maker.py config.yaml --bgm /path/to/custom.mp3
Common timbres
| Timbre ID | Style |
|---|---|
| zh-CN-YunyangNeural | Male voice, news broadcast |
| zh-CN-YunxiNeural | Male voice, sunny and lively |
| zh-CN-XiaoxiaoNeural | Female voice, warm and natural |
| zh-CN-XiaoyiNeural | Female voice, lively and cute |
Directory structure
video-creator/
├── SKILL.md
├── scripts/
│ ├── video_maker.py # 主脚本:图片+音频→视频(内置duration硬卡)
│ ├── verify_alignment.py # 合成前强制校验(时长+语义交叉比对)
│ ├── tts_generator.py # TTS 语音生成
│ └── scene_splitter.py # 场景拆分器(可选)
├── assets/
│ ├── outro.mp4 # 通用片尾(16:9)
│ ├── outro_9x16.mp4 # 竖版片尾
│ ├── outro_3x4.mp4 # 3:4片尾
│ └── bgm_*.mp3 # 22首BGM(详见BGM资源表)
└── references/
└── edge_tts_voices.md
Dependencies
# 系统依赖
brew install ffmpeg # Mac
# Python 依赖
pip install edge-tts pyyaml