PersonalWebApplication/docs/superpowers/specs/2026-06-29-special-folder-video-media-design.md

248 lines
10 KiB
Markdown
Raw Normal View History

# 设计:特殊文件夹视频媒体(上传后服务器压缩)
> 在特殊文件夹共享照片墙(`#<文件夹名>`)里支持视频作为墙上媒体元素。
> 视频上传后先显示处理中占位卡,服务器用 `ffmpeg` 压缩为低带宽 MP4完成后在墙上播放。
> 本设计只开放给特殊文件夹;个人 `#photo` 暂不开放视频入口。
2026-07-09 23:55:17 +08:00
>
> 状态:规划中,当前代码尚未实现 `/media*` 接口、视频上传、转码和视频卡片渲染。
前置阅读:
- [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. 目标与范围
目标:
- 特殊文件夹支持图片和视频混合展示。
- 视频像照片一样可以上传、拖拽、缩放、置顶/置底、删除。
2026-07-09 23:55:17 +08:00
- 视频上传后不阻塞页面等待转码墙上立即出现“thinking”风格的处理中卡片。
- 服务器将常见视频格式统一压缩为适合 3M 带宽播放的 MP4。
2026-07-09 23:55:17 +08:00
- 不提供显式视频下载入口,不添加下载按钮;这不是防下载安全边界。
非目标:
- 个人 `#photo` 视频上传。
- 多清晰度转码、自适应码率、HLS/DASH。
- 视频封面生成、时间轴截图、在线播放统计。
- 大视频断点续传。
## 2. 总体架构
前端把 `PhotoWallPage` 从“照片元素”扩展为“媒体元素”,新增 `video` 类型。特殊文件夹使用新的媒体 API 加载图片和视频;图片仍按现有视觉展示,视频按状态渲染为处理中卡片、失败卡片或 `<video controls preload="metadata">` 播放器。
后端把特殊文件夹存储从“图片列表”升级为“媒体列表”。图片可以同步保存并立即进入 `ready` 状态。视频上传后先写入临时文件,创建状态文件,后台调用 `ffmpeg` 转码;转码完成后只保留压缩后的 `.mp4` 和状态文件,删除原始临时文件。
2026-07-09 23:55:17 +08:00
`ffmpeg -movflags +faststart` 只用于优化浏览器在线播放体验:把 MP4 索引移动到文件开头,让 `<video>` 不必等完整文件下载完才开始播放。UI 不提供显式下载按钮;浏览器原生视频控件仍需加 `controlsList="nodownload"` 降低下载入口暴露,但这不构成防下载或防复制安全边界。
## 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 \
2026-07-09 23:55:17 +08:00
-vf "scale='min(iw,if(gte(iw,ih),1280,720))':'min(ih,if(gte(iw,ih),720,1280))':force_original_aspect_ratio=decrease:force_divisible_by=2,fps=24" \
-c:v libx264 -preset veryfast -crf 30 -maxrate 900k -bufsize 1800k \
-c:a aac -b:a 96k \
-movflags +faststart output.mp4
```
2026-07-09 23:55:17 +08:00
实现计划必须用横屏、竖屏和近正方形视频测试该 filter确认不会放大小视频且输出不会超过
1280x720 或 720x1280 的目标边界。
并发策略:
- Rust 进程内视频转码并发限制为 1。
- 上传请求只负责接收文件、创建状态、启动后台任务,然后立即返回。
- 后台任务成功后把状态改为 `ready`,删除临时原视频。
- 后台任务失败后把状态改为 `failed`,删除临时原视频,保留短错误摘要。
## 7. 前端交互
特殊文件夹工具栏把“上传照片”改为“上传媒体”。选择文件、拖拽文件都支持图片和视频;剪贴板仍只要求保持图片粘贴可用,视频粘贴不是本期目标。
渲染规则:
- `image + ready`:沿用当前照片卡片。
2026-07-09 23:55:17 +08:00
- `video + processing`显示软木板风格占位卡文案为“thinking”。
- `video + ready`:显示带控件的 `<video controls preload="metadata">`,不自动播放。
- `video + failed`:显示“视频处理失败”,允许右键删除。
视频卡片行为:
- 支持拖拽、缩放、旋转、置顶/置底、删除。
- 双击编辑说明文字,逻辑与照片 caption 一致。
- 用户主动点击才播放,避免多个视频同时消耗 3M 带宽。
2026-07-09 23:55:17 +08:00
- 不显示显式下载按钮;`<video>` 使用 `controlsList="nodownload"`。该属性只是 UI 降噪,
不能当成安全控制。
轮询:
- 上传视频返回 `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>
}
```
兼容策略:
2026-07-09 23:55:17 +08:00
- 前端读取状态时,如果存在 `mediaLayout`,优先使用。
- 如果只有旧的 `photoLayout`,前端把它当作 `mediaLayout` 读取。
- 保存状态时写 `mediaLayout`;可以同时保留 `photoLayout` 一段时间以兼容旧前端,但新前端不再依赖它。
2026-07-09 23:55:17 +08:00
- 后端 state 接口必须同步迁移:`PUT /api/web/folder/{name}/state` 需要接受
`mediaLayout` 和旧 `photoLayout``GET` 返回 `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`。
2026-07-09 23:55:17 +08:00
- ffmpeg 缩放测试覆盖横屏、竖屏、正方形/近正方形和小尺寸输入,确认不超过目标尺寸且不放大。
前端:
- 特殊文件夹图片上传和显示仍可用。
2026-07-09 23:55:17 +08:00
- 上传视频后立即出现“thinking”占位。
- 轮询到 `ready` 后替换为播放器。
- `failed` 状态显示失败卡并可删除。
- 视频拖拽、缩放、删除、caption、保存布局不破坏图片和装饰。
2026-07-09 23:55:17 +08:00
- `<video>` 渲染包含 `controlsList="nodownload"`,且产品文案只承诺不提供下载入口,
不承诺防下载。
端到端:
- 在本地安装 `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`:标注媒体接口和视频扩展已由本设计接续。