Create professional-grade videos, integratingreal web images(Google Images scraping), perfect audio synchronization, rich content display, and beautiful visual design. Uses Remotion + TTS + multi-source image search to generate video content with a top-tier major-company style.
🆕 Multi-Source Image Search (v2.0)
This tool now integrates Google Images web scraping, allowing you to obtain a large number of high-quality real images without an Unsplash API Key:
🥇 Python/Google Images → 🥈 Bing Images → 🥉 Unsplash → 🎨 渐变占位图
| Tier | Source | Description | Requires configuration |
|---|---|---|---|
| 1 | Google Images | Python + Playwright scrapes high-resolution original images | No API Key required |
| 2 | Bing Images | HTTP parsing, fast fallback | No API Key required |
| 3 | Unsplash | High-quality photographic images | Requires UNSPLASH_ACCESS_KEY |
| 4 | Placeholder image | Automatically generates gradient backgrounds | No configuration required |
Image search uses
# 一键搜索所有场景图片
node scripts/search_images.js scenes.json --output ./public/images
# 输出示例:
# ✅ 总计: 4 个场景
# Google: 3 个 ← 真实网页图片
# Bing: 1 个 ← 真实网页图片
# Unsplash: 0 个
# 占位图: 0 个
Each scene will automatically obtain 1-2 high-resolution images, prioritizing real Google Images images.
✨ Features
- 🖼️ Integrated image display - Images are part of the content, supporting various layouts such as side, floating, and card
- 📝 Rich text content - Supports multiple content types such as multi-paragraph, bullet point lists, statistical data, and quotations
- 💬 Intelligent subtitle system - Sentence-by-sentence subtitles are precisely synchronized with audio, supporting automatic timeline allocation
- 🔤 Extra-large font design - Font sizes optimized specifically for video, ensuring clear readability on various screens
- 🎬 Rich animation effects - Animations such as typewriter, line-by-line fade-in, keyword highlighting, and number scrolling
- ✅ Perfect audio-video synchronization - Each scene waits for audio playback to complete
- 🎙️ Multi-TTS support - Supports Doubao, Volcano, or any TTS service, using a natural male voice by default
- 📐 Dynamic duration calculation - Automatically calculates the number of frames based on audio duration
- 🔧 FFmpeg stitching - Seamlessly merges all scenes
Workflow
脚本 → TTS 音频 → 搜索图片 → 测量时长 → 渲染场景 → 拼接视频
Multi-source image search (new)
This tool now supports multi-source image search, automatically obtaining high-quality images by priority:
Google Images → Bing Images → Unsplash → 渐变占位图
- Google Images: Preferred, scrapes high-resolution original images
- Bing Images: Fallback, no API Key required
- Unsplash: Last resort, high-quality photographic images (API Key required)
- Placeholder image: Final fallback, automatically generates gradient colors based on the scene theme
How to use:
node scripts/search_images.js scenes.json --output ./public/images
After the search is complete, a summary report will be output, informing you of the image source used for each scene.
Prerequisites
1. System dependencies
# macOS
brew install ffmpeg
# Python 3.8+ 和 Playwright(用于 Google Images 爬取)
pip3 install playwright
npx playwright install chromium
2. Node.js dependencies
npm install @remotion/cli remotion react react-dom
Unsplash API configuration
1. Obtain an Unsplash Access Key
- Visit Unsplash Developers
- Register and create a new application
- Obtain Access Key
2. Set environment variables
# 添加到 ~/.zshrc 或 ~/.bashrc
export UNSPLASH_ACCESS_KEY="your_access_key_here"
# 立即生效
source ~/.zshrc
Project structure
my-video/
├── src/
│ ├── index.tsx # 注册所有 compositions
│ ├── scenes/
│ │ └── SceneTemplate.tsx # 专业风格模板
│ └── components/
│ ├── Typography.tsx # 文字组件
│ ├── ImageCard.tsx # 图片卡片组件
│ ├── Animations.tsx # 动画组件
│ ├── GradientOverlay.tsx # 渐变遮罩
│ └── UnsplashImage.tsx # 图片展示
├── scripts/
│ ├── search_images.js # 🔥 多源图片搜索主脚本
│ ├── generate_placeholder.js # 渐变占位图生成
│ └── lib/ # 爬取库
│ ├── crawl_google_images.py # Python Google 图片爬取
│ └── download_images.py # 图片下载器
├── public/
│ ├── audio/ # TTS 音频文件
│ └── images/ # 下载的真实图片
├── scenes.json # 场景配置
├── image-map.json # 图片路径映射
└── package.json
Scene configuration (scenes.json)
Basic scene
{
"id": "intro",
"searchQuery": "artificial intelligence technology abstract blue",
"title": "人工智能革命",
"subtitle": "探索改变世界的技术力量",
"caption": "Introduction",
"variant": "hero"
}
Rich content scene
{
"id": "solution",
"searchQuery": "neural network ai brain technology",
"title": "智能解决方案",
"caption": "Our Solution",
"variant": "content-rich",
"layout": {
"imageLayout": "side-right",
"imageStyle": "card",
"imageAnimation": "float",
"textAlign": "left",
"accentColor": "#10b981"
},
"paragraphs": [
"我们的 AI 平台能够自动处理海量数据,从中提取有价值的洞察。",
"通过深度学习算法,系统可以识别复杂的模式和趋势。"
],
"bulletPoints": [
"实时数据处理,毫秒级响应",
"准确率高达 99.7%",
"自动生成可视化报告"
],
"stat": {
"value": "300",
"label": "效率提升",
"suffix": "%"
},
"highlightKeywords": ["AI", "深度学习"]
}
Configuration options explained in detail
Variant
| Variant | Description | Image layout |
|---|---|---|
hero |
Full-screen background, centered title | background |
centered |
Centered layout, vignette mask | background |
split |
Left-right split layout | side-right |
minimal |
Minimal bottom alignment | background |
content-rich |
Rich content display | side-right or side-left |
Layout configuration
{
"layout": {
"imageLayout": "side-right", // 图片位置
"imageStyle": "card", // 图片样式
"imageAnimation": "float", // 图片动画
"textAlign": "left", // 文字对齐
"accentColor": "#3b82f6" // 强调色
}
}
imageLayout options
background- Full-screen background (traditional method)side-left- Image on the left, occupying 42% widthside-right- Image on the right, occupying 42% widthfloating- Image floating at the upper rightinline- Image embedded within the text content
imageStyle options
none- No border, no shadowrounded- Rounded corners (24px)card- Card style (rounded corners + shadow + border)polaroid- Polaroid style (white border)circle- Circle
imageAnimation options
none- No animationzoom- Slow zoom (Ken Burns effect)fade- Fade inslide- Slide infloat- Hover float
Content fields
| Field | Type | Description |
|---|---|---|
title |
string | Main title (required) |
subtitle |
string | Subtitle |
caption |
string | Section label (uppercase letters, e.g. "INTRODUCTION") |
paragraphs |
string[] | Paragraph text array |
bulletPoints |
string[] | Bullet point list |
quote |
{text, author?} | Quote text and author |
stat |
{value, label, suffix?} | Statistics display |
highlightKeywords |
string[] | Keywords to highlight |
subtitles |
object | Sentence-by-sentence subtitle configuration (see below) |
Sentence-by-sentence subtitle configuration
Configure subtitles synchronized with audio via the subtitles field:
{
"id": "intro",
"title": "AGI 早已实现",
"subtitles": {
"sentences": [
"Nature 重磅评论:",
"AGI 早已实现,人类却不敢承认。",
"就在五年前,我们还没有 AGI;",
"而今天,我们已经拥有它。"
],
"mode": "word-count",
"style": {
"position": "bottom",
"bgOpacity": 0.8,
"fontSize": 52,
"maxWidth": "90%",
"bottomOffset": 100
}
}
}
Subtitle configuration options:
| Field | Type | Description |
|---|---|---|
sentences |
string[] | Subtitle sentence array; each sentence is displayed according to time |
mode |
string | Time allocation mode:word-count(by word count) / equal(even) |
style.position |
string | Position:bottom(bottom) / middle(middle) |
style.bgOpacity |
number | Background opacity, 0-1 |
style.fontSize |
number | Font size (px) |
style.maxWidth |
string | Maximum width, e.g. "90%" |
style.bottomOffset |
number | Distance from bottom (px) |
Time allocation mode:
word-count(default): allocate audio duration proportionally by each sentence's word count; longer sentences display longerequal: evenly distribute the audio duration across each sentence
💡 Tip: allocating by word count is usually more natural, because longer sentences need more time to read.
Font size specifications
Optimized specifically for 1920x1080 video:
| Element | Size | Purpose |
|---|---|---|
| Main title (xl) | 160px | Important section title |
| Main title (lg) | 120px | Standard section title |
| Main title (md) | 90px | Secondary title |
| Subtitle | 64px | Subtitle description |
| Paragraph text | 44px | Body content |
| Bullet point list | 40px | List item |
| Statistics value | 220px | Large number display |
| Statistics label | 44px | Number description |
| Quote text | 56px | Quote content |
| Description Label | 28px | Chapter Marker |
Animation Effects
Text Animation
import { TypewriterText, StaggerContainer, HighlightText } from './components';
// 打字机效果
<TypewriterText text="逐字出现的文字" speed={2} />
// 逐行淡入
<StaggerContainer delay={20} staggerDelay={15}>
<Paragraph>第一行</Paragraph>
<Paragraph>第二行</Paragraph>
</StaggerContainer>
// 关键词高亮
<HighlightText
text="这是AI驱动的深度学习解决方案"
keywords={["AI", "深度学习"]}
highlightColor="#3b82f6"
/>
Number Rolling
import { AnimatedCounter } from './components';
<AnimatedCounter
value={300}
suffix="%"
duration={60}
/>
TTS Voice Configuration
Option 1: Doubao TTS (Recommended ⭐)
Create voice.json:
{
"provider": "doubao",
"voice": "zh_male_jieshuoxiaoming_moon_bigtts",
"speed": 1.0,
"volume": 1.0,
"pitch": 0
}
Environment Variable Settings:
export VOLCANO_TTS_APPID="your_app_id"
export VOLCANO_TTS_ACCESS_TOKEN="your_access_token"
export VOLCANO_TTS_SECRET_KEY="your_secret_key"
Option 2: System TTS (macOS)
If Doubao is temporarily unavailable, you can use the built-in system TTS:
say -v "Ting-Ting" "你的文案" -o output.aiff
# 转换为 mp3
ffmpeg -i output.aiff output.mp3
⚠️ System TTS audio quality is poor; it is recommended to prioritize Doubao TTS
Recommended Male Voices
Recommended for News/Science Videos:
zh_male_jieshuoxiaoming_moon_bigtts- Narrator Xiaoming ⭐ Most recommended, no permissions requiredzh_male_xinwenxiaozhi_mars_bigtts- News Xiaozhi (permissions required)zh_male_jingdianxiaoming_mars_bigtts- Classic Xiaoming (permissions required)
Emotional/Story Videos:
zh_male_xiaomo_mars_bigtts- Xiao Mo, warm and friendlyzh_male_xudong_conversation_wvae_bigtts- Happy Xiaodong, sunny and cheerful
Complete Example Scenario
[
{
"id": "intro",
"searchQuery": "artificial intelligence technology",
"title": "AI 革命",
"subtitle": "正在改变我们的世界",
"variant": "hero",
"layout": {
"imageLayout": "background",
"imageAnimation": "zoom",
"accentColor": "#3b82f6"
}
},
{
"id": "challenge",
"searchQuery": "complex data visualization",
"title": "数据挑战",
"caption": "THE CHALLENGE",
"variant": "content-rich",
"layout": {
"imageLayout": "side-right",
"imageStyle": "card",
"accentColor": "#ef4444"
},
"paragraphs": [
"数据爆炸时代已经来临,企业面临着前所未有的挑战。",
"传统方法已无法满足现代数据处理需求。"
],
"bulletPoints": [
"数据量年增长 300%",
"人工处理效率低下",
"决策成本急剧上升"
],
"highlightKeywords": ["数据爆炸", "挑战"]
},
{
"id": "result",
"searchQuery": "success business growth",
"title": "显著成效",
"caption": "RESULTS",
"variant": "content-rich",
"layout": {
"imageLayout": "side-left",
"imageStyle": "rounded",
"accentColor": "#10b981"
},
"stat": {
"value": "500",
"label": "客户增长率",
"suffix": "%"
},
"quote": {
"text": "这是我们做过的最明智的投资",
"author": "某知名企业 CEO"
}
}
]
Quick Start
# 1. 创建项目
mkdir my-video && cd my-video
npm init -y
npm install @remotion/cli remotion react react-dom axios
# 2. 复制 skill 文件
cp -r ~/clawd/skills/remotion-synced-video/src .
cp ~/clawd/skills/remotion-synced-video/scripts .
cp ~/clawd/skills/remotion-synced-video/scenes.json .
# 3. 可选:设置 Unsplash API Key(用于兜底)
export UNSPLASH_ACCESS_KEY="your_key_here"
# 4. 搜索图片(自动多源搜索)
node scripts/search_images.js scenes.json --output ./public/images
# 5. 预览
npx remotion preview src/index.tsx
# 6. 渲染
npx remotion render src/index.tsx Scene-intro out/intro.mp4
Best Practices
🎙️ TTS Voice Selection
Strongly recommended to use Doubao TTS, it is much more natural than system TTS:
# 安装豆包 TTS skill
cd ~/clawd/skills/doubao-open-tts
pip install -r requirements.txt
# 设置环境变量
export VOLCANO_TTS_APPID="your_app_id"
export VOLCANO_TTS_ACCESS_TOKEN="your_access_token"
export VOLCANO_TTS_SECRET_KEY="your_secret_key"
Recommended Chinese Male Voices(for News/Science Videos):
| Voice | Voice Type | Features | Notes |
|---|---|---|---|
| Narrator Xiaoming | zh_male_jieshuoxiaoming_moon_bigtts |
News broadcast style, clear and professional | ⭐ Recommended, no additional permissions required |
| News Xiaozhi | zh_male_xinwenxiaozhi_mars_bigtts |
Standard news broadcast male voice | Permissions required |
| Classic Xiaoming | zh_male_jingdianxiaoming_mars_bigtts |
Classic documentary male voice | Permissions required |
| Happy Xiaodong | zh_male_xudong_conversation_wvae_bigtts |
Sunny and cheerful, natural and friendly | Emotional Voice |
⚠️ Note: Some voices require applying for permissions in the Volcano Engine console; it is recommended to test with Narrator Xiaoming first.
📦 Video File Size Management
Telegram limits to 16MB, compression is needed:
# 压缩至 16MB 以内(1080p 保持可接受画质)
ffmpeg -i input.mp4 \
-vcodec h264 -acodec aac \
-b:v 1.5M -b:a 128k \
-movflags +faststart \
output_compressed.mp4
# 如需更小文件,降低视频码率
ffmpeg -i input.mp4 -b:v 800k -b:a 96k output.mp4
Bitrate Reference:
- High quality:
-b:v 3M(~25MB/minute) - Standard quality:
-b:v 1.5M(~12MB/minute) ⭐ Recommended - Low quality:
-b:v 800k(~6MB/minute)
🔧 Complete Generation Workflow
# 1. 创建项目
mkdir my-video && cd my-video
npm init -y
npm install @remotion/cli remotion react react-dom axios
# 2. 复制 skill 文件
cp -r ~/clawd/skills/remotion-synced-video/src .
cp ~/clawd/skills/remotion-synced-video/scenes.json .
# 3. 设置环境变量
export UNSPLASH_ACCESS_KEY="your_key_here"
export VOLCANO_TTS_APPID="your_app_id"
export VOLCANO_TTS_ACCESS_TOKEN="your_token"
export VOLCANO_TTS_SECRET_KEY="your_secret"
# 4. 生成 TTS 音频(使用豆包)
python ~/clawd/skills/doubao-open-tts/scripts/tts.py "你的文案" \
-v zh_male_jieshuoxiaoming_moon_bigtts \
-o public/audio/scene1.mp3
# 5. 搜索图片
node src/../scripts/search_images.js scenes.json public/images
# 6. 测量音频时长并生成配置文件(关键步骤!)
# 这确保每个场景的时长与音频完全同步
echo "{"> audio-durations.json
for file in public/audio/*.mp3; do
filename=$(basename "$file" .mp3)
# 获取音频时长并加2秒缓冲
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" | cut -d. -f1)
duration=$((duration + 2))
echo " \"$filename\": $duration," >> audio-durations.json
done
# 删除最后一个逗号并关闭 JSON
truncate -s-2 audio-durations.json
echo "" >> audio-durations.json
echo "}" >> audio-durations.json
# 7. 预览
npx remotion preview src/index.tsx
# 8. 渲染所有场景
npx remotion render src/index.tsx Scene-intro out/intro.mp4
npx remotion render src/index.tsx Scene-history out/history.mp4
# ... 渲染其他场景
# 9. 拼接视频
ffmpeg -f concat -i filelist.txt -c copy output/final.mp4
# 10. 压缩(如需)
ffmpeg -i output/final.mp4 -b:v 1.5M -b:a 128k output/final_compressed.mp4
💡 Content Creation Tips
- Script length: Keep each scene within 50-80 characters, corresponding to 5-10 seconds of audio
- Total duration: Short videos are recommended to be 30-60 seconds, with 4-6 scenes
- Image search terms: Use English keywords, such as
"artificial intelligence technology"、"neural network" - highlightKeywords: Use highlights on key concepts to enhance memory retention
🐛 Common Troubleshooting
Issue: TTS generation failed
Solution: 检查 VOLCANO_TTS_* 环境变量是否设置正确
Issue: Voice reports permission error
Solution: 更换为解说小明 (zh_male_jieshuoxiaoming_moon_bigtts) 或其他无需权限的音色
Issue: Video file is too large
Solution: 使用 FFmpeg 压缩,降低 -b:v 码率参数
Issue: The scene switches before the voiceover finishes
原因:场景时长固定,没有根据实际音频长度调整
Solution:
1. 使用 ffprobe 测量每个音频的实际时长
2. 生成 audio-durations.json 配置文件
3. 在 index.tsx 中读取配置并动态计算帧数
示例代码:
// index.tsx
import audioDurations from '../audio-durations.json';
function getAudioDuration(sceneId: string): number {
return (audioDurations as Record<string, number>)[sceneId] || 5;
}
// 在 Composition 中使用
<Composition
durationInFrames={getAudioDuration(scene.id) * 30} // 30fps
...
/>
🎯 Best Practices for Audio Duration Synchronization
Why is synchronization needed?
When a video is assembled from multiple scenes, each scene'svisual duration must exactly match the audio duration, otherwise the following will occur:
- ❌ The voice hasn't finished yet, but the visuals cut to the next scene
- ❌ The voice has ended, but the visuals are still on the current scene
Solution: Audio duration configuration
Step 1: Create an audio duration measurement script
Create scripts/measure-audio.sh:
#!/bin/bash
AUDIO_DIR="public/audio"
OUTPUT_FILE="audio-durations.json"
echo "{"
first=true
for file in "$AUDIO_DIR"/*.mp3; do
filename=$(basename "$file" .mp3)
duration=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" | cut -d. -f1)
if [ -z "$duration" ]; then duration=5; fi
duration=$((duration + 2)) # 加2秒缓冲
if [ "$first" = true ]; then first=false; else echo ","; fi
echo " \"$filename\": $duration"
done
echo ""
echo "}"
Step 2: Update index.tsx to read the durations
import {Composition, registerRoot} from 'remotion';
import {SceneTemplate} from './scenes/SceneTemplate';
import audioDurations from '../audio-durations.json'; // 导入配置
const FPS = 30;
// 从配置文件获取音频时长
function getAudioDuration(sceneId: string): number {
return (audioDurations as Record<string, number>)[sceneId] || 5;
}
// 计算帧数
function calculateFrames(durationInSeconds: number): number {
return Math.ceil(durationInSeconds * FPS);
}
export const RemotionRoot: React.FC = () => {
return (
<>
{scenes.map((scene) => (
<Composition
key={scene.id}
id={`Scene-${scene.id}`}
component={SceneTemplate}
durationInFrames={calculateFrames(getAudioDuration(scene.id))}
fps={FPS}
width={1920}
height={1080}
defaultProps={{...}}
/>
))}
</>
);
};
registerRoot(RemotionRoot);
Step 3: Perform the measurement in the rendering flow
# 生成 TTS 音频后,必须执行时长测量
./scripts/measure-audio.sh > audio-durations.json
# 然后再渲染
./scripts/render.sh
Duration configuration example
Generated audio-durations.json format:
{
"intro": 7,
"gpt4o": 16,
"xai": 15,
"google": 15,
"moltbot": 11,
"datacenter": 16,
"outro": 8
}
💡 Tip: The values are in seconds and already include a +2 second buffer to ensure the voice plays completely.
FAQ
Q: How do fonts adapt to different resolutions?
A: Font sizes are designed based on 1920x1080 and will automatically scale when played at 4K. To adjust, use the textScale property.
Q: How do I choose an image layout?
A: For short, punchy titles use background, and when you need to show a lot of content use side-right or side-left。
Q: How do I customize the animation speed?
A: Most components support the delay and duration parameters, in frames (30fps = 1 second).
Q: Do statistics support decimals?
A: Yes, use the string format value: "99.7" or combine it with formatNumber: false to display decimals.
Pro Tip: Using highlightKeywords can make core concepts more eye-catching in the video and strengthen audience memory!