video2slides: 重构为纯客户端 app(浏览器抽帧 + IndexedDB),后端归零
deploy articulate / build-and-deploy (push) Successful in 1m1s
deploy cube / build-and-deploy (push) Successful in 1m22s
deploy karaoke / build-and-deploy (push) Successful in 55s
deploy llm-proxy / build-and-deploy (push) Successful in 1m44s
deploy music / build-and-deploy (push) Successful in 2m14s
deploy notes / build-and-deploy (push) Successful in 1m40s
deploy simpleasm / build-and-deploy (push) Successful in 1m17s
deploy video2slides / build-and-deploy (push) Successful in 39s
deploy werewolf / build-and-deploy (push) Successful in 1m7s
deploy webgl / build-and-deploy (push) Successful in 1m17s
deploy write / build-and-deploy (push) Successful in 1m13s
deploy articulate / build-and-deploy (push) Successful in 1m1s
deploy cube / build-and-deploy (push) Successful in 1m22s
deploy karaoke / build-and-deploy (push) Successful in 55s
deploy llm-proxy / build-and-deploy (push) Successful in 1m44s
deploy music / build-and-deploy (push) Successful in 2m14s
deploy notes / build-and-deploy (push) Successful in 1m40s
deploy simpleasm / build-and-deploy (push) Successful in 1m17s
deploy video2slides / build-and-deploy (push) Successful in 39s
deploy werewolf / build-and-deploy (push) Successful in 1m7s
deploy webgl / build-and-deploy (push) Successful in 1m17s
deploy write / build-and-deploy (push) Successful in 1m13s
- 不再上传视频:<video>+canvas 原生解码按时间戳 seek 抽帧,逐帧 256px 灰度 MAE 算差异,缩略图(320)+大图(1280) 随抽随写 IndexedDB,带进度条+ETA - 阈值/手动 保留弃用/缩放偏好 持久化到 IndexedDB,刷新仍在 - PDF 导出回到客户端 jsPDF,保留帧逐张 base64 嵌入、单帧处理防 OOM - 后端删光业务逻辑(core.rs/handlers.rs),main.rs 缩成 cube_core::base 静态服务 - 不再需要 ffmpeg → Dockerfile 回归 FROM scratch;k8s 去掉 hostPath 卷、降资源 - 真浏览器(Playwright)验证:抽帧/差异/阈值/持久化/导出 全通过
This commit is contained in:
+14
-24
@@ -4,43 +4,33 @@
|
||||
|
||||
线上:<https://video2slides.famzheng.me>
|
||||
|
||||
## 架构
|
||||
## 纯客户端,无后端
|
||||
|
||||
跟 cube 其它 app 同款:Rust + axum + `cube-core`,前端纯静态单页(零构建)。唯一特殊处——依赖 `ffmpeg`/`ffprobe`,所以镜像不是 `FROM scratch` 而是 `debian-slim` 装 ffmpeg + 拷静态 musl binary。
|
||||
视频**不上传**——浏览器用 `<video>` + canvas 原生解码、按时间戳 seek 抽帧,逐帧差异、缩略图、PDF 导出全在前端。结果(缩略图 + 大图 + 元数据)存 **IndexedDB**,每个浏览器各自持久化、刷新还在,换设备/清缓存即清空。
|
||||
|
||||
- 后端 `src/`:`core.rs`(存储 / 后台任务 / ffmpeg 抽帧 / image crate 逐帧灰度 MAE 差异)、`handlers.rs`(REST)、`main.rs`(路由)。
|
||||
- 前端 `frontend/index.html`:上传(进度) → 分析(进度) → 结果网格;顶部常驻 toolbar(阈值滑块 + 仅显示保留帧 + 保留/总数 + 分页),悬停缩略图浮出彩色大图。
|
||||
因此后端没有任何业务逻辑/存储,这个 Rust 进程只是个**静态文件服务器**(`cube_core::base`),把前端发出去而已。也因此镜像回归 cube 惯例:`FROM scratch` + 静态 musl binary,不再需要 ffmpeg / hostPath。
|
||||
|
||||
为什么不上传 + 不用服务端 ffmpeg:大视频(1~5GB)上传慢且易失败,服务端还得存。纯客户端零上传、无限扩展,且内存安全——抽帧逐帧即弃、只在内存留一张 256px 灰度算 diff;缩略图/大图随抽随写 IndexedDB。
|
||||
|
||||
## 流程
|
||||
|
||||
1. **上传** —— 拖拽或选文件,multipart 流式落盘,进度条到 100% 后 ffprobe 校验。
|
||||
2. **分析** —— 设「每隔 N 秒抽一帧」(默认 8),`ffmpeg fps=1/N` 抽帧(限宽 1280),逐帧生成缩略图并算相邻帧灰度逐像素 MAE(0–100)。后台线程跑,前端轮询进度。
|
||||
3. **挑帧** —— `diff ≥ 阈值` = 关键变化高亮保留;`< 阈值` = 跟上一帧太像变灰。纯前端实时切换,不回后端。
|
||||
1. **选视频** —— 拖拽或选本地文件(不上传)。
|
||||
2. **处理** —— 设「每隔 N 秒抽一帧」(默认 8)→ `<video>` 逐点 seek、canvas 抓帧 → 生成缩略图(320px)+大图(1280px) 写 IndexedDB,相邻帧 256px 灰度逐像素 MAE(0–100) 算差异。带进度条 + 预计剩余(ETA)。
|
||||
3. **挑帧** —— toolbar 常驻:差异阈值滑块(`diff ≥ 阈值`=关键变化高亮保留,`<`=变灰)、缩略图缩放、仅显示保留、保留/总数、分页;**点击卡片手动切换 保留/弃用**(覆盖阈值,带角标 + 重置);悬停缩略图浮彩色大图。阈值/手动选择都存回 IndexedDB。
|
||||
4. **导出 PDF** —— 客户端 jsPDF,把保留帧逐张(大图 JPEG 以 base64 直接嵌入、单帧处理避免 OOM)打成 PDF 下载。
|
||||
|
||||
## 隔离与存储
|
||||
## 格式支持
|
||||
|
||||
- 前端在 `localStorage` 存随机 `v2s_client`,所有请求带 `X-Client-Id`,后端按 client 分目录。换浏览器 = 换身份,互相看不到。
|
||||
- 每个视频一个目录:`source.*` 原片、`frames/` 抽出的彩色帧(大图预览)、`thumbs/` 缩略图、`meta.json`(含每帧时间戳与 diff)。
|
||||
- 线上数据落 hostPath `/var/lib/cube/video2slides`(单节点 k3s,pod 重启不丢)。
|
||||
走浏览器原生解码,支持浏览器能播的格式(mp4/H.264、webm/VP9/AV1 等,演讲视频基本都是 mp4)。mkv/avi/冷门编码可能读不了——浏览器播不了就处理不了。
|
||||
|
||||
## 本地跑
|
||||
|
||||
```bash
|
||||
# 后端(数据 / 前端目录用 env 指过去,端口 8080)
|
||||
VIDEO2SLIDES_DATA_DIR=/tmp/v2sdata VIDEO2SLIDES_DIST_DIR=apps/video2slides/frontend \
|
||||
cargo run -p video2slides
|
||||
VIDEO2SLIDES_DIST_DIR=apps/video2slides/frontend cargo run -p video2slides
|
||||
```
|
||||
|
||||
打开 <http://localhost:8080>。需要本机有 `ffmpeg` / `ffprobe`。
|
||||
打开 <http://localhost:8080>。后端纯静态,不需要 ffmpeg。
|
||||
|
||||
## 部署
|
||||
|
||||
push 到 master 且改了 `apps/video2slides/**` → `.gitea/workflows/deploy-video2slides.yml` 自动 build musl → 打 debian+ffmpeg 镜像(`--no-cache`)→ push registry → `kubectl apply` + rollout。
|
||||
|
||||
namespace `cube-video2slides`,已手工建好 `registry-creds`(从别的 ns 拷的,不进 git)。换 ns 重建命令:
|
||||
|
||||
```bash
|
||||
kubectl -n cube-webgl get secret registry-creds -o yaml \
|
||||
| sed 's/namespace: cube-webgl/namespace: cube-video2slides/' \
|
||||
| kubectl apply -f -
|
||||
```
|
||||
push 到 master 改 `apps/video2slides/**` → CI build musl → `FROM scratch` 镜像 → push registry → `kubectl apply` + rollout。namespace `cube-video2slides`,`registry-creds` 已手工建好(不进 git)。
|
||||
|
||||
Reference in New Issue
Block a user