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

22 KiB
Raw Blame History

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. 設計原則總結

  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

-- 搜索所有視頻中包含 "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;