Files
momentry_core/docs/CHUNK_DESIGN.md
accusys 383201cacd feat: Initial v0.9 release with API Key authentication
## v0.9.20260325_144654

### Features
- API Key Authentication System
- Job Worker System
- V2 Backup Versioning

### Bug Fixes
- get_processor_results_by_job column mapping

Co-authored-by: OpenCode
2026-03-25 14:53:41 +08:00

535 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Momentry Core 數據管理設計文檔 (v4)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 0. 核心概念:雙 UUID 系統
為減少資料庫大小,在現有的 videos 表中增加內部 ID 映射:
### 0.1 設計原則
- **external_uuid**: 用戶可見的識別碼(如 UUID
- **id**: 資料庫自動產生的內部 ID (SERIAL),節省空間
- **映射關係**: 透過 videos 表的 `id` 欄位關聯
### 0.2 videos 表 (檔案映射表)
現有結構,增加 `id` 作為內部 ID
```sql
-- 現有 videos 表結構
CREATE TABLE videos (
id SERIAL PRIMARY KEY, -- 內部 ID (自動產生)
uuid VARCHAR(32) UNIQUE NOT NULL, -- 外部 UUID (用戶可見)
file_name VARCHAR(255) NOT NULL,
file_path TEXT,
duration DOUBLE PRECISION,
width INTEGER,
height INTEGER,
fps DOUBLE PRECISION,
probe_json JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_videos_uuid ON videos(uuid);
```
### 0.3 對照的好處
| 方式 | 儲存空間 (1000個視頻每個1000個chunk) |
|------|---------------------------------------|
| 直接用 uuid (32字元) | ~32MB |
| 使用 id (4字元) | ~4MB |
## 1. 數據流架構
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 輸入階段 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 視頻文件 │→ │ ffprobe │ │ ASR │ │ YOLO │ │
│ │ (.mp4) │→ │ (probe) │→ │ (asr) │→ │ (yolo) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ ASRX │ │ CUT │ │ OCR │ │ FACE │ │
│ │ (asrx) │→ │ (cut) │→ │ (ocr) │→ │ (face) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Pre-Chunk / Frame 階段 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ pre_chunks 表 │ │
│ │ file_id → videos.id (FK) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ type=sentence │ from asr, asrx │ 句子邊界範圍 │ │ │
│ │ │ type=cut │ from cut detection │ 場景切換範圍 │ │ │
│ │ │ type=time │ from time split │ 固定時間範圍 (10s) │ │ │
│ │ │ type=trace │ from yolo trace │ 物件追蹤範圍 │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ frames 表 │ │
│ │ file_id → videos.id (FK) │ │
│ │ - yolo 每幀識別結果 │ │
│ │ - ocr 每幀識別結果 │ │
│ │ - face 每幀識別結果 (如需要) │ │
│ │ - 單一圖像識別結果 → 直接入 frame │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Chunk 階段 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ chunks 表 │ │
│ │ file_id → videos.id (FK) │ │
│ │ │ │
│ │ 組合規則1: pre_chunk → chunk (直接轉換) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ sentence_pre_chunk → sentence_chunk │ │ │
│ │ │ cut_pre_chunk → cut_chunk │ │ │
│ │ │ time_pre_chunk → time_chunk │ │ │
│ │ │ trace_pre_chunk → trace_chunk │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 組合規則2: pre_chunk + frame 內容 → chunk (集合內容) │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ sentence_pre_chunk + 涵蓋範圍內的 frames → 豐富的 sentence_chunk │ │ │
│ │ │ time_pre_chunk + 涵蓋範圍內的 frames → 豐富的 time_chunk │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Vector 階段 │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ PostgreSQL vectors │ │ Qdrant vectors │ │
│ │ (chunk_vectors) │ │ (chunk_v3) │ │
│ └──────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
## 2. Pre-Chunk 類型定義
### 2.1 Pre-Chunk 來源與類型對照表
| 來源類型 | source_type | 產出 Pre-Chunk Type | 說明 |
|---------|-------------|---------------------|------|
| ASR ( Whisper ) | asr | sentence | 句子邊界 |
| ASRX ( with timestamps ) | asrx | sentence | 帶時間戳的句子 |
| CUT (場景檢測) | cut | cut | 場景切換點 |
| TIME (固定時間) | time | time | 每 10 秒 |
| YOLO Trace | yolo_trace | trace | 物件追蹤軌跡 |
| YOLO (單幀) | yolo | **→ frame** | 不入 pre_chunk |
| OCR (單幀) | ocr | **→ frame** | 不入 pre_chunk |
| FACE (單幀) | face | **→ frame** | 不入 pre_chunk |
| PROBE | probe | metadata | 視頻元數據 |
### 2.2 Pre-Chunk Schema
```sql
CREATE TABLE pre_chunks (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
-- 來源識別
source_type VARCHAR(32) NOT NULL, -- 'asr', 'asrx', 'cut', 'time', 'yolo_trace', 'probe'
source_file TEXT, -- 原始 JSON 文件路徑
-- Chunk 類型
chunk_type VARCHAR(32) NOT NULL, -- 'sentence', 'cut', 'time', 'trace'
-- 時間範圍
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
-- Frame 範圍 (精確到 frame)
start_frame INTEGER NOT NULL,
end_frame INTEGER NOT NULL,
-- FPS (用於 frame 計算)
fps DOUBLE PRECISION NOT NULL,
-- 原始 JSON 內容
raw_json JSONB NOT NULL,
-- 解析後的文字內容 (如有)
text_content TEXT,
-- 處理狀態
processed BOOLEAN DEFAULT FALSE,
chunk_id VARCHAR(64), -- 轉換後的 chunk_id
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(file_id, source_type, start_frame, end_frame)
);
CREATE INDEX idx_pre_chunks_file_id ON pre_chunks(file_id);
CREATE INDEX idx_pre_chunks_type ON pre_chunks(file_id, chunk_type);
CREATE INDEX idx_pre_chunks_time ON pre_chunks(file_id, start_time, end_time);
CREATE INDEX idx_pre_chunks_frame ON pre_chunks(file_id, start_frame, end_frame);
CREATE INDEX idx_pre_chunks_processed ON pre_chunks(file_id, processed);
```
## 3. Frame 管理原則
### 3.1 哪些數據進入 Frame
只儲存**單一圖像識別**的結果:
- YOLO 每幀檢測結果
- OCR 每幀識別結果
- FACE 每幀檢測結果
### 3.2 Frame Schema
```sql
CREATE TABLE frames (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
frame_number INTEGER NOT NULL,
timestamp DOUBLE PRECISION NOT NULL,
fps DOUBLE PRECISION NOT NULL,
-- YOLO 結果 (JSONB 陣列)
yolo_objects JSONB,
-- OCR 結果 (JSONB 陣列)
ocr_results JSONB,
-- Face 結果 (JSONB 陣列)
face_results JSONB,
-- 原始幀圖像路徑 (可選)
frame_path TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(file_id, frame_number)
);
CREATE INDEX idx_frames_file_id ON frames(file_id);
CREATE INDEX idx_frames_frame ON frames(file_id, frame_number);
CREATE INDEX idx_frames_timestamp ON frames(file_id, timestamp);
```
## 4. Chunk 組合規則
### 4.1 組合規則 1: 直接轉換 (rule_1)
將 pre_chunk 直接轉換為 chunk
```
sentence_pre_chunk → sentence_chunk (rule: "rule_1")
cut_pre_chunk → cut_chunk (rule: "rule_1")
time_pre_chunk → time_chunk (rule: "rule_1")
trace_pre_chunk → trace_chunk (rule: "rule_1")
```
### 4.2 組合規則 2: 集合內容 (rule_2)
將 pre_chunk 與其時間區間內的所有 frame 識別結果集合:
```
sentence_pre_chunk + frames[在 start_time~end_time 範圍內] → 豐富的 sentence_chunk (rule: "rule_2")
time_pre_chunk + frames[在 start_time~end_time 範圍內] → 豐富的 time_chunk (rule: "rule_2")
```
### 4.3 Chunk Schema
```sql
CREATE TABLE chunks (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL, -- 'sentence', 'cut', 'time', 'trace'
-- 組合規則 (payload 中記錄)
-- rule: 'rule_1' = 直接轉換, 'rule_2' = 集合內容
-- 時間範圍
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
-- Frame 範圍 (精確到 frame)
start_frame INTEGER NOT NULL,
end_frame INTEGER NOT NULL,
-- FPS
fps DOUBLE PRECISION NOT NULL,
-- 主要內容
text_content TEXT,
-- 完整內容 (JSONB) - 包含 rule 欄位
content JSONB NOT NULL,
-- 來源的 pre_chunk IDs
pre_chunk_ids INTEGER[],
-- 包含的 frame 數量
frame_count INTEGER DEFAULT 0,
-- 向量 ID
vector_id VARCHAR(64),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(file_id, chunk_id)
);
CREATE INDEX idx_chunks_file_id ON chunks(file_id);
CREATE INDEX idx_chunks_type ON chunks(file_id, chunk_type);
CREATE INDEX idx_chunks_time ON chunks(file_id, start_time, end_time);
CREATE INDEX idx_chunks_frame ON chunks(file_id, start_frame, end_frame);
CREATE INDEX idx_chunks_vector ON chunks(vector_id);
```
## 5. 處理流程範例
### 5.1 輸入數據
假設視頻長度 30 秒fps=30
| 來源 | 產出 |
|------|------|
| ASR | 3 個 sentence_pre_chunk (每句約 10s) |
| CUT | 2 個 cut_pre_chunk (場景 1, 場景 2) |
| TIME | 3 個 time_pre_chunk (0-10s, 10-20s, 20-30s) |
| YOLO | 900 個 frame 記錄 (每幀) |
| OCR | 依實際識別結果入 frame |
### 5.2 Chunk 產出
**使用規則 1 (直接轉換):**
- rule: "rule_1"
- 3 個 sentence_chunk
- 2 個 cut_chunk
- 3 個 time_chunk
**使用規則 2 (集合內容):**
- rule: "rule_2"
- 3 個 sentence_chunk (各含涵蓋時間範圍內的 yolo/ocr 結果)
- 3 個 time_chunk (各含涵蓋時間範圍內的 yolo/ocr 結果)
## 8. 數據示例
### 8.1 videos 表 (檔案映射)
```json
{
"id": 1,
"uuid": "abc123def456",
"file_name": "video_001.mp4",
"file_path": "/path/to/video_001.mp4",
"duration": 300.5,
"width": 1920,
"height": 1080,
"fps": 30.0
}
```
### 8.2 pre_chunks 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"source_type": "asr",
"chunk_type": "sentence",
"start_time": 0.0,
"end_time": 5.5,
"start_frame": 0,
"end_frame": 165,
"fps": 30.0,
"raw_json": {...},
"text_content": "This is the first sentence"
}
```
### 8.3 frames 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"frame_number": 300,
"timestamp": 10.0,
"fps": 30.0,
"yolo_objects": [
{"class": "person", "confidence": 0.9, "bbox": [100, 50, 200, 150]},
{"class": "car", "confidence": 0.85, "bbox": [50, 100, 150, 180]}
],
"ocr_results": [],
"face_results": []
}
```
### 8.4 chunks 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"rule": "rule_2",
"start_time": 10.0,
"end_time": 15.5,
"start_frame": 300,
"end_frame": 465,
"fps": 30.0,
"text_content": "The second sentence from the audio",
"content": {
"rule": "rule_2",
"asr_text": "The second sentence from the audio",
"objects": [
{"class": "person", "first_frame": 300, "last_frame": 450, "appears_in_frames": [300, 310, 320, ...]},
{"class": "car", "first_frame": 350, "last_frame": 465, "appears_in_frames": [350, 360, ...]}
],
"ocr": [...],
"faces": [...]
},
"pre_chunk_ids": [5],
"frame_count": 301
}
```
### 8.5 chunk_vectors 表 (使用 file_id 關聯 videos)
```json
{
"file_id": 1,
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 10.0,
"end_time": 15.5,
"embedding": "[0.1, 0.2, ...]",
"metadata": {"text": "The second sentence..."}
}
```
### 8.6 Qdrant Payload
```json
{
"file_uuid": "abc123def456",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 10.0,
"end_time": 15.5,
"text": "The second sentence from the audio"
}
```
## 7. 向量管理原則
### 7.1 Vector Schema
```sql
-- Chunk 向量表 (PostgreSQL)
CREATE TABLE chunk_vectors (
id SERIAL PRIMARY KEY,
-- 檔案識別 (使用 videos 表的內部 ID 以節省空間)
file_id INTEGER NOT NULL REFERENCES videos(id),
chunk_id VARCHAR(64) NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
-- 向量數據
embedding TEXT, -- JSON 格式的向量
embedding_vector VECTOR(768), -- pgvector 類型 (如可用)
-- 時間範圍 (用於時間查詢)
start_time DOUBLE PRECISION,
end_time DOUBLE PRECISION,
-- Metadata
metadata JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(chunk_id)
);
-- 索引
CREATE INDEX idx_chunk_vectors_file_id ON chunk_vectors(file_id);
```
### 7.2 Qdrant Collection
- Collection 名稱: `chunks_v3`
- Vector 維度: 768 (nomic-embed-text)
- Payload 包含: `file_uuid`, `chunk_id`, `chunk_type`, `start_time`, `end_time`, `text`
> **注意**: Qdrant 中仍使用 uuid (字串)因為需要可讀性和跨系統整合。PostgreSQL 內部使用 videos.id (整數) 以節省空間。
## 9. 設計原則總結
1. **單一圖像識別 → Frame**: yolo, ocr, face 等單幀識別結果直接入 frame 表
2. **時間序列識別 → Pre-Chunk**: asr, asrx, cut, time, trace 等有時間範圍的結果入 pre_chunk 表
3. **組合規則 1 (直接)**: pre_chunk → chunk (保持原有邊界)
4. **組合規則 2 (集合)**: pre_chunk + frames → chunk (加入識別內容)
5. **精確到 Frame**: 所有時間範圍都記錄 start_frame, end_frame
6. **雙向量存儲**: 同時支持 PostgreSQL 和 Qdrant
7. **跨視頻搜索**: 透過 videos 表的 uuid 進行搜索,內部使用 id 節省空間
8. **空間優化**: 內部表使用 videos.id (4 bytes) 而非 uuid (32 bytes)
## 10. 查詢範例
### 10.1 跨視頻搜索所有 chunk
```sql
-- 搜索所有視頻中包含 "hello" 的 chunk
SELECT c.*, v.uuid, v.file_name
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE c.text_content ILIKE '%hello%';
```
### 10.2 查詢特定視頻的 chunk
```sql
-- 查詢 uuid 為 'abc123' 的視頻的所有 chunk
SELECT c.*
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE v.uuid = 'abc123';
```
### 10.3 按時間範圍搜索
```sql
-- 搜索所有視頻在 10-20 秒範圍內的 chunk
SELECT c.*, v.uuid
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE c.start_time >= 10.0 AND c.end_time <= 20.0;
```