20260429_232813-fix: video frame display pipeline — default project seed, presigned URLs, Canvas/FrameTimeline real frames, upload-parse flow

This commit is contained in:
2026-04-29 23:42:18 +08:00
parent 51f1a60216
commit 35d6e1503c
16 changed files with 454 additions and 56 deletions

View File

@@ -0,0 +1,66 @@
# 实现方案 — 视频帧显示链路修复
## 后端
### 1. `backend/schemas.py` — ProjectOut 增加 frame_count
```python
class ProjectOut(ProjectBase):
id: int
created_at: datetime
updated_at: datetime
frame_count: int = 0
```
### 2. `backend/routers/projects.py` — 三处修改
- `list_projects`: 为每个项目计算 `frame_count = len(p.frames)`(利用 ORM relationship
- `list_frames`: 返回前将每个 `frame.image_url` 替换为 `get_presigned_url(frame.image_url, expires=3600)`
- `get_project`: 同样添加 `frame_count`
### 3. `backend/main.py` — lifespan 默认视频种子
启动时异步后台任务:
- 若数据库无项目且 `Data_MyVideo_1.mp4` 存在
- 创建 Project → 上传 MinIO → 调用 FFmpeg 解析帧 → 上传帧到 MinIO → 注册 Frame 记录 → 更新 status="ready"
- 使用 `asyncio.to_thread()` 避免阻塞事件循环
### 4. `backend/routers/media.py` — upload 自动创建项目
`project_id` 为空时:
- 以文件名创建 Project
- 将上传文件关联到该 project
- 返回中增加 `project_id`
## 前端
### 5. `src/lib/api.ts` — 新增两个 API
```ts
export async function getProjectFrames(projectId: string): Promise<Array<{ id: number; project_id: number; frame_index: number; image_url: string; width: number | null; height: number | null }>>
export async function parseMedia(projectId: string): Promise<{ project_id: number; frames_extracted: number; status: string; message: string }>
```
### 6. `src/components/VideoWorkspace.tsx` — 帧加载中枢
- 读取 `currentProject``frames`
- `useEffect` 监听 `currentProject.id`
- 调用 `getProjectFrames`
- 映射后端字段到前端 `Frame[]` 结构写入 store
- 若帧数为 0 且项目有 video_path自动调用 `parseMedia` 触发解析,解析完成后重新获取
- 将当前帧 URL 通过 prop 传给 `CanvasArea`
- 将帧列表和索引控制传给 `FrameTimeline`
- 标题显示 `currentProject?.name`
### 7. `src/components/CanvasArea.tsx` — 真实帧渲染
- 新增 `frameUrl` prop
- `const [image] = useImage(frameUrl || '')`
- `runInference` 使用 `frameUrl` 替代硬编码 URL
### 8. `src/components/FrameTimeline.tsx` — 真实帧导航
- 从 store 读取 `frames``currentFrameIndex`
- `totalFrames = frames.length`
- 每个帧方块显示为 `<img>` 缩略图
- 点击调用 `setCurrentFrame(index)`
- 进度条范围 1..frames.length
### 9. `src/components/ProjectLibrary.tsx` — 完整上传链路
上传流程改为:
1. `createProject({ name: file.name })`
2. `uploadMedia(file, projectId)`
3. `parseMedia(projectId)`
4. `getProjects()` 刷新列表