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
@@ -0,0 +1,202 @@
# Processor Resume Strategy Design (Processor 續傳機制設計)
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-04-25 |
| 文件版本 | V1.0 |
---
## 1. 現狀分析 (Current State Analysis)
### 1.1 目前行為
目前系統的 Processor (如 YOLO, Face, OCR) **不支援高效續傳**
* **中斷處理**: 若處理過程因主動 (使用者取消) 或被動 (OOM, Crash) 中斷,重新執行時通常會**從頭開始 (Frame 0)**。
* **效能瓶頸**: 對於長影片,中斷後重新解碼和運算前 90% 的幀是巨大的資源浪費。
### 1.2 缺失環節
* **缺乏狀態記錄**: 處理器未記錄「已處理到第幾幀」。
* **缺乏增量寫入**: 輸出檔案通常為全量 JSON,無法在尾部追加新數據而不破壞格式。
* **缺乏跳轉指令**: Worker 啟動腳本時未傳遞 `--start-frame` 參數。
---
## 2. 核心設計理念 (Core Concept)
設計目標:**Checkpoints (檢查點) + Append Mode (追加模式)**
* **Checkpoint**: 定期將當前進度 (`current_frame`) 寫入獨立檔案或 Redis。
* **Seek**: 啟動時若發現進度記錄,使用 OpenCV `set(CAP_PROP_POS_FRAMES)` 快速跳轉。
* **Append**: 結果輸出採用 **JSON Lines (.jsonl)** 格式,避免中斷導致 JSON 結構損壞,且支援尾部追加。
---
## 3. 資料流與協定 (Protocol)
### 3.1 檔案結構
對於一個 Video UUID (`vid_001`) 和 Processor (`yolo`),檔案佈局如下:
```text
output/vid_001/
├── vid_001.mp4
├── yolo_result.jsonl # 增量結果 (每一行是一個 Frame 的 JSON)
├── yolo_progress.json # 檢查點檔案 (記錄最後一幀的索引與時間戳)
└── yolo_final.json # (可選) 最終轉換後的完整 JSON
```
### 3.2 yolo_progress.json 結構
```json
{
"video_uuid": "vid_001",
"processor": "yolo",
"last_frame_index": 12500,
"last_timestamp": 416.66,
"total_frames": 50000,
"status": "running"
}
```
### 3.3 指令列參數協定 (CLI Arguments)
Rust Worker 在啟動 Python 腳本時,需檢測 `progress` 檔案並注入參數:
| 參數 | 類型 | 說明 |
|------|------|------|
| `--resume` | Flag | 啟用續傳模式。若無此 Flag,腳本應清空舊結果並從頭開始。 |
| `--start-frame` | INT | 指定起始幀索引 (例如 `12501`)。 |
| `--output-file` | STR | 指定輸出的 JSONL 路徑。 |
| `--progress-file` | STR | 指定寫入進度的 JSON 路徑。 |
**範例指令 (Resume)**:
```bash
python3 scripts/yolo_processor.py \
--input video.mp4 \
--resume \
--start-frame 12501 \
--output-file yolo_result.jsonl \
--progress-file yolo_progress.json
```
---
## 4. 實作細節 (Implementation Details)
### 4.1 Python Script 端 (Processor)
#### A. 初始化與 Seek
```python
import cv2, json, os, sys
# 1. 解析參數
start_frame = 0
if args.resume and os.path.exists(args.progress_file):
with open(args.progress_file) as f:
progress = json.load(f)
start_frame = progress['last_frame_index'] + 1
# 2. 開啟影片並跳轉
cap = cv2.VideoCapture(args.input)
if start_frame > 0:
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
current_frame = start_frame
else:
current_frame = 0
# 3. 開啟輸出 (Append Mode)
mode = 'a' if args.resume else 'w'
out_f = open(args.output_file, mode)
```
#### B. 循環與寫入
```python
while True:
ret, frame = cap.read()
if not ret:
break
# 處理邏輯...
result = process_frame(frame)
# 寫入 JSONL (一行一筆 JSON)
out_f.write(json.dumps({"frame": current_frame, "data": result}) + "\n")
# 更新進度 (每 N 幀寫入一次,避免 I/O 瓶頸)
if current_frame % 30 == 0:
save_progress(current_frame)
current_frame += 1
```
#### C. 信號處理 (Graceful Exit)
攔截 `SIGINT` / `SIGTERM`,確保最後一次進度被保存。
```python
import signal
def handler(signum, frame):
save_progress(current_frame)
sys.exit(0)
signal.signal(signal.SIGTERM, handler)
signal.signal(signal.SIGINT, handler)
```
### 4.2 Rust Worker 端 (Manager)
#### A. 啟動前檢查
`src/worker/processor.rs``run_processor` 函數中:
```rust
// 1. 檢查進度檔案
let progress_path = output_dir.join("yolo_progress.json");
let mut start_frame = 0;
let mut is_resume = false;
if progress_path.exists() {
if let Ok(json) = std::fs::read_to_string(&progress_path) {
if let Ok(p) = serde_json::from_str::<Value>(&json) {
if let Some(f) = p["last_frame_index"].as_i64() {
start_frame = (f + 1) as usize;
is_resume = true;
}
}
}
}
// 2. 建構指令
let mut args = vec!["--input", video_path];
if is_resume {
args.push("--resume");
args.push("--start-frame");
args.push(&start_frame.to_string());
}
// ... 其他 args
```
#### B. 後處理 (Post-Processing)
Processor 完成後,若輸出為 `.jsonl`,需轉換為系統預期的 `.json` (List of Objects) 以便存入 DB。
* *Optimization*: 可以在 Rust 端直接 `BufRead` 解析 JSONL 並批次 Insert DB,無需轉換檔案。
---
## 5. 資料庫狀態更新
目前的 `monitor_jobs``processor_results` 表需支援**部分完成**的語意。
* **新增欄位**: `processed_frames` (BIGINT) 於 `processor_results``jobs` 表。
* **用途**: Worker 定期讀取 `progress.json` 並更新此欄位,以便前端顯示「處理進度 25%」。
---
## 6. 優勢分析
1. **容錯性**: 即使伺服器斷電,重啟後僅損失最近 30 幀 (約 1 秒) 的運算結果。
2. **I/O 效率**: 採用 Append 寫入,避免每次都重寫巨大的 JSON 檔案。
3. **資源節約**: OpenCV 的 `seek` 操作比重新解碼快數百倍。
4. **靈活性**: 支援主動暫停 (Pause),只需發送 `SIGINT` 讓腳本安全退出即可。
---
## 版本資訊
- 版本: V1.0
- 建立日期: 2026-04-25
@@ -0,0 +1,151 @@
# Rule Specification & Data Flow (規則規範與數據流)
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-04-25 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-04-25 | 定義 Rule 的 Input/Logic/Output 及依賴關係 (DAG) | OpenCode | OpenCode |
---
## 1. 核心概念
**Rule (規則)** 負責將 **Processor** 產出的原始數據 (`pre_chunks`) 聚合、過濾、推論為有意義的 **Chunks**
### 1.1 數據流向
```mermaid
graph LR
Video((Video File)) -->|Process| P1[Processor: YOLO/Face/OCR]
Video -->|Process| P2[Processor: ASR/CUT]
P1 -->|JSON/JSONL| DB[(pre_chunks)]
P2 -->|JSON/JSONL| DB
DB -->|Read| R1[Rule 1: Sentence]
DB -->|Read| R2[Rule 2: Visual]
DB -->|Read| R3[Rule 3: Scene]
R1 -->|Write| Chunks[(chunks)]
R2 -->|Write| Chunks
R3 -->|Write| Chunks
Chunks -->|Read| Agent[AI Agent: 5W1H/Summary]
Agent -->|Update| Chunks
```
---
## 2. 依賴管理 (DAG)
為了解決依賴關係糾結的問題,系統採用 **顯式依賴宣告 (Explicit Dependency Declaration)**
Job Worker 根據此配置決定何時觸發 Rule。
```json
{
"rules": [
{
"rule_id": "rule_1_sentence",
"description": "語句級聚合 (基於 ASR)",
"dependencies": ["processor_asr"],
"agent_involved": false
},
{
"rule_id": "rule_2_visual",
"description": "視覺片段聚合 (基於 YOLO/Face)",
"dependencies": ["processor_yolo", "processor_face"],
"agent_involved": false
},
{
"rule_id": "rule_3_scene",
"description": "場景級聚合 (基於 CUT + ASR 對齊)",
"dependencies": ["processor_cut", "processor_asr"],
"agent_involved": false
},
{
"rule_id": "rule_4_summary",
"description": "生成段落摘要 (依賴所有 Rule 1-3 完成)",
"dependencies": ["rule_1_sentence", "rule_3_scene"],
"agent_involved": true
}
]
}
```
**觸發邏輯**:
*`dependencies` 中列出的所有項目狀態變為 `completed` 時,觸發該 Rule。
---
## 3. Rule 詳細定義
### 3.1 Rule 1: Sentence (語句聚合)
* **用途**: 將 ASR 的逐字/逐句輸出聚合為可搜尋的文本 Chunk。
* **Input**: `pre_chunks` 表中 `processor_type = 'asr'` 的紀錄。
* **Logic**:
1. 按時間順序讀取 ASR segments。
2. (可選) 結合物件標籤 (YOLO) 豐富 metadata。
3. 生成 `ChunkType::Sentence`
* **Output**: 寫入 `chunks` 表。
### 3.2 Rule 2: Visual (視覺聚合)
* **用途**: 將零散的 Frame 聚合為有意義的視覺事件 (例如:「某人出現在畫面中 5 秒」)。
* **Input**:
* `pre_chunks` 表中 `processor_type = 'yolo'` 的紀錄。
* `pre_chunks` 表中 `processor_type = 'face'` 的紀錄。
* **Logic**:
1. **滑動視窗**: 若連續 N 幀都偵測到同一 Identity,視為一個 Visual Chunk。
2. **事件過濾**: 忽略低於信心度閾值的偵測。
3. 生成 `ChunkType::Visual`
* **Output**: 寫入 `chunks` 表。
### 3.3 Rule 3: Scene (場景聚合)
* **用途**: 基於鏡頭切換 (CUT) 與語音內容,定義場景邊界。
* **Input**:
* `pre_chunks` 表中 `processor_type = 'cut'` 的紀錄。
* `chunks` 表中 Rule 1 產生的 Sentence Chunks (用於填充場景內容)。
* **Logic**:
1. 讀取 CUT 偵測到的 Scene 邊界。
2. 將該時間段內的所有 Rule 1 Chunks 關聯到此 Scene。
3. 生成 `ChunkType::Scene`
* **Output**: 寫入 `chunks` 表。
### 3.4 Rule 4: Summary / Agent (AI 摘要)
* **用途**: 利用 LLM 生成場景或影片的摘要、5W1H 標籤。
* **Input**: `chunks` 表中已完成的 Scene/Sentence Chunks。
* **Logic**:
1. 收集 Chunk 的文本、視覺標籤。
2. 調用 **Translation/Summary Agent**
3. 將生成的摘要與標籤更新回 `chunks` 表的 `metadata` 欄位。
* **Output**: 更新 `chunks` 表 (In-place Update)。
---
## 4. Agent 在 Rule 中的角色
AI Agent 不再是獨立的「黑盒子」,而是作為 Rule 的執行引擎之一。
* **Prompt Injection**: Rule 負責組裝 Context (Input Chunks),注入預定義的 Prompt Template。
* **Structured Output**: Agent 必須輸出符合 Schema 的 JSON,以便 Rust 寫入資料庫。
**範例 (Rule 4 摘要)**:
> "You are a movie analyst. Based on the following dialogue and visual tags, summarize the scene in 50 words and extract the 5W1H entities."
---
## 版本資訊
- 版本: V1.0
- 建立日期: 2026-04-25