forked from 77media/video-flow
322 lines
7.9 KiB
Markdown
322 lines
7.9 KiB
Markdown
# 视频导出流式接口 API 规范
|
||
|
||
## 接口概述
|
||
|
||
**接口地址**: `POST /api/export/stream`
|
||
**接口类型**: Server-Sent Events (SSE) 流式接口
|
||
**功能描述**: 实时流式视频导出,支持进度推送和高质量流复制模式
|
||
|
||
## 请求参数
|
||
|
||
### 请求头
|
||
```http
|
||
Content-Type: application/json
|
||
Accept: text/event-stream
|
||
```
|
||
|
||
### 请求体结构
|
||
|
||
```typescript
|
||
interface ExportRequest {
|
||
project_id?: string; // 项目ID(可选)
|
||
ir: IRData; // 时间轴中间表示数据(必需)
|
||
options?: ExportOptions; // 导出选项(可选)
|
||
videoFiles?: Record<string, string>; // 视频文件base64数据(可选)
|
||
}
|
||
```
|
||
|
||
## 详细参数说明
|
||
|
||
### 1. project_id (可选)
|
||
- **类型**: `string`
|
||
- **描述**: 项目唯一标识符
|
||
- **默认值**: 如果未提供,系统会生成 `default_project_{task_id前8位}`
|
||
- **示例**: `"project_12345"`
|
||
|
||
### 2. ir (必需) - 时间轴中间表示数据
|
||
|
||
```typescript
|
||
interface IRData {
|
||
width: number; // 视频宽度(必需)
|
||
height: number; // 视频高度(必需)
|
||
fps: number; // 帧率(必需)
|
||
duration: number; // 总时长,单位毫秒(必需)
|
||
video: VideoElement[]; // 视频轨道数据(必需)
|
||
texts?: TextElement[]; // 字幕轨道数据(可选)
|
||
audio?: AudioElement[]; // 音频轨道数据(可选)
|
||
transitions?: TransitionElement[]; // 转场效果(可选)
|
||
}
|
||
```
|
||
|
||
#### VideoElement 结构
|
||
```typescript
|
||
interface VideoElement {
|
||
id: string; // 视频元素唯一ID
|
||
src: string; // 视频源路径/URL/blob URL
|
||
start: number; // 在时间轴上的开始时间(毫秒)
|
||
end?: number; // 在时间轴上的结束时间(毫秒)
|
||
in: number; // 视频内部开始时间(毫秒)
|
||
out: number; // 视频内部结束时间(毫秒)
|
||
_source_type?: 'local' | 'remote_url' | 'blob'; // 源类型标识
|
||
}
|
||
```
|
||
|
||
#### TextElement 结构
|
||
```typescript
|
||
interface TextElement {
|
||
id: string; // 字幕元素唯一ID
|
||
text: string; // 字幕内容
|
||
start: number; // 开始时间(毫秒)
|
||
end: number; // 结束时间(毫秒)
|
||
style?: TextStyle; // 字幕样式
|
||
}
|
||
|
||
interface TextStyle {
|
||
fontFamily?: string; // 字体,默认 'Arial'
|
||
fontSize?: number; // 字体大小,默认 40
|
||
color?: string; // 字体颜色,默认 '#FFFFFF'
|
||
backgroundColor?: string; // 背景色,默认 'transparent'
|
||
fontWeight?: 'normal' | 'bold'; // 字体粗细
|
||
fontStyle?: 'normal' | 'italic'; // 字体样式
|
||
align?: 'left' | 'center' | 'right'; // 对齐方式
|
||
shadow?: boolean; // 是否显示阴影
|
||
rotation?: number; // 旋转角度
|
||
}
|
||
```
|
||
|
||
### 3. options (可选) - 导出选项
|
||
|
||
```typescript
|
||
interface ExportOptions {
|
||
quality?: 'preview' | 'standard' | 'professional'; // 质量等级
|
||
codec?: string; // 编码器,默认 'libx264'
|
||
subtitleMode?: 'hard' | 'soft'; // 字幕模式,默认 'hard'
|
||
bitrate?: string; // 比特率,如 '5000k'
|
||
preset?: string; // 编码预设,如 'medium'
|
||
}
|
||
```
|
||
|
||
**默认值**:
|
||
```json
|
||
{
|
||
"quality": "standard",
|
||
"codec": "libx264",
|
||
"subtitleMode": "hard"
|
||
}
|
||
```
|
||
|
||
### 4. videoFiles (可选) - Base64视频数据
|
||
|
||
```typescript
|
||
interface VideoFiles {
|
||
[blobId: string]: string; // blobId -> base64编码的视频数据
|
||
}
|
||
```
|
||
|
||
**使用场景**: 当 `VideoElement.src` 为 blob URL 时,需要提供对应的 base64 数据
|
||
|
||
## 完整请求示例
|
||
|
||
### 基础示例
|
||
```json
|
||
{
|
||
"project_id": "demo_project_001",
|
||
"ir": {
|
||
"width": 1920,
|
||
"height": 1080,
|
||
"fps": 30,
|
||
"duration": 15000,
|
||
"video": [
|
||
{
|
||
"id": "video_1",
|
||
"src": "https://example.com/video1.mp4",
|
||
"start": 0,
|
||
"end": 10000,
|
||
"in": 2000,
|
||
"out": 12000,
|
||
"_source_type": "remote_url"
|
||
},
|
||
{
|
||
"id": "video_2",
|
||
"src": "blob:http://localhost:3000/abc-123",
|
||
"start": 10000,
|
||
"end": 15000,
|
||
"in": 0,
|
||
"out": 5000,
|
||
"_source_type": "blob"
|
||
}
|
||
],
|
||
"texts": [
|
||
{
|
||
"id": "subtitle_1",
|
||
"text": "欢迎观看演示视频",
|
||
"start": 1000,
|
||
"end": 4000,
|
||
"style": {
|
||
"fontSize": 48,
|
||
"color": "#FFFFFF",
|
||
"fontFamily": "Arial",
|
||
"align": "center"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"options": {
|
||
"quality": "professional",
|
||
"codec": "libx264",
|
||
"subtitleMode": "hard"
|
||
},
|
||
"videoFiles": {
|
||
"abc-123": "data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28y..."
|
||
}
|
||
}
|
||
```
|
||
|
||
## 响应格式 (SSE)
|
||
|
||
接口返回 Server-Sent Events 流,每个事件包含以下格式:
|
||
|
||
```
|
||
data: {"type": "progress", "message": "处理中...", "progress": 0.5}
|
||
|
||
```
|
||
|
||
### 事件类型
|
||
|
||
#### 1. start - 开始事件
|
||
```json
|
||
{
|
||
"type": "start",
|
||
"message": "开始导出...",
|
||
"timestamp": "2024-01-01T12:00:00.000Z"
|
||
}
|
||
```
|
||
|
||
#### 2. progress - 进度事件
|
||
```json
|
||
{
|
||
"type": "progress",
|
||
"stage": "preparing|stream_copy|uploading",
|
||
"message": "当前阶段描述",
|
||
"progress": 0.65,
|
||
"timestamp": "2024-01-01T12:00:30.000Z"
|
||
}
|
||
```
|
||
|
||
#### 3. complete - 完成事件
|
||
```json
|
||
{
|
||
"type": "complete",
|
||
"message": "🎬 高清视频导出完成",
|
||
"timestamp": "2024-01-01T12:01:00.000Z",
|
||
"file_size": 52428800,
|
||
"export_id": "export_abc123",
|
||
"quality_mode": "stream_copy",
|
||
"download_url": "https://cdn.example.com/video.mp4",
|
||
"cloud_storage": true
|
||
}
|
||
```
|
||
|
||
#### 4. error - 错误事件
|
||
```json
|
||
{
|
||
"type": "error",
|
||
"message": "导出失败: 文件不存在",
|
||
"timestamp": "2024-01-01T12:00:45.000Z"
|
||
}
|
||
```
|
||
|
||
## 前端集成示例
|
||
|
||
### JavaScript/TypeScript
|
||
```typescript
|
||
async function exportVideo(exportRequest: ExportRequest) {
|
||
const response = await fetch('/api/export/stream', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'text/event-stream'
|
||
},
|
||
body: JSON.stringify(exportRequest)
|
||
});
|
||
|
||
const reader = response.body?.getReader();
|
||
const decoder = new TextDecoder();
|
||
|
||
while (true) {
|
||
const { done, value } = await reader!.read();
|
||
if (done) break;
|
||
|
||
const chunk = decoder.decode(value);
|
||
const lines = chunk.split('\n');
|
||
|
||
for (const line of lines) {
|
||
if (line.startsWith('data: ')) {
|
||
const data = JSON.parse(line.slice(6));
|
||
handleProgressEvent(data);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function handleProgressEvent(event: any) {
|
||
switch (event.type) {
|
||
case 'start':
|
||
console.log('导出开始');
|
||
break;
|
||
case 'progress':
|
||
console.log(`进度: ${event.progress * 100}% - ${event.message}`);
|
||
break;
|
||
case 'complete':
|
||
console.log('导出完成:', event.download_url);
|
||
break;
|
||
case 'error':
|
||
console.error('导出失败:', event.message);
|
||
break;
|
||
}
|
||
}
|
||
```
|
||
|
||
## 重要注意事项
|
||
|
||
### 1. 视频源处理优先级
|
||
1. **本地文件路径** - 直接使用
|
||
2. **HTTP/HTTPS URL** - 自动下载
|
||
3. **Blob URL** - 需要提供 `videoFiles` 中的 base64 数据
|
||
|
||
### 2. 高质量流复制模式
|
||
- 系统默认启用流复制模式,保持原始视频质量
|
||
- 处理速度提升 10-20 倍
|
||
- 零质量损失
|
||
|
||
### 3. 音频兼容性
|
||
- 自动检测混合音频情况
|
||
- 智能处理有音频/无音频片段的兼容性
|
||
|
||
### 4. 错误处理
|
||
- 无效视频源会被自动跳过
|
||
- 详细的错误信息通过 SSE 实时推送
|
||
|
||
### 5. 云存储集成
|
||
- 支持七牛云自动上传
|
||
- 上传失败时提供本地下载链接
|
||
|
||
## 验证接口
|
||
|
||
在正式导出前,建议先调用验证接口:
|
||
|
||
```http
|
||
POST /api/export/validate
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"ir": { /* 同导出接口的ir参数 */ },
|
||
"options": { /* 同导出接口的options参数 */ }
|
||
}
|
||
```
|
||
|
||
验证接口会检查:
|
||
- IR 数据完整性
|
||
- 视频分辨率、帧率、时长
|
||
- 导出选项有效性
|
||
- 返回详细的验证错误信息 |