依 R5 五輪決策把 visionA-local 從「Wails 內嵌 Next.js」重構為「Wails
本機伺服器控制台 + 瀏覽器 Web UI」模式(類比 Docker Desktop / Ollama)。
程式碼變動
- M8-1 砍 yt-dlp 全套(後端 resolver / URL handler / 前端 URL tab /
Makefile vendor / installer / bootstrap / CI workflow,-555 行)
- M8-2 砍 Mock 模式全套(driver/mock、mock_camera、Settings runtimeMode、
VISIONA_MOCK 環境變數,-528 行)
- M8-3 ffmpeg 從 GPL 切換到 LGPL 混合方案:Windows/Linux 用 BtbN 現成
LGPL binary,macOS 自 build minimal decoder-only 進 git
(vendor/ffmpeg/macos/ffmpeg 5.7MB + ffprobe 5.6MB,比 GPL 版省 85% 空間)
- M8-4 Wails Server Controller:state machine、log ring buffer 2000 行、
preferences.json atomic write、boot-id、Gin SkipPaths、shutdown 7+1 秒、
notify_*.go 三平台 OS 通知、watchServer 改 Error state 不 os.Exit
- M8-4b 啟動階段管線 R5-E:6 階段進度 event、20s soft / 60s hard timeout、
stage 5/6 skip 規則、sentinel file、RestartStartupSequence 5 步驟
- M8-5 Wails 控制台 vanilla HTML/JS/CSS(9 檔 ~2012 行)取代 M7-B splash:
state 視覺、log panel、startup progress panel、Stage 6 manual CTA
pulse、shutdown modal、Settings、Dark Mode、i18n 中英雙語
- M8-6 上傳影片副檔名擴充(mp4/avi/mov/mpeg/mpg)
- M8-7 Web UI Server Offline Overlay(role=alertdialog + focus trap +
wsEverConnected 容錯 + Page Visibility)
- M8-8 CORS middleware(127.0.0.1/localhost only + suffix attack 防護)+
ws/origin.go 獨立 WebSocket CheckOrigin 避 package cycle
- MAJ-4 server:shutdown-imminent WebSocket broadcast 機制
(/ws/system endpoint + notifyShutdownImminent helper)
- M8-9 Boot-ID + 瀏覽器 tab 自動重連(sessionStorage loop guard)
品質
- ~105+ 新 unit test + race detector (-count=2) 全綠
- 10 個 milestone 全部通過 Reviewer 審查
- 三方 v2 + v2.1 文件(PRD / Design Spec / TDD)+ 交叉互審紀錄
收錄在 .autoflow/
交付前待處理(M8-10)
- 重跑 make payload-macos 把舊 GPL 77MB binary 換成新 LGPL
- 三平台 end-to-end build 驗證
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
272 lines
12 KiB
Markdown
272 lines
12 KiB
Markdown
# Reviewer 審查 M8-3 ffmpeg LGPL 切換(2026-04-15)
|
||
|
||
對照文件:
|
||
- TDD spec:`/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/v2/ffmpeg-lgpl.md`
|
||
- Research:`/Users/jimchen/visionA/local-tool/.autoflow/04-architecture/ffmpeg-lgpl-research.md`
|
||
- BUILD.md:`/Users/jimchen/visionA/local-tool/vendor/ffmpeg/macos/BUILD.md`
|
||
|
||
---
|
||
|
||
## 摘要
|
||
|
||
| 項目 | 結論 |
|
||
|------|------|
|
||
| 總結論 | ✅ 通過(1 Minor + 2 Suggestion + 3 待處理事項) |
|
||
| LGPL 合規 | ✅ 三平台齊備(macOS 自 build decoder-only;Windows/Linux BtbN n7.1 LGPL) |
|
||
| Binary 大小減量 | ✅ macOS 77 MB → 11.32 MB(含 ffprobe),減量約 85% |
|
||
| 阻擋 M8-10? | ❌ 不阻擋,所有硬性條件均通過 |
|
||
|
||
---
|
||
|
||
## A. Makefile 變更正確性
|
||
|
||
對照 `Makefile:1-449` ↔ TDD §2.3/§3/§4,18 項檢查全過:
|
||
|
||
| # | 驗證項 | 結果 | 位置 |
|
||
|---|-------|------|------|
|
||
| 1 | `FFMPEG_VERSION=n7.1` + sha256 常數 | ✅ | `Makefile:73-77` |
|
||
| 2 | `vendor-ffmpeg` 退化為「驗證存在 + LGPL guard」 | ✅ | `Makefile:112-130` |
|
||
| 3 | `vendor-ffmpeg-macos-build` 僅限 Darwin + `nasm`/`yasm` | ✅ | `Makefile:136-140` |
|
||
| 4 | 下載 tarball + sha256 驗證 + fail 印實際 sha | ✅ | `Makefile:142-148` |
|
||
| 5 | configure 21 個 flag 與 TDD §2.3 逐字對齊 | ✅ | `Makefile:152-178` |
|
||
| 6 | `--disable-everything` + decoder 白名單 h264/hevc/mpeg1/2video/mpeg4/mjpeg/prores/vp8/vp9/aac/mp2/mp3/pcm_* | ✅ | `Makefile:167` |
|
||
| 7 | demuxer 白名單 mov,avi,mpegps,mpegts,matroska,image2 | ✅ | `Makefile:166` |
|
||
| 8 | strip + ad-hoc codesign | ✅ | `Makefile:184-189` |
|
||
| 9 | build 後 LGPL grep guard | ✅ | `Makefile:190-193` |
|
||
| 10 | 複製 COPYING.LGPLv3 | ✅ | `Makefile:194-195` |
|
||
| 11 | `FFMPEG_URL_WINDOWS` → BtbN n7.1 LGPL zip | ✅ | `Makefile:277` |
|
||
| 12 | `FFMPEG_URL_LINUX` → BtbN n7.1 LGPL tar.xz | ✅ | `Makefile:387` |
|
||
| 13 | `vendor-ffmpeg-windows` 解壓 ffmpeg.exe + ffprobe.exe + LICENSE + COPYING.LGPLv3 | ✅ | `Makefile:319-348` |
|
||
| 14 | `vendor-ffmpeg-linux` 解壓 ffmpeg + ffprobe + LICENSE,`strip-components=1` | ✅ | `Makefile:417-434` |
|
||
| 15 | `payload-macos` 複製 ffmpeg + ffprobe + ffmpeg-COPYING.LGPLv3 | ✅ | `Makefile:244-247` |
|
||
| 16 | `payload-windows` 複製 ffprobe.exe + LICENSE/COPYING(skipifsourcedoesntexist) | ✅ | `Makefile:358-362` |
|
||
| 17 | `payload-linux` 複製 ffprobe + ffmpeg-LICENSE.txt | ✅ | `Makefile:444-446` |
|
||
| 18 | `payload-macos:241` 有 `rm -rf payload/darwin` 清舊 | ✅ | `Makefile:241` |
|
||
|
||
**五 decoder + 五 demuxer 白名單對照 TDD 驗收條件 §10**(h264/hevc/mpeg2video/mpeg4/aac;mov-mp4/avi/mpeg/mpegts/matroska):全數涵蓋。
|
||
|
||
---
|
||
|
||
## B. Binary 正確性(親自驗證)
|
||
|
||
```
|
||
$ ls -la vendor/ffmpeg/macos/
|
||
-rw-r--r-- 10500 BUILD.md
|
||
-rw-r--r-- 7651 COPYING.LGPLv3
|
||
-rwxr-xr-x 6007520 ffmpeg (5.73 MB)
|
||
-rwxr-xr-x 5865568 ffprobe (5.59 MB)
|
||
```
|
||
|
||
**sha256 與 BUILD.md `lines 26-30` 100% 吻合**:
|
||
```
|
||
c3cb9f1dad66730267c12fca92c6344d2f8939ab227889caac33005f8947992c ffmpeg
|
||
bd388fb4372ed5f7e44ee331a51be6383d702fb2c067bf562cabbdfbdd8b0c5e ffprobe
|
||
da7eabb7bafdf7d3ae5e9f223aa5bdc1eece45ac569dc21b3b037520b4464768 COPYING.LGPLv3
|
||
```
|
||
|
||
**`ffmpeg -version` configuration**:
|
||
- ✅ 無 `--enable-gpl`、無 `libx264`、無 `libx265`
|
||
- ✅ 有 `--enable-version3`、`--disable-network`、`--disable-autodetect`、`--enable-static`、`--disable-everything`
|
||
|
||
**Decoder 驗證**:`ffmpeg -decoders | grep -E ' (h264|hevc|aac|mpeg2video|mpeg4) '` 五個全中。
|
||
|
||
**Demuxer 驗證**:`ffmpeg -demuxers` 顯示 `avi` / `matroska,webm` / `mov,mp4,m4a,3gp,3g2,mj2` / `mpeg` / `mpegts` 五個全中。
|
||
|
||
**`otool -L` 動態依賴**:
|
||
```
|
||
/usr/lib/libSystem.B.dylib
|
||
/System/Library/Frameworks/CoreFoundation.framework/.../CoreFoundation
|
||
/System/Library/Frameworks/CoreVideo.framework/.../CoreVideo
|
||
/System/Library/Frameworks/CoreMedia.framework/.../CoreMedia
|
||
```
|
||
ffprobe 相同四項。**無任何第三方 dylib**(x264/x265/vpx/opus/aom 都不在),符合 LGPL static self-contained。
|
||
|
||
**`ffprobe -version`**:可執行,顯示同一 configuration。
|
||
|
||
**`codesign -v`**:ffmpeg + ffprobe 皆 exit 0(ad-hoc signing 通過)。
|
||
|
||
---
|
||
|
||
## C. .gitignore 雙層
|
||
|
||
**Inner `local-tool/.gitignore:6-15`**:
|
||
```
|
||
/vendor/**
|
||
!/vendor/.gitkeep
|
||
!/vendor/README.md
|
||
!/vendor/ffmpeg/
|
||
!/vendor/ffmpeg/macos/
|
||
!/vendor/ffmpeg/macos/**
|
||
```
|
||
|
||
**Outer `visionA/.gitignore:20-27`**:同樣四行 un-ignore + `local-tool/vendor/**` 為基底。
|
||
|
||
`git check-ignore` 驗證:
|
||
|
||
| 路徑 | inner repo | outer repo |
|
||
|------|-----------|-----------|
|
||
| `vendor/ffmpeg/macos/ffmpeg` | exit 1(未 ignore ✅) | exit 1 ✅ |
|
||
| `vendor/ffmpeg/macos/BUILD.md` | exit 1 ✅ | exit 1 ✅ |
|
||
| `vendor/ffmpeg/windows/ffmpeg.exe` | exit 0(ignored ✅) | exit 0 ✅ |
|
||
| `vendor/ffmpeg/linux/ffmpeg` | exit 0 ✅ | exit 0 ✅ |
|
||
|
||
`git status --ignored vendor/` 顯示:
|
||
- `?? vendor/ffmpeg/macos/`(untracked,待 `git add`)
|
||
- `!! vendor/ffmpeg/{linux,windows}/`、`!! vendor/{python,wheels,yt-dlp}/`(ignored)
|
||
|
||
雙層規則正確;outer repo `local-tool/vendor/` 仍為整個 untracked,需在交付前 `git add` — 屬待處理事項而非實作錯誤。
|
||
|
||
---
|
||
|
||
## D. BUILD.md 可重現性
|
||
|
||
BUILD.md(295 行)完整度:
|
||
|
||
| 欄位 | 有無 |
|
||
|------|------|
|
||
| ffmpeg release n7.1 + source URL + sha256 | ✅ |
|
||
| Build host(macOS 14.7.6,Apple clang 16.0.0) | ✅ |
|
||
| Toolchain(nasm 3.01、brew 5.1.6、Xcode CLT) | ✅ |
|
||
| Configure flags 完整複製貼上 | ✅ |
|
||
| Binary sha256(三項) | ✅ |
|
||
| Binary 大小(bytes + 人類可讀) | ✅ |
|
||
| Build 耗時(2 分 44 秒,user 559s/sys 56s) | ✅ |
|
||
| 實測驗證輸出(ffmpeg -version / decoder / demuxer / otool / codesign 節錄) | ✅ |
|
||
| Rebuild 指令 `make vendor-ffmpeg-macos-build` | ✅ |
|
||
| Commit 清單(僅允許 ffmpeg/ffprobe/COPYING/BUILD.md 四檔) | ✅ |
|
||
|
||
**兩年後重現評估**:可以。source tarball URL + sha256 固定、configure flags 可直貼、系統依賴明列、Makefile 本身就是 reproducibility script。唯一風險是 Homebrew nasm/clang 版本漂移,但 ffmpeg 7.1 對 toolchain 包容度高,不預期 break。
|
||
|
||
---
|
||
|
||
## E. GPL flag 清除 grep
|
||
|
||
全 repo grep `VISIONA_ALLOW_GPL_FFMPEG` / `--enable-gpl` / `libx264` / `libx265`:
|
||
|
||
| 類別 | 殘存位置 | 評估 |
|
||
|------|---------|------|
|
||
| `.autoflow/*`(progress/TDD/research/spec) | 允許(歷史紀錄) | ✅ |
|
||
| `Makefile:126,190-193` | guard 驗證邏輯 | ✅ |
|
||
| `vendor/ffmpeg/macos/BUILD.md` | 驗證輸出說明 | ✅ |
|
||
| `build/ffmpeg-macos/src/*` | upstream source tarball 解壓產物 | ✅(非專案碼) |
|
||
| `scripts/bootstrap-{windows,linux}.*` | 已改為 `LGPL v3 build` 標語 | ✅ |
|
||
| `.github/workflows/build.yml` | 零殘留 | ✅ |
|
||
| `installer/**` | 零殘留 | ✅ |
|
||
| `server/**.go` + `visiona-local/**.go` | 零殘留 | ✅ |
|
||
|
||
**GPL flag 完全清除**,只剩文件類歷史 + guard 邏輯。
|
||
|
||
---
|
||
|
||
## F. Installer / Bootstrap / CI
|
||
|
||
- **Windows Inno Setup** `installer/windows/visiona-local.iss:74-79`:含 ffmpeg.exe + ffprobe.exe + ffmpeg-LICENSE.txt + ffmpeg-COPYING.LGPLv3(後兩者用 `skipifsourcedoesntexist` fallback)✅
|
||
- **Linux AppImage** `installer/linux/build-appimage.sh:61-76`:`for tool in ffmpeg ffprobe` 迴圈 + 把 ffmpeg-LICENSE.txt 放到 `AppDir/usr/share/doc/visiona-local/` ✅
|
||
- **macOS**:無獨立 installer,payload-macos 直接把 COPYING.LGPLv3 複製到 `payload/darwin/bin/ffmpeg-COPYING.LGPLv3`,經 stage-macos 進 `.app/Contents/Resources/bin/` ✅
|
||
- **bootstrap-windows.ps1:71 / bootstrap-linux.sh:58**:只剩 LGPL 提示語,無 GPL flag ✅
|
||
- **`.github/workflows/build.yml`**:只保留 `vendor-ffmpeg-windows`(line 135)和 `vendor-ffmpeg-linux`(line 232)呼叫,無 env var 設定 ✅
|
||
|
||
---
|
||
|
||
## G. Payload 流程相容性 + 真實 decode 測試
|
||
|
||
### G.1 server runtime 相容性
|
||
|
||
`server/main.go:88-93` 把 `VISIONA_BUNDLE_BIN_DIR` prepend 到 PATH;`server/internal/deps/checker.go:23-94` 的 `resolveTool` 優先在 `$VISIONA_BUNDLE_BIN_DIR/<name>` 找;`visiona-local/app.go:482-484` Wails shell 啟動 server 時注入 `VISIONA_BUNDLE_BIN_DIR = locateBundleBinDir()`(macOS→`Contents/Resources/bin`,Windows/Linux→`<exe>/bin`,開發→`cwd/payload/<os>/bin`)。
|
||
|
||
payload-macos `Makefile:244-245` 把 `ffmpeg` + `ffprobe` 放到 `payload/darwin/bin/`,stage-macos 會完整複製到 bundle Resources。`server/internal/camera/video_source.go:24` 的 `exec.Command("ffprobe", ...)` 依賴 basename lookup,加入 ffprobe 後完全相容。✅
|
||
|
||
### G.2 實際 decode 測試
|
||
|
||
先用系統 ffmpeg 產生 h264 mp4:`/usr/local/bin/ffmpeg -f lavfi -i "testsrc=duration=1:size=320x240:rate=10" -c:v libx264 ... /tmp/testsrc.mp4`。
|
||
|
||
LGPL ffmpeg 解碼 → mjpeg:
|
||
```
|
||
$ ./vendor/ffmpeg/macos/ffmpeg -i /tmp/testsrc.mp4 \
|
||
-frames:v 1 -f image2pipe -vcodec mjpeg -q:v 5 /tmp/test_out.jpg
|
||
Stream #0:0(und): Video: mjpeg, yuv444p, 320x240, 10 fps ...
|
||
frame= 1 fps=0.0 q=5.0 Lsize= 9KiB
|
||
$ file /tmp/test_out.jpg
|
||
/tmp/test_out.jpg: JPEG image data, JFIF standard 1.02, ..., 320x240
|
||
```
|
||
|
||
LGPL ffprobe probe h264 stream:
|
||
```
|
||
$ ./vendor/ffmpeg/macos/ffprobe -v error -show_streams /tmp/testsrc.mp4
|
||
codec_name=h264 codec_type=video width=320 height=240 pix_fmt=yuv444p
|
||
```
|
||
|
||
**真實 decode 路徑通過**,server extract first-frame 流程可直接使用。
|
||
|
||
### G.3 `payload/darwin/bin/` 當前為舊 GPL 77 MB binary
|
||
|
||
```
|
||
$ shasum -a 256 payload/darwin/bin/ffmpeg
|
||
b68f795f7fb4528daf697f57a2b6780846a1ae762a71907e994442ad103ee88f
|
||
```
|
||
|
||
**非 M8-3 實作錯誤**:payload-macos 尚未在 M8-3 後重跑。`Makefile:241` 的 `rm -rf payload/darwin` 與 `stage-macos:265` 的 `rm -rf visiona-local/build/darwin/Resources` 會自動清乾淨。列入待處理。
|
||
|
||
---
|
||
|
||
## H. Size 比較
|
||
|
||
| 項目 | 舊 GPL | 新 LGPL | 差異 |
|
||
|------|-------|--------|------|
|
||
| macOS ffmpeg | ~77 MB(progress.md M6-1 紀錄) | **5.73 MB** | −71.3 MB(−92%) |
|
||
| macOS ffprobe | 未附 | **5.59 MB** | 新增 |
|
||
| macOS 合計 | ~77 MB | **11.32 MB** | **−65.7 MB(−85%)** |
|
||
|
||
macOS 成果超越 TDD §2.1「~20 MB」目標,`--disable-everything` + 白名單策略效益顯著。Windows/Linux BtbN LGPL build 大小待 M8-10 CI runner 下載後驗證。
|
||
|
||
---
|
||
|
||
## I. 問題清單
|
||
|
||
### Critical / Major
|
||
|
||
無。
|
||
|
||
### Minor
|
||
|
||
| # | 位置 | 問題 | 建議 |
|
||
|---|------|------|------|
|
||
| 1 | `vendor/ffmpeg/macos/BUILD.md:186-194` §Verification §5 | 預期 `spctl --assess --verbose` 為「accepted」不準確:實測 ad-hoc signed binary 在 macOS 14.7 下被 `rejected`(exit 3)。Gatekeeper 由 outer `.app` bundle signing 決定,不對內嵌 binary 單獨 assess | 把該步驟改為 `codesign -v`(exit 0 即可),並註記「Gatekeeper 最終由 outer `.app` bundle signing 決定」 |
|
||
|
||
### Suggestion
|
||
|
||
| # | 位置 | 建議 |
|
||
|---|------|------|
|
||
| 1 | `Makefile:126-130` `vendor-ffmpeg` target | 可多驗 `shasum -a 256` 對照 BUILD.md 記錄,防 binary 被意外覆蓋 / 損毀(非必要,git 已保護) |
|
||
| 2 | `Makefile:361-362` payload-windows | 兩份授權檔都用 `skipifsourcedoesntexist`,若 BtbN zip 內 LICENSE.txt 與 COPYING.LGPLv3 同時缺失,installer 會無授權檔交付。建議補「至少其中一個存在」assertion |
|
||
|
||
### 待處理(非 M8-3 scope,但影響 release)
|
||
|
||
1. **outer repo `git add`**:`/Users/jimchen/visionA/` 的 `git status` 仍顯示 `local-tool/vendor/` 為整個 untracked。交付前須 `git add local-tool/vendor/ffmpeg/macos/{ffmpeg,ffprobe,COPYING.LGPLv3,BUILD.md}` 並 commit。
|
||
2. **`payload/darwin/bin/ffmpeg` 仍為舊 GPL 77 MB binary**:下次 `make payload-macos` 會自動清。M8-10 前應重跑。
|
||
3. **`vendor/yt-dlp/` 目錄仍在**:屬 M8-2 清理範圍,非 M8-3。
|
||
|
||
---
|
||
|
||
## J. 結論
|
||
|
||
**M8-3 ffmpeg LGPL 切換:✅ 通過。**
|
||
|
||
核心成果確認:
|
||
- Makefile 三平台變更逐字對齊 TDD §2-§4
|
||
- macOS binary 親測 LGPL 合規(無 gpl/x264/x265,有 version3)、五 decoder + 五 demuxer 全中、只依賴 macOS 系統 framework
|
||
- sha256 與 BUILD.md 100% 吻合
|
||
- 實測 h264 mp4 decode → mjpeg JPEG 成功;ffprobe probe 成功
|
||
- 雙層 `.gitignore` inner + outer 規則均正確
|
||
- BUILD.md 完整可重現(兩年後可 rebuild)
|
||
- GPL flag 在程式碼 / installer / bootstrap / CI 完全清除
|
||
- Installer Windows/Linux 含 ffprobe + 授權檔
|
||
- Size 減量 85%
|
||
|
||
**僅 1 Minor(BUILD.md 的 spctl 預期描述錯誤)+ 2 Suggestion + 3 待處理事項,不阻擋 M8-10。**
|
||
|
||
Orchestrator 建議:
|
||
1. Minor #1(BUILD.md spctl 描述修正)列為 M8-3 尾巴,由 Architect/DevOps 補
|
||
2. M8-10 CI 交付前執行 outer repo `git add` + commit
|
||
3. M8-10 時確保 `make payload-macos` + `stage-macos` 重跑清掉舊 GPL payload
|