# 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
{
  "file_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
