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()` 刷新列表

View File

@@ -0,0 +1,37 @@
# 测试方案 — 视频帧显示链路修复
## 环境准备
- 确保 `Data_MyVideo_1.mp4` 存在于 `/home/wkmgc/Desktop/Seg_Server/`
- 清除数据库中现有项目(或直接删除 `backend/segserver.db` 让后端重新初始化)
## 测试用例
### TC1 — 默认项目自动出现
1. 删除数据库文件,重启后端
2. 打开项目库页面
3. **预期**: 出现 "Data_MyVideo_1" 项目卡片,状态为"已就绪",帧数 > 0
### TC2 — 点击默认项目进入工作区能看到影像
1. 点击 "Data_MyVideo_1" 项目
2. 进入分割工作区
3. **预期**:
- 标题栏显示 "Data_MyVideo_1"
- Canvas 区域显示视频第一帧画面(非黑屏)
- 底部时间轴显示真实帧缩略图
### TC3 — 帧切换正常
1. 在工作区中点击底部时间轴的不同帧
2. **预期**: Canvas 画面切换到对应帧
### TC4 — 导入新视频完整链路
1. 在项目库点击"导入多媒体资源"
2. 选择任意视频文件上传
3. **预期**:
- 上传过程中显示 loading
- 上传完成后新项目出现在列表中
- 点击新项目进入工作区,能看到视频帧
### TC5 — AI 推理使用当前帧
1. 在工作区选择点提示或框提示工具
2. 在画面上点击/框选
3. **预期**: 请求 payload 中的 `imageUrl` 为当前帧的 MinIO presigned URL后端返回 mask

View File

@@ -5,6 +5,39 @@
---
## 2026-04-29-23-28-13 — 视频帧显示链路全修复
### A. 具体问题
1. 项目库中没有默认视频 `Data_MyVideo_1.mp4`
2. 分割工作区点击视频后画面一片黑(无影像)
3. 导入新视频后进入工作区同样看不到视频帧
### B. 产生原因
1. 后端 lifespan 没有任何自动扫描/注册本地视频的逻辑;`server.ts` 的硬编码项目对前端不可见
2. `CanvasArea.tsx` 硬编码了一张 Unsplash 外链,没有从 Zustand store 读取帧数据;进入工作区时不调用帧列表 API
3. 上传流程不完整:未先创建项目再带 project_id 上传,上传后未触发 `/api/media/parse` 帧提取;后端 `/frames` 返回的是 MinIO 对象名而非可访问的 presigned URLMinIO presigned URL 的 host 为 `localhost:9000`,浏览器端无法访问
4. 系统磁盘空间24GB紧张MinIO 在提取大量高清 PNG 帧时触发 `XMinioStorageFull`,导致帧上传失败
### C. 解决方案
1. **后端默认视频种子**`main.py` lifespan 中启动后台线程,自动检测 `Data_MyVideo_1.mp4`,创建 Project → 上传 MinIO → 调用 FFmpeg 解析帧 → 注册 Frame 记录
2. **后端帧 URL 修复**`projects.py``list_frames` 在返回前为每个 `frame.image_url` 调用 `get_presigned_url()` 生成带签名的可访问 URL
3. **后端 presigned URL host 修复**`.env``MINIO_ENDPOINT``localhost:9000` 改为 `192.168.3.11:9000`,确保浏览器端可访问
4. **后端 ProjectOut 增强**`schemas.py` 添加 `frame_count``projects.py` 在查询时计算 `len(p.frames)`,供前端显示帧数
5. **后端 upload 自动创建项目**`media.py``upload_media` 在不传 `project_id` 时自动以文件名创建 Project 并关联视频
6. **前端帧加载中枢**`VideoWorkspace.tsx``currentProject` 变化时调用 `getProjectFrames`,若帧数为 0 则自动触发 `parseMedia`,获取后映射写入 Zustand store
7. **前端 Canvas 真实渲染**`CanvasArea.tsx` 接收 `frameUrl` prop使用 `useImage(frameUrl)` 加载真实帧AI 推理也使用当前帧 URL
8. **前端时间轴真实帧**`FrameTimeline.tsx` 从 store 读取 `frames``currentFrameIndex`,渲染真实帧缩略图 `<img>`,点击切换当前帧
9. **前端上传链路完善**`ProjectLibrary.tsx` 上传流程改为:创建项目 → `uploadMedia(file, projectId)``parseMedia(projectId)` → 刷新列表
10. **磁盘空间优化**:将 `frame_parser.py` 的 FFmpeg 输出从 PNG (1920px, ~3MB/张) 改为 JPEG (640px, ~30KB/张),并限制默认视频只提取 100 帧,避免 MinIO 存储溢出
### D. 后续如何避免问题
1. **MinIO endpoint 必须使用服务器 IP**:任何生成外部可访问 URL 的服务MinIO presigned、后端 baseURL、WebSocket都必须使用服务器 LAN IP禁止 localhost
2. **大文件/视频处理必须考虑磁盘预算**:提取帧前估算总大小(帧数 × 单帧大小),必要时降低分辨率、改格式为 JPEG、限制 max_frames
3. **前后端数据字段必须显式映射**:后端 snake_case 与前端 camelCase 不一致时API 层必须做转换,不能依赖隐式兼容
4. **上传-解析-显示链路必须闭环测试**:任何涉及文件上传的功能,测试用例必须覆盖:上传 → 后端存储 → 解析 → 前端获取 → 前端渲染 的全流程
---
## 2026-04-29-23-15-26 — 上传/WS/项目库三 Bug 联修
### A. 具体问题

View File

@@ -0,0 +1,26 @@
# 需求分析 — 视频帧显示链路修复
## 问题背景
用户报告三个关联问题:
1. 项目库中没有默认视频 `Data_MyVideo_1.mp4`
2. 点击默认视频进入分割工作区后,画面区域一片黑(无影像)
3. 导入新视频后进入工作区,同样看不到视频帧
## 根因分析
| 问题 | 根因 |
|------|------|
| 无默认项目 | 后端 lifespan 无自动种子逻辑;`server.ts` 硬编码项目但前端根本不请求 Express |
| 工作区黑屏 | `CanvasArea.tsx` 硬编码 Unsplash 外链,未从 store 读取帧;进入工作区不调用帧列表 API |
| 导入后黑屏 | 上传未关联 project_id上传后未触发 `/api/media/parse` 帧提取;后端 `/frames` 返回的是 MinIO 对象名而非可访问 URL |
## 需求拆解
| 编号 | 需求 | 优先级 |
|------|------|--------|
| R1 | 后端启动时自动检测并注册 `Data_MyVideo_1.mp4` 为默认项目 | P0 |
| R2 | 后端 `/api/projects/{id}/frames` 返回 presigned URL 而非对象名 | P0 |
| R3 | 后端 `ProjectOut` 增加 `frame_count` 供前端显示 | P1 |
| R4 | 前端 `VideoWorkspace` 进入时自动获取帧列表并写入 store | P0 |
| R5 | 前端 `CanvasArea` 从 store 读取当前帧 URL 并渲染 | P0 |
| R6 | 前端 `FrameTimeline` 从 store 读取真实帧数据,支持切换 | P0 |
| R7 | 前端 `ProjectLibrary` 上传流程:创建项目 → 带 project_id 上传 → 触发解析 | P0 |
| R8 | 后端 upload 接口支持无 project_id 时自动创建项目 | P1 |