依 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>
12 KiB
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(後兩者用skipifsourcedoesntexistfallback)✅ - 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)
- 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。 payload/darwin/bin/ffmpeg仍為舊 GPL 77 MB binary:下次make payload-macos會自動清。M8-10 前應重跑。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 成功
- 雙層
.gitignoreinner + 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 建議:
- Minor #1(BUILD.md spctl 描述修正)列為 M8-3 尾巴,由 Architect/DevOps 補
- M8-10 CI 交付前執行 outer repo
git add+ commit - M8-10 時確保
make payload-macos+stage-macos重跑清掉舊 GPL payload