feat: backup architecture docs, source code, and scripts

This commit is contained in:
Warren
2026-04-25 17:15:45 +08:00
parent 59809dae1f
commit 1f84e5469f
368 changed files with 146329 additions and 261 deletions

View File

@@ -0,0 +1,563 @@
# Momentry Core - Metadata 及 處理器總覽
本文檔說明 Momentry Core 中 chunks 資料表的 metadata 結構,以及各類處理器的輸出欄位。
## 1. Chunks 資料表結構
### 1.1 直接欄位 (Direct Columns)
這些欄位直接儲存於 chunks 資料表中:
| 欄位 | 類型 | 來源處理器 | 說明 |
|------|------|----------|------|
| `id` | serial | 系統 | 主鍵 |
| `uuid` | varchar(32) | 系統 | 影片 UUID |
| `chunk_id` | varchar(64) | 系統 | Chunk ID (如 sentence_0001) |
| `chunk_index` | integer | 系統 | 順序編號 |
| `chunk_type` | varchar(32) | 系統 | sentence/cut/time |
| `text_content` | text | ASR processor | 語音轉文字結果 |
| `content` | jsonb | - | 原始內容 (rule, data 等) |
| `metadata` | jsonb | 多個處理器 | 參閱下方 1.2 |
| `visual_stats` | jsonb | add_yolo_to_chunks.py | YOLO 識別結果 |
| `speaker_ids` | text[] | ASRX processor | 說話者 ID 陣列 |
| `face_ids` | integer[] | Face processor | 臉部 ID 陣列 |
| `summary_text` | text | generate_chunk_summaries.py | LLM 生成摘要 |
| `parent_chunk_id` | varchar(64) | 系統 | 父 chunk ID |
| `fps` | double | ffprobe | 幀率 |
| `start_frame` | bigint | ffprobe | 開始幀 |
| `end_frame` | bigint | ffprobe | 結束幀 |
| `metadata_version` | integer | 系統 | Metadata 版本 (5W1H, identity, visual) |
| `content_version` | integer | 系統 | Content 版本 (text_content, summary_text) |
| `created_at` | timestamp | 系統 | 建立時間 |
| `updated_at` | timestamp | 系統 | 最後更新時間 |
### 版本控制說明
| 欄位 | 說明 | 遞增時機 |
|------|------|----------|
| `metadata_version` | Metadata 版本 | 更新 5W1H, identity, visual 時 |
| `content_version` | Content 版本 | 更新 text_content, summary_text 時 |
| `updated_at` | 最後更新時間 | 任何更新時自動更新 |
**判別更新語法**:
```sql
-- 檢查哪些 chunk 需要重新生成 5W1H
SELECT chunk_id, metadata_version, content_version, updated_at
FROM dev.chunks
WHERE metadata_version < 1;
-- 檢查特定時間後的更新
SELECT chunk_id, updated_at
FROM dev.chunks
WHERE updated_at > '2024-01-01';
-- 檢查版本差異 (需要重新處理)
SELECT c.*
FROM dev.chunks c
WHERE c.metadata_version <
(SELECT MAX(metadata_version) FROM dev.chunks WHERE uuid = c.uuid);
```
## 11. 動態 Metadata 管理
### 11.1 欄位動態增減
Metadata JSONB 支援動態欄位,可根據處理器執行結果動態添加:
```python
# 動態添加欄位
metadata = existing_metadata or {}
metadata[field_name] = value
UPDATE chunks SET metadata = metadata || %s::jsonb
```
### 11.2 常見動態欄位
| 欄位 | 新增時機 | 來源處理器 |
|------|----------|------------|
| `chunk_5w1h` | 生成 summary | generate_chunk_summaries.py |
| `chunk_identity` | ASRX/Face 執行後 | 來源欄位聚合 |
| `chunk_visual` | YOLO 執行後 | add_yolo_to_chunks.py |
| `chunk_emotion` | 情緒分析 | future emotion_processor.py |
| `chunk_pose` | 姿勢辨識 | future pose_processor.py |
| `chunk_sentiment` | 情感分析 | future sentiment_processor.py |
### 11.3 版本升級策略
每次重大更新時遞增版本號:
```python
if新增重大欄位:
metadata_version += 1
# 記錄變更日誌
```
### 11.4 重跑機制
```bash
# 重跑特定版本後的 chunk
python scripts/generate_chunk_summaries.py --uuid <uuid> --min-version 1
# 查看版本分佈
SELECT metadata_version, COUNT(*)
FROM dev.chunks
GROUP BY metadata_version;
```
### 1.2 Metadata 結構 (JSONB)
`metadata` 欄位包含多個子欄位,由不同處理器產生:
```json
{
"chunk_5w1h": {
"who": "演員或角色",
"what": "主要動作或事件",
"when": "時間上下文",
"where": "地點",
"why": "目的或原因",
"how": "表達方式"
},
"chunk_identity": {
"speakers": ["speaker_001", "speaker_002"],
"faces": ["face_1", "face_3"]
},
"chunk_visual": {
"objects": ["person", "car", "tree"],
"places": ["street", "office"]
},
"structured_summary": {
"who": "Parent 級別角色",
"what": "Parent 級別動作",
...
}
}
```
| 子欄位 | 類型 | 來源處理器 | 說明 |
|--------|------|----------|------|
| `chunk_5w1h` | jsonb | generate_chunk_summaries.py | Chunk 級別的 5W1H + Emotion + Actions |
| `chunk_5w1h.who` | string | person | 人物名稱 (含來源標記) |
| `chunk_5w1h.what` | string | action | 具體動作 |
| `chunk_5w1h.when` | string | position | 場景中位置 (beginning/middle/end) |
| `chunk_5w1h.where` | string | location | 地點 |
| `chunk_5w1h.why` | string | purpose | 目的 |
| `chunk_5w1h.how` | string | manner | 表達方式 |
| `chunk_5w1h.emotion` | string | emotion | 情緒/語氣 |
| `chunk_5w1h.actions` | string[] | verbs | 動作動詞 |
| `chunk_identity` | jsonb | 來源欄位聚合 | speaker_ids + face_ids 資訊 |
| `chunk_visual` | jsonb | add_yolo_to_chunks.py | YOLO 物體識別結果 |
| `structured_summary` | jsonb | regenerate_parent_5w1h.py | Parent 級別 5W1H + tone + characters + key_events |
### chunk_5w1h 欄位說明 (Chunk 級)
| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `who` | string | 此 chunk 出現的角色 (含來源) | "John (SPEAKER_1), Mary (face_3)" |
| `what` | string | 此 chunk 的具體動作 | "Giving warning" |
| `when` | string | 相對時間位置 | "Mid-scene" |
| `where` | string | 地點 (如提及) | "Near taxi" |
| `why` | string | 此動作的目的 | "Warn about danger" |
| `how` | string | 表達/呈現方式 | "Urgent tone" |
| `emotion` | string | 情緒/語氣 | "Fearful, urgent" |
| `actions` | string[] | 動作動詞 | ["run", "shout", "warn"] |
**Prompt 增強內容**:
- 從 person_identities 取得驗證的人物名稱
- 包含 speaker_id 和 face_id 來源標記
- 視覺辨識: objects, places, actions
- Time range 傳入 chunk 時間範圍
- Emotion + Actions 額外欄位
### chunk_identity 欄位說明
| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `speakers` | string[] | 說話者 ID | ["speaker_001", "speaker_002"] |
| `faces` | string[] | 臉部 ID | ["face_1", "face_3"] |
| `global_identity` | string | 對應的全局人物 ID | "person_001" |
| `person_name` | string | 識別的人物名稱 | "John" |
> 說明:
> - `speakers`/`faces` 來自 ASRX/Face processor
> - `global_identity` 來自 `person_identities` 表,關聯 face_identity_id
> - `person_name` 來自 `person_identities.name`,經過確認的人物名稱
### 全域人物 Identity (person_identities 表)
每個影片會識別並記錄出現的人物,儲存於 `dev.person_identities` 表:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `person_id` | varchar(255) | 人物唯一 ID (如 person_001) |
| `name` | varchar(255) | 人物名稱 (可確認) |
| `speaker_id` | varchar(255) | 對應的說話者 ID |
| `video_uuid` | varchar(255) | 影片 UUID |
| `face_identity_id` | integer | 對應的 global identity |
| `appearance_count` | integer | 出現次數 |
| `first_appearance_time` | double | 首次出現時間 |
| `last_appearance_time` | double | 最後出現時間 |
| `confidence` | double | 辨識信心度 |
| `is_confirmed` | boolean | 是否已確認 |
### 全域 Identity (face_identities 表)
跨影片的全局人物身份:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `id` | serial | 主鍵 |
| `face_id` | integer | 臉部 ID |
| `name` | varchar(255) | 識別姓名 |
| `embedding` | blob | 人臉向量特徵 |
### 人物識別流程
Momentry 的人物識別分為三個層級:
```
層級 1: 原始識別 (chunks 表)
├── chunks.face_ids → 臉部 ID (local to chunk)
└── chunks.speaker_ids → 說話者 ID (local to chunk)
層級 2: 影片級識別 (person_identities 表)
├── person_id → 人物 ID (影片內唯一)
├── name → 識別出的人物名稱 (如 "John")
├── speaker_id → 對應的說話者
└── face_identity_id → 對應的全局 Identity
層級 3: 全局身份 (face_identities 表)
├── id → 全局唯一 ID
├── face_id → 臉部特徵 ID
├── name → 確認的姓名
└── embedding → 人臉向量 (用於比對)
```
**識別流程說明**:
```
Step 1: ASRX Processor
chunks.speaker_ids ← 說話者分離
Step 2: Face Processor
chunks.face_ids ← 臉部偵測
Step 3: Auto-identify
person_identities ← 合併 speaker + face (影片級)
Step 4: Global Matching
face_identities ← 人臉向量比對 (全局 Identity)
合併相同人臉者為同一 Identity
```
**命名原則**:
- `person_id` = 角色名 (如 "John", "Adam")
- 而非 "Person_8"
- 透過 speaker 對應 + 手動確認
**範例**:
```sql
-- 取得影片中的人物列表
SELECT person_id, name, speaker_id, appearance_count
FROM dev.person_identities
WHERE video_uuid = '384b0ff44aaaa1f1'
ORDER BY appearance_count DESC;
-- 取得 chunk 的人物
SELECT c.chunk_id, pi.name, pi.speaker_id
FROM dev.chunks c
JOIN dev.person_identities pi ON c.uuid = pi.video_uuid
WHERE c.chunk_id = 'sentence_0001';
```
### 取得 chunk 的人物資訊
```sql
-- 取得某 chunk 的人物
SELECT pi.name, pi.speaker_id, pi.appearance_count
FROM dev.person_identities pi
JOIN dev.chunks c ON c.uuid = pi.video_uuid
WHERE c.chunk_id = 'sentence_0001';
```
### chunk_visual 欄位說明
| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `objects` | string[] | YOLO 識別物體 | ["person", "car", "tree"] |
| `places` | string[] | Places365 識別地點 | ["street", "office"] |
## 2. 處理器對照表
### 2.1 ASR 處理器 (語音辨識)
**用途**:將影片音軌轉換為文字
| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| asr_processor_small_multilingual.py | text_content | Small 模型,多語言 |
| asr_processor_simplified.py | text_content | 簡化版 |
| asr_processor_contract_v1.py | text_content | 契約版本 v1 |
| asr_processor_contract_v2.py | text_content | 契約版本 v2 |
**輸出**
- `text_content`: 語音轉文字結果
- 寫入 `chunks.content``chunks.text_content`
### 2.2 ASRX 處理器 (增強說話者辨識)
**用途**:說話者分離 (Diarization)
| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| asrx_processor.py | speaker_ids | 標準版 |
| asrx_processor_contract_v1.py | speaker_ids | 契約版 v1 |
**輸出**
- `speaker_ids`: 說話者 ID 陣列,如 `["speaker_001", "speaker_002"]`
- 目前為空 `{}`,需執行後才會填充
### 2.3 Face 處理器 (臉部偵測)
**用途**:偵測並追蹤人臉
| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| analyze_video_faces.py | face_ids | 臉部偵測 |
**輸出**
- `face_ids`: 臉部 ID 陣列,如 `[1, 3, 5]`
- 目前為空 `{}`,需執行後才會填充
### 2.4 YOLO 處理器 (物體識別)
**用途**:識別場景中的物體和地點
| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| add_yolo_to_chunks.py | visual_stats, chunk_visual | YOLO + Places365 |
**輸出**
- `visual_stats`: 原始識別結果
- `metadata.chunk_visual`: 簡化格式 `{objects: [...], places: [...]}`
### 2.5 Summary 處理器 (生成摘要)
**用途**:生成 chunk 摘要和 5W1H 分析
| 處理器 | 輸出欄位 | 說明 |
|--------|---------|------|
| generate_chunk_summaries.py | summary_text, chunk_5w1h, chunk_identity, chunk_visual | LLM 生成 |
| regenerate_parent_5w1h.py | structured_summary | Parent 場景級 5W1H |
**輸入**
- chunk.text_content
- parent_chunks.summary_text
- parent_chunks.metadata.structured_summary
- chunk.speaker_ids (用於 chunk_identity)
- chunk.face_ids (用於 chunk_identity)
- chunk.visual_stats (用於 chunk_visual)
**輸出**
- `summary_text`: 2-3 句摘要
- `metadata.chunk_5w1h`: Who/What/When/Where/Why/How
- `metadata.chunk_identity`: speakers, faces
- `metadata.chunk_visual`: objects, places
## 3. Parent Chunks 結構
Parent chunks 代表場景 (scene) 層級:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `id` | serial | 主鍵 |
| `uuid` | varchar(32) | 影片 UUID |
| `scene_order` | integer | 場景順序 |
| `summary_text` | text | 場景摘要 (LLM 生成) |
| `metadata` | jsonb | 包含 structured_summary |
### Parent Metadata 結構
```json
{
"structured_summary": {
"who": "主要角色",
"what": "主要事件",
"when": "時間線",
"where": "地點",
"why": "動機",
"how": "方式",
"tone": ["緊張", "懸疑", "溫馨"],
"characters": ["角色A", "角色B", "角色C"],
"key_events": ["事件1", "事件2", "事件3"],
"summary_5lines": "5行摘要..."
},
"auto_generated_by": "gemma4",
"chunk_count": 885
}
```
### structured_summary 欄位說明
| 欄位 | 類型 | 說明 | 範例 |
|------|------|------|------|
| `who` | string | 主要角色 | "Mr. Balletman, Adam" |
| `what` | string | 主要動作或事件 | "Escape attempt" |
| `when` | string | 時間上下文 | "During critical moment" |
| `where` | string | 地點 | "Near taxi" |
| `why` | string | 動機或原因 | "Evade capture" |
| `how` | string | 執行方式 | "Quickly moving to taxi" |
| `tone` | string[] | 語氣/情緒 | ["Urgent", "Tense", "Fearful"] |
| `characters` | string[] | 場景中的角色 | ["Mr. Balletman", "Adam", "Antagonist"] |
| `key_events` | string[] | 關鍵事件 | ["Decision to flee", "Warning given"] |
| `summary_5lines` | string | 5行摘要 | "Line 1\nLine 2..." |
## 4. Chunk 類型說明
| 類型 | 需要搜尋 | 說明 |
|------|----------|------|
| `sentence` | ✓ | 有 text_content需向量化存入 Qdrant |
| `cut` | ✗ | 場景剪輯點,無文字內容 |
| `time` | ✗ | 時間區間標記,無文字 |
**搜尋適用性**
- sentence: 有文字內容,可進行語意搜尋
- cut/time: 無文字,僅供時間定位使用
## 5. 處理流程 (Pipeline)
```
1. ffprobe → 取得影片資訊 (fps, frame count)
2. ASR processor → text_content
3. [ASRX processor] → speaker_ids (選用)
4. [Face processor] → face_ids (選用)
5. add_yolo_to_chunks.py → visual_stats
6. generate_chunk_summaries.py → summary_text + metadata
7. [vectorize_chunk_summaries.py] → Qdrant 向量
```
## 6. Qdrant Collections
| Collection | 向量類型 | 用途 |
|------------|----------|------|
| `momentry_dev_chunk_summaries` | nomic-embed-text | Chunk summary 語意搜尋 |
| `momentry_dev_vectors` | 原始向量 | 備用 |
## 7. API 回傳格式
Chunk Detail API 合併 chunk 和 parent 的 metadata
```
metadata
├── chunk_5w1h (chunk 級)
├── chunk_identity (chunk 級)
├── chunk_visual (chunk 級)
├── structured_summary (parent 級) ← 只在有 parent 時
├── auto_generated_by
└── chunk_count
```
## 8. 執行狀態檢查
```bash
# 檢查 summary 生成進度
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) as total,
COUNT(CASE WHEN summary_text IS NOT NULL THEN 1 END) as generated
FROM dev.chunks WHERE chunk_type = 'sentence';"
# 檢查執行中的處理器
ps aux | grep -E "processor|generate" | grep -v grep
# 檢查 visual_stats
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.chunks WHERE visual_stats IS NOT NULL;"
```
## 9. 待執行處理器
### 人物識別處理器 (依序執行)
```bash
# Step 1: ASRX 執行說話者分離
python scripts/asrx_processor.py --uuid 384b0ff44aaaa1f1
# Step 2: Face 執行臉部偵測
python scripts/analyze_video_faces.py --uuid 384b0ff44aaaa1f1
# Step 3: Auto-identify 建立影片級人物
python scripts/auto_identify_persons.py --uuid 384b0ff44aaaa1f1
# Step 4: 全局 Identity 比對 (需累積一定數量的 face_identities)
python scripts/match_faces_to_identities.py
# Step 5: 重新生成 chunk 5W1H (包含新的 identity 資訊)
python scripts/generate_chunk_summaries.py --uuid 384b0ff44aaaa1f1
```
### 檢查待處理狀態
```bash
# 檢查 speaker_ids
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.chunks
WHERE speaker_ids IS NOT NULL AND array_length(speaker_ids, 1) > 0;"
# 檢查 face_ids
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.chunks
WHERE face_ids IS NOT NULL AND array_length(face_ids, 1) > 0;"
# 檢查 person_identities
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.person_identities
WHERE video_uuid = '384b0ff44aaaa1f1';"
# 檢查 face_identities (全局)
psql -h localhost -U accusys -d momentry -c "
SELECT COUNT(*) FROM dev.face_identities;"
```
## 10. 自動化重新生成機制
### 觸發條件
當以下事件發生時,應自動重新生成 chunk 的 5W1H 和相關 metadata
| 事件 | 觸發動作 |
|------|----------|
| 第一次執行 ASRX | 重新生成含 speaker_ids 的 5W1H |
| 第一次執行 Face | 重新生成含 face_ids 的 5W1H |
| 新增 chunk | 為新 chunk 生成 5W1H |
| 修改 chunk 內容 | 更新 5W1H 和 summary |
| 新增/修改 speaker | 重新生成含新 speaker 的 5W1H |
| 新增/修改 face | 重新生成含新 face 的 5W1H |
### 重新生成流程
```
事件觸發
更新 speaker_ids / face_ids / person_identities
呼叫 generate_chunk_summaries.py --uuid <uuid> --regenerate
重新產生:
├── summary_text (2-3 句)
├── metadata.chunk_5w1h (Who/What/When/Where/Why/How)
├── metadata.chunk_identity (更新後的 speakers/faces)
└── metadata.chunk_visual (若 visual_stats 有更新)
```
### 重點
每次處理器執行後Chunk metadata 會包含最新的:
1. **speaker_ids** → 進入 `chunk_identity.speakers`
2. **face_ids** → 進入 `chunk_identity.faces`
3. **person_identities** → 進入 `chunk_identity.person_name`
確保 LLM 產生的 5W1H 包含最新的角色資訊。