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

9.4 KiB
Raw Blame History

设计:特殊文件夹视频媒体(上传后服务器压缩)

在特殊文件夹共享照片墙(#<文件夹名>)里支持视频作为墙上媒体元素。 视频上传后先显示处理中占位卡,服务器用 ffmpeg 压缩为低带宽 MP4完成后在墙上播放。 本设计只开放给特殊文件夹;个人 #photo 暂不开放视频入口。

前置阅读:

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. 媒体模型

特殊文件夹媒体项:

type MediaType = 'image' | 'video'
type MediaStatus = 'ready' | 'processing' | 'failed'

interface FolderMediaItem {
  id: string
  type: MediaType
  status: MediaStatus
}

服务器为视频维护状态文件,建议路径为 SPECIAL_DIR/<folder>/media/<id>.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 + 白名单 删除媒体文件、临时文件和状态文件

上传响应:

{
  "id": "a1b2c3d4e5f60708",
  "type": "video",
  "status": "processing"
}

错误码:

  • 403:用户无权访问该特殊文件夹。
  • 404:文件夹或媒体不存在。
  • 409:达到特殊文件夹元素上限。
  • 413:上传体或文件过大。
  • 415:不支持的媒体格式。
  • 500IO 或转码任务启动失败。

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 参数方向:

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

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:标注媒体接口和视频扩展已由本设计接续。