feat: backup architecture docs, source code, and scripts
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user