remotion-synced-video
npx skills add https://github.com/xdrshjr/jr-openclaw-skills --skill remotion-synced-video
Agent 安装分布
Skill 文档
Remotion Synced Video with Real Web Images
å建ä¸ä¸çº§è§é¢ï¼éæçå®ç½é¡µå¾çï¼Google Images ç¬åï¼ãå®ç¾é³é¢åæ¥ã丰å¯å 容å±ç¤ºåç²¾ç¾è§è§è®¾è®¡ãä½¿ç¨ Remotion + TTS + 夿ºå¾çæç´¢ï¼çæå ·æé¡¶çº§å¤§å飿 ¼çè§é¢å 容ã
ð 夿ºå¾çæç´¢ï¼v2.0ï¼
æ¬å·¥å ·ç°å·²éæ Google Images ç½é¡µç¬åï¼æ é Unsplash API Key å³å¯è·å大éé«è´¨éçå®å¾çï¼
ð¥ Python/Google Images â ð¥ Bing Images â ð¥ Unsplash â ð¨ æ¸åå ä½å¾
| å±çº§ | æ¥æº | 说æ | éè¦é ç½® |
|---|---|---|---|
| 1 | Google Images | Python + Playwright ç¬å髿¸ åå¾ | æ é API Key |
| 2 | Bing Images | HTTP è§£æï¼å¿«éå¤é | æ é API Key |
| 3 | Unsplash | é«è´¨éæå½±å¾ | é UNSPLASH_ACCESS_KEY |
| 4 | å ä½å¾ | èªå¨çææ¸åèæ¯ | æ éé ç½® |
å¾çæç´¢ä½¿ç¨
# ä¸é®æç´¢ææåºæ¯å¾ç
node scripts/search_images.js scenes.json --output ./public/images
# è¾åºç¤ºä¾ï¼
# â
æ»è®¡: 4 ä¸ªåºæ¯
# Google: 3 个 â çå®ç½é¡µå¾ç
# Bing: 1 个 â çå®ç½é¡µå¾ç
# Unsplash: 0 个
# å ä½å¾: 0 个
æ¯ä¸ªåºæ¯å°èªå¨è·å 1-2 å¼ é«æ¸ å¾çï¼ä¼å ä½¿ç¨ Google Images çå®å¾çã
⨠Features
- ð¼ï¸ èå ¥å¼å¾çå±ç¤º – å¾çä½ä¸ºå 容çä¸é¨åï¼æ¯æä¾§è¾¹ãæµ®å¨ãå¡ççå¤ç§å¸å±
- ð 丰å¯çæåå 容 – æ¯æå¤æ®µè½ãè¦ç¹å表ãç»è®¡æ°æ®ãå¼ç¨çå¤ç§å 容类å
- ð¬ æºè½åå¹ç³»ç» – éå¥åå¹ä¸é³é¢ç²¾ååæ¥ï¼æ¯æèªå¨æ¶é´è½´åé
- ð¤ è¶ å¤§åä½è®¾è®¡ – ä¸ä¸ºè§é¢ä¼åçåä½å¤§å°ï¼ç¡®ä¿å¨åç§å±å¹ä¸æ¸ æ°å¯è¯»
- ð¬ 丰å¯çå¨ç»ææ – æåæºãéè¡æ·¡å ¥ãå ³é®è¯é«äº®ãæ°åæ»å¨çå¨ç»
- â å®ç¾é³è§é¢åæ¥ – æ¯ä¸ªåºæ¯çå¾ é³é¢ææ¾å®æ
- ðï¸ å¤ TTS æ¯æ – æ¯æè±å ãVolcano æä»»ä½ TTS æå¡ï¼é»è®¤ä½¿ç¨èªç¶ç·å£°
- ð 卿æ¶é¿è®¡ç® – æ ¹æ®é³é¢æ¶é¿èªå¨è®¡ç®å¸§æ°
- ð§ FFmpeg æ¼æ¥ – æ ç¼åå¹¶ææåºæ¯
工使µç¨
èæ¬ â TTS é³é¢ â æç´¢å¾ç â æµéæ¶é¿ â æ¸²æåºæ¯ â æ¼æ¥è§é¢
夿ºå¾çæç´¢ï¼æ°ï¼
æ¬å·¥å ·ç°å¨æ¯æå¤æºå¾çæç´¢ï¼èªå¨æä¼å 级è·åé«è´¨éå¾çï¼
Google Images â Bing Images â Unsplash â æ¸åå ä½å¾
- Google Images: é¦éï¼ç¬å髿¸ åå¾
- Bing Images: å¤éï¼æ é API Key
- Unsplash: å åºï¼é«è´¨éæå½±å¾ï¼é API Keyï¼
- å ä½å¾: æç»å åºï¼æ ¹æ®åºæ¯ä¸»é¢èªå¨çææ¸åè²
ä½¿ç¨æ¹æ³ï¼
node scripts/search_images.js scenes.json --output ./public/images
æç´¢å®æåä¼è¾åºæè¦æ¥åï¼åç¥æ¯ä¸ªåºæ¯ä½¿ç¨çå¾çæ¥æºã
åç½®è¦æ±
1. ç³»ç»ä¾èµ
# macOS
brew install ffmpeg
# Python 3.8+ å Playwrightï¼ç¨äº Google Images ç¬åï¼
pip3 install playwright
npx playwright install chromium
2. Node.js ä¾èµ
npm install @remotion/cli remotion react react-dom
Unsplash API é ç½®
1. è·å Unsplash Access Key
- è®¿é® Unsplash Developers
- 注åå¹¶å建æ°åºç¨
- è·å Access Key
2. 设置ç¯å¢åé
# æ·»å å° ~/.zshrc æ ~/.bashrc
export UNSPLASH_ACCESS_KEY="your_access_key_here"
# ç«å³çæ
source ~/.zshrc
项ç®ç»æ
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
åºæ¯é ç½® (scenes.json)
åºç¡åºæ¯
{
"id": "intro",
"searchQuery": "artificial intelligence technology abstract blue",
"title": "人工æºè½é©å½",
"subtitle": "æ¢ç´¢æ¹åä¸ççææ¯åé",
"caption": "Introduction",
"variant": "hero"
}
丰å¯å å®¹åºæ¯
{
"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", "深度å¦ä¹ "]
}
é ç½®é项详解
Variant åä½
| åä½ | æè¿° | å¾çå¸å± |
|---|---|---|
hero |
å ¨å±èæ¯ï¼å± 䏿 é¢ | background |
centered |
å± ä¸å¸å±ï¼æè§é®ç½© | background |
split |
å·¦å³åå²å¸å± | side-right |
minimal |
ç®æ´åºé¨å¯¹é½ | background |
content-rich |
丰å¯å 容å±ç¤º | side-right æ side-left |
Layout é ç½®
{
"layout": {
"imageLayout": "side-right", // å¾çä½ç½®
"imageStyle": "card", // å¾çæ ·å¼
"imageAnimation": "float", // å¾çå¨ç»
"textAlign": "left", // æå对é½
"accentColor": "#3b82f6" // 强è°è²
}
}
imageLayout é项
background– å ¨å±èæ¯ï¼ä¼ ç»æ¹å¼ï¼side-left– å¾çå¨å·¦ä¾§ï¼å 42% 宽度side-right– å¾çå¨å³ä¾§ï¼å 42% 宽度floating– å¾çæµ®å¨å¨å³ä¾§ä¸æ¹inline– å¾çå åµå¨æåå 容ä¸
imageStyle é项
none– æ è¾¹æ¡æ é´å½±rounded– åè§ï¼24pxï¼card– å¡çæ ·å¼ï¼åè§ + é´å½± + è¾¹æ¡ï¼polaroid– æç«å¾æ ·å¼ï¼ç½è²è¾¹æ¡ï¼circle– åå½¢
imageAnimation é项
none– æ å¨ç»zoom– ç¼æ ¢æ¾å¤§ï¼Ken Burns ææï¼fade– æ·¡å ¥slide– æ»å ¥float– æ¬æµ®æµ®å¨
å 容忮µ
| åæ®µ | ç±»å | æè¿° |
|---|---|---|
title |
string | 主æ é¢ï¼å¿ å¡«ï¼ |
subtitle |
string | 坿 é¢ |
caption |
string | ç« èæ ç¾ï¼å¤§å忝ï¼å¦ “INTRODUCTION”ï¼ |
paragraphs |
string[] | æ®µè½æåæ°ç» |
bulletPoints |
string[] | è¦ç¹å表 |
quote |
{text, author?} | å¼ç¨æååä½è |
stat |
{value, label, suffix?} | ç»è®¡æ°æ®å±ç¤º |
highlightKeywords |
string[] | éè¦é«äº®çå ³é®è¯ |
subtitles |
object | éå¥åå¹é ç½®ï¼è§ä¸æ¹ï¼ |
éå¥åå¹é ç½®
éè¿ subtitles åæ®µé
ç½®ä¸é³é¢åæ¥çåå¹ï¼
{
"id": "intro",
"title": "AGI æ©å·²å®ç°",
"subtitles": {
"sentences": [
"Nature éç£
è¯è®ºï¼",
"AGI æ©å·²å®ç°ï¼äººç±»å´ä¸æ¢æ¿è®¤ã",
"å°±å¨äºå¹´åï¼æä»¬è¿æ²¡æ AGIï¼",
"èä»å¤©ï¼æä»¬å·²ç»æ¥æå®ã"
],
"mode": "word-count",
"style": {
"position": "bottom",
"bgOpacity": 0.8,
"fontSize": 52,
"maxWidth": "90%",
"bottomOffset": 100
}
}
}
åå¹é ç½®é项ï¼
| åæ®µ | ç±»å | æè¿° |
|---|---|---|
sentences |
string[] | åå¹å¥åæ°ç»ï¼æ¯å¥ä¼ææ¶é´æ¾ç¤º |
mode |
string | æ¶é´åé
模å¼ï¼word-count(æåæ°) / equal(å¹³å) |
style.position |
string | ä½ç½®ï¼bottom(åºé¨) / middle(ä¸é´) |
style.bgOpacity |
number | èæ¯éæåº¦ï¼0-1 |
style.fontSize |
number | åä½å¤§å°(px) |
style.maxWidth |
string | æå¤§å®½åº¦ï¼å¦ "90%" |
style.bottomOffset |
number | è·åºé¨è·ç¦»(px) |
æ¶é´åé æ¨¡å¼ï¼
word-count(é»è®¤)ï¼æå¥ååæ°æ¯ä¾åé é³é¢æ¶é¿ï¼é¿å¥æ¾ç¤ºæ´ä¹equalï¼å¹³ååé é³é¢æ¶é¿ç»æ¯å¥è¯
ð¡ æç¤ºï¼æåæ°åé é常æ´èªç¶ï¼å 为é¿å¥éè¦æ´å¤æ¶é´é 读ã
åä½å¤§å°è§æ ¼
ä¸ä¸º 1920×1080 è§é¢ä¼åï¼
| å ç´ | å¤§å° | ç¨é |
|---|---|---|
| 主æ é¢ (xl) | 160px | éè¦ç« èæ é¢ |
| 主æ é¢ (lg) | 120px | æ åç« èæ é¢ |
| 主æ é¢ (md) | 90px | æ¬¡è¦æ é¢ |
| 坿 é¢ | 64px | 坿 é¢æè¿° |
| æ®µè½æå | 44px | æ£æå 容 |
| è¦ç¹å表 | 40px | å表项 |
| ç»è®¡æ°æ®å¼ | 220px | 大æ°åå±ç¤º |
| ç»è®¡æ ç¾ | 44px | æ°å说æ |
| å¼ç¨æå | 56px | å¼ç¨å 容 |
| è¯´ææ ç¾ | 28px | ç« èæ è¯ |
å¨ç»ææ
æåå¨ç»
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"
/>
æ°åæ»å¨
import { AnimatedCounter } from './components';
<AnimatedCounter
value={300}
suffix="%"
duration={60}
/>
TTS è¯é³é ç½®
æ¹æ¡ä¸ï¼è±å TTSï¼æ¨è âï¼
å建 voice.jsonï¼
{
"provider": "doubao",
"voice": "zh_male_jieshuoxiaoming_moon_bigtts",
"speed": 1.0,
"volume": 1.0,
"pitch": 0
}
ç¯å¢åé设置ï¼
export VOLCANO_TTS_APPID="your_app_id"
export VOLCANO_TTS_ACCESS_TOKEN="your_access_token"
export VOLCANO_TTS_SECRET_KEY="your_secret_key"
æ¹æ¡äºï¼ç³»ç» TTSï¼macOSï¼
å¦æææ¶æ æ³ä½¿ç¨è±å ï¼å¯ç¨ç³»ç»èªå¸¦ TTSï¼
say -v "Ting-Ting" "ä½ çææ¡" -o output.aiff
# 转æ¢ä¸º mp3
ffmpeg -i output.aiff output.mp3
â ï¸ ç³»ç» TTS é³è´¨è¾å·®ï¼å»ºè®®ä¼å 使ç¨è±å TTS
æ¨èç·å£°é³è²
æ°é»/ç§æ®ç±»è§é¢æ¨èï¼
zh_male_jieshuoxiaoming_moon_bigtts– è§£è¯´å°æ â ææ¨èï¼æ éæézh_male_xinwenxiaozhi_mars_bigtts– æ°é»å°å¿ï¼éå¼éæéï¼zh_male_jingdianxiaoming_mars_bigtts– ç»å ¸å°æï¼éå¼éæéï¼
æ æç±»/æ äºç±»è§é¢ï¼
zh_male_xiaomo_mars_bigtts– å°è«ï¼æ¸©æå好zh_male_xudong_conversation_wvae_bigtts– å¼å¿å°ä¸ï¼é³å 弿
宿´ç¤ºä¾åºæ¯
[
{
"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"
}
}
]
å¿«éå¼å§
# 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
æä½³å®è·µ
ðï¸ TTS é³è²éæ©
å¼ºçæ¨è使ç¨è±å TTSï¼æ¯ç³»ç» 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"
æ¨èç䏿ç·å£°é³è²ï¼æ°é»/ç§æ®ç±»è§é¢ï¼ï¼
| é³è² | Voice Type | ç¹ç¹ | 夿³¨ |
|---|---|---|---|
| è§£è¯´å°æ | zh_male_jieshuoxiaoming_moon_bigtts |
æ°é»ææ¥é£æ ¼ï¼æ¸ æ°ä¸ä¸ | â æ¨èï¼æ éé¢å¤æé |
| æ°é»å°å¿ | zh_male_xinwenxiaozhi_mars_bigtts |
æ 忰黿æ¥ç·å£° | éå¼éæé |
| ç»å ¸å°æ | zh_male_jingdianxiaoming_mars_bigtts |
ç»å ¸çºªå½çç·å£° | éå¼éæé |
| å¼å¿å°ä¸ | zh_male_xudong_conversation_wvae_bigtts |
é³å 弿ï¼èªç¶äº²å | æ æç±»é³è² |
â ï¸ æ³¨æï¼é¨åé³è²éè¦å¨ç«å±±å¼ææ§å¶å°ç³è¯·å¼éæéï¼å»ºè®®å ç¨è§£è¯´å°ææµè¯ã
ð¦ è§é¢æä»¶å¤§å°ç®¡ç
Telegram éå¶ 16MBï¼éè¦å缩ï¼
# åç¼©è³ 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
ç çåèï¼
- é«è´¨éï¼
-b:v 3M(~25MB/åé) - æ åè´¨éï¼
-b:v 1.5M(~12MB/åé) â æ¨è - ä½è´¨éï¼
-b:v 800k(~6MB/åé)
ð§ 宿´çææµç¨
# 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
ð¡ å 容åä½å»ºè®®
- èæ¬é¿åº¦ï¼æ¯ä¸ªåºæ¯æ§å¶å¨ 50-80 åï¼å¯¹åº 5-10 ç§é³é¢
- æ»æ¶é¿ï¼çè§é¢å»ºè®® 30-60 ç§ï¼4-6 ä¸ªåºæ¯
- å¾çæç´¢è¯ï¼ä½¿ç¨è±æå
³é®è¯ï¼å¦
"artificial intelligence technology"ã"neural network" - highlightKeywordsï¼å¨å ³é®æ¦å¿µä¸ä½¿ç¨é«äº®ï¼å¢å¼ºè®°å¿ç¹
ð 常è§é®é¢ææ¥
é®é¢ï¼TTS çæå¤±è´¥
Solution: æ£æ¥ VOLCANO_TTS_* ç¯å¢å鿝å¦è®¾ç½®æ£ç¡®
é®é¢ï¼é³è²æ¥æéé误
Solution: æ´æ¢ä¸ºè§£è¯´å°æ (zh_male_jieshuoxiaoming_moon_bigtts) æå
¶ä»æ éæéçé³è²
é®é¢ï¼è§é¢æä»¶å¤ªå¤§
Solution: ä½¿ç¨ FFmpeg å缩ï¼éä½ -b:v ç çåæ°
é®é¢ï¼è¯é³è¿æ²¡è¯´å®ç»é¢å°±åæ¢äº
åå ï¼åºæ¯æ¶é¿åºå®ï¼æ²¡ææ ¹æ®å®é
é³é¢é¿åº¦è°æ´
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
...
/>
ð¯ é³é¢æ¶é¿åæ¥æä½³å®è·µ
为ä»ä¹éè¦åæ¥ï¼
å½è§é¢ç±å¤ä¸ªåºæ¯æ¼æ¥èææ¶ï¼æ¯ä¸ªåºæ¯çç»é¢æ¶é¿å¿ é¡»ä¸é³é¢æ¶é¿å®å ¨å¹é ï¼å¦åä¼åºç°ï¼
- â è¯é³è¿æ²¡è¯´å®ï¼ç»é¢å°±åå°ä¸ä¸ä¸ªåºæ¯
- â è¯é³å·²ç»ç»æï¼ç»é¢è¿åçå¨å½ååºæ¯
è§£å³æ¹æ¡ï¼é³é¢æ¶é¿é ç½®
æ¥éª¤ 1ï¼å建é³é¢æ¶é¿æµéèæ¬
å建 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 "}"
æ¥éª¤ 2ï¼æ´æ° index.tsx è¯»åæ¶é¿
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);
æ¥éª¤ 3ï¼å¨æ¸²ææµç¨ä¸æ§è¡æµé
# çæ TTS é³é¢åï¼å¿
é¡»æ§è¡æ¶é¿æµé
./scripts/measure-audio.sh > audio-durations.json
# ç¶å忏²æ
./scripts/render.sh
æ¶é¿é 置示ä¾
çæç audio-durations.json æ ¼å¼ï¼
{
"intro": 7,
"gpt4o": 16,
"xai": 15,
"google": 15,
"moltbot": 11,
"datacenter": 16,
"outro": 8
}
ð¡ æç¤ºï¼æ°å¼åä½ä¸ºç§ï¼å·²å å« +2 ç§ç¼å²æ¶é´ï¼ç¡®ä¿è¯é³å®æ´ææ¾ã
常è§é®é¢
Q: åä½å¨ä¸åå辨çä¸å¦ä½éé ï¼
A: åä½å¤§å°åºäº 1920×1080 设计ï¼å¨ 4K ææ¾æ¶ä¼èªå¨ç¼©æ¾ãå¦éè°æ´ï¼ä½¿ç¨ textScale 屿§ã
Q: å¾çå¸å±å¦ä½éæ©ï¼
A: ç®çæåçæ é¢ç¨ backgroundï¼éè¦å±ç¤ºå¤§éå
容æ¶ç¨ side-right æ side-leftã
Q: å¦ä½èªå®ä¹å¨ç»é度ï¼
A: 大夿°ç»ä»¶æ¯æ delay å duration åæ°ï¼åä½ä¸ºå¸§ï¼30fps = 1ç§ï¼ã
Q: ç»è®¡æ°æ®æ¯æå°æ°åï¼
A: æ¯æï¼ä½¿ç¨åç¬¦ä¸²æ ¼å¼ value: "99.7" æé
å formatNumber: false æ¾ç¤ºå°æ°ã
Pro Tip: ä½¿ç¨ highlightKeywords å¯ä»¥è®©æ ¸å¿æ¦å¿µå¨è§é¢ä¸æ´å éç®ï¼å¢å¼ºè§ä¼è®°å¿ï¼