430 lines
15 KiB
Markdown
430 lines
15 KiB
Markdown
# 视频渲染
|
||
|
||
<cite>
|
||
**本文档引用的文件**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts)
|
||
- [index.ts](file://src/index.ts)
|
||
- [main.py](file://main.py)
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx)
|
||
- [CashFlowChart.tsx](file://src/CashFlowChart.tsx)
|
||
- [Composition.tsx](file://src/Composition.tsx)
|
||
- [Root.tsx](file://src/Root.tsx)
|
||
- [types.ts](file://src/types.ts)
|
||
- [data_builder.py](file://backend/data_builder.py)
|
||
- [mock-data.json](file://src/mock-data.json)
|
||
- [mock-cashflow.json](file://src/mock-cashflow.json)
|
||
- [remotion.config.ts](file://remotion.config.ts)
|
||
</cite>
|
||
|
||
## 目录
|
||
1. [简介](#简介)
|
||
2. [项目结构](#项目结构)
|
||
3. [核心组件](#核心组件)
|
||
4. [架构概述](#架构概述)
|
||
5. [详细组件分析](#详细组件分析)
|
||
6. [依赖分析](#依赖分析)
|
||
7. [性能考虑](#性能考虑)
|
||
8. [故障排除指南](#故障排除指南)
|
||
9. [结论](#结论)
|
||
10. [附录](#附录)(如有必要)
|
||
|
||
## 简介
|
||
本项目是一个自动化人生规划视频生成系统,旨在将财务数据和人生事件转化为专业的可视化视频报告。系统采用前后端分离架构,后端使用Python进行数据处理和脚本生成,前端使用Remotion框架进行视频渲染。核心功能包括:从PDF报告中提取客户财务数据,通过Gemini Pro生成叙述性脚本,使用Pexels API获取B-Roll素材,模拟未来资产走势,并最终生成包含动态图表和同步解说的专业视频。该系统特别适用于金融咨询领域,能够快速为客户提供个性化的可视化人生规划报告。
|
||
|
||
## 项目结构
|
||
项目采用清晰的分层架构,分为前端渲染、后端处理和资源资产三大模块。前端使用Remotion框架构建视频组件,后端使用Python处理数据和生成脚本,资产目录存储输入、处理中和输出的文件。
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "前端渲染"
|
||
frontend[frontend]
|
||
src[src]
|
||
index_ts[index.ts]
|
||
render_props_types[render-props-types.ts]
|
||
end
|
||
subgraph "后端处理"
|
||
backend[backend]
|
||
main_py[main.py]
|
||
data_builder[data_builder.py]
|
||
extractor[extractor.py]
|
||
scripter[scripter.py]
|
||
media_fetcher[media_fetcher.py]
|
||
end
|
||
subgraph "资源资产"
|
||
assets[assets]
|
||
input[0_input]
|
||
processed[1_processed]
|
||
output[2_output]
|
||
end
|
||
main_py --> data_builder
|
||
main_py --> extractor
|
||
main_py --> scripter
|
||
main_py --> media_fetcher
|
||
data_builder --> processed
|
||
scripter --> processed
|
||
media_fetcher --> processed
|
||
frontend --> processed
|
||
processed --> output
|
||
```
|
||
|
||
**图表来源**
|
||
- [main.py](file://main.py#L1-L305)
|
||
- [data_builder.py](file://backend/data_builder.py#L1-L200)
|
||
|
||
**本节来源**
|
||
- [main.py](file://main.py#L1-L305)
|
||
- [backend](file://backend#L1-L200)
|
||
- [frontend](file://frontend#L1-L251)
|
||
|
||
## 核心组件
|
||
系统的核心组件包括数据契约定义、视频渲染逻辑和后端处理管道。`render-props-types.ts`文件定义了前后端通信的数据契约,确保类型安全的数据传递。`index.ts`是Remotion渲染的入口点,`main.py`是后端处理的主控脚本。这些组件协同工作,实现了从原始数据到最终视频的完整转换流程。
|
||
|
||
**本节来源**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L1-L251)
|
||
- [index.ts](file://src/index.ts#L1-L6)
|
||
- [main.py](file://main.py#L1-L305)
|
||
|
||
## 架构概述
|
||
系统采用端到端的自动化视频生成架构,从前端数据输入到后端处理,再到最终视频渲染,形成了一个完整的闭环。
|
||
|
||
```mermaid
|
||
graph TD
|
||
subgraph "输入"
|
||
PDF[PDF报告]
|
||
Video[视频采访]
|
||
end
|
||
subgraph "后端处理"
|
||
Extractor[数据提取器]
|
||
Scripter[脚本生成器]
|
||
DataBuilder[数据构建器]
|
||
MediaFetcher[媒体获取器]
|
||
RenderProps[render_props.json]
|
||
end
|
||
subgraph "前端渲染"
|
||
Remotion[Remotion渲染器]
|
||
Charts[图表组件]
|
||
BRoll[B-Roll叠加]
|
||
Transitions[转场效果]
|
||
end
|
||
subgraph "输出"
|
||
FinalVideo[最终视频]
|
||
end
|
||
PDF --> Extractor
|
||
Video --> Extractor
|
||
Extractor --> Scripter
|
||
Extractor --> DataBuilder
|
||
Scripter --> RenderProps
|
||
DataBuilder --> RenderProps
|
||
MediaFetcher --> RenderProps
|
||
RenderProps --> Remotion
|
||
Remotion --> Charts
|
||
Remotion --> BRoll
|
||
Remotion --> Transitions
|
||
Charts --> FinalVideo
|
||
BRoll --> FinalVideo
|
||
Transitions --> FinalVideo
|
||
```
|
||
|
||
**图表来源**
|
||
- [main.py](file://main.py#L1-L305)
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L1-L251)
|
||
|
||
## 详细组件分析
|
||
### 数据契约与类型安全
|
||
系统通过TypeScript接口和Zod模式确保前后端数据传递的类型安全。`render-props-types.ts`文件定义了`RenderProps`接口,与后端生成的`render_props.json`结构完全匹配。
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class RenderProps {
|
||
+meta : Meta
|
||
+charts : Charts
|
||
+script : ScriptSegment[]
|
||
+assets : AssetCollection
|
||
}
|
||
class Meta {
|
||
+title : string
|
||
+currencyUnit : string
|
||
+duration : number
|
||
+clientName? : string
|
||
+generatedAt? : string
|
||
}
|
||
class Charts {
|
||
+assetDepletion : AssetDepletionChartData
|
||
+cashFlow : CashFlowChartData
|
||
}
|
||
class ScriptSegment {
|
||
+id : number
|
||
+text : string
|
||
+duration : number
|
||
+type : ScriptSegmentType
|
||
+assetUrl? : string
|
||
+chartSync? : ChartSync
|
||
}
|
||
class AssetCollection {
|
||
+broll : Record~string, string~
|
||
+videoClips : VideoClipReference[]
|
||
+ttsAudio? : string
|
||
}
|
||
RenderProps --> Meta
|
||
RenderProps --> Charts
|
||
RenderProps --> ScriptSegment
|
||
RenderProps --> AssetCollection
|
||
```
|
||
|
||
**图表来源**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L117-L251)
|
||
|
||
**本节来源**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L1-L251)
|
||
- [main.py](file://main.py#L145-L198)
|
||
|
||
### 视频合成逻辑
|
||
`src/index.ts`文件是Remotion渲染的入口点,通过`registerRoot`函数注册根组件。实际的视频合成逻辑在`Root.tsx`中实现,定义了多个`Composition`组件,每个组件对应一个可渲染的视频片段。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Main as main.py
|
||
participant Render as Remotion
|
||
participant Index as index.ts
|
||
participant Root as Root.tsx
|
||
Main->>Main : run()
|
||
Main->>Main : build_render_props()
|
||
Main->>Main : save render_props.json
|
||
Main->>Render : render_video()
|
||
Render->>Index : npx remotion render
|
||
Index->>Root : registerRoot()
|
||
Root->>Root : Define Compositions
|
||
Root->>Charts : Pass defaultProps
|
||
Charts->>Charts : Render video frames
|
||
```
|
||
|
||
**图表来源**
|
||
- [index.ts](file://src/index.ts#L1-L6)
|
||
- [Root.tsx](file://src/Root.tsx#L1-L68)
|
||
- [main.py](file://main.py#L200-L225)
|
||
|
||
**本节来源**
|
||
- [index.ts](file://src/index.ts#L1-L6)
|
||
- [Root.tsx](file://src/Root.tsx#L1-L68)
|
||
- [main.py](file://main.py#L200-L225)
|
||
|
||
### 图表动画实现
|
||
系统实现了两种主要的财务图表:资产消耗图和现金流图。`LifePlanChart.tsx`使用Recharts库创建面积图,展示资产余额随时间的变化趋势。`CashFlowChart.tsx`使用组合图展示收入与支出的对比。
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start([开始渲染]) --> Animation["动画逻辑: interpolate(frame)"]
|
||
Animation --> DataSlice["数据切片: slice(0, dataEndIndex + 1)"]
|
||
DataSlice --> Interpolation["插值计算: 平滑过渡"]
|
||
Interpolation --> Gradient["渐变逻辑: 蓝色(正) 红色(负)"]
|
||
Gradient --> Render["渲染图表"]
|
||
Render --> HUD["渲染HUD显示"]
|
||
HUD --> End([完成渲染])
|
||
```
|
||
|
||
**图表来源**
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx#L1-L349)
|
||
- [CashFlowChart.tsx](file://src/CashFlowChart.tsx#L1-L441)
|
||
|
||
**本节来源**
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx#L1-L349)
|
||
- [CashFlowChart.tsx](file://src/CashFlowChart.tsx#L1-L441)
|
||
- [types.ts](file://src/types.ts#L1-L30)
|
||
|
||
### 双图表视图
|
||
`DualChartView.tsx`组件实现了上下双图表布局,同时展示资产趋势和收支明细。该组件通过统一的动画逻辑确保两个图表的同步。
|
||
|
||
```mermaid
|
||
classDiagram
|
||
class DualChartView {
|
||
+cashflowData : CashFlowData
|
||
+frame : number
|
||
+durationInFrames : number
|
||
+exactProgress : number
|
||
+dataEndIndex : number
|
||
+yearProgress : number
|
||
+currentData : CashFlowData
|
||
+lifePlanTimeline : YearlySnapshot[]
|
||
+lifePlanMeta : Meta
|
||
}
|
||
class LifePlanChart {
|
||
+timeline : YearlySnapshot[]
|
||
+meta : Meta
|
||
+hideHeader : boolean
|
||
+externalDataIndex : number
|
||
+externalFraction : number
|
||
}
|
||
class CashFlowChart {
|
||
+timeline : CashFlowData[]
|
||
+meta : Meta
|
||
+hideHeader : boolean
|
||
+externalDataIndex : number
|
||
+externalYearProgress : number
|
||
}
|
||
DualChartView --> LifePlanChart
|
||
DualChartView --> CashFlowChart
|
||
DualChartView --> "useMemo() lifePlanTimeline"
|
||
DualChartView --> "useMemo() lifePlanMeta"
|
||
```
|
||
|
||
**图表来源**
|
||
- [DualChartView.tsx](file://src/DualChartView.tsx#L1-L176)
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx#L1-L349)
|
||
- [CashFlowChart.tsx](file://src/CashFlowChart.tsx#L1-L441)
|
||
|
||
**本节来源**
|
||
- [DualChartView.tsx](file://src/DualChartView.tsx#L1-L176)
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx#L1-L349)
|
||
- [CashFlowChart.tsx](file://src/CashFlowChart.tsx#L1-L441)
|
||
|
||
### 解说同步与B-Roll叠加
|
||
系统通过`ScriptSegment`类型实现解说与视觉元素的同步。`chartSync`字段允许在特定时间点高亮图表数据,`assetUrl`字段用于叠加B-Roll素材。
|
||
|
||
```mermaid
|
||
erDiagram
|
||
SCRIPT_SEGMENT {
|
||
int id PK
|
||
string text
|
||
float duration
|
||
string type
|
||
string assetUrl FK
|
||
int chartSync.chartId
|
||
int chartSync.highlightYear
|
||
int chartSync.animationStart
|
||
}
|
||
ASSET_COLLECTION {
|
||
string broll.keyword PK
|
||
string broll.url
|
||
string videoClips.id PK
|
||
string videoClips.sourcePath
|
||
float videoClips.startTime
|
||
float videoClips.duration
|
||
string videoClips.outputPath
|
||
string ttsAudio
|
||
}
|
||
SCRIPT_SEGMENT ||--o{ ASSET_COLLECTION : "references"
|
||
```
|
||
|
||
**图表来源**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L74-L114)
|
||
- [main.py](file://main.py#L120-L134)
|
||
|
||
**本节来源**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L74-L114)
|
||
- [main.py](file://main.py#L120-L134)
|
||
|
||
### 后端渲染命令调用
|
||
`main.py`中的`render_video`方法负责调用Remotion进行视频渲染,通过`subprocess`执行Node.js命令。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Pipeline as VideoGeneratorPipeline
|
||
participant Subprocess as subprocess
|
||
participant Remotion as Remotion CLI
|
||
Pipeline->>Pipeline : render_video(props_file)
|
||
Pipeline->>Subprocess : cmd = ["npx", "remotion", "render", ...]
|
||
Subprocess->>Remotion : Execute command
|
||
Remotion->>Remotion : Load render_props.json
|
||
Remotion->>Remotion : Render video frames
|
||
Remotion-->>Subprocess : Return result
|
||
Subprocess-->>Pipeline : Return output_path
|
||
```
|
||
|
||
**图表来源**
|
||
- [main.py](file://main.py#L200-L225)
|
||
|
||
**本节来源**
|
||
- [main.py](file://main.py#L200-L225)
|
||
|
||
## 依赖分析
|
||
系统依赖关系清晰,后端模块之间耦合度低,前端组件可复用性强。
|
||
|
||
```mermaid
|
||
graph TD
|
||
main.VideoGeneratorPipeline[VideoGeneratorPipeline] --> main.VideoGeneratorPipeline.run[run]
|
||
main.VideoGeneratorPipeline.run --> main.VideoGeneratorPipeline.extract_pdf[extract_pdf]
|
||
main.VideoGeneratorPipeline.run --> main.VideoGeneratorPipeline.extract_video[extract_video]
|
||
main.VideoGeneratorPipeline.run --> main.VideoGeneratorPipeline.generate_script[generate_script]
|
||
main.VideoGeneratorPipeline.run --> main.VideoGeneratorPipeline.fetch_broll[fetch_broll]
|
||
main.VideoGeneratorPipeline.run --> main.VideoGeneratorPipeline.build_render_props[build_render_props]
|
||
main.VideoGeneratorPipeline.run --> main.VideoGeneratorPipeline.render_video[render_video]
|
||
main.VideoGeneratorPipeline.build_render_props --> backend.data_builder.build_chart_data[build_chart_data]
|
||
backend.data_builder.build_chart_data --> backend.data_builder.FinancialSimulator[FinancialSimulator]
|
||
backend.data_builder.FinancialSimulator --> backend.data_builder.FinancialSimulator.simulate_timeline[simulate_timeline]
|
||
backend.data_builder.FinancialSimulator.simulate_timeline --> backend.data_builder.FinancialSimulator._get_event_cost[_get_event_cost]
|
||
backend.data_builder.FinancialSimulator.simulate_timeline --> backend.data_builder.FinancialSimulator._get_event_labels[_get_event_labels]
|
||
backend.data_builder.FinancialSimulator.simulate_timeline --> backend.data_builder.FinancialSimulator.build_cashflow_data[build_cashflow_data]
|
||
```
|
||
|
||
**图表来源**
|
||
- [main.py](file://main.py#L33-L83)
|
||
- [data_builder.py](file://backend/data_builder.py#L15-L200)
|
||
|
||
**本节来源**
|
||
- [main.py](file://main.py#L33-L83)
|
||
- [data_builder.py](file://backend/data_builder.py#L15-L200)
|
||
|
||
## 性能考虑
|
||
系统在性能方面采用了多种优化策略。前端使用`React.memo`避免不必要的组件重新渲染,`useMemo`缓存计算结果。动画插值使用`interpolate`函数实现平滑过渡,避免了帧率波动。图表的Y轴域基于完整数据集计算,防止了Y轴的跳动。对于少于10个数据点的情况,系统会填充空年份以保持图表对齐。
|
||
|
||
**本节来源**
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx#L70-L77)
|
||
- [CashFlowChart.tsx](file://src/CashFlowChart.tsx#L75-L82)
|
||
- [DualChartView.tsx](file://src/DualChartView.tsx#L50-L67)
|
||
|
||
## 故障排除指南
|
||
常见问题包括数据契约不匹配、渲染命令失败和图表显示异常。确保`render-props-types.ts`与后端输出完全匹配,使用Zod模式进行运行时验证。检查`render_props.json`文件是否存在且格式正确。确认Remotion CLI已正确安装,Node.js版本兼容。对于图表问题,检查数据格式是否符合预期,特别是年份和数值的类型。
|
||
|
||
**本节来源**
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L241-L249)
|
||
- [main.py](file://main.py#L73-L76)
|
||
- [remotion.config.ts](file://remotion.config.ts#L1-L12)
|
||
|
||
## 结论
|
||
本视频渲染合成系统成功实现了从财务数据到专业视频报告的自动化转换。通过严格的类型定义和清晰的架构设计,系统确保了前后端数据传递的可靠性和类型安全性。Remotion框架的使用使得复杂的动画和视觉效果得以高效实现。未来可扩展方向包括支持更多图表类型、增强B-Roll智能匹配算法、优化渲染性能以处理更长的视频内容。
|
||
|
||
## 附录
|
||
### 自定义视频模板示例
|
||
```typescript
|
||
// 自定义新图表组件
|
||
import React from "react";
|
||
import { LineChart, Line, XAxis, YAxis } from "recharts";
|
||
import { useCurrentFrame, useVideoConfig, interpolate } from "remotion";
|
||
|
||
interface CustomChartProps {
|
||
data: Array<{ year: number; value: number }>;
|
||
title: string;
|
||
}
|
||
|
||
export const CustomChart: React.FC<CustomChartProps> = ({ data, title }) => {
|
||
const frame = useCurrentFrame();
|
||
const { durationInFrames } = useVideoConfig();
|
||
|
||
const progress = interpolate(frame, [0, durationInFrames], [0, data.length - 1]);
|
||
const visibleData = data.slice(0, Math.floor(progress) + 1);
|
||
|
||
return (
|
||
<div style={{ width: "100%", height: "100%" }}>
|
||
<h2>{title}</h2>
|
||
<LineChart data={visibleData}>
|
||
<XAxis dataKey="year" />
|
||
<YAxis dataKey="value" />
|
||
<Line type="monotone" dataKey="value" stroke="#8884d8" />
|
||
</LineChart>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 添加新可视化组件步骤
|
||
1. 在`src`目录下创建新的`.tsx`文件
|
||
2. 定义组件接口和实现
|
||
3. 在`Root.tsx`中导入并注册为新的`Composition`
|
||
4. 更新`render-props-types.ts`以包含新组件所需的数据结构
|
||
5. 在`main.py`中修改`build_render_props`方法以包含新数据
|
||
|
||
**本节来源**
|
||
- [LifePlanChart.tsx](file://src/LifePlanChart.tsx#L1-L349)
|
||
- [Root.tsx](file://src/Root.tsx#L1-L68)
|
||
- [render-props-types.ts](file://frontend/src/render-props-types.ts#L1-L251)
|
||
- [main.py](file://main.py#L145-L198) |