You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5.7 KiB
5.7 KiB
分镜视频缩略图功能实现
日期: 2026-02-14
类型: Feature
影响范围: 后端 API、数据库、AI 生成流程
📋 变更概述
为 storyboard_videos 表添加 thumbnail_url 字段,并在视频生成完成后自动提取第一帧作为缩略图。
🗄️ 数据库变更
1. Model 更新
文件: server/app/models/storyboard_resource.py
class StoryboardVideo(SQLModel, table=True):
# ... 其他字段
thumbnail_url: Optional[str] = Field(default=None, max_length=500) # 新增字段
2. 数据库迁移
文件: server/alembic/versions/20260214_1214_add_thumbnail_url_to_storyboard_videos.py
ALTER TABLE storyboard_videos ADD COLUMN thumbnail_url VARCHAR(500);
执行命令:
docker exec jointo-server-app python scripts/db_migrate.py upgrade
🔧 业务逻辑实现
1. 缩略图生成服务
文件: server/app/services/ai_generation_result_service.py
新增方法: _generate_video_thumbnail
实现逻辑:
- 从 MinIO 下载视频文件
- 使用
ffmpeg提取第一帧(0 秒位置) - 缩放到 300x300(保持宽高比)
- 上传缩略图到 MinIO(category:
thumbnail_video) - 返回缩略图 URL
参考实现: project_resource_service._generate_video_thumbnail
2. 视频保存逻辑更新
文件: server/app/services/ai_generation_result_service.py
方法: _save_storyboard_video
变更内容:
# 生成视频缩略图
thumbnail_url = await self._generate_video_thumbnail(
video_url=output_data['file_url'],
checksum=output_data['checksum'],
user_id=user_id
)
# 保存到数据库
video = StoryboardVideo(
# ... 其他字段
thumbnail_url=thumbnail_url, # ✅ 新增
)
3. Schema 更新
文件: server/app/schemas/storyboard_resource.py
类: VideoResponse
class VideoResponse(BaseModel):
# ... 其他字段
thumbnail_url: Optional[str] = Field(None, serialization_alias="thumbnailUrl", description="缩略图URL")
@field_serializer("thumbnail_url")
def serialize_thumbnail_url(self, value: Optional[str]) -> Optional[str]:
"""统一构建缩略图访问 URL"""
if value is None:
return None
return build_file_url(value)
📡 API 响应变更
获取分镜视频列表/详情
API: GET /api/v1/storyboards/{storyboard_id}/videos
响应示例:
{
"success": true,
"data": {
"videoId": "01a2b3c4-...",
"storyboardId": "01b3c4d5-...",
"url": "http://localhost:9000/jointo/videos/video_xxx.mp4",
"thumbnailUrl": "http://localhost:9000/jointo/thumbnail_video/thumbnail_xxx.jpg", // ✅ 新增
"status": 2,
"isActive": true,
// ... 其他字段
}
}
🧪 测试命令
1. 创建 AI 对话并生成视频
# 1. 创建对话会话
curl -X POST 'http://localhost:8000/api/v1/ai/conversations' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"projectId": "PROJECT_ID",
"targetType": 1,
"targetId": "STORYBOARD_ID",
"mediaType": 2
}'
# 2. 发送消息生成视频(替换 {conversation_id})
curl -X POST 'http://localhost:8000/api/v1/ai/conversations/{conversation_id}/messages' \
-H 'Authorization: Bearer YOUR_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"content": "生成一个日落海滩的视频",
"generate": {
"generationType": "video",
"modelId": "wan-video-1.0",
"aiParams": {
"resolution": "1080p",
"aspectRatio": "16:9",
"duration": 5
}
}
}'
2. 查询视频缩略图
# 查看视频详情(确认 thumbnailUrl 字段)
curl -X GET 'http://localhost:8000/api/v1/storyboards/{storyboard_id}/videos' \
-H 'Authorization: Bearer YOUR_TOKEN'
🔍 技术要点
ffmpeg 命令
ffmpeg -i input.mp4 \
-ss 00:00:00 \ # 从第 0 秒开始
-vframes 1 \ # 提取 1 帧
-vf scale=300:300:force_original_aspect_ratio=decrease \ # 缩放
-y output.jpg # 覆盖输出
依赖关系
- ffmpeg: 容器内必须安装(已在 Dockerfile 中配置)
- httpx: 下载视频文件
- FileStorageService: 上传缩略图到 MinIO
错误处理
- 缩略图生成失败不影响视频保存(
thumbnail_url为NULL) - 日志记录所有错误便于排查
- 临时文件自动清理
📝 相关文件清单
server/
├── alembic/versions/
│ └── 20260214_1214_add_thumbnail_url_to_storyboard_videos.py # 迁移脚本
├── app/
│ ├── models/
│ │ └── storyboard_resource.py # Model 更新
│ ├── schemas/
│ │ └── storyboard_resource.py # Schema 更新
│ └── services/
│ └── ai_generation_result_service.py # 业务逻辑实现
└── docs/server/changelogs/
└── 2026-02-14-storyboard-video-thumbnail.md # 本文档
✅ 验证清单
- 数据库字段已添加
- 迁移脚本已执行成功
- Model 更新完成
- Schema 更新完成
- 缩略图生成逻辑已实现
- API 响应包含
thumbnailUrl字段 - 前端集成测试(待前端实现)
- 生产环境部署验证(待部署)
🚀 后续优化建议
- 异步处理: 将缩略图生成改为 Celery 异步任务,避免阻塞视频保存
- 多帧选择: 支持提取视频中间帧或多帧合成
- 缓存策略: 对同一视频的缩略图请求添加缓存
- CDN 加速: 缩略图 URL 接入 CDN 提升加载速度
- 质量配置: 支持自定义缩略图尺寸和质量参数