此 Skill 根据已有的分析数据生成数据可视化视频:
设计原则:视频风格与 byteplan-html/byteplan-ppt 统一,深色渐变背景、卡片式设计。
前置条件:必须先使用 byteplan-analysis skill 完成数据分析。
| 场景 | 内容 | 时长 |
|---|---|---|
| ------ | ------ | ------ |
| 1 | 封面(标题 + 主题) | 5s |
| 2-N | 数据场景(图表 + 解说) | 每个 8-15s |
| N+1 | 洞察场景(关键发现) | 8s |
| N+2 | 总结场景(行动建议) | 8s |
| N+3 | 结尾页 | 3s |
主题色:
- purple: #667eea(主强调色)
- dark-purple: #764ba2(渐变终点色)
背景色:
- dark-bg: #1a1a2e(主背景)
- dark-bg-2: #16213e(次背景)
文字色:
- text-white: #FFFFFF(标题文字)
- text-gray: #CBD5E1(正文文字)
数据色:
- green: #4ade80(正向数据)
- red: #f87171(负向数据)
- blue: #60a5fa(链接)
- pink: #f472b6(图表色)
- cyan: #06B6D4(图表色)
字体:微软雅黑(Microsoft YaHei)
标题:44px, Bold
场景标题:32px, Bold
解说文字:24px
数据数值:36px, Bold, Green
入场动画:fadeIn (0.5s)
图表动画:growFromLeft (1s)
文字动画:typewriter (按字数计算)
转场动画:slide (0.3s)
# 使用默认数据文件 (video_data.json)
cd skills/byteplan-video
pnpm run generate -o report.mp4
# 指定数据文件
pnpm run generate -o report.mp4 -d /path/to/data.json
# 指定分辨率和帧率
pnpm run generate -o report.mp4 --width 1920 --height 1080 --fps 30
import { generateVideo } from './scripts/generate_video.js';
await generateVideo('report.mp4', 'video_data.json');
⚠️ 所有必需字段必须显式提供,无默认值
{
"title": "报告标题",
"subtitle": "副标题",
"period": "2026年3月",
"scenes": [
{
"id": "scene_1",
"type": "title",
"title": "跨境电商边际贡献分析",
"subtitle": "数据驱动决策",
"narration": "本视频将为您分析跨境电商边际贡献情况。",
"visualConfig": {
"title": "跨境电商边际贡献分析",
"subtitle": "数据驱动决策"
},
"duration": 5
},
{
"id": "scene_2",
"type": "bar_chart",
"title": "渠道边际贡献对比",
"dataKey": "channelContribution",
"narration": "渠道A边际贡献最高,达到1627万元。",
"visualConfig": {
"title": "渠道边际贡献对比",
"dataKey": "channelContribution"
},
"duration": 10
},
{
"id": "scene_3",
"type": "line_chart",
"title": "营收趋势",
"dataKey": "revenueTrend",
"narration": "营收呈上升趋势,Q2环比增长50%。",
"visualConfig": {
"title": "营收趋势",
"dataKey": "revenueTrend"
},
"duration": 10
},
{
"id": "scene_4",
"type": "insight",
"title": "关键洞察",
"narration": "关键洞察:渠道A表现卓越,但存在产品亏损风险。",
"visualConfig": {
"title": "关键洞察",
"highlights": [
"渠道A表现卓越,贡献率达68%",
"风险提示:部分产品亏损"
]
},
"duration": 8
},
{
"id": "scene_5",
"type": "summary",
"title": "行动建议",
"narration": "建议加大渠道A投入,优化亏损产品线。",
"visualConfig": {
"title": "行动建议",
"points": [
"加大渠道A投入",
"优化亏损产品线"
],
"source": "BytePlan 数据平台"
},
"duration": 8
}
]
}
注意:
visualConfig 包含组件渲染所需的配置dataKey 用于从 chartData.json 获取图表数据highlights 是洞察场景的要点数组points 是总结场景的要点数组source 显示在总结场景底部(可选)| 字段 | 类型 | 必需 | 说明 |
|---|---|---|---|
| ------ | ------ | ------ | ------ |
type | string | ✅ | 场景类型(见下表) |
title | string | ✅ | 场景标题(必须提供,无默认值) |
subtitle | string | ❌ | 副标题(封面场景) |
dataKey | string | ⭐ | 图表数据键(图表场景必需) |
highlights | string[] | ⭐ | 洞察要点(insight 场景必需) |
points | string[] | ⭐ | 总结要点(summary 场景必需) |
source | string | ❌ | 数据来源(显示在总结场景底部) |
narration | string | ⭐ | 解说文本(用于语音合成) |
duration | number | ❌ | 场景时长(秒),有语音时自动计算 |
⚠️ 重要约束:所有必需字段必须显式提供,组件不会使用任何默认值。缺少必需字段将导致渲染错误。
| 类型 | 说明 | 必需字段 | 支持解说 |
|---|---|---|---|
| ------ | ------ | ---------- | ---------- |
title | 封面场景 | title(subtitle 可选) | ✅ |
bar_chart | 柱状图场景 | title, dataKey | ✅ |
line_chart | 折线图场景 | title, dataKey | ✅ |
insight | 洞察场景 | title, highlights[] | ✅ |
summary | 总结场景 | title, points[], source(可选) | ✅ |
⭐ 重要:narration 字段用于语音合成,如果提供则自动生成语音解说,场景时长由语音时长决定。
确保 video_data.json 包含所有必要字段,特别是 narration 字段用于语音合成:
# 从分析结果生成数据文件
# 或使用示例数据
cp examples/sample-video-data.json video_data.json
cd skills/byteplan-video/remotion-project
# 安装项目依赖(包含 node-edge-tts)
pnpm install
TTS 语音合成工具(二选一):
| 方式 | 安装命令 | 说明 |
|---|---|---|
| ------ | ---------- | ------ |
| npm 包(推荐) | pnpm add node-edge-tts | 纯 Node.js,无需 Python |
| Python edge-tts | pip install edge-tts | 备用方案 |
脚本会自动检测并优先使用 npm 包,失败时回退到 Python edge-tts。
# 自动生成语音并渲染视频
pnpm run generate -o analysis_report.mp4
# 或分步执行:
# 1. 生成语音
pnpm run generate:audio
# 2. 渲染视频
pnpm run render
生成的视频包含:
open analysis_report.mp4
支持两种 TTS 方式,脚本自动检测并选择可用方式:
# 安装 node-edge-tts
pnpm add node-edge-tts
# 或
npm install node-edge-tts
纯 Node.js 实现,无需 Python 依赖,跨平台兼容性更好。
# 安装 Python edge-tts
pip install edge-tts
# 或
pip3 install edge-tts
注意:Python edge-tts 需要 ffprobe 获取音频时长:
choco install ffmpegbrew install ffmpegsudo apt install ffmpeg| 语音ID | 名称 | 特点 |
|---|---|---|
| -------- | ------ | ------ |
zh-CN-XiaoxiaoNeural | 晓晓(女声) | 自然流畅,推荐使用 |
zh-CN-YunxiNeural | 云希(男声) | 温和稳重 |
zh-CN-YunyangNeural | 云扬(男声) | 新闻播报风格 |
zh-CN-XiaoyiNeural | 晓伊(女声) | 活泼年轻 |
// 默认配置
const voiceConfig = {
voice: 'zh-CN-XiaoxiaoNeural', // 语音ID
rate: '+0%', // 语速:-50% 到 +100%
pitch: '+0Hz' // 音调:-50Hz 到 +50Hz
};
// 好的解说文本
"渠道A边际贡献最高,达到1627万元,占比68%。"
// 不好的解说文本(太长、太书面)
"经过数据分析,我们发现渠道A的边际贡献在整个渠道体系中排名第一,具体金额为一千六百二十七万元。"
# 进入项目目录
cd skills/byteplan-video/remotion-project
# 安装依赖(包含 node-edge-tts)
pnpm install
# 运行语音生成脚本
pnpm run generate:audio
# 或
node scripts/generate_audio.js
脚本会自动:
语音生成后,系统会自动:
audioDuration// remotion.config.ts
export const config = {
width: 1920,
height: 1080,
fps: 30,
durationInFrames: 'auto', // 根据场景自动计算
codec: 'h264',
outputFormat: 'mp4'
};
pnpm install 安装 Remotion 依赖node-edge-tts npm 包(无需额外依赖)问题:interpolate 函数的动画值在超出输入范围后会继续线性增长,导致柱状图高度一直变大。
原因:Remotion 的 interpolate 默认会进行线性外推(extrapolation)。
解决方案:所有 interpolate 调用必须添加 extrapolateRight: "clamp" 参数:
// ❌ 错误:动画值会持续增长
const opacity = interpolate(frame, [0, 20], [0, 1]);
// ✅ 正确:动画值在达到目标后保持不变
const opacity = interpolate(frame, [0, 20], [0, 1], { extrapolateRight: "clamp" });
// 柱状图高度动画
const animatedHeight = interpolate(
frame,
[delay, delay + 30],
[0, barHeight],
{ extrapolateRight: "clamp" } // 必须添加
);
问题:饼图显示在画布外或位置偏移。
原因:SVG 圆心坐标使用了绝对值(如 cx: 960, cy: 540),但 SVG 尺寸较小。
解决方案:圆心坐标应相对于 SVG 尺寸计算:
// ❌ 错误:圆心超出 SVG 范围
const cx = 960; // 画面中心
const cy = 540;
<svg width={450} height={450}>...</svg>
// ✅ 正确:圆心在 SVG 中心
const svgSize = 400;
const cx = svgSize / 2; // 200
const cy = svgSize / 2; // 200
<svg width={svgSize} height={svgSize}>...</svg>
问题:柱状图位置不居中或高度计算错误。
原因:
height 和 justifyContent: "center"paddingLeft 与 flexbox 居中冲突解决方案:
// ❌ 错误:多种定位方式冲突
<div style={{
display: "flex",
alignItems: "flex-end",
height: chartHeight, // 固定高度
paddingLeft: startX, // 手动偏移
}}>
...
</div>
// ✅ 正确:使用 flexbox 自动居中
<div style={{
display: "flex",
flexDirection: "row",
alignItems: "flex-end", // 底部对齐
gap: barGap,
justifyContent: "center", // 自动居中
}}>
{data.map((d, i) => (
<div key={i} style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
}}>
{/* 数值在柱状条上方 */}
<div style={{ ... }}>{d.value}</div>
{/* 柱状条 */}
<div style={{ height: animatedHeight, ... }} />
{/* 标签在柱状条下方 */}
<div style={{ ... }}>{d.name}</div>
</div>
))}
</div>
问题:首次渲染时下载 Chrome Headless Shell 非常慢(90MB)。
解决方案:使用 --browser-executable 参数指定本地 Chrome:
# 下载 Chrome Headless Shell 到本地
# macOS: https://storage.googleapis.com/chrome-for-testing-public/144.0.7559.20/mac-arm64/chrome-headless-shell-mac-arm64.zip
# 赋予执行权限
chmod +x /path/to/chrome-headless-shell
# 渲染时指定路径
npx remotion render SalesReport output.mp4 \
--browser-executable=/path/to/chrome-headless-shell
问题:frame range 0-719 is not inbetween 0-299
原因:Composition 的 durationInFrames 设置过小,与实际场景总帧数不匹配。
解决方案:根据场景数量和时长动态计算总帧数:
// 计算总帧数
const sceneDurations = [9, 13, 14, 15, 15, 15, 12, 8]; // 每个场景秒数
const fps = 30;
const totalFrames = sceneDurations.reduce((sum, d) => sum + d * fps, 0);
<Composition
id="SalesReport"
component={SalesReport}
durationInFrames={totalFrames} // 动态计算
fps={fps}
...
/>
问题:场景切换时机不对或内容闪烁。
原因:Sequence 的 from 和 durationInFrames 计算有误。
解决方案:提前计算每个场景的起始帧和结束帧:
// 预计算场景帧范围
let currentFrame = 0;
const slideData = sceneDurations.map((s, i) => {
const startFrame = currentFrame;
currentFrame += s.duration * fps;
return {
...s,
startFrame,
endFrame: currentFrame
};
});
// Sequence 使用计算好的值
<Sequence
from={slideData[0].endFrame}
durationInFrames={slideData[1].duration * fps}
>
<KpiSlide ... />
<Audio src={...} />
</Sequence>
问题:画面结束但语音还在播放,或语音已结束画面还在等待。
解决方案:使用 ffprobe 获取准确的语音时长,并添加缓冲时间:
// 获取音频时长
const durationStr = execSync(
`ffprobe -i "${audioPath}" -show_entries format=duration -v quiet -of csv="p=0"`
).toString().trim();
const audioDuration = parseFloat(durationStr);
// 添加 0.5 秒缓冲,确保语音播放完毕
const sceneDuration = Math.ceil(audioDuration + 0.5);
# 完整渲染流程
# 1. 生成语音
node scripts/generate_audio.js
# 2. 渲染视频(指定 Chrome)
npx remotion render SalesReport output.mp4 \
--browser-executable=/Users/fudebao/Downloads/chrome-headless-shell-mac-arm64/chrome-headless-shell
# 或使用 npm script
npm run render
共 1 个版本