## 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
22 KiB
22 KiB
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:
-- 現有 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
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
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
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 表 (檔案映射)
{
"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)
{
"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)
{
"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)
{
"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)
{
"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
{
"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
-- 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. 設計原則總結
- 單一圖像識別 → Frame: yolo, ocr, face 等單幀識別結果直接入 frame 表
- 時間序列識別 → Pre-Chunk: asr, asrx, cut, time, trace 等有時間範圍的結果入 pre_chunk 表
- 組合規則 1 (直接): pre_chunk → chunk (保持原有邊界)
- 組合規則 2 (集合): pre_chunk + frames → chunk (加入識別內容)
- 精確到 Frame: 所有時間範圍都記錄 start_frame, end_frame
- 雙向量存儲: 同時支持 PostgreSQL 和 Qdrant
- 跨視頻搜索: 透過 videos 表的 uuid 進行搜索,內部使用 id 節省空間
- 空間優化: 內部表使用 videos.id (4 bytes) 而非 uuid (32 bytes)
10. 查詢範例
10.1 跨視頻搜索所有 chunk
-- 搜索所有視頻中包含 "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
-- 查詢 uuid 為 'abc123' 的視頻的所有 chunk
SELECT c.*
FROM chunks c
JOIN videos v ON c.file_id = v.id
WHERE v.uuid = 'abc123';
10.3 按時間範圍搜索
-- 搜索所有視頻在 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;