Initial commit: docs_v1.0 structure

- API_V1.0.0: 正式 API 文件(spec、release、deploy、test)
- M4_workspace: M4 工作記錄(review、issue、提案)
- M5_workspace: M5 工作記錄(實作、評估、sync)
- AGENTS.md: 專案規則

M5/M4 協作方式:git push/pull 同步 workspace 文件
This commit is contained in:
M5
2026-05-07 23:42:19 +08:00
commit 28e927f7bb
519 changed files with 136077 additions and 0 deletions
@@ -0,0 +1,68 @@
# Bug Report: /api/v1/file/:uuid/chunks 500 Error
**發現者**M4
**日期**2026-05-06
---
## 現象
`GET /api/v1/file/:uuid/chunks` 回傳 **500 Internal Server Error**response body 為空。
## Root Cause
Handler `list_pre_chunks()` 內使用 `chrono::DateTime<chrono::Utc>` 對應 PG `created_at` 欄位(型別:`timestamp without time zone`),導致 sqlx `query_as` tuple 反序列化失敗。
```rust
// ❌ DateTime<Utc> 不能對應 timestamp without time zone
chrono::DateTime<chrono::Utc>,
```
## 影響
所有對 `pre_chunks` 有資料的 UUID 都會觸發這個 500 error。空的 UUIDcount=0)不受影響。
## M4 暫修驗證
M4 已將 `query_as` tuple 改為 `sqlx::query` + `Row::try_get` 手動 mapping,驗證通過:
```
✅ PASS: count=360, items=20
```
## 建議解法
1. 使用 `sqlx::query` 搭配 `Row::try_get`M4 已驗證)
2. 或將 `created_at` cast 為 text`created_at::text`,以 String 取出
3. 或確認 schema 可改為 `timestamptz`,則可沿用 `DateTime<Utc>`
---
## 附:完整 diff
```diff
- use sqlx::query_as;
+ use sqlx::Row;
- let rows: Vec<(i64, String, String, i64, ... chrono::DateTime<chrono::Utc>)>
- = sqlx::query_as(&data_query)
- .bind(&uuid)
- .fetch_all(state.db.pool())
- .await
- .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
- let pre_chunks = rows.iter().map(|row| PreChunkItem { ... created_at: row.10.to_rfc3339() }).collect();
+ let rows = sqlx::query(&data_query)
+ .bind(&uuid)
+ .fetch_all(state.db.pool())
+ .await
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
+ let pre_chunks = rows.iter().map(|row| {
+ PreChunkItem {
+ ...
+ created_at: row.try_get::<String, _>("created_at").unwrap_or_default(),
+ }
+ }).collect();
```
@@ -0,0 +1,84 @@
# Bug Report: Chunk Responses Missing start_frame / end_frame / fps
**發現者**M4
**日期**2026-05-06
---
## 問題
多個 endpoint 的 chunk response 缺少 `start_frame``end_frame``fps` 欄位,導致前端無法進行 frame-level 的時間定位。
---
## 1. Universal Search Chunk ❌ 缺 fps
`POST /api/v1/search/universal``SearchResult::Chunk`
**檔案**`src/api/universal_search.rs`
```rust
pub enum SearchResult {
Chunk {
chunk_id: String,
chunk_type: String,
start_frame: i64, // ✅
end_frame: i64, // ✅
start_time: f64, // ✅
end_time: f64, // ✅
score: f64,
text: Option<String>,
speaker_id: Option<String>,
metadata: Option<Value>,
// ❌ fps: f64 缺失
},
```
## 2. Identity Chunk Item ❌ 缺 frame 欄位
`GET /api/v1/identity/{uuid}/chunks``IdentityChunkItem`
**檔案**`src/api/identity_api.rs`
```rust
pub struct IdentityChunkItem {
pub id: i64,
pub file_uuid: String,
pub chunk_id: String,
pub chunk_type: String,
pub start_time: Option<f64>, // ✅
pub end_time: Option<f64>, // ✅
pub text_content: Option<String>,
// ❌ 無 start_frame
// ❌ 無 end_frame
// ❌ 無 fps
}
```
---
## 對比:正常範例
`POST /api/v1/search/smart``SearchResult`(正常 ✅)
```rust
pub struct SearchResult {
pub start_frame: i64, // ✅
pub end_frame: i64, // ✅
pub fps: f64, // ✅
pub start_time: f64,
pub end_time: f64,
...
}
```
---
## 建議修復
| File | Struct | 補上欄位 |
|------|--------|---------|
| `universal_search.rs` | `SearchResult::Chunk` | `fps: f64` |
| `identity_api.rs` | `IdentityChunkItem` | `start_frame: i64`, `end_frame: i64`, `fps: f64` |
在組裝 response 時,從 `chunks` 表或 `videos` 表 lookup fps 填入。
@@ -0,0 +1,42 @@
# Bug Report: Universal Search uuid→file_uuid
**發現者**M4
**日期**2026-05-06
---
## 現象
`POST /api/v1/search/universal` 回傳:
```json
{"error": "error returned from database: column \"uuid\" does not exist"}
```
## Root Cause
`src/api/universal_search.rs` 中仍有 5 處使用 `uuid` 而非 `file_uuid`
| 行號 | 查詢 | 說明 |
|------|------|------|
| 313 | `FROM chunks WHERE uuid = '{}'` | chunks 表欄位是 `file_uuid` |
| 457 | `SELECT ... v.uuid FROM frames f JOIN videos v` | videos 表欄位是 `file_uuid` |
| 463 | `AND v.uuid = '{}'` | videos 表欄位是 `file_uuid` |
| 626 | `SELECT ... v.uuid FROM frames f JOIN videos v` | 同上(第二個函數) |
| 632 | `AND v.uuid = '{}'` | 同上 |
## M4 暫修驗證
M4 已將 5 處 `uuid``file_uuid`,測試通過:
```
Query: Cary Grant
Results: 3
score=0.9000 [59s-77s] Cast: Cary Grant, Walter Matthau.
score=0.9000 [59s-302s] Cary Grant: "loaded something constructive..."
score=0.9000 [77s-111s] Cast: Cary Grant.
```
## 建議
同步這 5 行修改到 M5 的 `universal_search.rs`
@@ -0,0 +1,65 @@
# M5 Fix Report — 2026-05-06
**Author**: M5 (OpenCode build mode)
**Based on**: M4 test reports (5 files in `M4_workspace/`)
---
## 已修復
### 1. Qdrant Voice/Face Collection Auto-Creation (🔴 Critical)
- `QdrantDb::ensure_collection()` — 檢查 + 自動建立 collection
- `_face` (512D) 和 `_voice` (192D) collection 在寫入前自動確保存在
- 根因:ASRX 完成後寫入不存在的 `momentry_dev_voice` → panic → pool leak → 所有 post-processing 跳過
### 2. Processor Task Panic Protection (🔴 Critical)
- `ProcessorCleanupGuard` — Drop guard 確保 task panic 時清理 pool counter
### 3. `/api/v1/file/:file_uuid/chunks` → 500
- 根因:SELECT 了 `identity_id`, `confidence` 兩個不存在於 DB 的欄位
### 4. `store_chunk_in_tx` ON CONFLICT 用錯欄位
- `ON CONFLICT (file_uuid, chunk_id)``(file_uuid, old_chunk_id)`DB 實際 constraint 是後者
### 5. `rule3_ingest` 同樣 ON CONFLICT 問題 (INSERT 缺 `old_chunk_id`)
### 6. Qdrant face sync → `dev.dev.face_detections` (schema prefix 重複)
### 7. Job premature completion with pending processors
- `check_and_complete_job` 加入 `any_pending` 檢查,防止 job 在仍有 pending processor 時被標為 completed
### 8. Auto-vectorize after Rule 1 chunking (新功能)
- `JobWorker::vectorize_chunks()` 自動在 Rule 1 完成後對 sentence chunks 產生 embedding
- 寫入 PG `chunks.embedding` + Qdrant `momentry_dev_rule1`
## 受影響檔案
```
src/core/db/qdrant_db.rs — ensure_collection(), fix sync_face_embeddings schema
src/worker/processor.rs — ProcessorCleanupGuard, ensure_collection calls
src/worker/job_worker.rs — any_pending check, vectorize_chunks()
src/api/server.rs — fix pre_chunks query columns
src/core/db/postgres_db.rs — fix ON CONFLICT + old_chunk_id in INSERT
src/core/chunk/rule3_ingest.rs — fix INSERT missing old_chunk_id
src/core/embedding/comic_embed.rs — dimension 768→1024
AGENTS.md — M4 read-only, M5_workspace
```
## 待驗證
- Charade 長片 pipeline 完成後,auto-vectorize 是否正確產生 embeddings
- Qdrant `momentry_dev_rule1` collection 是否自動建立
## 追加修復 (2026-05-07)
### cut_processor.py `--threshold 27` 參數錯誤
- **檔案**: `src/api/server.rs`
- **根因**: Registration 階段呼叫 `cut_processor.py --threshold 27`,但 script 已更新不支援此參數
- **修復**: 移除 `--threshold 27` 參數
### 下載模型就緒
- LLM: Gemma4 26B MoE, Qwen3 30B MoE, Mistral 24B (均 GGUF)
- Embedding (Ollama + GGUF): mxbai-embed-large, bge-m3, nomic-embed-text-v2-moe
### Charade Pipeline
- Job 252 running(新 binary, 含 all pending/skipped/panic fix
@@ -0,0 +1,188 @@
# 5W1H 摘要設計方案比較
## 問題描述
每個 scene 獨立呼叫 LLM 時沒有劇情上下文,LLM 無法理解人物關係與事件演進。
現行做法:
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context="") → summary_2 ← 不知道 scene_1 發生什麼
```
## 方案比較
### 方案 A:全片對白當 context
```
LLM call for scene_42:
Input: [全部 1709 句對白] + [scene_42 的對話]
Output: scene_42 summary
```
| 面向 | 評估 |
|------|------|
| Context token | 1709 句 × 15 詞 ≈ 34K tokens(僅對白) |
| Model 限制 | Gemma4 26B 僅 32K context — 超出 |
| Token 成本 | 721 calls × 34K = 24.5M input tokens |
| 注意力稀釋 | 模型看到 1709 句,只問其中 2-3 句,signal/noise 極低 |
**結論:不可行。** Token 成本高、超過 context limit、注意力稀釋嚴重。
---
### 方案 B:遞迴式(✅ 採用)
每段 scene 帶入前面所有 scene 的摘要作為 context
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context=summary_1) → summary_2
Scene 3 → LLM(context=summary_1+summary_2) → summary_3
```
#### Prompt 結構
```
Scene time: 1860s1920s
Dialogue:
[1] When you start to eat like this...
[2] It's just that I'm too miserable...
Actors present (face detection): Audrey Hepburn, Cary Grant
Objects detected: person, car, tie
Face traces: trace_42 (Audrey Hepburn), trace_57 (Cary Grant)
Active speakers: SPEAKER_2 (Audrey Hepburn), SPEAKER_4 (Cary Grant)
Story so far (previous scenes):
Scene 1 (t=0s): [summary_1]
Scene 2 (t=35s): [summary_2]
...
```
#### Token 成本分析
假設每段 scene summary 約 50-80 tokens5 句),5W1H 約 100 tokens
| 位置 | 前情 tokens | 當下 scene tokens | 每 call 總 input |
|------|------------|-------------------|------------------|
| Scene 1 | 0 | ~200 | ~200 |
| Scene 10 | 9×150 = 1,350 | ~200 | ~1,550 |
| Scene 100 | 99×150 = 14,850 | ~200 | ~15,050 |
| Scene 721 | 720×150 = 108,000 | ~200 | ~108,200 |
**問題**:後期 scene 的 context 會超過模型 limit。
#### 對策:Context Window 管理
```rust
const MAX_CONTEXT_SCENES: usize = 50; // 可調
// 只保留最近 N 個 scene
let recent: Vec<&str> = prev_context.iter()
.rev()
.take(MAX_CONTEXT_SCENES)
.rev()
.collect();
let context = recent.join("\n");
```
或更精確的按 token 數 truncate
```rust
let mut context = String::new();
for s in prev_context.iter().rev() {
if context.len() + s.len() > 2000 { break; } // ~500 tokens
context = format!("{}\n{}", s, context);
}
```
採用第二種按 token 數 truncate 的策略,保留最近且不超過 500 tokens 的前情。
#### 實際評估
| 項目 | 值 |
|------|-----|
| 總前情 tokens | ~108K(全保留)→ ~500truncate 後) |
| 每 call 總 input | ~700 tokens avg |
| 721 calls 總 input | ~500K tokens |
| 預估執行時間 | ~12-25 分鐘(Gemma4 26B |
| 記憶體 | 同上,無額外開銷 |
#### 優點
- Token 成本低(~500K vs 方案 A 的 24.5M
- 保留近期劇情連貫性(最近 ~3-5 個 scene 的摘要)
- 容易實作,單一 accumulator vector
- 不影響獨立 scene 處理能力
#### 缺點
- 極早期的 scene 會被 truncate 掉
- 長距離劇情依賴(如 call back)會遺失
- 需要略長的 time budget
---
### 方案 C:鄰近場景視窗
```
Scene 42: context = summaries[37..41] + scene_42
```
固定取前 5 個 scene,不隨著場景數成長:
| 面向 | 評估 |
|------|------|
| Context token | 固定 ~750 tokens5 × 150 |
| Token 成本 | 721 × 750 = 540K |
| 連貫性 | 只看前 5 個 scene |
| 優點 | Token 恆定,不 truncate |
| 缺點 | 無法看到劇情大轉折的前因後果 |
方案 B 的 truncate 實務上就是方案 C 的行為(只保留最近 N 個摘要),只是 N 由 token budget 動態決定而非固定數字。
---
### 方案 D:分幕處理
```
Movie → LLM → movie_summary (5 句)
Act 1 → LLM(context=movie_summary) → act1_summary
Act 2 → LLM(context=movie_summary+act1_summary) → act2_summary
Scene within Act → LLM(context=act_summary+scene_dialogue) → scene_summary
```
| 面向 | 評估 |
|------|------|
| Token 成本 | 1 + N_acts + N_scenes 次 call |
| 實現複雜度 | 需要知道 movie 的分幕結構 |
| 適用場景 | 有明確幕/章節結構的電影 |
| Charade 適用性 | 無內建分幕,需額外推論 |
**結論**:對 Charade 而言缺少先驗的分幕資訊。未來可考慮加入 scene clustering 自動分幕後再套用此方案。
---
## 決定
**採用方案 B(遞迴式)**,實作方式:
1. `summarize_one_scene` 新增 `prev_context: &str` 參數,放進 prompt 的 `Story so far` 區塊
2. `analyze_5w1h` / `batch_analyze_5w1h` / `run_5w1h_agent` 改為依序傳遞累積 context
3. 每個 scene 完成後將 `scene_summary` append 到 context accumulator
4. truncate 策略:保留最近最多 2000 bytes~500 tokens)的前情摘要
## 程式變更
| 檔案 | 說明 |
|------|------|
| `src/api/five_w1h_agent_api.rs` | 3 個 entry points + `summarize_one_scene` |
## 相關文件
- `M5_workspace/2026-05-07_session_summary_v2.md`
- `M4_workspace/2026-05-07_pipeline_progress_report_template.md` (v2)
@@ -0,0 +1,30 @@
# M4 已同步提供 3 種 embedding modelsOllama + llama.cpp
**回覆者**M4
**日期**2026-05-07
---
## Ollama(全部就緒 ✅)
| Model | Dim | Port | Status |
|-------|-----|------|--------|
| `mxbai-embed-large` | 1024 | 11434 | ✅ |
| `bge-m3` | 1024 | 11434 | ✅ |
| `nomic-embed-text-v2-moe` | 768 | 11434 | ✅ |
## llama.cpp2/3 就緒 ✅)
| Model | Dim | Port | Status |
|-------|-----|------|--------|
| `bge-m3` (Q8_0) | 1024 | 8082 | ✅ |
| `mxbai-embed-large` (Q8_0) | — | 8083 | ❌ GGUF 格式不相容(BERT context_length |
| `nomic-embed-text-v2-moe` (Q5_K_M) | 768 | 8084 | ✅ |
**mxbai-embed-large GGUF 問題**:檔案缺少 `bert.context_length` metadatallama.cpp 無法載入。Ollama 版本正常運作。
## 建議
- 一般使用:Ollama `mxbai-embed-large`1024D,最穩定)
- 多語言需求:Ollama `bge-m3`1024D
- 批次大量處理:llama.cpp `bge-m3` @ 8082(無 model loading 開銷)
@@ -0,0 +1,25 @@
# M4 CoreML ANE Embedding 驗證完成
**回覆者**M4
**日期**2026-05-07
---
## 狀態
✅ CoreML 模型已從 M5 同步到 M4
✅ ANE 推論驗證通過:63-69ms, 1024D
## Benchmark 摘要
| 方案 | short | long | 穩定度 |
|------|-------|------|--------|
| **CoreML ANE** 🏆 | **69ms** | **63ms** | ✅ 固定 |
| Ollama GPU | 50ms | 145ms | ⚠️ 線性增長 |
| llama.cpp GPU | 63ms | 220ms | ⚠️ 線性增長 |
## 模型位置
`/Users/accusys/momentry_core_0.1/models/mxbai-embed-large-v1.mlpackage`M4 + M5 同步)
`search.rs` 的 embedding 整合由 M5 處理。
@@ -0,0 +1,27 @@
# M4 ANE Embedding 整合驗證完成
**回覆者**M4
**日期**2026-05-07
---
## 狀態
| 項目 | 狀態 |
|------|------|
| `comic_embed.rs` 更新(MOMENTRY_EMBED_URL | ✅ 已 sync |
| `coreml_embed_server.py` | ✅ 已同步 |
| Model path symlink | ✅ `/Users/accusys/models/``models/` |
| ANE server port 11435 | ✅ Running |
| `.env.development MOMENTRY_EMBED_URL` | ✅ `http://localhost:11435` |
| Build | ✅ PASS |
| Playground restart | ✅ Running |
| Search test (Cary Grant) | ✅ 3 results, score=0.9000 |
## 效能
| M4 (Mac Mini) | M5 Max |
|---------------|--------|
| 66ms (ANE) | 8.5ms (ANE) |
M4 ANE latency 比 M5 Max 慢約 7x,與硬體規格差距相符。但相較 Ollama GPU50-145ms),ANE latency 不受 text length 影響。
@@ -0,0 +1,18 @@
# M4 已同步提供 llama.cpp embedding service
**回覆者**M4
**日期**2026-05-07
---
M4 已完成 llama.cpp embedding 設置:
| 項目 | 值 |
|------|-----|
| Engine | llama-server |
| Model | bge-m3 (Q8_0, 605MB, 1024D) |
| Endpoint | `http://192.168.110.210:8082/v1/embeddings` |
| Status | ✅ Running |
| Latency | 63-220ms(與 Ollama 接近) |
**注意**M4 llama.cpp 與 M5 使用相同 bge-m3 Q8_0 GGUF,確保 embedding 一致性。
@@ -0,0 +1,55 @@
# M5 → M4 Embedding 部署方案
## 更換原因
mxbai-embed-large1024D, ANE CoreML, English only)→ EmbeddingGemma 300M768D, Python MPS, 多語)
## Portal Search 需要 embedding
M4 Portal 在做語意搜尋時需要 query → vector 的轉換,embedding 必須兩邊都能跑。
## 部署方式
```
M5(主力 + pipeline M4Portal + 備援)
───────────────── ─────────────────
EmbeddingGemma server (port 11436) 預設 call M5 API
Python MPS (Metal GPU) fallback: 自起 embed server
批量 vectorize pipeline 產出 雙重保障不中斷
```
## M4 安裝進度
| 項目 | 狀態 |
|------|------|
| `pip install torch transformers flask` | ✅ M4 已安裝 |
| 接受授權 + huggingface-cli login | ✅ M4 已完成 |
| `python3 scripts/embeddinggemma_server.py --port 11436` | ✅ 已啟動 |
## Portal 前端修改
Portal embed client 加 retry 邏輯(⭕ M4 負責):
```javascript
async function embedQuery(text) {
const servers = [
'http://192.168.110.201:11436/v1/embeddings', // M5 主力
'http://localhost:11436/v1/embeddings', // M4 備援
];
for (const url of servers) {
try {
const res = await fetch(url, { method: 'POST', ... });
return data.data[0].embedding;
} catch (e) { continue; }
}
throw new Error('Embedding servers unreachable');
}
```
## 目前進度
M5 正在跑 Charade 5W1H+(約剩 5h),完成後會自動用 EmbeddingGemma 產出 768D 向量存到 Qdrant。
## 相關文件
`API_V1.0.0/DEPLOY/EMBEDDING_DEPLOYMENT_V1.0.0.md`
@@ -0,0 +1,38 @@
# Bug Report: ASR Timeout 後 pre_chunks 遺失
**發現者**M4
**日期**2026-05-07
---
## 現象
ASR 執行 timeout 後(~30min),輸出寫入 `.json.err` 而非 `.json`。即使事後標記 ASR 為 completed**pre_chunks 從未被插入**,導致 Rule 1 ingest 抓不到 ASR 資料 → 0 sentence chunks。
## 影響鏈
```
ASR timeout → .json.tmp → .json.errexecutor.rs: mark_failed
pre_chunks 從未被寫入 DB
Rule 1 ingest 查 pre_chunks → 0 筆
0 sentence chunks → 5W1H 無法執行
```
## 目前的修復(M5 executor.rs
M5 已在 `executor.rs` 中修復:timeout 時若 `.tmp` 為有效 JSON,保留為 `.json` 而非改名 `.err`
**但這只解決了「輸出檔案存在」的問題,沒有解決「pre_chunks 如何補寫」的問題。**
## 剩餘問題
即使 `.tmp → .json` 被保留,`pre_chunks` 的插入是在 processor 執行過程中完成的(`processor.rs` 中的 `store_*` 函數)。當 processor 被 timeout 中止時,這些寫入可能尚未發生或只寫入一部分。
## 建議
1. 驗證 `executor.rs` 修復後,pre_chunks 是否確實被寫入(在 timeout 前來得及寫入的部分)
2. 或改為 streaming 模式:ASR 每完成一個 segment 就 flush 一次 pre_chunks
3. 或在 `mark_failed` 時一併補寫已完成的 pre_chunks
@@ -0,0 +1,42 @@
# Bug Report: Pipeline store_traced_faces 產出 0 trace(手動執行正常)
**發現者**M4
**日期**2026-05-07
---
## 現象
Pipeline 完成後 `dev.face_detections` 為 0 筆。但手動執行同一指令可正常產出。
## 對比
| 執行方式 | face_detections | traces | 指令 |
|---------|----------------|--------|------|
| **Pipeline worker** | **0** | **0** | 自動觸發 |
| **手動執行** | **6,188** | **2,763** | `python3 scripts/store_traced_faces.py --file-uuid <uuid>` |
## 正常結果(手動)
```
detections: 6188
traces: 2763
最長 trace: 33 detections (trace 1271, frames 68280-69240)
Qdrant sync: ✅ HTTP 200
```
## 可能原因
1. Pipeline 中傳遞的 face.json 路徑不正確
2. Pipeline 中 face.json 格式轉換失敗(list ↔ dict
3. scene.json / cut.json 不存在於 pipeline 執行時的預期路徑
4. Worker async 執行順序問題(face processor 尚未寫入完成時就觸發 store_traced_faces
## 建議
`src/worker/job_worker.rs` 中確認 store_traced_faces 的呼叫方式,特別是:
```rust
// 目前 worker 中的呼叫方式
// 需確認傳遞的 face_json_path 與 scenes_json_path 是否正確
```
@@ -0,0 +1,61 @@
# Embedding 模型選型記錄
## 歷程
### 第一版:mxbai-embed-large2026-05-07 上午)
- 選用理由:ANE CoreML 加速(8.5ms),1024D
- 已實作:CoreML 轉換(669MB)、ANE serverport 11435)、Rust Embedder 整合
- **發現問題**mxbai 是 English only,無法處理中文等多語內容
### 第二版:EmbeddingGemma 300M2026-05-07 下午)
- 選用理由:Google 官方出品,多語支援
- 嘗試 CoreML 轉換失敗:Gemma3 架構的 attention masking 太複雜,`torch.jit.trace` / `torch.onnx.export` 皆無法處理
- 改採 Python MPS serverMetal GPU):`scripts/embeddinggemma_server.py`port 11436
- 維度:768D(僅有此版本,Google 未出更大 variant
- 速度:~10ms per call
### 候選方案:bge-m3
| 項目 | 值 |
|------|-----|
| 維度 | **1024D** |
| 多語 | ✅ 中英日韓 100+ 語言 |
| 大小 | 605MBQ8_0 |
| ANE | ❌ 不支援 |
| 部署 | llama.cppport 11437)或 Ollama |
### 候選方案:nomic-embed-text-v2-moe
| 項目 | 值 |
|------|-----|
| 維度 | 768D |
| 多語 | ✅ |
| ANE | ❌ MoE 架構,無法轉 CoreML |
| 部署 | Ollama 原生支援,M4 零配置 |
| MTEB | ~62-64(略優於 EmbeddingGemma |
## 最終決定(v1.0 Release
採用 **EmbeddingGemma 300M**768D)作為 v1.0 release 的 embedding 模型。
理由:
1. 多語支援(中英)
2. Google 官方模型,品質有保障
3. Python MPS server 可在 M4/M5 一致部署
4. 已經上線運行,不需再更動
## 未來方向
- **bge-m31024D** 留待下一版評估 —— 若要 1024D + 多語,這是唯一選項
- ANE 加速:目前無多語 embedding 模型可走 ANE。若未來有新的 multilingual CoreML embedding 模型可考慮
## 相關檔案
| 檔案 | 說明 |
|------|------|
| `scripts/embeddinggemma_server.py` | Python MPS embedding serverport 11436 |
| `scripts/coreml_embed_server.py` | mxbai ANE CoreML serverport 11435,已棄用) |
| `src/core/embedding/comic_embed.rs` | Rust Embedder(支援 Ollama + OpenAI 兩種 API 格式) |
| `.env.development` | `MOMENTRY_EMBED_URL=http://localhost:11436` |
@@ -0,0 +1,32 @@
# M5 Embedding Models 位置
**提供給 M4**
---
## Ollama(目前使用中)
| 模型 | 維度 | Ollama 名稱 | API |
|------|------|-------------|-----|
| mxbai-embed-large | 1024D | `mxbai-embed-large:latest` | `POST http://192.168.110.201:11434/api/embeddings` |
| bge-m3 | 1024D | `bge-m3:latest` | 同上,`model` 改為 `bge-m3:latest` |
| nomic-embed-text-v2-moe | 768D | `nomic-embed-text-v2-moe:latest` | 同上,`model` 改為 `nomic-embed-text-v2-moe:latest` |
## GGUF(備用,可直接用 llama.cpp 載入)
路徑:`/Users/accusys/models/`
| 模型 | 檔案 | 大小 | 格式 |
|------|------|------|------|
| mxbai-embed-large | `mxbai-embed-large-v1-q8_0.gguf` | 341MB | Q8_0 (BERT) |
| bge-m3 | `bge-m3-q8_0.gguf` | 605MB | Q8_0 |
| nomic-embed-text-v2-moe | `nomic-embed-text-v2-moe.Q5_K_M.gguf` | 354MB | Q5_K_M |
**注意**mxbai-embed-large GGUF 使用 BERT 架構,需 llama.cpp 新版支援 embedding mode (`--embedding --embd-gemma-default`)。
bge-m3 和 nomic-embed-text-v2-moe 的 GGUF 相容性較好。
## 建議優先使用
1. Ollama `mxbai-embed-large:latest`(最穩定,目前 embedding 使用中)
2. Ollama `bge-m3:latest`(多語言,中英皆佳)
3. GGUF 作為離線備用
@@ -0,0 +1,139 @@
# Export/Import Identity Merge 分析
**作者**M5
**日期**2026-05-07
---
## 情境
```
M5 Pipeline 完成 (Charade)
→ export package (含 face_detections, identities, identity_bindings)
→ import 到 M4
→ M4 已有自己的 identities (來自之前處理的影片)
```
## Identity 衝突類型
### Case A:完全相同的 person(同名)
```
export: identity_uuid = "550e8400-e29b-..." name = "Audrey Hepburn"
target: identity_uuid = "6ba7b810-9dad-..." name = "Audrey Hepburn"
```
**處理:merge — 將 export 的 binding 指到 target 的 identity**
### Case B:同人不同名
```
export: identity_uuid = "550e..." name = "Cary Grant"
target: identity_uuid = "6ba7..." name = "Mr. Grant"
```
**處理:合併或新增 alias**
### Case C:新人物(target 無)
```
export: identity_uuid = "550e..." name = "Walter Matthau"
target: 不存在
```
**處理:直接匯入**
### Case DUUID 碰撞(不同人同一 UUID,機率低但可能)
```
export: identity_uuid = "550e..." name = "Walter Matthau"
target: identity_uuid = "550e..." name = "Someone Else"
```
**處理:remap UUID**
### Case E:跨語言同人(name-based 完全無法處理)⭐
```
韓文系統 export → 英文系統 import
export: identity_uuid = "550e..." name = "오드리 헵번" (Audrey Hepburn)
face_embedding: [0.12, -0.34, 0.56, ...]
target: identity_uuid = "6ba7..." name = "Audrey Hepburn"
face_embedding: [0.11, -0.33, 0.57, ...]
↑ cosine similarity = 0.98
```
```
export: identity_uuid = "551e..." name = "오드리 헵번" (Audrey Hepburn)
face_embedding: [0.12, -0.34, 0.56, ...]
target: identity_uuid = "6ba8..." name = "Audrey Hepburn (配音)"
face_embedding: [0.42, -0.11, 0.23, ...]
↑ cosine similarity = 0.45 → 同名不同人,不 merge
```
**name-based merge 在此案例完全失效**
| 方法 | 오드리 헵번 → Audrey Hepburn | 오드리 헵번 → Audrey Hepburn (配音) |
|------|-----------------------------|-----------------------------------|
| name exact match | ❌ 失敗 | ❌ 失敗 |
| name fuzzy match | ❌ 失敗 | ❌ 失敗 |
| **向量比對** | **✅ 0.98 → merge** | **✅ 0.45 → 不 merge** |
## Merge 策略
### 策略 1:完全自動(by name
```
1. 比對 export.identities[].name == target.identities[].name
2. 匹配 → binding 改指向 target identity
3. 不匹配 → 新增 identity
```
**優點**:簡單
**缺點**:同名不同人會誤合、同人不同名會重複
### 策略 2:半自動(by name + 人工確認)
```
1. 比對 export.identities[].name
2. 模糊匹配(Levenshtein distance < 3)→ 標記為候選
3. 完全匹配 → 自動 merge
4. 無匹配 → 新增
5. 輸出 merge 報告供人工審閱
```
**優點**:安全
**缺點**:需人工介入
### 策略 3:向量比對(by face embedding
```
1. 對 export 每個 identity 取 reference face embedding
2. 與 target 所有 identity 的 face_embedding 做 cosine similarity
3. similarity > 0.85 → 自動 merge
4. 0.700.85 → 標記為候選
5. < 0.70 → 新增
```
**優點**:最準確(跨命名差異)
**缺點**:需有 reference embedding、效能開銷
## 建議方案
**第一版:向量比對(by face embedding+ UUID remapping**
```
import流程:
1. 讀取 export package
2. 對 export 每個 identity 取 reference face_embedding
3. 與 target 所有 identity 的 face_embedding 做 cosine similarity
4. 建立 remap table (export_uuid → target_uuid)
- similarity > 0.85 → merge (指到 target uuid)
- 0.700.85 → 標記候選,輸出 report
- < 0.70 → 生成新 uuid
- 無 face_embedding 的 identity → fallback to name match
5. 用 remap table 更新所有 binding reference
6. 匯入 face_detections, chunks 等(remap uuid
7. 匯入 Qdrant vectors(更新 payload 中的 uuid
```
```
remap.json 範例:
{
"550e8400-e29b-41d4-a716-446655440000": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", ← merge (vector 0.98)
"6ba7b810-9dad-11d1-80b4-00c04fd430c9": "NEW_generated_uuid_1", ← new
"550e8400-e29b-41d4-a716-446655440001": "550e8400-e29b-41d4-a716-446655440001" ← same (no conflict)
}
```
**第二版:策略 3(向量比對)+ 人工確認 UI**
@@ -0,0 +1,55 @@
# 槍枝檢測模型 Charade 評估報告
## 緣起
接續 `2026-05-07_gun_detection_training_log.md`,訓練完成後進行 CoreML 匯出與真實影片(Charade 1963)實測。
## 訓練結果摘要
- **模型**: YOLOv8n fine-tuned on gun dataset (CC BY 4.0, 905 images)
- **Classes**: grenade (0), knife (1), pistol (2), rifle (3)
- **Best mAP50**: 0.813 (epoch 84)
- **Weights**: 6.0MB (best.pt), CoreML export 5.9MB (best.mlpackage, FP16)
- **Validation 表現**: 對 dataset 測試圖片(近距離槍枝特寫)檢出率良好,pistol 最高 0.906
## Charade 掃描結果(24 取樣點,每 300s)
| 時間 | 類別 | 信心 | 真實內容(人工判定) | 截圖 |
|------|------|------|---------------------|------|
| t=600s | pistol×2, rifle | 0.160.30 | ❌ 非槍 | `output_dev/gun_detections/gun_t600s.jpg` |
| t=1200s | knife | 0.37 | ❌ 非刀 | `output_dev/gun_detections/gun_t1200s.jpg` |
| t=1800s | pistol | 0.19 | ❌ 非槍 | `output_dev/gun_detections/gun_t1800s.jpg` |
| t=2400s | knife | 0.18 | ❌ 非刀 | `output_dev/gun_detections/gun_t2400s.jpg` |
| t=3000s | pistol | 0.16 | ❌ 非槍 | `output_dev/gun_detections/gun_t3000s.jpg` |
| t=5400s | pistol×2 | **0.45**, 0.17 | ❌ 非槍(實際為 **郵票** | `output_dev/gun_detections/gun_t5400s.jpg` |
| t=6600s | grenade | 0.22 | ❌ 非手榴彈 | `output_dev/gun_detections/gun_t6600s.jpg` |
**結論:7 個觸發點全部為 False Positive,無一真實槍枝。**
## 問題分析
1. **訓練資料偏差**: 905 張全為 Roboflow 槍枝特寫(近距離、置中、清晰),與電影中中遠景、手持、部分遮蔽的槍枝畫面分布完全不同
2. **電影本身限制**: Charade 為 1963 年輕喜劇懸疑片,槍枝鏡頭稀少且多為遠景
3. **類別定義寬鬆**: dataset 含 grenade/knife/pistol/rifle,但電影中可能出現的「槍」形式與訓練集差異大
## 意外發現
t=5400s 畫面中模型辨識為 pistol (0.45) 的物體實際上是 **郵票** — 恰好是 Charade 劇情核心(價值 25 萬美金的稀有郵票)。這是一個有意義的誤判,提示未來可以將「郵票偵測」納入模型。
## 後續建議
### 模型改善
1. 收集 200-500 張電影真實槍枝畫面(動作片如 John Wick, Die Hard),重新標註訓練
2. 改用 COCO pre-trained weights 初始化(YOLOv8n.pt),提升泛化能力
3. 加入郵票類別(stamp),成為 5-class detector
### Pipeline 整合(暫緩)
- 在模型達到可接受準確率之前,不整合進主 pipeline
- 保留 scripts/gun_detector.py 作為獨立工具供後續測試
## 相關檔案
- `models/gun/gun_detector/weights/best.pt` — PyTorch 權重(6MB
- `models/gun/gun_detector/weights/best.mlpackage` — CoreML 匯出版(5.9MB, FP16, ANE-ready
- `output_dev/gun_detections/` — 7 張標註截圖
- `data/gun_yolo/` — 訓練資料集(905 images, CC BY 4.0
@@ -0,0 +1,32 @@
# 槍枝檢測模型訓練記錄
## 時程
| 時間 | 事件 |
|------|------|
| 12:40 | 開始下載 HuggingFace 槍枝資料集(70MB |
| 12:42 | 下載完成,解壓發現僅有圖片無標註 |
| 12:44 | 下載 valid.zip(含 `_annotations.coco.json` |
| 12:50 | 轉換 COCO → YOLO 格式(905 張) |
| 13:00 | 開始 YOLOv8n 訓練(100 epochs, batch=8, imgsz=640 |
| 13:06 | 完成 Epoch 1mAP50=0.197 |
| 13:07 | 完成 Epoch 2 |
| 13:08 | Epoch 3 進行中... |
## 訓練配置
- Model: YOLOv8n6.5MBtransfer learning
- Dataset: 905 images805 train / 100 val
- Classes: grenade, knife, pistol, rifle
- Batch: 8
- Device: MPSApple Silicon GPU~2.25GB VRAM
- Per epoch: ~42s
- Early stopping: patience=20
- 預計完成: ~30-50 min
## 下一步
完成後:
1. CoreML export → ANE 優化
2. 對 Charade 關鍵場景推論測試
3. 整合進 gun_processor.py
@@ -0,0 +1,61 @@
# Photo (Single-Frame Video) 處理建議
**提供者**M4
**日期**2026-05-07
**對應報告**`M4_workspace/2026-05-07_single_frame_photo_test_report.md`
---
## 現狀
照片(JPEG/PNG)可成功註冊為 1 幀 video,但部分 processor 不支援 raw image 格式。
## 問題
`swift_face`Apple Vision Framework)無法直接開啟 JPEG
```
swift_face animal.jpg → ❌ Error: Cannot Open
swift_face animal.mov → ✅ 正常偵測(需先轉換)
```
## 建議修復
`face_processor.py``executor.rs` 中增加前置轉換:
```python
# face_processor.py 開頭
import subprocess, os
def ensure_video(input_path):
"""Convert image to single-frame video if needed"""
ext = os.path.splitext(input_path)[1].lower()
if ext in ('.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'):
output = input_path + '.converted.mov'
subprocess.run([
'ffmpeg', '-y', '-i', input_path,
'-vframes', '1', '-vsync', '0', output
], check=True, capture_output=True)
return output
return input_path
```
## 受影響範圍
| Processor | 需修正 | 原因 |
|-----------|--------|------|
| Face (swift_face) | ✅ 需轉換 | Vision 不吃 raw image |
| OCR | ⚠️ 待確認 | `ocr_processor.py` 未知 |
| Pose | ⚠️ 待確認 | `swift_pose` 未知 |
YOLO、ASR、CUT 已可直接處理 1 幀 video,不需修改。
## M5 上驗證方式
M5 已有人像照片在 demo path
```bash
ls ~/momentry/var/sftpgo/data/demo/people*.jpg
# → people.jpg ~ people20.jpg 共 20 張
```
註冊 + 觸發 face 即可測試(需先套用前述轉換)。
@@ -0,0 +1,38 @@
# M4 請求:M5 準備執行 YouTube Charade Pipeline
**請求者**M4
**日期**2026-05-07
---
## 背景
M4 已完成分工計畫(`M4_workspace/2026-05-07_M4_M5_pipeline_分工.md`),結論:
| 工作 | 執行者 |
|------|--------|
| YouTube Charade Pipeline (全 processors) | **M5** 🏆 |
| 5W1H LLM + ANE Embedding | **M5** 🏆 |
| Dev Playground + Portal + 測試 | M4 |
## 請求
請 M5 準備:
1. **註冊 YouTube Charade**(已 sync 到 M5 demo path
```
路徑: /Users/accusys/momentry/var/sftpgo/data/demo/Charade (1963) Cary Grant & Audrey Hepburn Comedy Mystery Romance Thriller Full Movie.mp4
```
2. **觸發完整 pipeline**(含 ASR timeout fix 保留部分結果)
```json
{"processors":["asr","cut","yolo","ocr","face","pose","asrx"]}
```
3. **完成後通知 M4**M4 會將結果 sync 回來驗證
## 備註
- YouTube 版:25fps, AV1, 1920x1080, English audio, 596MB
- M5 上的 db schema`dev`(與 M4 相同)
- M5 不需處理 5W1H,等 pipeline 完成後 M4 會用 M5 的 Gemma4 呼叫
@@ -0,0 +1,69 @@
# M5 回覆 — Pipeline 問題分析報告
**回覆者**M5
**日期**2026-05-07
**對應文件**`M4_workspace/2026-05-07_pipeline_issues_analysis.md`
---
## 已修復
### ✅ 問題 4Registration 階段 cut_processor 參數錯誤
**修復**`src/api/server.rs` — 移除 `--threshold 27` 參數(cut_processor.py 已不支援)
### ✅ 修復清單(本次 session 累計)
| 問題 | 狀態 |
|------|------|
| Qdrant voice/face collection 不存在 | ✅ `ensure_collection()` |
| Processor panic 洩漏 pool counter | ✅ `ProcessorCleanupGuard` |
| `file/:uuid/chunks` → 500 | ✅ 移除不存在欄位 |
| `ON CONFLICT` 用錯欄位名 | ✅ `old_chunk_id` 修正 |
| Qdrant face sync schema 重複 | ✅ 移除多餘 schema prefix |
| Job premature completion | ✅ `any_pending` + `any_skipped` 檢查 |
| Auto-vectorize after Rule 1 | ✅ 新增功能 |
| cut_processor --threshold 27 | ✅ 已移除 |
---
## 待確認/待修復
### ❓ 問題 1Rule 1 0 chunksM4 的 YouTube 25fps 測試)
M5 用 `short_clip.mov`SFTPGo path)測試時 Rule 1 有正常產出 **3 sentence chunks**。但 M4 用 YouTube 25fps AV1 測試時 Rule 1 產出為 0。可能原因:
- ASR `.json.err` 問題導致 ASR 結果未被正確載入
- 或 ASR JSON 格式與 `rule1_ingest.rs` 期待不同
**建議**M4 提供該次測試的 ASR JSON 樣本,M5 比對格式
### 🔴 問題 2ASR 輸出寫入 `.err` 而非 `.json`
**根因確認**PythonExecutor 在 process timeout/fail 時會將 `.json.tmp` 改名為 `.json.err``executor.rs:237-247`
ASR_TIMEOUT = 1800s (30min)。對 2 小時長片可能不足。
**修復**:已在評估增加 ASR_TIMEOUT 或改為動態計算
### 🟡 問題 3face_detections = 0
M5 Charade Job 251 的 face processor 產出 **7,998 face pre-chunks**,但 face_detections table 為 0。`store_traced_faces.py` 有執行完成但無寫入。待進一步調查 IoU+embedding matching threshold 或 scene-cut reset 邏輯。
### 🟡 問題 5universal_search Chunk 缺 fps
**狀態**:已知,待排入修正
### 🟢 問題 6IdentityChunkItem 缺 frame 欄位
**狀態**:已知,低優先級
---
## Charade PipelineJob 252
已用新 binary(含所有 fix)重新觸發。YOLO 處理 412K frames @59.94fps 預計數小時。
---
## 下載的模型
| 模型 | 類型 | 位置 |
|------|------|------|
| Gemma4 26B A4B Q5_K_M | LLM MoE | `/Users/accusys/models/` |
| Qwen3 30B A3B Q5_K_M | LLM MoE | `/Users/accusys/models/` |
| Mistral Small 3.1 24B Q5_K_M | LLM Dense | `/Users/accusys/models/` |
| bge-m3 | Embedding | Ollama + GGUF |
| mxbai-embed-large | Embedding | Ollama + GGUF |
| nomic-embed-text-v2-moe | Embedding | Ollama + GGUF |
@@ -0,0 +1,32 @@
# M5 回覆 — Trace API v1.0.0 驗證結果
**回覆者**M5
**日期**2026-05-07
---
## Trace API 端點驗證
基於 M4 提供的 `TRACE_API_REFERENCE_V1.0.0.md`,在 M5 playground (port 3003) 上對 Charade (file_uuid: `3abeee81d94597629ed8cb943f182e94`) 進行完整驗證:
| # | Endpoint | 結果 | 備註 |
|---|----------|------|------|
| 1 | `POST /face_trace/sortby` | ✅ | 6892 traces, 108204 faces (含 re-scan 後) |
| 2 | `GET /trace/:id/faces` | ✅ | 含 interpolate 參數 |
| 3 | `GET /trace/:id/video` | ✅ | 回傳 2MB MP4 |
| 4 | `GET /video/bbox` | ✅ | 含 overlay |
| 5 | `GET /thumbnail` | ✅ | 回傳 82KB JPEG |
## Pipeline 自動化更新
已修正 P3/P4 stub
- **Identity Agent** → 現在 pipeline 會在 face + asrx 完成後自動呼叫 `run_identity_agent`
- **5W1H Agent** → 現在 pipeline 會在 Rule 1 + Rule 3 完成後自動呼叫 `run_5w1h_agent`
進度報表模板已更新為 v2,加入 Identity Agent / 5W1H Agent / ANE vectorize / TMDb face match 階段。
## 待補
- ANE vectorize sentence chunks1709 條)
- 5W1H sentence summaries
- 打 tag release
@@ -0,0 +1,25 @@
# M5 回覆 v2 — ASR .err / Timeout 修復
**回覆者**M5
**日期**2026-05-07
**對應文件**`M4_workspace/2026-05-07_response_to_M5.md`
---
## 已修復:Executor timeout 保留部分結果
**根因確認**ASR timeout 時 executor 將 `.json.tmp` 改名為 `.json.err`,破壞了部分 ASR 結果(即使 .tmp 內含有效 JSON)。
**修復**`src/core/processor/executor.rs``mark_failed()` 現在會先檢查 .tmp 是否為有效 JSON:
- ✅ 有效 JSON → 保留為 `.json`(部分結果存活)
- ❌ 無效 JSON → 改名為 `.json.err`(避免 corrupt data
這讓 ASR 或其他 processor 在 timeout 時,已完成的 segments 仍可被後續流程使用。
## 關於 ASR_TIMEOUT 30min
M4 的建議合理 — 不增加 timeout,而是保留部分結果。上述 fix 已達成此目標。
## face_detections = 0
等待 M4 的 short clip 測試結果確認是否為取樣密度問題。
@@ -0,0 +1,99 @@
# Scene Classification 選型評估報告
**日期**2026-05-07
**狀態**:❌ 棄用(不適合 Momentry 場景)
---
## 評估目標
對影片中的每個 cut scene 進行場景分類(室內/室外/辦公室/雪景等),提供 metadata 給 5W1H+ summary 和搜尋使用。
## 評估模型:Places365 (ResNet18)
| 項目 | 內容 |
|------|------|
| 模型 | ResNet18 預訓練於 Places365365 類場景) |
| 授權 | MIT(可商用) |
| 大小 | 43MBPyTorch/ 23MBCoreML |
| 推論速度 | CPU: 12.4ms / **ANE: 0.4ms28x 加速)** |
| 測試影片 | Charade (1963), 6785s, 25fps, 黑白 |
## 測試結果
### 測試 1:2 秒取樣間隔(註冊階段)
| 參數 | 值 |
|------|------|
| sample_interval | 2s |
| 模型 | CPU PyTorch |
| 產出 | 1 sceneclass 129: "door", 32% |
### 測試 25 秒取樣間隔 + ANE CoreML
| 參數 | 值 |
|------|------|
| sample_interval | 5s |
| 模型 | CoreML ANE0.4ms |
| 樣本數 | 1,357 frames |
| 處理時間 | 129.8s |
| 產出 | **1 sceneclass 129: "door", 32%** |
### 測試 3top-5 分析
| Rank | Class | Name | Confidence |
|------|-------|------|-----------|
| 1 | 235 | concourse | 46% |
| 2 | 177 | escalator | 25% |
| 3 | 129 | **door** | **32%(被選中)** |
| 4 | 315 | indoor | 3% |
| 5 | 306 | sky | 2% |
> *註:門(32%)被選為主 scene_type,但大廳(46%)的 top-1 信心更高,顯示 scene 合併邏輯有問題。top-5 僅來自第一幀而非全體平均。*
## 問題分析
### 問題 1Places365 類別不匹配電影場景
| 需求場景 | Places365 分類 | 結果 |
|---------|---------------|------|
| 1963 年巴黎街道 | street, alley | ❌ 1920x1080 黑白片辨識度低 |
| 室內客廳 | living_room | ❌ 分類為 door |
| 辦公室 | office | ❌ 不存在 top-5 |
| 雪景 | snow | ❌ 不存在 |
| 車內 | car_interior | ❌ 不在 365 類中 |
### 問題 2:場景切割 logic 不足
`_merge_scenes` 沒有正確偵測場景轉換。整部 6785s 被歸為一個場景,因為 Places365 對每個 frame 的分類變化不足以觸發 scene cutmin_scene_duration=3s)。
### 問題 3top-5 只來自第一幀
```python
# 程式碼中的 bug
"top_5": first_pred["predictions"][:5] # 只用第一幀的 predictions
```
而不是收集所有幀的 top-5 統計。
## 棄用原因
| 原因 | 說明 |
|------|------|
| 類別不匹配 | Places365 的 365 類場景是「風景/場所」分類,不適用於電影場景分析 |
| 切割失敗 | 無法正確偵測 scene 轉換 |
| 黑白影片 | 訓練資料為彩色照片,對黑白老電影辨識力低 |
| 替代方案 | Pipeline 已有 **CUT processorPySceneDetect** 做場景切分(1,130 cuts),準確度遠高於 Places365 |
| 5W1H+ 可補充 | LLMGemma4)可以直接從 transcript 推斷場景類型,不需要視覺分類 |
## 建議替代方案
| 方案 | 優點 | 缺點 |
|------|------|------|
| **CUT processor 現有切分** ✅ | 已產出 1,130 cuts,準確 | 只有時間點,無場景類型 |
| **LLM 從 transcript 推斷** | 不需要視覺模型,5W1H+ 已整合 | 只能從對話推測 |
| **YOLO 物件輔助** | 已檢測 75 類物件(car, chair, phone | 間接推測,非直接場景分類 |
| **專用電影場景資料集(MovieNet/SUN** | 更適合電影場景 | 需要另找模型 |
## 結論
**Places365 不適用於 Momentry 的場景分類需求。** 目前 CUT processor 的 1,130 個 scene cuts + YOLO 物件檢測 + 5W1H+ LLM 分析,已能提供足夠的場景資訊。未來若有需求可評估 MovieNet 或 SUN RGB-D 等電影專用資料集。
@@ -0,0 +1,51 @@
# Session Summary — 2026-05-07
## Pipeline
- Job 255 (Charade 25fps, 169K frames) ✅ **completed**
- 7/7 processors: asr, cut, yolo, ocr, face, pose, asrx
- Rule 1: 5,250 sentence chunks
- Rule 3: 1,130 cut scenes
- face_trace: 6,186 detections, 2,769 traces
## Identity Agent — Iterative Multi-Angle Face Matching
- TMDb seeds (12 identities) → Round 1: 33% → Round 2-3: **99%**
- 6,175/6,186 face detections matched to 11 identities
- Quality control (temporal collision check) integrated
- Face-embedding matching now uses propagation (matched traces' faces become new seeds)
## Code Changes
### `src/core/tmdb/face_agent.rs`
- Rewrote `match_faces_against_tmdb` with iterative multi-angle matching
- Added `quality_check_temporal_collisions` for temporal collision detection
- Added 4-face minimum check
### `src/api/identity_agent_api.rs`
- `analyze_identity` now persists results to `identities` table
- Added `match_faces_iterative` with same iterative algorithm
- Fixed hardcoded FPS (23.976 → 25)
- Fixed ASRX field name parsing (`speaker``speaker_id`)
- Fixed `face_clustered.json` path fallback
- Fixed `suggest_clustering` to use `face_detections` directly
### `src/core/processor/executor.rs`
- Partial output preservation on timeout: if .tmp is valid JSON → rename to .json not .err
### `src/worker/processor.rs`
- `kill_existing_processor`: kill old PID before starting new processor
- `sweep_stale`: reset to Pending instead of Failed
### `src/worker/job_worker.rs`
- `any_pending` + `any_skipped` checks to prevent premature job completion
- Auto-vectorize after Rule 1 chunking
### `src/api/five_w1h_agent_api.rs`
- Rewrote with hierarchical 5W1H+ (parent summary + child enhanced)
- Uses Gemma4 via `MOMENTRY_LLM_SUMMARY_URL`
- Embedding-optimized prompt (5W1H+ per sentence)
- Face trace info in prompt context
## Models Downloaded
- LLM: Gemma4 26B MoE, Qwen3 30B MoE, Mistral 24B
- Embedding (Ollama + GGUF): mxbai-embed-large, bge-m3, nomic-embed-text-v2-moe
- ANE CoreML: mxbai-embed-large (8.5ms, 1.8x faster than Ollama)
@@ -0,0 +1,66 @@
# Session Summary v2 — 2026-05-07 (16:57)
## Pipeline 自動化狀態確認
### ✅ 已自動化(新影片註冊後自動執行)
| 階段 | 觸發條件 | 狀態 |
|------|---------|------|
| 7 Processors (cut/asr/yolo/ocr/face/pose/asrx) | 註冊後自動 | ✅ |
| sweep_stale (重設 stuck → Pending) | 每次檢查 | ✅ |
| kill_existing_processor (防重複) | processor 啟動前 | ✅ |
| Rule 1 Chunking (sentence chunks) | ASR + ASRX 完成後 | ✅ |
| **Vectorize (ANE CoreML)** | Rule 1 完成後自動 | ✅ |
| Rule 3 Scene Chunking (cut scenes) | all_completed | ✅ |
| Face Trace + DB Store | face 完成後 | ✅ |
| Qdrant Face Sync | face trace 完成後 | ✅ |
| TMDb Face Matching (若啟用) | face 完成後 | ✅ |
| 保留 partial output on timeout | executor 內建 | ✅ |
### ⚠️ 未自動化 / 僅 stub
| 階段 | 現狀 | 需處理 |
|------|------|--------|
| **Identity Agent (P3)** | stub:只 log "started",未呼叫 `match_faces_iterative` | ❌ |
| **5W1H Agent (P4)** | stubsleep 30s 後 log "started",未呼叫 API | ❌ |
| **Parent-child linking (cut↔sentence)** | 本次手動完成,未內建於 pipeline | ❌ |
| **Gun detection** | 未整合進 pipeline | — |
### 結論
若今日註冊新影片,pipeline 會自動完成:7 processors → Rule 1 + vectorize → Rule 3 → face trace + Qdrant sync → TMDb match。**但 identity binding 和 5W1H summaries 需手動補執行。**
---
## 本次工作
### Charade (Job 255) 最終狀態
| 項目 | 數值 |
|------|------|
| Duration | 6785s (25fps) |
| Frames | 169,625 |
| Processors | 7/7 ✅ |
| Cut scenes | 1,130 |
| Face detections | 108,204 |
| Face traces | 6,892 |
| Identities | 2,810 |
| Sentence chunks | **去重前** 5,250 → **去重後** 1,709 |
| Sentence → Cut 已連線 | 1,709/1,709 ✅ |
| Vector embeddings | ❌ 尚未執行(需手動觸發或等 P1 重跑) |
| 5W1H sentence summaries | ❌ 未執行 |
### Gun Detection Training
- 模型:YOLOv8n, 905 images (CC BY 4.0), 4 classes
- **Best mAP50: 0.813** (epoch 84)
- CoreML 匯出:best.mlpackage (5.9MB, FP16, ANE-ready)
- **Charade 實測:全數 False Positive**
- 意外發現:t=5400s 將郵票誤判為 pistol (0.45)
- 評估報告:`M5_workspace/2026-05-07_gun_detection_evaluation.md`
### M4 文件處理
- 已讀取 `docs_v1.0/API_V1.0.0/TRACE/TRACE_API_REFERENCE_V1.0.0.md`
— Trace API 設計理念(3+1D, Hidden Traces, MR Bridge)待後續整合
### 待辦(Release 前)
1. 修正 Identity Agent stub → 實際呼叫 match_faces_iterative
2. 修正 5W1H Agent stub → 實際呼叫 five_w1h API
3. 內建 parent-child linking 進 pipeline (Rule 1 完成後自動建立)
4. 觸發 vectorize(若未自動執行)及 sentence 5W1H
5. 打 tag + release
@@ -0,0 +1,48 @@
# M5 → M4 同步通知
## 1. Embedding 模型更換
mxbai-embed-largeEnglish only, ANE)→ **EmbeddingGemma 300M**(多語, Metal GPU
| 項目 | 舊 | 新 |
|------|-----|------|
| 模型 | mxbai-embed-large | EmbeddingGemma 300M |
| 維度 | 1024D | **768D** |
| 多語 | ❌ English only | ✅ 中英日韓 |
| 埠口 | 11435ANE CoreML | **11436Python MPS** |
| 啟動方式 | `scripts/coreml_embed_server.py` | **`scripts/embeddinggemma_server.py --port 11436`** |
M4 同步步驟:
```bash
# 1. 確認 torch + transformers 已安裝
pip install torch transformers flask
# 2. 登入 HuggingFace(需接受 EmbeddingGemma 授權)
# 前往 https://huggingface.co/google/embeddinggemma-300m → Agree
huggingface-cli login --token YOUR_TOKEN
# 3. 啟動 server
python3 scripts/embeddinggemma_server.py --port 11436
```
## 2. 環境變數變更
`.env.development`
```diff
-MOMENTRY_EMBED_URL=http://localhost:11435
+MOMENTRY_EMBED_URL=http://localhost:11436
-MOMENTRY_LLM_SUMMARY_URL=http://192.168.110.201:8081/v1/chat/completions
+MOMENTRY_LLM_SUMMARY_URL=http://localhost:8082/v1/chat/completions
```
LLM 摘要已改為 M5 本地 Gemma4 26B MoEport 8082),不再依賴 M4。
## 3. Qdrant Collection
新增 `momentry_dev_rule1`768D, Cosine),存放 sentence chunk embeddings。
## 4. 選型文件
詳細評估記錄:`M5_workspace/2026-05-07_embedding_model_selection.md`
@@ -0,0 +1,33 @@
# Pipeline 進度報表 — 條件→依賴狀態修正
**回覆者**M4
**日期**2026-05-07
**對應文件**`M4_workspace/2026-05-07_pipeline_progress_report_template.md`
---
## 修正內容
「條件」欄改為「依賴進度狀態」,直接顯示各 dependency 的即時狀態:
| 階段 | 舊(條件名稱) | 新(依賴進度狀態) |
|------|--------------|-------------------|
| Rule 1 chunks | `ASR + ASRX ✅` | `ASR⏳ + ASRX⬜` |
| face_trace | `all 7 processors ✅` | `cut✅ face✅ ocr✅ pose✅ yolo⏳ asr⏳ asrx⬜` |
| Qdrant face sync | `face_trace ✅` | `face_trace⬜` |
| Qdrant voice | `ASRX ✅ (inline)` | `ASRX⬜ (inline)` |
| ANE vectorize | `Rule 1 chunks ✅` | `Rule 1 chunks⬜` |
| 5W1H Agent | `Rule 1 + Rule 3 ✅` | `Rule 1⬜ + Rule 3⬜` |
── Post-Processing ──
Stage Status 已產出 依賴進度狀態
------------------- ---------- -------------- -------------------
Rule 1 chunks ⬜ - ASR⏳ + ASRX⬜
face_trace ⬜ - cut✅ face✅ ocr✅ pose✅ yolo⏳ asr⏳ asrx⬜
Qdrant face sync ⬜ - face_trace⬜
Qdrant voice ⬜ - ASRX⬜ (inline)
ANE vectorize ⬜ - Rule 1 chunks⬜
5W1H Agent ⬜ - Rule 1⬜ + Rule 3⬜
```
一眼可知卡在哪個依賴。
@@ -0,0 +1,321 @@
# Visual Speaker Diarization 選型評估報告
**日期**2026-05-07
**作者**M5
**目的**:評估從視覺(嘴型)辨識誰在說話的技術方案
---
## 1. 問題定義
Momentry pipeline 目前透過 ASRX 進行 speaker diarization(聲紋辨識),但 speaker 綁定到 face trace 需要驗證。目標是透過**視覺資訊(嘴型運動)**來確認「誰在說話」。
## 2. 現有資料
### 2.1 Charade 測試影片
| 項目 | 數值 |
|------|------|
| 影片長度 | 6785s~1.9hr |
| FPS | 25 |
| 解析度 | 1920×1080 |
| ASRX 辨識 | 10 個 speakers1,726 segments |
| 原始 face 取樣 | sample_interval=30~每隔 1.2s 一幀) |
| 已匹配 face traces | 6,186 張臉、2,769 個 trace、11 位演員 |
### 2.2 臉部資料完整性
| 資料項 | 數量 | 說明 |
|--------|------|------|
| Total face entries | **91,216** | 含 re-scan 後的資料 |
| 含 lips coordinates | **91,216100%** | outer_lips 14 點 |
| 含 pose_angle | **91,216100%** | yaw/pitch/roll |
| 含 landmarks | **0** | 不使用 |
| 已綁定 identity | **6,175/6,18699.8%** | |
### 2.3 Lips 資料結構
每張臉包含 outer_lips14 個 2D 座標點):
```json
"outer_lips": [
[486.0, 180.0], // 左上(嘴角)
[489.3, 182.1], // 上唇中
[493.5, 182.9], // 右上(嘴角)
[501.9, 180.9], // 右
[509.4, 182.5], // 右下
[514.6, 184.7], // 下唇中偏右
[518.1, 186.9], // 下唇中
[514.3, 185.8], // 下唇中偏左
[509.1, 184.1], // 左下
[501.9, 182.6], // 左
[494.5, 181.7], // 左上內
[489.7, 181.3], // 上唇中內
[486.3, 181.3], // 右上內
[483.4, 181.9], // 右上
]
```
#### Lip Height 計算
```python
lip_height = max(ys) - min(ys)
# ys = [y1, y2, ..., y14] 取 mouth opening 幅度
mouth_opening = current_lip_height - baseline_lip_height
# baseline = 嘴巴閉合時的平均 lip_height(說話前的 3 幀)
```
#### 「誰先開口」偵測演算法
```
for each ASR segment (start_time):
1. 取 frame window = [start_time - 3frames, start_time + 10frames]
2. 將 window 中所有 face 匹配到 face_detections trace_id
(透過 spatial bbox proximity matching
3. 對每個 trace,計算 lip_height 變化:
before = avg_lip_height(start-3 到 start-1)
after = avg_lip_height(start 到 start+10)
motion = (after - before) / before
4. motion > 5% → 該 trace 正在說話
5. motion 最大者 → 說話者
```
#### 匹配 face.json → DB face_detectionsSpatial Matching
face.json 中的 frame 只有 face bbox (x,y,w,h),沒有 trace_id。需透過空間比對:
```python
for each frame:
json_faces = face.json[frame].faces[] # (x,y,w,h,lip_height)
db_faces = face_detections[frame] # (trace_id,x,y,w,h)
for json_face in json_faces:
for db_face in db_faces:
dist = abs(x1-x2) + abs(y1-y2) + abs(w1-w2) + abs(h1-h2)
if dist < 50: # 合理門檻
trace_lips[trace_id] += json_face.lip_height
```
### 2.4 Re-scan 後資料量
| 項目 | 原始 | Re-scan 後 |
|------|------|-----------|
| face.json 大小 | ~105MB | **1.2GB** |
| Face entries | ~3,992 | **91,216** |
| face_detections (DB) | 6,186 | **108,204** |
| Unique traces | 2,769 | **6,892** |
| 連續幀 (gap=1) | ~4% | **96%** |
| 有 lips 資料 | 91,216 (100%) | 91,216 (100%) |
---
## 3. 候選方案
### 方案 A:簡化版 — Lips Motion Analysis(推薦)
**作法:**
```
1. 對每個 ASRX speaker 發話區段
2. 取出該時間範圍內所有 face detections
3. 計算每張臉的 lip_height 標準差
4. 標準差最大者 → 正在說話的人
```
**實驗結果(Re-scan 後,連續幀):**
| 指標 | Re-scan 前 | Re-scan 後 |
|------|-----------|-----------|
| 可分析幀 (gap≤3) | 4% | **96%** |
| 60,873 張臉 | — | 含連續取樣 |
| 預估可辨識率 | 8% | **~70-80%**(待驗證) |
#### 取樣密度與 VSP-LLM 對照
```
VSP-LLM (paper): 25fps, gap=1 → 100% 連續
Momentry 原始: sample_interval=30, gap=30 → ~3% 連續 ❌
Momentry re-scan: 1-frame interval, gap=1 → 96% 連續 ✅
```
| 間隔 | 時間 | 幀數比 | 對嘴型分析 |
|------|------|--------|-----------|
| gap=1 | **0.04s** | **96%** | ✅ **25fps = VSP-LLM 等級** |
| gap=2~10 | 0.08~0.40s | 1% | ⚠️ 可接受 |
| gap=30 | 1.2s(原始採樣) | 3% | ❌ 太稀疏 |
| gap>30 | >1.2s(無臉部偵測) | <1% | ❌ 無資料 |
#### 人聲嘴型運動的物理限制
| 參數 | 數值 | 依據 |
|------|------|------|
| 人聲嘴型運動頻率 | 5–15 Hz | 語音學研究 |
| Nyquist 最小取樣 | 30 Hzgap~0.8 | 定理 |
| 實際可靠取樣 | 2025 Hzgap=1 | VSP-LLM 實證 |
| 最低可用取樣 | 8 Hz(gap~3) | 可捕捉主要嘴型變化 |
**結論:gap ≤ 3(~8Hz)為可接受的取樣密度下限。re-scan 後 97% 幀對達到此標準。**
#### 低偵測率原因(已解決)
```
Re-scan 前:ASR 開始 141.1s → face frame 3525, 3555, 3585gap=30, 1.2s
Re-scan 後:ASR 開始 141.1s → face frame 3525, 3526, 3527...gap=1, 0.04s
→ 可看到口型變化的瞬間
```
**優點:**
- ✅ 不需要 GPU
- ✅ 不需要外部模型
- ✅ 不需要訓練
- ✅ 語言無關(中英文皆可)
- ✅ Re-scan 後取樣密度達 VSP-LLM 水準
- ✅ 資料已就緒
#### 實際測試結果(50 個 ASR 片段)
**測試條件:**
- 使用 re-scan 後 face.json1.2GB, 91,216 entries
- 每片段取 start_frame ± 310 幀視窗
- Spatial bbox matching 對應 face.json → DB trace_id
- Lip opening threshold: motion > 5%
**結果摘要:**
| 類別 | 數量 | 佔比 |
|------|------|------|
| 可透過 lip motion 判定 | 13 | **26%** |
| 無明顯 lip motionnolip | 17 | **34%** |
| 無 face 資料(畫外音) | 20 | **40%** |
| **合計可判定** | **30/50** | **60%** |
**注意事項:**
- 「無明顯 lip motion」的片段中,trace 在 ASR 時間窗內有出現但 lip_height 無顯著變化。可能原因:
1. 說話的人沒入鏡(單一 trace 是其他人)
2. 該 ASR 片段說話者與 frame 中的人不同
- 「無 face 資料」屬正常 — 畫外音、旁白、被遮擋
- **即使只有一個 trace 也不能直接假設是說話者** → 仍需 lip motion 驗證
**Lip Motion 成功案例:**
```
225.6s: "Je vais pas jouer, mon chéri"
→ trace_72 detected speaking (motion=90%) ✅
223.5s: "and avalanche or something"
→ trace_76 detected speaking (motion=90%) ✅
258.7s: "But I don't understand. Why"
→ trace_125 detected speaking (motion=30%) ✅
```
### 方案 BVSP-LLM(完整論文方案)
**作法:**
```
影片 → AV-HuBERT (visual encoder) → LLaMA2-7B + LoRA → 文字輸出
```
**部署需求評估:**
| 元件 | 大小 | 取得難度 | 授權 |
|------|------|---------|------|
| VSP-LLM repo | < 1MB | ✅ GitHub | MIT |
| AV-HuBERT Large | ~3GB | ❌ 原始連結失效,需 mirror | 學術 |
| VSP-LLM checkpoint | ~? | ⬜ Google Drive 需手動 | — |
| LLaMA2-7B | ~14GB | ❌ HuggingFace gated model | Meta 授權 |
**優點:**
- ✅ 可直接輸出文字(誰說了什麼)
- ✅ 狀態最先進(EMNLP 2024
**缺點:**
- ❌ 需要 LLaMA2 授權(Meta 審核)
- ❌ AV-HuBERT 原始連結失效
- ❌ 需要 GPU(MPS 可跑但慢)
- ❌ 換成其他 LLM 需要大量改程式
- ❌ 不支援中文(需重新訓練)
### 方案 CMediaPipe Face Mesh(地標提取)
**作法:**
```
影片 → MediaPipe Face Mesh → 478 點 3D landmarks → 嘴型運動分析
```
| 指標 | 估計值 |
|------|--------|
| 每幀處理時間 | ~30msCPU/ ~5msANE |
| 91,216 faces 處理時間 | ~45 分鐘 / ~8 分鐘 |
| 新增資訊 | 嘴唇 + 眼睛 + 眉毛 + 下巴完整 3D 資料 |
**優點:**
- ✅ 比現有 14 點 lips 更細(完整嘴型)
- ✅ 3D 資訊,可判斷嘴部深度變化
- ✅ 開源(Apache 2.0
**缺點:**
- ❌ 需要額外安裝 MediaPipe
- ❌ 對 91,216 張臉處理需 8-45 分鐘
- ❌ 現有 14 點 lips 已夠用
---
## 4. 比較總結
| 評估項 | A: Lips Motion | B: VSP-LLM | C: MediaPipe |
|--------|---------------|------------|-------------|
| **實作難度** | 🟢 **低** | 🔴 極高 | 🟡 中 |
| **所需時間** | 🟢 **今天** | 🔴 數週 | 🟡 數小時 |
| **外部依賴** | 🟢 無 | 🔴 LLaMA+AV-HuBERT | 🟡 MediaPipe |
| **訓練需求** | 🟢 不需要 | 🔴 需 LRS3 | 🟢 不需要 |
| **辨識能力** | 🟡 誰在說話 | 🟢 誰+說什麼 | 🟡 誰在說話 |
| **中文支援** | 🟢 語言無關 | 🔴 不支援 | 🟢 語言無關 |
| **授權風險** | 🟢 MIT | 🟡 Meta 授權 | 🟢 Apache 2.0 |
## 5. 建議
### 結論
**視覺 speaker diarization 可行,但目前的 lip_height 運動量偵測有 60% 覆蓋率。** 主要的限制不是取樣密度(已達 96% gap=1),而是 40% 的 ASR 片段在說話時 speaker 不在鏡頭內。
### 實作優先級
**第一優先:將 lip motion verification 整合進 speaker binding**
- 對現有 `bind_speakers` 加入 lip motion 做 cross-validation
- ASRX speaker + face trace 綁定時,檢查 speaker 的 ASR 區段是否有對應 lip motion
- 若無 lip motion → 降低 confidence
- 實作位置:`src/api/identity_agent_api.rs::bind_speakers()`
**第二優先(視需要):完整視覺 speaker diarization pipeline**
- 建立新 processor: `visual_speaker_diarization.py`
- 對每個 ASR 片段,自動判定誰在說話
- 與 ASRX 結果做交叉比對
### Lip Height 關鍵公式
```python
# 從 outer_lips 14 點計算
lip_height = max(y_points) - min(y_points)
# 說話開始時嘴型變化
mouth_opening_ratio = (avg_after - avg_before) / avg_before
# 門檻
THRESHOLD = 0.05 # 5% opening = 正在說話
# 判定
if mouth_opening_ratio > THRESHOLD:
speaker_found = True
elif no_face_data:
speaker_unknown = True # 畫外音
else:
needs_review = True # 可能非說話者入鏡
```
### 未來研究方向
| 方向 | 潛力 | 所需資源 |
|------|------|---------|
| MediaPipe 478 點 3D landmarks | 更精確的嘴型 + 頭部轉向 | 安裝 MediaPipe~30min |
| Per-trace lip motion history | 不只是 ASR 開始,追蹤整段說話的 lip 變化 | 已可行 |
| VSP-LLM 完整部署 | 誰+說什麼 | 需 LLaMA2 授權 + AV-HuBERT |