# 实现方案 — 视频帧显示链路修复 ## 后端 ### 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> 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` - 每个帧方块显示为 `` 缩略图 - 点击调用 `setCurrentFrame(index)` - 进度条范围 1..frames.length ### 9. `src/components/ProjectLibrary.tsx` — 完整上传链路 上传流程改为: 1. `createProject({ name: file.name })` 2. `uploadMedia(file, projectId)` 3. `parseMedia(projectId)` 4. `getProjects()` 刷新列表