reel-scout

短影音拆解 · 本機優先

v1.5.0 · MIT · OPEN SOURCE

DOCS

開發路線

Reel Scout — Roadmap

最後校正:2026-10-08(本輪只在 v1.5 那列追記 10-08 的行長 gate;Phase 9 與 v1.5 的里程碑是 10-05 那次寫的,上方各 Phase 仍是 2026-09-01 的讀數、未逐項重驗 —— 這行區分的是「我這次量了什麼」與「我這次抄了什麼」) 前次校正:2026-09-01(對照 code+實際 DB 內容逐項驗證。這份檔上一次校正在 2026-07-21,之後漂了約六週:版號、schema、測試數全錯,而且 §4E 那條 🔴「證據層從沒在語料上跑過」早就不成立了——shot_metrics 現在 116 列,是本次逐項實查才發現的。⚠️ 這份檔自己示範了它命名過的那個病:completion-blind drift 不只讓壞消息隱形,也讓好消息隱形了六週。詳見 §現況 與 §4E 回填段的 2026-09-01 更新) 前次校正:2026-07-15(對照實際 code 逐項驗證,非憑記憶) 2026-07-17 增補:crv 對標(§4E pacing/BPM 實測化 + §4F 燒錄字幕 OCR + 參考案例 crv)→ docs/crv-vs-reel-scout.md 2026-07-18 drift 修正:測試 162→177(實跑驗證)+ 已完成清單補 inspect(PR #29 遺漏回寫) 2026-07-18 §4E 實作:evidence-based pacing(shot-table cuts/min + audio energy/BPM)落地,schema v7,測試 →200(含 codex+harness 雙審修正) 2026-07-18 §4F 實作:燒錄字幕 OCR / L3.5(vlm 復用 + tesseract opt-in)落地,schema v8,測試 →207 2026-07-20 GUI + 學生包:view/inspect 合併成單一 app、vulture.s 換膚(theme.py,偏離已 narrate)、品牌字型內嵌 + CJK 動態 subset、export --format bundle 自包含學生包、inspector 返回鍵、view 併發修復(ThreadingHTTPServer)。測試 →244 2026-07-18 Wave 3 一波:3B patterns / 3A instaloader / 4B inspire / 4D track(schema v9)/ 4C MCP(8 tools) / 5A CHANGELOG + 5C docs 全落地,測試 →228(含 codex+harness 雙審修正:inspire 非-JSON fallback、track partial-update COALESCE、MCP channels 驗證、instaloader limit=0) 2026-07-20 §5A L0/L1/L2 安裝階梯:新增 ingest {vision,score}(agent 當 backend,零本地模型/零 API key)+ show 補列 keyframe id 與分數;SKILL.md 三層 surface 改寫。測試 →263 2026-07-20 §5A L1 補完:ingest analysis(merger 同樣需要 LLM,無 LLM 時 analyses 整列不存在=4-beat/hook/CTA 全缺;枚舉值驗證)+ 學生包 filmstrip 顯示逐格描述。另修 --skip-vision 連 keyframe 都不抽(L1 整條實際為空)與 batch 裸名呼叫。測試 →324 2026-07-20 §5A batch:Google Doc/Sheet 清單批次跑(免 API 金鑰,/edit 自動轉匯出端點);能力偵測後由使用者選 mode(full/agent/transcript),無 VLM 不靜默降級。測試 →304 🔴 2026-07-20 §4E 回填發現:shot_metrics / ocr_captions 實際 0 列 — §4E/§4F 的 code 上了但從沒在語料上跑過,_measured_block() 永遠回空字串 → 現有 12 筆分數的 pacing 仍 100% 是 LLM 猜的,§4E 想解的問題原封不動。roadmap 標 ✅ 但資料為空=completion-blind drift,見 §4E 回填段 2026-07-20 §4G 評分透明化:inspector 加權重滑桿(即時重算 overall + default-vs-yours),權重從三處重複收斂成 config.SCORE_WEIGHTS 單一定義。測試 →324(PR #35) 2026-07-21 §4G 真瀏覽器驗證 + bug 修:M2 Max 實拖滑桿驗證,抓到 zero-weight 顯示 bug(訊息說 no verdict 但 overall 仍顯示殘值)→ 修為顯示 — + 清空 bar(fe87ff2)。JS/Python 對拍測不到(回傳值等價 ≠ 渲染正確)→ 加迴歸測試釘住。 2026-07-21 §4H 介面中英切換(i18n):inspector + viewer(列表頁 + 學生包 export)全支援 EN/中文即時切換,瀏覽器語言自動偵測,localStorage 持久化。抽 reel_scout/i18n.py 單一翻譯來源(51 鍵 en/zh)。只翻介面 chrome,模型產出(reasoning/逐字稿/decoded 值)永不翻。測試 →329(PR #35) 2026-07-20 §5A 分發補洞(乾淨機器實測):wheel 原本只含 reel_scout/,pip install 後 SKILL.md//scout/prompts/setup.py 全部缺席=agent 無物可載;改 force-include 進 wheel + 新增 skill install。另修 setup.py 對無 clone 使用者印 <repo-root> 佔位符的 bug(該檔原本零測試覆蓋)。測試 →280

定位與 Non-goals

Reel Scout 是工具,不是末端產品。

它的形態是 CLI + MCP + library:給會用終端機(或會叫 AI 幫你跑)的人,把短影音變成結構化資料。它不會變成雙擊即用的 GUI app。

為什麼不產品化(2026-07-15 決策):

  1. 平台存取隨時可能被關。整條 pipeline 的入口靠 yt-dlp。IG 已經在 rate-limit,yt-dlp 的 IG user extractor 2026.4 就壞過。 實測爆炸半徑(2026-07-15):門一關,crawl / browse / analyze(整條 pipeline,也就是核心價值)全死 —— analyze 只吃 URL,餵本機路徑直接 ValueError: Unsupported platform for URL。 活下來的只有 transcribe <path> / vision <path>(個別階段)與 score / show / export(吃已存的 DB)。 也就是說這個風險目前零緩解措施 —— 補上 analyze <local-path>(見 5B)才是真正的保險。工具的存活優勢是可以做到,不是現在就有。
  2. 追平台變動的維護成本極高。把它做成終端使用者產品,等於簽下一份「平台每改一次、我就得修一次,而且使用者會抱怨」的長期合約。這不是這個專案想付的代價。
  3. 因此 star 數 / 使用者數不是這個 repo 的成功指標。成功 = 自己(和會用 CLI 的人)能穩定拿到可信的分析資料。

Non-goals:GUI 桌面 app、一鍵安裝器、社群營運 / Product Hunt / 行銷推廣、SaaS 或託管服務。

GUI 這條 2026-07-17 於 v1.0 收尾後重新檢驗過一次 → docs/gui-evaluation.md: 全 GUI app no-go;只讀 DB 的最小本機 dashboard conditional-go(有日常 review 需求才做)。


現況

Phase 1  ████████████████████  ✅ Core Pipeline(crawl + transcribe + vision + merger + DB + CLI + MCP)
Phase 2  ████████████████████  ✅ Advanced Analysis(audio/PANNs + diarize/pyannote + scorer + LLM backends)
Phase 2.5████████████████████  ✅ 品質強化(subtitle-first / keyframe budget / prompt pack / skill / 雙語轉錄)
Phase 3  ████████████████████  ✅ Batch Intelligence — browse ✅、compare ✅、3C ✅、3D stats ✅、patterns ✅、instaloader fallback ✅(channel 表/batch-resume 為刻意延後的大重構)
Phase 4  ████████████████████  ✅ Content Strategy Engine — 4A research ✅、4B inspire ✅、4C MCP 擴充 ✅、4D track ✅、4E 評分證據化 ✅、4F L3.5 OCR ✅
Phase 5  ████████████████████  ✅ Tool Hygiene — LICENSE/README/CHANGELOG ✅、analyze-local ✅、yt-dlp 健壯性 ✅、CI ✅、config check ✅、**PyPI 上架 ✅(v1.2.0,Trusted Publishing 零 token)**
Phase 5+ ████████████████████  ✅ 靜默錯誤清剿(2026-07-22 → 08-26,v1.3.x + Unreleased;下方「2026-07-21 之後」清單)
Phase 6  ████████████████████  ✅ Shot-level 反解 — **6A/6B/6C/6D/6E 完成**(6C 於 2026-09-03 換訊號來源後重啟出貨)
Phase 7  ████████████████████  ✅ 語料健康度 — **7A `db health`/7B 路徑解析/7C 分鏡回程/7D 介面中文化補完**
Phase 8  ████████████████████  ✅ 外部審計收斂(2026-09-05,v1.4.1 + v1.4.2)— 一次獨立唯讀審計的 **3 Blocker + 10 Real 全數收掉**,並把「文件宣稱」變成可執行的 gate
Phase 9  ████████████████████  ✅ 對外曝光面(2026-10-05)— 官網(Astro / GitHub Pages / GA4)+ 紙質外殼下沉到三個介面;repo 補上 description 與 homepage

目前版本:v1.5.0 | DB schema:v22

schema 階梯:v14 shots/v15 shot_labels/v16 translations/v17 shot_labels.prompt_hash/v18 shot_motion/v19 shot_motion.zoom+rotation/v20 post_meta/v21 transcripts.text_full_original+segments_json_original+zh_conversion/v22 transcripts.script_mix。

🔴 這兩行以前寫過三次互相矛盾的數字,而且是連續的。 上上版一行 schema v15、下一行 v16;上一版修好了那個,同一段立刻變成一行 v18、下一行 v17 —— 警告這個失效模式的註記,就掛在一個活生生的實例正上方。

2026-09-05 起這兩個數字由 tests/test_docs_are_current.py 對 reel_scout.__version__ 與 db.SCHEMA_VERSION 驗證,改碼不改這行會紅。測試數不再寫在這裡 —— 它每次 PR 都變,是最會漂的那種值,而且沒有任何人靠它做決定。

上一版此行寫「v1.2.0/324 passing/schema v9」,三個數字全是 2026-07-20 的舊值。

語料現況(2026-09-01 實查 data/reel_scout.db)

videos 117 | analyses 117 | scores 115 | keyframes 2872
shot_metrics 116 | ocr_captions 113 支           ← §4E / §4F 的產物,兩張表都活著
有分數但無實測證據:1 支

這推翻了 §4E 回填段那條 🔴。 2026-07-20 記的「shot_metrics / ocr_captions 實際 0 列」在 2026-09-01 已不成立——證據層 116/117 覆蓋,_measured_block() 現在對幾乎每一支都吐得出東西。v1.3 里程碑的第二個條件(stats 要報得出證據覆蓋率)在本次同一輪補上了 —— 這份表現在 reel-scout stats 自己就講得出來,不必再手動 SELECT COUNT(*)。

已完成功能清單(2026-07-15 驗證)

2026-07-21 之後新增的能力(本次補登,上一版完全沒有這段)

v1.3.0 發布後到 2026-08-26 之間有 35 顆 commit 進 master,其中大半是「跑得動但結果悄悄不對」那一類的修復。這批沒有一條回寫過 roadmap,所以列在這裡:


Phase 3 — Batch Intelligence(跨影片模式分析)

目標:從「逐支分析」進化到「跨影片批量模式識別」,回答「什麼類型的短影音表現好?」

3A. 批量爬取 + 頻道模式 🔨 半套(2026-07-15 補完主體)

3B. 跨影片比較分析 🔨 半套

3C. 模式標籤系統 ✅

3D. 統計 ✅(2026-07-17)


Phase 4 — Content Strategy Engine(從分析到行動)

目標:把分析資料變成可執行的產出。全部維持 CLI/MCP 形態。

4A. 競品研究報告 ✅(2026-07-17)

4B. 內容靈感產生器

4C. MCP 擴充

4D. 表現回填 + A/B 結構比較

4E. 評分證據化:pacing 從「LLM 猜」→「實測 shot-table」

問題:scorer 的 pacing(四維之一)目前是 LLM 憑感覺給的,且 model-dependent——換一顆 VLM/LLM 分數就飄(見對標 docs/crv-vs-reel-scout.md §5 與 memory「Reel Scout 評分依賴模型」)。這是評分可信度的已知軟肋。

啟發來源:crv Pro 的 --motion 產出 shot table(per-shot duration / cuts per minute / 節奏變化)——客觀測量、可重現,不靠模型主觀。同樣講「節奏」,他量、你猜。

⚠️ 邊界:crv Pro 閉源、未購未跑,這裡偷的是概念(pacing 該用實測 shot-table / BPM 背書),不是抄它 cut 偵測的閾值或宣稱它準。

🔴 4E 回填:code 上了,但從沒在語料上跑過(2026-07-20 實查 DB 發現)

上面三個 [x] 是實作完成,不是生效。查 data/reel_scout.db 實際內容:

videos 20 | analyses 18 | scores 12 | transcripts 19 | keyframes 213
shot_metrics 0 | ocr_captions 0        ← §4E / §4F 的產物,兩張表都是空的

shot_metrics 零列 ⇒ merger 沒有 measured 可折 ⇒ scorer._measured_block() 對每一支影片都回 "" ⇒ prompt 裡那段「Measured Signals(objective — prefer these for pacing)」從來沒出現過。

也就是說:§4E 想解的問題原封不動還在。 現有 12 筆分數的 pacing 仍然百分之百是 LLM 憑感覺給的,跟 §4E 落地前沒有差別——差別只在「現在有能力算,但沒算」。roadmap 標 ✅ 而語料為空,是典型的 completion-blind drift:驗收看的是測試綠,不是資料有沒有真的長出來。

這條的來源是 JapowDB 拆解(hevin-ai-os case-study 2026-07-20)。該站把評分門檻交給讀者調,逼出一個對照問題:我們的分數底下有沒有東西可以被檢驗? 答案是有設計、沒資料。PR #35 的權重滑桿讓「怎麼合成」變透明了,但被合成的四個數字本身,目前仍無客觀依據——先補證據層,滑桿才不只是漂亮。

4F. 燒錄字幕 OCR → 補強 transcript / 信號可靠度

問題:純視覺 / 零口白 / 吵雜片,L3(transcript)給不出料或不可靠(見 prompts/signal-reliability-cheatsheet.md 4 層信號模型)。目前只靠 L4 VLM「讀招牌字」,沒有專門的時間戳 OCR。

啟發來源:crv Pro 的 --ocr——帶時間戳、可搜尋的螢幕文字,且用畫面燒錄字幕反過來校正 STT(CJK 特別強)。這等於在 L3↔L4 之間補一層可實測的文字證據,不是抄功能,是補你信號可靠度模型的真空。

⚠️ 邊界:dedicated OCR 引擎(pytesseract)列為 opt-in extra、預設不啟,守 minimal-deps;PaddleOCR-CJK 更重故未納,需要再評。 ⚠️ 契約(codex+harness 雙審確認、非 bug):OCR captions 只透過 merge prompt 影響分析(跟 transcript/vision/audio 一樣,只在「首次 merge」折入)。重跑已分析的舊片會存 ocr_captions 但不會重新 merge → 分析不變,直到真正重新 merge。§4E measured 能 backfill 是因為 scorer 直接讀它;OCR 文字沒有下游可 append,故不對稱是刻意的。


Phase 5 — Tool Hygiene(工具品質,非社群營運)

目標:讓這個工具可安裝、可信賴、不會安靜爛掉。這裡沒有推廣/社群項目——見「定位與 Non-goals」。

5A. 可安裝

5B. 不會安靜爛掉

5C. 文件


Phase 6 — Shot-level 反解(提議中,2026-09-01 開)

起點:用 reel-scout 拆了三支 MV(4–5.5 分鐘的音樂錄影帶,非短影音)之後,跟一套分鏡/PPM 製作工具(STB,同作者的 macOS App,案子的唯一真相是一份 project.json)對照,發現兩邊是同一個資料結構的兩個時間方向:

分鏡工具的 cut = 計畫(要拍什麼)      ← 正向
reel-scout 的 shot = 紀錄(他拍了什麼) ← 反向

這正是 README/課程一直在講的「拆爆款 → 抽 SOP → 倒推自己的分鏡」閉環——目前那個閉環只有教材,沒有工具。

🔴 6A. shots 表:剪點時間碼現在被算出來然後丟掉

reel_scout/shots.py 的 parse_cut_count():

return len(_TS_PATTERN.findall(stderr))   # ← pts_time 全解出來了,只取 len()

每一刀的時間碼每次 analyze 都算一次,然後整串扔掉,只留一個數字進 shot_metrics。

⚠️ 這不是新範圍,是 §4E 沒做完的那一半。§4E 的啟發來源自己寫著「crv Pro 的 --motion 產出 shot table(per-shot duration / cuts per minute / 節奏變化)」——當時偷了 cuts/min,per-shot 那半沒偷。

6B. 受限詞彙:景別

VLM 現在用散文說「close-up shot」,沒有正規化欄位。而詞彙表已經寫在自己 repo 裡了——prompts/storyboard-visualize.md 的 ECU / CU / MCU / MS / MLS / LS / ELS,只是它現在服務的是正向(鏡頭表 → 生圖 prompt)。反向要的是同一套詞當受限輸出。

🔴 6B 盲測與提示詞天花板(2026-09-03):提示詞這條路已經走完了

Hevin 問「景別識別失敗是技術問題還是成本問題」,追下去的結論是兩者都不是。 以下每一條都有實測,寫在這裡是為了讓下一次不要重跑這些死路。

① 盲測 12 楨:一致 4/12,而其中兩張是送分題。 先只取路徑、判完才揭曉模型答案(避免定錨)。真正在判景別的只對到 2 張。 不一致的方式才是資訊:

畫面人判qwen2.5vl:7b
手寫字卡 MATCH CUTUNKNOWNECU
紅黑抽象、極小人影UNKNOWNECU
上下拼接的模糊畫面UNKNOWNECU
筆電螢幕上的剪輯時間軸UNKNOWNMCU
招財貓擺飾CUMCU

② ECU 是模型的「我看不出來」,不是量測。 用既有 VLM 描述量全庫 372 筆現行標籤:

景別該碼裡「描述完全沒提到人」的比例
ECU54%
CU44%
LS40%
MCU19%

372 筆裡 124 筆(33%)畫面中根本沒有人,而只有 5% 被標成 UNKNOWN。 ECU 的無人率最高 —— 配上盲測那三張,機制清楚:畫面越讀不出來,越傾向回 ECU。

③ 任務對三分之一的語料本來就無解。 值域的定義全是人體切點 (頭到胸/腰以上/膝蓋以上)。唱片、招財貓、大樓外牆、筆電螢幕沒有正確答案可選。 模型不是判錯,是被問了一個無解的問題 —— 這一塊換多大的模型都不會好。

④ 🔴 把量到的例外類別列進七選一提示詞裡 → 更差,而且慢三倍。不要再試。 新增條款列舉「字卡/螢幕錄影/拼接版面/太暗太糊/主體不是人」五類。同 12 楨:

舊提示詞  2/12    12.8 s/楨
新提示詞  0/12    35.6 s/楨   ← 連原本答對的「純黑」「介面條」都弄壞了

同一套規則寫成「二元問題」卻是 10/12(ill-posed 的 9 張全數擋下、零誤放)。 ⇒ 不是規則錯,是「長例外清單 × 寬輸出空間」在 7B 上撐不住。 唯一實測可行的形狀是兩段式:先問一個二元的「這題成不成立」,成立才問景別。 ⚠️ 但那個二元閘誤拒嚴重(well-posed 只認出 1/3),且每楨多 25s —— 未解。

⑤ 一個中途發過又收回的懷疑:盲測第一張是躺著的,當下記了一筆「管線沒套用旋轉」。 查完 —— 全庫沒有任何一支帶 rotation metadata,那是內容本身橫著拍進直式畫布, 創作者的選擇,不是 bug。記在這裡是因為它看起來太像 bug 了,下一個人會再懷疑一次。

⑥ 🔴 換三倍大的模型:沒有比較好,還略差。這條把「是不是模型能力問題」關掉了。 同 12 楨、同提示詞:

qwen2.5vl:7b   ( 6.0 GB)   2/12    12.8 s/楨
gemma3:27b     (17.4 GB)   1/12     8.8 s/楨

兩個模型錯在同一批畫面上,唯一都答對的是純黑。gemma 甚至把 qwen 答對的 「介面細長條」判成 MCU。⇒ 瓶頸不在模型容量,在任務對這批語料沒有定義。

⑦ ✅ 落地:兩段式閘門(shot_size.subject_gate_prompt)。 先問一個二元的「這題成不成立」,成立才問景別。實跑同 12 楨、走完整程式路徑:

無閘   2/12
有閘  10/12    (11 楨被閘擋下)

按實際標籤分布抽 30 楨量真實比例:閘門擋下 70%、放行 30%、零筆無法解析, 10.8 s/楨。全庫推估 3.4h vs 無閘 2.9h = +17%,不是兩倍 —— 因為被擋下的 七成根本不用再問第二次。被擋下的 21 張裡 ECU 佔 8 張(抽到的 ECU 全數被擋)。

🔴 6B 前人技術調查與 bbox 實驗(2026-09-03):這個題目有 SOTA,而我們離它很遠

前人做過,而且有數字。 SGNet (ECCV 2020) 的 MovieShots(7K 部預告片、46K 個鏡頭)Table 3,同一資料集上各家方法的景別準確率:

方法Acc_S
CAMHID(motion vector)52.37
2DMH(motion vector)52.35
TSN-ResNet50(單看畫面)84.08
SGNet(主體導向)87.77

🔴 運動補償/motion vector 對「景別」是錯的工具,這在文獻裡已經量過:52% vs 84–88%, 差 35 個百分點。論文自己也寫,加入 flow 與 variance map 對景別只有 0.2–0.3 的改善, 對運鏡卻是 +14% 相對改善。原因很直觀 —— 運動向量記的是「東西怎麼移動」, 景別問的是「東西佔畫面多少」,兩者正交:大特寫的臉可以靜止,遠景的人可以跑很快。

⚠️ 但 §6C(運鏡)該改用它,而本檔 §6C 的依賴註記是錯的。 那條寫「有 mestimate 但輸出是視覺疊層不是資料檔」,查的是濾鏡,不是 codec 自己的向量。 2026-09-03 實測 ffprobe -flags2 +export_mvs -show_frames → 40 楨裡 36 楨有 Motion vectors side data。拿得到,而且是解碼時順便就有的,零額外計算 —— 6C spike 那 8 MB 的 numpy 相位相關是白繞的。

現成 repo 都不能直接用:唯一有預訓練權重可下載的 rsomani95/shot-type-classifier 是 CC BY-NC 4.0(非商用),而本工具服務的是商業品牌工作。授權過不去。 (另有 sssabet/Shot_Type_Classification 5 級 0.90、einthomas/CVSP2019 4 級,皆未附商用可用權重。)

bbox 實驗(照 SGNet 的「先找主體、再看佔比」方向):否定,但撈到一個副產品。

#畫面真值頭框映射直接問人框長寬比
2石柱群中的女子LSMCU ✗✗LS ✓4.60
4戴耳機的後腦CUCU ✓MCU1.14
6車庫裡站著的男子LSCU ✗✗LS ✓4.61

① 頭框 → 頭高佔比 → 代碼:失敗,死因明確 —— 要頭框,它回的是人框。 ground-truth 12 楨上 5/12(優於直接問的 2/12),但贏的都是「該回 UNKNOWN 而它回不出框」 那種,不是真的在判景別。手判 6 張有人的畫面:bbox 1/6,直接問 2/6。 遠景上災難級:實際頭高 5–9% 的畫面回報 47% 與 86%(後者幾乎正好是全身高度)。 錯得最離譜的地方,正好是分類法最需要精度的地方。 門檻本身是有依據的(人體約 7.5 頭高 → 全身填滿畫面時頭約 13%),死的是輸入不是映射。

② 副產品:人框的長寬比是乾淨的廣角訊號。 兩張 LS 量到 4.60 / 4.61, 其餘 18 張全在 0.57–1.71。站姿遠景又高又窄,頭肩近景接近方形。 ⚠️ 兩個洞:分不開 CU / MCU / MS(語料大宗都在這段),且被橫拍內容打爆 (站著的人橫躺在直式畫布裡 → 長寬比 0.61,看起來像特寫)。

③ 🔴 比實驗本身更重要的發現:閘門放行後,直接問代碼會塌到 MCU。 18 張放行的畫面裡 12 張答 MCU(67%)。ECU 那個垃圾桶被閘門清掉之後, 病灶換成全部塌到中間那一級。⇒ 上線的閘門擋掉了確信的錯答案, 但留下來的標籤資訊量也很低。

⇒ 現階段的判定:提示詞、模型大小、bbox 三條路都試到底了,7B VLM 在這個題目上 的天花板遠低於可用。要真的做好,只有訓練一個 SGNet 級的模型(87.8%)這條路, 而那是另一個量級的投入。在那之前,這個欄位就是訊號、不是 ground truth, 而閘門的價值在於「不說錯話」,不在於「說得多」。

🔴 GPT-5.5 對照:2/12,跟本機 7B 一模一樣 —— 這不是模型問題,是題目沒定義好

codex exec -i 走 GPT-5.5,同一批 12 楨、同一段提示詞(6.4 s/楨、約 7.2k tok/次):

qwen2.5vl:7b   2/12
gemma3:27b     1/12
GPT-5.5        2/12

三個模型、三個量級,分數一樣。 而且錯的方式一致:黑膠唱片兩個都判 CU, 招財貓一個 CU 一個 MCU,手寫字卡兩個都判 ECU。

⇒ 它們不是看錯,是都拒絕回 UNKNOWN。 把「主體」理解成畫面裡最顯眼的東西、 然後照提示詞要求選一個代碼 —— 那是遵守指令,不是感知失敗。 「值域由人體切點定義 ⇒ 沒有人就沒有答案」這條規則從頭到尾只在寫規格的人腦裡, 沒寫進提示詞。這也解釋了為什麼換大模型、換前沿模型都紋風不動:它們沒有錯到需要被修。

⚠️ 本檔先前的「7B 天花板」說法要收窄:已證明的是「同族本機模型換大顆沒用」+ 「前沿模型在同一段提示詞下也沒有比較好」,不是「任何模型都不行」。 另外,成本那一欄先前也算錯了方向 —— 走 API 約 7.2k tok/楨,全庫 825 楨 量級是個位數美金,不是工程專案。真正的約束是「本機、免費」這個我們自己加的前提。

✅ 落地:每支片的「可分析比例」,只報數字、不擋(Hevin 2026-09-03 拍板)

Hevin 提議「只做純影像/MV 這種短影音,字卡超過一定比例就判 no go」。資料支持這個切法 —— 每支片被閘門擋下的代表幀比例是雙峰的,不是連續光譜(樣本 33 支:48% 整支全滅, 中間帶各只有 4 支)。語料本來就分成「拍出來的作品」和「螢幕錄影/圖文製作」兩種東西。

但判準不用「字卡比例」 —— 全滅的那些片裡,字卡只是其中一類,還有螢幕錄影、 UI 轉場、拼接比較版面、純物件插入。用閘門的擋下率當判準比單獨數字卡準。

拍板是不擋、只報:標籤照寫,show 多一行可分析比例,判斷權留給讀的人。 理由是這跟 db health 既有的 detect-and-surface 紀律同一個形狀,且零誤刪風險。

全庫實測分布(94 支,重跑進行中、混著新舊提示詞):

  0%  ████████████████████████████████████████ 40 支
 10%  ███████████████ 15    20%  ██████ 6    30%  ███████ 7
 40%  █████ 5             50%  █████████ 9   60%  ██ 2
 80%  █████ 5             90%  ███ 3        100%  ██ 2
- 🔴 **同日補:「過期」這條規則本來寫了三份。** 落地當下它同時存在於 `label_shots` 的 Python
  predicate、`health.py` 的 SQL、以及 repo 外那支重跑腳本裡 —— 而 **Python 那份沒有任何
  production 呼叫者**,是一段有測試、卻沒在判任何東西的碼。三份會一路同意到其中一份被改動
  的那天,而漂掉的樣子是「health 說 0 筆過期,重跑腳本卻一直找到事做」。收斂成
  `label_shots.STALE_PROMPT_SQL` + `stale_videos()` 一處,另外兩處引用它

6C. 運鏡(固定/手持/搖/跟/推拉)

目前完全沒有。 vision/keyframe.py 的 motion strategy 是 mpdecimate 選幀,不是運鏡判讀。

📌 這一項在 §參考案例 的「刻意不偷」清單上並不存在。那張清單列的是:情緒/mood/ai-report、變速鑑識、$19 漏斗。「鏡頭運動」從未被納入、也從未被否決,它是懸空的——而它通得過本專案自己那條篩子(「只偷讓判讀更可實測的部分」),因為幀間位移是可量的,不是 model-dependent 的主觀輸出。

🔴 6C spike 結果(2026-09-02,兩輪):不出貨,而且第一輪的診斷是錯的

寫在這裡是為了讓下一次不要重跑這三條死路。

① 位移訊號本身是實的、而且便宜。 全片一次 ffmpeg pass 解成 64×64 灰階 @8fps (4 分鐘=1,942 楨、約 8 MB),純 numpy FFT 相位相關。實測 Wax On Wax Off: 61% 相鄰幀零位移、p90 = 2.24px。統計量必須用中位數 —— 有顆鏡頭中位數 0.00 但平均 4.16,少數大跳動把平均帶走。

② 一顆鏡頭被判成「方向一致度 0.98 的搖鏡」。抽首尾兩幀肉眼看,是兩組完全不同的素材。

③ 🔴 第一輪我把原因寫成「scene 偵測漏了一個剪點」—— 那是錯的(本段即為更正)。 第二輪沿著 74–78s 逐幀抽出來看:

75.5s  人物臉部大特寫在右,五個小人影在左
76.0s  兩者同框,一大一小
76.3s  五個人放大填滿畫面,臉不見了

那裡沒有剪點,是一個設計過的合成轉場(畫面內一個大元素在縮放位移)。 scene 偵測沒有漏。錯的是我把「畫面裡的大元素在動」讀成「攝影機在動」。

⇒ 真正的難點:相位相關量的是表觀全域運動,它分不出「攝影機動了」與 「畫面裡一個大元素動了」。 而修剪點偵測救不了這件事,因為根本沒有剪點要修。

④ 三個候選判別訊號,全部實測推翻:

訊號直覺實測
相位相關峰值高度兩幀不相干時峰值低反了:跨剪點 p50=0.53、鏡頭內 p50=0.36(黑底畫面互相關性強)
幀差(相鄰幀平均絕對差)剪點處幀差大反了:已知剪點 p50=0.025、鏡頭內 p90=0.093
分格一致度(4×4 各格位移是否同向)攝影機動=全格一起動;元素動=只有局部那顆合成轉場拿 0.898,排在高段。且 64×64 切 4×4 後每格僅 16×16px,暗畫面上相位相關本身不可靠

⑤ 結論:這條路在「便宜 + 純 numpy + 這種素材」的交集下不成立。 出一個分類器等於把「元素運動」包裝成「運鏡判讀」—— 一個看起來有信心、實際沒依據的值, 正是 §4E 整條在解的病。

要真的做起來需要什麼(都不便宜,故未做):(a) 更高解析度的取樣; (b) 能區分整體與局部運動的稠密光流 —— 要引入 opencv/scipy 級依賴,違反 minimal-deps; (c) 或先在素材較單純的語料上驗證 —— 音樂錄影帶(大量合成、黑底、快剪) 幾乎是最差的測試素材。

⚠️ 本項維持未勾。要重啟先讀上表,別再測一次同樣的三個訊號。 2026-09-03 已重啟並出貨 —— 換了訊號來源,不是換了門檻。 見下節。

✅ 6C 重啟(2026-09-03):改讀 codec 自己的 motion vector

上次的死因是「買不起稠密光流」,而 H.264 早就帶著一份。 每個 inter 編碼的 macroblock 都有一個向量,解碼器順手就交出來 —— 有位置也有位移,這正是 「攝影機動了」與「畫面裡一個大元素動了」可分離的前提。上表 §⑤(b) 說要 opencv/scipy, 其實不用。

⚠️ 一條要更正的話:本檔上午寫「ffprobe -flags2 +export_mvs 拿得到、零額外計算」 講太滿。ffprobe 只報「這幀有 Motion vectors 這種 side data」,不吐值 (4 幀的 JSON 才 4.6 KB,沒有任何 src_x/motion_x 欄位)。真正拿得到值的是 PyAV(frame.side_data["MOTION_VECTORS"].to_ndarray(),40 bytes/向量的結構陣列)。 它是 faster-whisper 的傳遞依賴,所以裝了 [whisper] 的人本來就有 —— 但那是既有 而非免費,故新增 [motion] extra 明說。

四個狀態,每個都有肉眼驗過的例子。 每幀兩個數:全域中位位移(速度)+ 同意該位移的塊比例(一致度)——

速度一致度狀態驗證案例
≈0高static訪談片 50 顆鏡頭全 0.00、一致度 76%
≈0低still_subject_movesWax 74–78s 那個合成轉場:中位仍 0.00,60–73% 的塊在動、一致度掉到 46%
>0高camera_movesyt_Zh32NmKpktI 67.4–70.5s,速度 3.34/一致度 72%,構圖肉眼可見位移
>0低unsteadyWax 鏡頭 45,速度 12.12/一致度 3%,手持+人群推擠

🔴 still_subject_moves 就是重啟的全部理由 —— 上次沒有名字可以給它, 所以一路把它讀成運鏡。跨 25 支片 585 顆鏡頭的分布: 78% static/11% still_subject_moves/10% unsteady/1% camera_moves。

刻意不出 pan / tilt / dolly。 dx/dy 有存,方向讀得到,但命名一個運鏡 等於宣稱攝影機做了某件事,而 585 顆裡一致的運鏡只有 7 顆 —— 七顆撐不起一套詞彙。 上次不出貨正是因為快要命名量不到的東西。

兩個實作中抓到的自傷:

✅ 接上 web(2026-09-03):inspector 的「鏡頭語法」區塊

🔴 先講一個被漏掉的事實:整個 Phase 6 的鏡頭層工作從來沒有接上 web UI —— inspector.py 裡 shot_size / shot_labels / shot_motion / analysable 出現次數 全部是 0。CLI 有、web 沒有,而 Hevin 用的是 web。 (另有一層:在服務的 reel-scout-web worktree 當時落後 origin/master 27 顆, 連翻譯對照都看不到 —— user-global §8「裝了不等於跑的是那份」的實例。)

schema v18 新增 shot_motion:自己一張表,不是 shot_labels 的列 —— 它不是標籤(沒有模型回答、沒有提示詞產生),是量測,而且把 speed 與 agreement 存在旁邊,讓讀的人看得到這個分類是憑什麼給的。掛在 t_sec 不掛 shots.id, 理由同 shot_labels(save_shots 是 replace)。

6D. 交付格式:export --format <storyboard>

沿用 export --format skeleton 已經立好的先例(資料格式交接,兩邊都不 import 對方)。目標工具讀到外部改寫的 project.json 會在 2 秒內自動重載——等於匯出即刷新。

⬛ 刻意不做:器材、焦段、感光元件格式(ff/s35)這些欄位留空。從成片回推焦距是估計問題,而那些欄位的填寫者是現場的攝影師。猜一個數字填進去,它會混進客戶看的 PPM 而沒有人知道那是猜的。props 可以用 objects_json 產候選清單,但要標成建議。

🔴 6E. 紅線:來源標記

reel-scout 的 shot 是別人的片;分鏡工具的 project.json 是要送到客戶面前的 PPM。這座橋一通,「參考」與「模板」之間就只剩一次存檔。

排序(刻意的)

6A 不是第一步。 這個 repo 有紀錄的頭號失效模式是 completion-blind drift(§4E 的 0 列一次、0.0 分那列一次),而 Phase 6 會新增第三個可以安靜空掉的抽取層。所以順序是:

  1. stats 報證據覆蓋率(v1.3 欠的 (b)) ✅ 2026-09-01
  2. db check-invalid --apply 跑過既有語料 ✅ 2026-09-02 —— 命中 1 列(4h11m 直播,0 keyframes / 55,521 字逐字稿 / 存 0.0 分)。標掉之後 stats 地板從 0.0 回到 3.05,而且證據覆蓋率的「without measured」那行整個消失:那支孤兒跟那筆假的 0.0 是同一列
  3. 開 shots 表 ✅ 2026-09-02 —— 排在 1、2 之後是刻意的,落地當下就有 stats 在量證據層有沒有真的長出資料
  4. 下一步:6B → 6C → 6D/6E

Phase 7 — 語料健康度(2026-09-02 開)

起點是一天之內同一個形狀出現三次:這個庫手上握著補救工具、卻從來沒被要求跑過。

那筆假的 0.0 分     `db check-invalid` 就是為它寫的      六週沒人跑
113 支沒有 span     `db backfill-shots` 填得起來          落地當天才發現
102 條相對路徑      `db normalize-paths` 早就存在          今天絆倒三次

沒有一個是 bug。每個工具都能用。只是沒有任何東西說缺口在那裡。

這正是 §4E 已經付過兩次六週學費的形狀,而 #87 把教訓寫下來了:能力在用但沒有 介面說,跟沒在用是同一件事。 四個補救、四個各自獨立的「你要先發現自己需要它」, 是同一個缺陷的更大爆炸半徑。

7A. db health — 每個缺口跟修它的指令放在同一行 ✅ 2026-09-02

🔴 這個模組自己的頭兩版是錯的,而且錯法正是它要防的那種(實跑第一次就抓到): ① validity.scan 不排除已標記的列,所以它把早上才處理掉的那支報成未處理的缺口; ② measurable 沒把「媒體不在」算進去,於是一個沒人能關的缺口被列進了待辦。 一個報出不存在的缺口的儀表板,會訓練人忽略它 —— 比沒有儀表板更糟。兩個都有變異守著。

🔴 ③ 2026-09-19:同一個病的第三版 —— 分子跟分母數的不是同一群片。 實機輸出是 shot table 114 / 113 measurable clip(s) —— 分子大於分母。 measurable 要求「媒體在這台機器上」,with_shots 只問「shots 表裡有沒有這支」, 於是一支當初有檔案時建好 shot 表、之後檔案不見了的片,留在分子、掉出分母。

比值大於 1 只是看得見的那一半。看不見的那一半才是要命的: gap = max(0, measurable - with_shots) 讓一支「有表但量不了」的片 抵銷掉一支「量得了但沒有表」的片 —— 於是還有工作沒做,那一行卻是綠的。 而那個 max(0, …) 正是把「兩個母體不一致」這件事壓下來不讓它出聲的東西。

修法是讓分子從分母裡取(measurable & has_shots),兩個母體變成同一個, 抵銷在算術上就不可能發生;max(0, …) 一併拿掉 —— 交集之後負數只可能代表 collector 壞了,而那正是該讓它出聲、不是該夾住的情況。 欄位名也跟著改成 measurable_with_shots:原本的 bug 有一半是名字沒把限制帶著走, with_shots 讀起來就是「有 shot 表的片」,而它被拿去跟一個更窄的分母相比, 算式裡沒有任何地方說了這件事。

7B. 🔴 路徑:診斷是錯的,而錯的是讀取端不是資料 ✅ 2026-09-02

本節原本寫著「101 支影片 + 1,455 個 keyframe 存的是相對路徑,db normalize-paths 現成,跑就好」。那是錯的,在按下去之前查出來。

normalize_media_paths 的 docstring 自己就寫著:

Reads resolve both, so this is cleanup rather than a prerequisite.

reel_scout/utils/paths.py 的 resolve_media_path() 依序試資料根、其父層、cwd、 以及 videos 目錄的 basename —— 兩種形狀在任何工作目錄下都解得開。實測從 /tmp:

paths.exists('./data/videos/x.mp4')  -> True
os.path.exists('./data/videos/x.mp4') -> False   ← 我用的就是這個

⇒ 今天每一次歸咎於「相對路徑」的失敗,真正的原因都是我寫的 code 繞過了這個解析器。 四支全是同一個錯:label_shots(回報 39 refused,其實是讀不到)、storyboard (aspect 一律 unknown)、health(把健康的庫報成壞掉)、backfill_shots。

🔴 這批修復的頭一輪測試完全沒有牙齒:五個「改回裸 os.path.exists」的變異 全部 MISSED。原因是既有測試都用絕對路徑或 patch("os.path.exists"), 在那個世界裡解析器與裸判斷行為相同。要驗出差異,必須用真實的相對路徑 + 不同的 cwd —— 也就是四支模組真正踩到的那個情境。補上之後七個變異全 CAUGHT。

教訓(feedback_grep_existing_cli_before_building 的同族):寫一個路徑判斷之前, 先 grep 這個 repo 有沒有現成的路徑解析器。它有,而且註解裡就寫著它為什麼存在。

7C. 分鏡回程 ✅ 2026-09-02

storyboard-diff <video> <project.json>:把改過的分鏡案跟它出發的那份拆解比對。

diff 是明顯的產出,但不是有意思的那個。 有意思的那個從 §6E 掉出來 —— 匯出時每一格都帶著來源 URL,所以數還帶著它的格子有幾個,就量到了 「這份分鏡表還有多少是借來的」:

10 / 12 of your cuts still carry the source URL

這不是合規檢查。 那是一份草稿誠實的狀態:那些格子仍在描述別人的畫面,而 「現階段這樣行不行」只有拿著檔案的人能決定。這個數字只是讓它不再隱形。 輸出刻意寫成 Whether that is fine at this stage is your call —— 報狀態,不說教、不擋。

幾個設計決定

7D. 介面中文化:原本那條規則太寬 ✅ 2026-09-02

§4H 的 i18n 本身是滿的 —— 68 鍵、中英都有、頁面上沒有硬寫的英文(實測渲染後 逐節點稽核,唯一未覆蓋的都是逐字稿/標題/頻道名,那些本來就不該翻)。

缺的是「解碼出來的值」。 中文介面上到處是 hook-body-cta、talking_head、 neutral、educational —— 而 i18n.py 檔頭明文寫著它們永不翻,理由是 「翻它們等於要重跑模型」。

🔴 那個理由對自由文字成立,對封閉值域不成立。 content_structure/ content_type/format/pacing/opening_type/cta_type 的值域是寫死在 analyze/merger.py 的 prompt 裡的,模型的工作只是從裡面挑一個。 把挑中的那個顯示成中文,跟旁邊 row.*/dim.* 標籤一樣是顯示層映射 —— 不跑模型、不改存下來的值。

規則因此收窄成更誠實的版本:

封閉值域  → 可翻(VALUE_KEYS,31 個值)
自由文字  → 永不翻(opening_text/cta_text/summary/reasoning/逐字稿/標題)

7E. 自由文字的中文對照 ✅ 2026-09-02

§7D 讓封閉值域可翻,因為模型只是從清單裡挑一個。自由文字不一樣:摘要、影格描述、 逐字稿、評分理由 —— 那些字是模型自己的,翻譯是放在旁邊的第二份產物,不是換掉第一份。

所以翻譯自成一張表(schema v16 translations),原文永遠留著、永遠顯示, 每一列帶著產生它的 engine 與 model(同 scores / shot_labels 那一課)。

🔴 source_hash 是這張表的承重欄。 換 VLM 會重跑描述、修語言軌會重切逐字稿 —— 一份沒有指紋的翻譯會繼續被顯示在它已經不匹配的原文旁邊,靜默地,而且看起來 跟現行的一模一樣。is_stale() 就是這一欄存在的全部理由;預設會重翻過期的, --keep-stale 才留著。

不翻已經是中文的。 語料裡約四分之一本來就是中文,翻它只會得到更差的版本。 判準是 CJK 比例不是語言分類器 —— 字幕混排是常態,硬分類器兩個方向都會錯。

逐字稿按 segment 翻不整段翻:最長一支 46,996 字元,沒有單一請求該扛那個量, 而 segment 正是播放器已經在 seek 的單位。

⚠️ 實跑第一次就抓到品質問題:只要求「Traditional Chinese」拿回來的是 繁體字+大陸用語(「該視頻」)。兩者都是 zh-Hant,只有一個是本 repo 的慣例。 prompt 改成指名台灣用法並附對照組(影片 not 視頻/品質 not 質量/網路 not 網絡), 重跑得到「這段影片」。測試斷言的是對照組本身,因為只斷言「Taiwan 出現過」 的版本被變異繞過去了。

規模與成本(實測,非推算):需翻約 122 萬字元(影格描述 969k/逐字稿 153k/ timeline 51k/summary 21k/reasoning 21k)。qwen2.5:14b 實測 68 字元/秒 → 全庫約 5 小時。工具不預設跑,跑不跑由人決定。


參考案例(study cases)

claude-real-video / crv(2026-07-17 對標)

HUANGCHIHHUNGLeo/claude-real-video — 「讓 LLM 看得見影片」的擷取工具(Python/MIT,2.5 週衝 1,699★),另有閉源付費 **crv Pro($19 一次性)**加鏡頭運動/情緒/OCR/變速鑑識。不同層:crv(含 Pro)是感知+測量層(把影片攤平給 LLM 看、量化「怎麼拍的」),Reel Scout 是判讀層(帶 rubric 打分+反解會不會紅+競品語料)。重疊只在 ingest。

完整對照 → docs/crv-vs-reel-scout.md。

📌 2026-09-01 修正這段的「不同層」:§Phase 6C(運鏡)一旦做下去,reel-scout 就跨進了 crv 那個感知/測量層,「重疊只在 ingest」不再成立。這不是改變立場——原則仍是「只偷可實測的部分」,運鏡通得過那條篩子——但這句話該停止當成兩邊互不相干的證據。真正沒被偷的仍然是:情緒/mood/ai-report、變速鑑識、$19 漏斗。

對本專案的關聯:可偷的實測強化已開兩條 → §4E(pacing/BPM 實測化)、§4F(燒錄字幕 OCR 補 transcript)。原則是只偷讓判讀更可實測的部分,crv 另一半 model-dependent 主觀輸出(情緒/mood/ai-report)、變速鑑識、病毒開源→$19 漏斗刻意不偷(前者放大既有軟肋、後者違反「工具不是產品」定位,只當課程 case study 存檔)。

lapian-notes(2026-07 觀察)

bkingfilm/lapian-notes — AI 輔助電影拉片工具(TS/React/Vite,MIT)。不同類別(末端產品,非工具),但架構有一點值得存檔備用:

AI-agnostic package handoff:它自己完全不跑推論。本機抽幀 + 抓字幕 + metadata → 打包 ZIP → 使用者自己丟給任何 AI → 把回傳的 JSON 匯入 → 工具只負責視覺化。零 API key、零 GPU、零後端。

對本專案的關聯:目前 不需要 — reel-scout 已有 --llm-backend openclaw(走 proxy 吃訂閱制,同樣不需要 GPU),是比手動貼 ZIP 更好的機制。

未來可能用得上的情境:若要讓完全不想配置任何後端的人也能跑 merge/score,可加 package / import 一對指令(reel-scout 已有 keyframe + transcript,工程量不大)。等真的有這個需求再做。


設計原則

  1. CLI-first — 所有功能先有 CLI,再有 MCP/API
  2. Offline-capable — 核心分析可用本機 LLM(oMLX/Ollama),不強制雲端;但雲端/訂閱後端(openclaw)也是一等公民
  3. Minimal dependencies — urllib not requests,argparse not click,SQLite not Postgres
  4. Python 3.9 — 維持較舊系統相容
  5. Batch-friendly — 大量影片分析是核心場景,不只是單支
  6. 工具不是產品 — 見「定位與 Non-goals」

里程碑

⚠️ 版號與 phase 已脫鉤:v0.2.0(雙語轉錄)是計畫外的品質修復,不在原 Phase 規劃內。以下里程碑改為以能力為準、不綁 phase 編號。

Milestone條件
v0.33A 補完(crawl --channel/--playlist) ✅ 2026-07-15 + 3B(compare) ✅ 2026-07-17
v0.43C 標籤正規化 ✅ + 3D(stats) ✅ 2026-07-17(達成,隨 v1.0 一次發布)
v0.54A(競品研究報告) ✅ 2026-07-17(達成,隨 v1.0 一次發布)
v1.05A + 5B 完成(PyPI build 就緒 + CI 綠 + yt-dlp 健康檢查 + config check) ✅ 2026-07-17(PyPI 上架待人工 token)
v1.2§4E 評分證據化 + §4F L3.5 OCR + Wave 3(3B patterns / 3A instaloader / 4B inspire / 4D track / 4C MCP 8-tools / 5A+5C docs) ✅ 2026-07-19(PR #31/#32/#33,schema v9,228 tests;⚠️ pacing 評分行為改變,跨版本分數不可比)
🔴 2026-07-20 更正:§4E「達成」僅指 code,語料上 shot_metrics 為 0 列、從沒真的跑過 → 證據層實質未生效,見 §4E 回填段
v1.3權重即時重算 + 中英介面切換 ✅ 2026-07-21(PR #35,v1.3.0 發布)
v1.3(原提議的證據層條件)條件有兩個,2026-09-01 實查:一過一沒過。
✅ (a) shot_metrics 列數 > 0 —— 116 列,遠超條件。
✅ (b) stats 能報出證據覆蓋率 —— 2026-09-01 補上(-- Evidence coverage -- + --json + --csv + --channel scope)。
⚠️ 記一下這條怎麼欠下的:版號當時已經走過去了(v1.3.1)而 (b) 沒跟上——里程碑當條件用、版號當進度用,兩者脫鉤就會這樣。原句「這條沒過之前,任何評分 UI 精修都是在裝飾沒有地基的數字」現在兩半都有了:地基 116 列,儀表板也在了。
v1.5(2026-10-05 達成)對外曝光面。此前這個 repo 沒有 description、沒有 homepage、沒有 Pages —— 一個已上 PyPI、文件齊全、19 個 MCP 工具的專案,在 GitHub 上點不進任何能回答「它跟丟連結給 AI 有什麼不同」的地方(7 ⭐ vs arkiv 76 ⭐)。
✅ 官網 vulture-s.github.io/reel-scout:Astro + GitHub Pages + GA4(與 arkiv/wafflehouse 共用同一個 Google Tag,供跨站歸因),七頁 13 路由,文件頁直接讀 docs/*.md 走白名單。
✅ 紙質外殼下沉到 theme.py ⇒ viewer/inspector/匯出單檔三個介面共用(PR #145)。
🔴 兩個判斷是被 render 推翻的,不是讀 code 推翻的:格線第一版只有文件頁看得到;表格遮蔽讓片庫整頁沒有紋理。量過才定案(格線 8% 與紙差 17/255、--rule-soft 差 36/255)。
🔴 順修:inspector 的重新加權滑桿一直用瀏覽器自己的 accent 畫成亮藍 —— 一個宣告全程無彩色的殼上最吵的東西,而且就在 README 主截圖裡。token 一直是對的,缺的是沒人告訴那個控制項。
🔁 2026-10-06 的九支是同一個 Phase 的收斂,不是新里程碑:中文斷句(text-wrap:pretty + 兩塊沒有行長上限的文字;孤行 Chrome 37→11、WebKit 40→6,全站最長行 90→44 字,#167)/桌面寬度改成流動 clamp(1240px, 100vw - 100px, 1560px)(#168)/首頁文案全面換成 Hevin 的定稿(#169–#174)/「一鍵安裝屬於你的拍片靈感池」從 11px 的 eyebrow 升成 H1,原 h1 降成 lede、<title> 與 meta description 一起改(#175)。🔴 #168 的加寬自己造出新問題:四欄差異化沒有 max-width、欄寬是紙張的一半 ⇒ 跟著長到 48 字/行,是加寬後重跑斷句審計才發現的。
🔁 2026-10-08:行長上限從「記得要檢查」變成 CI gate(#178)。同一個 regression 兩天犯兩次 —— 加寬之後沒有 max-width 的區塊跟著長,而兩次都是改完重跑審計才發現。site/scripts/measure-lines.mjs 自帶靜態伺服器 + playwright,6 頁 × 6 種寬度逐字取 client rect 量最長那一行,上限 46。🔴 寫這支 gate 自己踩了三個量測工具的坑,最貴的是 normalize('') 在 Node 回 '.' ⇒ 首頁 404、gate 靜靜只量五頁,而受測區塊只在首頁 ⇒ 負向測試恆綠 —— 前提沒成立跟全部合格在輸出上同形。同批把 eyebrow(OPEN SOURCE · RUNS ON YOUR MACHINE)放回 H1 上方。
⚠️ 訂閱框是空的:ENDPOINT 未設時退成 mailto。刻意不填一個猜的網址 —— 一個 POST 到不存在端點的表單,在使用者端跟成功一模一樣,而名單是空的。開 Substack/MailerLite 之後一行完成。
v1.4(提議)證據可見性 + shot-level 地基,順序是刻意的(見 §Phase 6):
✅ ① stats 報證據覆蓋率(補完 v1.3 的 (b))—— 2026-09-01 done
② db check-invalid --apply 跑過既有語料(Unreleased 已有 code,庫裡那列 0.0 還在)
✅ ③ shots 表(schema v14)—— 2026-09-02 done
條件已達成:stats 講得出證據覆蓋率(114/114)且 shots 有列(三支 MV 共 320 段實測)。①② 排在 ③ 前面不是禮貌,是因為 ③ 新增了第三個可以安靜空掉的抽取層,而這個 repo 已經在同一個坑裡跌過兩次。