docs: special folder video media design
This commit is contained in:
parent
edde910cfe
commit
c7cccb91c9
@ -0,0 +1,235 @@
|
||||
# 设计:特殊文件夹视频媒体(上传后服务器压缩)
|
||||
|
||||
> 在特殊文件夹共享照片墙(`#<文件夹名>`)里支持视频作为墙上媒体元素。
|
||||
> 视频上传后先显示处理中占位卡,服务器用 `ffmpeg` 压缩为低带宽 MP4,完成后在墙上播放。
|
||||
> 本设计只开放给特殊文件夹;个人 `#photo` 暂不开放视频入口。
|
||||
|
||||
前置阅读:
|
||||
- [photo-wall.md](../../photo-wall.md)
|
||||
- [2026-06-24-special-folders-design.md](2026-06-24-special-folders-design.md)
|
||||
- [2026-06-24-photo-wall-server-storage-design.md](2026-06-24-photo-wall-server-storage-design.md)
|
||||
|
||||
## 1. 目标与范围
|
||||
|
||||
目标:
|
||||
- 特殊文件夹支持图片和视频混合展示。
|
||||
- 视频像照片一样可以上传、拖拽、缩放、置顶/置底、删除。
|
||||
- 视频上传后不阻塞页面等待转码;墙上立即出现“AI 正在压缩视频...”风格的处理中卡片。
|
||||
- 服务器将常见视频格式统一压缩为适合 3M 带宽播放的 MP4。
|
||||
- 不提供视频下载入口,不添加下载按钮。
|
||||
|
||||
非目标:
|
||||
- 个人 `#photo` 视频上传。
|
||||
- 多清晰度转码、自适应码率、HLS/DASH。
|
||||
- 视频封面生成、时间轴截图、在线播放统计。
|
||||
- 大视频断点续传。
|
||||
|
||||
## 2. 总体架构
|
||||
|
||||
前端把 `PhotoWallPage` 从“照片元素”扩展为“媒体元素”,新增 `video` 类型。特殊文件夹使用新的媒体 API 加载图片和视频;图片仍按现有视觉展示,视频按状态渲染为处理中卡片、失败卡片或 `<video controls preload="metadata">` 播放器。
|
||||
|
||||
后端把特殊文件夹存储从“图片列表”升级为“媒体列表”。图片可以同步保存并立即进入 `ready` 状态。视频上传后先写入临时文件,创建状态文件,后台调用 `ffmpeg` 转码;转码完成后只保留压缩后的 `.mp4` 和状态文件,删除原始临时文件。
|
||||
|
||||
`ffmpeg -movflags +faststart` 只用于优化浏览器在线播放体验:把 MP4 索引移动到文件开头,让 `<video>` 不必等完整文件下载完才开始播放。UI 不提供下载功能。
|
||||
|
||||
## 3. 媒体模型
|
||||
|
||||
特殊文件夹媒体项:
|
||||
|
||||
```ts
|
||||
type MediaType = 'image' | 'video'
|
||||
type MediaStatus = 'ready' | 'processing' | 'failed'
|
||||
|
||||
interface FolderMediaItem {
|
||||
id: string
|
||||
type: MediaType
|
||||
status: MediaStatus
|
||||
}
|
||||
```
|
||||
|
||||
服务器为视频维护状态文件,建议路径为 `SPECIAL_DIR/<folder>/media/<id>.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4e5f60708",
|
||||
"type": "video",
|
||||
"status": "processing",
|
||||
"originalName": "trip.mov",
|
||||
"createdAt": "2026-06-29T12:34:56Z",
|
||||
"outputFile": "a1b2c3d4e5f60708.mp4",
|
||||
"error": null
|
||||
}
|
||||
```
|
||||
|
||||
状态语义:
|
||||
- `processing`:原视频已接收,后台正在转码。前端显示处理中占位卡并轮询状态。
|
||||
- `ready`:媒体可通过 `GET /media/{id}` 读取。图片返回原图,视频返回压缩后的 MP4。
|
||||
- `failed`:转码失败。前端显示失败卡片,允许删除后重传。
|
||||
|
||||
## 4. 后端接口
|
||||
|
||||
新增特殊文件夹媒体接口,旧 `/photos*` 接口先保留,前端特殊文件夹切到 `/media*`:
|
||||
|
||||
| 方法 | 路径 | 鉴权 | 行为 |
|
||||
|---|---|---|---|
|
||||
| GET | `/api/web/folder/{name}/media` | AuthUser + 白名单 | 列出图片和视频,返回 `{ items, max, totalBytes, overQuota }` |
|
||||
| POST | `/api/web/folder/{name}/media` | AuthUser + 白名单 | multipart 上传一个图片或视频 |
|
||||
| GET | `/api/web/folder/{name}/media/{id}` | AuthUser + 白名单 | 读取 `ready` 媒体字节 |
|
||||
| GET | `/api/web/folder/{name}/media/{id}/status` | AuthUser + 白名单 | 读取单个媒体状态 |
|
||||
| DELETE | `/api/web/folder/{name}/media/{id}` | AuthUser + 白名单 | 删除媒体文件、临时文件和状态文件 |
|
||||
|
||||
上传响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "a1b2c3d4e5f60708",
|
||||
"type": "video",
|
||||
"status": "processing"
|
||||
}
|
||||
```
|
||||
|
||||
错误码:
|
||||
- `403`:用户无权访问该特殊文件夹。
|
||||
- `404`:文件夹或媒体不存在。
|
||||
- `409`:达到特殊文件夹元素上限。
|
||||
- `413`:上传体或文件过大。
|
||||
- `415`:不支持的媒体格式。
|
||||
- `500`:IO 或转码任务启动失败。
|
||||
|
||||
## 5. 上传与格式
|
||||
|
||||
视频上传使用 multipart,而不是 data URL。原因是视频文件可能很大,data URL 会额外增加约三分之一体积,并迫使前后端把大文件整体放进内存。multipart 更适合 500MB 级文件。
|
||||
|
||||
支持的输入:
|
||||
- 图片:沿用现有 `jpg/png/gif/webp`。
|
||||
- 视频:支持常见格式,至少包括 `mp4/mov/m4v/webm/avi/mkv`。
|
||||
|
||||
约束:
|
||||
- 单个原始视频最大 500MB。
|
||||
- 单个图片仍沿用 8MiB 上限。
|
||||
- 同一个特殊文件夹仍受总元素上限约束,图片、视频、装饰合计不超过当前 `MAX_ITEMS`。
|
||||
- 视频上传不限制时长。
|
||||
|
||||
## 6. 视频转码
|
||||
|
||||
运行时依赖:服务器必须安装 `ffmpeg`,并保证 Rust 服务进程可以调用。
|
||||
|
||||
转码目标适配 3M 服务器带宽:
|
||||
- 容器:MP4。
|
||||
- 视频编码:H.264。
|
||||
- 音频编码:AAC。
|
||||
- 输出限制到 720p 档:横屏最大 1280x720,竖屏最大 720x1280,保持原方向,不放大小视频。
|
||||
- 帧率限制到 24fps。
|
||||
- 视频码率目标约 900kbps,缓冲 1800kbps。
|
||||
- 音频码率 96kbps。
|
||||
- `+faststart` 优化在线播放。
|
||||
|
||||
建议 ffmpeg 参数方向:
|
||||
|
||||
```bash
|
||||
ffmpeg -y -i input \
|
||||
-vf "scale='if(gt(iw,ih),min(1280,iw),-2)':'if(gt(ih,iw),min(1280,ih),-2)',fps=24" \
|
||||
-c:v libx264 -preset veryfast -crf 30 -maxrate 900k -bufsize 1800k \
|
||||
-c:a aac -b:a 96k \
|
||||
-movflags +faststart output.mp4
|
||||
```
|
||||
|
||||
并发策略:
|
||||
- Rust 进程内视频转码并发限制为 1。
|
||||
- 上传请求只负责接收文件、创建状态、启动后台任务,然后立即返回。
|
||||
- 后台任务成功后把状态改为 `ready`,删除临时原视频。
|
||||
- 后台任务失败后把状态改为 `failed`,删除临时原视频,保留短错误摘要。
|
||||
|
||||
## 7. 前端交互
|
||||
|
||||
特殊文件夹工具栏把“上传照片”改为“上传媒体”。选择文件、拖拽文件都支持图片和视频;剪贴板仍只要求保持图片粘贴可用,视频粘贴不是本期目标。
|
||||
|
||||
渲染规则:
|
||||
- `image + ready`:沿用当前照片卡片。
|
||||
- `video + processing`:显示软木板风格占位卡,文案为“AI 正在压缩视频...”。
|
||||
- `video + ready`:显示带控件的 `<video controls preload="metadata">`,不自动播放。
|
||||
- `video + failed`:显示“视频处理失败”,允许右键删除。
|
||||
|
||||
视频卡片行为:
|
||||
- 支持拖拽、缩放、旋转、置顶/置底、删除。
|
||||
- 双击编辑说明文字,逻辑与照片 caption 一致。
|
||||
- 用户主动点击才播放,避免多个视频同时消耗 3M 带宽。
|
||||
- 不显示下载按钮。
|
||||
|
||||
轮询:
|
||||
- 上传视频返回 `processing` 后,前端创建本地元素并开始轮询 `/status`。
|
||||
- 轮询间隔建议 3 秒。
|
||||
- 状态变为 `ready` 后获取 object URL 并替换播放器。
|
||||
- 状态变为 `failed` 后停止轮询并显示失败卡。
|
||||
- 组件卸载时停止轮询并释放 object URL。
|
||||
|
||||
## 8. 布局与状态兼容
|
||||
|
||||
当前特殊文件夹共享状态使用 `photoLayout`。本功能升级为通用 `mediaLayout`:
|
||||
|
||||
```ts
|
||||
interface MediaLayout {
|
||||
x: number
|
||||
y: number
|
||||
rot: number
|
||||
scale: number
|
||||
z: number
|
||||
w: number
|
||||
caption: string
|
||||
}
|
||||
|
||||
interface WallState {
|
||||
bgIndex: number
|
||||
decorations: El[]
|
||||
mediaLayout: Record<string, MediaLayout>
|
||||
}
|
||||
```
|
||||
|
||||
兼容策略:
|
||||
- 读取状态时,如果存在 `mediaLayout`,优先使用。
|
||||
- 如果只有旧的 `photoLayout`,把它当作 `mediaLayout` 读取。
|
||||
- 保存状态时写 `mediaLayout`;可以同时保留 `photoLayout` 一段时间以兼容旧前端,但新前端不再依赖它。
|
||||
- 已有图片布局不丢失;新视频布局按同一媒体 id 保存。
|
||||
|
||||
## 9. 删除与清理
|
||||
|
||||
删除媒体时:
|
||||
- `ready image`:删除图片文件。
|
||||
- `ready video`:删除压缩后的 `.mp4` 和状态文件。
|
||||
- `processing video`:写入删除标记或直接删除状态文件;后台任务结束后发现状态不存在即清理输出。
|
||||
- `failed video`:删除状态文件和可能残留的临时文件。
|
||||
|
||||
列表接口不返回孤儿临时文件。启动时不要求恢复未完成任务;若服务重启导致任务中断,残留 `processing` 可以在下一次列表时标记为 `failed` 或由清理逻辑处理。
|
||||
|
||||
## 10. 测试策略
|
||||
|
||||
后端:
|
||||
- 媒体 MIME/扩展名识别。
|
||||
- 视频 id 与图片 id 的路径安全校验。
|
||||
- 状态文件 `processing -> ready -> failed` 读写。
|
||||
- 特殊文件夹权限:未注册 404、无权 403、有权通过。
|
||||
- 删除会清理成品、状态和临时文件。
|
||||
- ffmpeg 命令构造包含 720p、24fps、900k、96k、`+faststart`。
|
||||
|
||||
前端:
|
||||
- 特殊文件夹图片上传和显示仍可用。
|
||||
- 上传视频后立即出现“AI 正在压缩视频...”占位。
|
||||
- 轮询到 `ready` 后替换为播放器。
|
||||
- `failed` 状态显示失败卡并可删除。
|
||||
- 视频拖拽、缩放、删除、caption、保存布局不破坏图片和装饰。
|
||||
|
||||
端到端:
|
||||
- 在本地安装 `ffmpeg` 后上传一个小视频,确认转码完成、播放不卡、刷新后布局保留。
|
||||
- 生产部署后用特殊文件夹验证上传视频、等待处理、播放、删除。
|
||||
|
||||
## 11. 部署与文档
|
||||
|
||||
这是后端改动,需要全量重部署:重编 Rust、上传二进制和前端静态文件、重启 Rust 服务。
|
||||
|
||||
服务器新增运行时依赖:
|
||||
- `ffmpeg`。
|
||||
|
||||
文档需要更新:
|
||||
- `docs/photo-wall.md`:说明特殊文件夹支持视频媒体,个人墙暂不支持。
|
||||
- `docs/superpowers/plans/2026-06-18-deploy.md`:补充安装 `ffmpeg` 和视频转码部署注意事项。
|
||||
- `docs/superpowers/specs/2026-06-24-special-folders-design.md`:标注媒体接口和视频扩展已由本设计接续。
|
||||
Loading…
Reference in New Issue
Block a user