## 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
24 KiB
n8n Video RAG Workflow - Node 設計
建立時間: 2026-03-22 目標: 讓 marcom 團隊能夠複製、貼上、修改使用的完整操作指南
完整 Workflow 架構
┌─────────────────────────────────────────────────────────────────────────────┐
│ n8n Workflow: Video RAG Demo │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 1: SFTPGo 準備 (全部在 n8n Node 內執行) │ │
│ │ │ │
│ │ ① Webhook Trigger │ │
│ │ ↓ │ │
│ │ ② Set Variables (解析 file_name, query) │ │
│ │ ↓ │ │
│ │ ③ Get SFTPGo Token │ │
│ │ ↓ │ │
│ │ ④ Upload to SFTPGo │ │
│ │ ↓ │ │
│ │ ⑤ Create Share Link │ │
│ │ ↓ │ │
│ │ ⑥ Verify Upload (List Files + List Shares) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 2: Momentry 註冊 (只處理 ASR, ASRX, STORY) │ │
│ │ │ │
│ │ ⑦ Register Video (modules=asr,asrx,story) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 3: Progress Loop (n8n Logs 記錄) │ │
│ │ │ │
│ │ ⑧ Wait 10s ─────────────────────────────────────────────────┐ │ │
│ │ ↓ │ │
│ │ ⑨ Check Progress (API) │ │
│ │ ↓ │ │
│ │ ⑩ Log Progress (Code Node → n8n Logs) │ │
│ │ ↓ │ │
│ │ ⑪ Is Complete? (IF) │ │
│ │ │ │ │
│ │ ├── NO ──────────────────────────────── Loop Back ─────────┘ │ │
│ │ └── YES ────────────────────────────────────────────── Exit ──┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Phase 4: 搜尋與回應 │ │
│ │ │ │
│ │ ⑫ Hybrid Search (Vector + BM25) │ │
│ │ ↓ │ │
│ │ ⑬ Build Response │ │
│ │ ↓ │ │
│ │ ⑭ Respond to Webhook │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
模組說明
| 模組 | 用途 | 輸出 |
|---|---|---|
asr |
語音轉文字 (Whisper) | 字幕/文字稿 |
asrx |
說話者分離 (WhisperX) | 誰在什麼時候說什麼 |
story |
故事線生成 (Parent-Child Chunks) | 敘事結構 + 父子區塊關聯 |
注意: 只處理語音和故事相關模組,跳過 YOLO、OCR、Face、Pose 等視覺分析。 ┌─────────────────────────────────────────────────────────────────────────────┐ │ n8n Workflow: Video RAG Demo │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Phase 1: SFTPGo 準備 (全部在 n8n Node 內執行) │ │ │ │ │ │ │ │ ① Webhook Trigger │ │ │ │ ↓ │ │ │ │ ② Set Variables (解析 file_name, query) │ │ │ │ ↓ │ │ │ │ ③ Get SFTPGo Token │ │ │ │ ↓ │ │ │ │ ④ Upload to SFTPGo │ │ │ │ ↓ │ │ │ │ ⑤ Create Share Link │ │ │ │ ↓ │ │ │ │ ⑥ Verify Upload (List Files + List Shares) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Phase 2: Momentry 註冊 │ │ │ │ │ │ │ │ ⑦ Register Video │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Phase 3: Progress Loop (n8n Logs 記錄) │ │ │ │ │ │ │ │ ⑧ Wait 10s ─────────────────────────────────────────────────┐ │ │ │ │ ↓ │ │ │ │ │ ⑨ Check Progress (API) │ │ │ │ │ ↓ │ │ │ │ │ ⑩ Log Progress (Code Node → n8n Logs) │ │ │ │ │ ↓ │ │ │ │ │ ⑪ Is Complete? (IF) │ │ │ │ │ │ │ │ │ │ │ ├── NO ──────────────────────────────── Loop Back ─────────┘ │ │ │ │ └── YES ────────────────────────────────────────────── Exit ──┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Phase 4: 搜尋與回應 │ │ │ │ │ │ │ │ ⑫ Natural Language Search │ │ │ │ ↓ │ │ │ │ ⑬ Get Media URL (含 media_url) │ │ │ │ ↓ │ │ │ │ ⑭ Build Response │ │ │ │ ↓ │ │ │ │ ⑮ Respond to Webhook │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
---
## Node 詳細配置
### Node ①: Webhook Trigger (觸發器)
```yaml
Node Name: "Webhook Trigger"
Node Type: "Webhook"
Configuration:
HTTP Method: POST
Path: "video-rag"
Response Mode: "Response Node"
Response Node: "Respond to Webhook"
Input JSON Example:
{
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
"query": "What is the movie about?"
}
Node ②: Set Variables (變數設定)
Node Name: "Set Variables"
Node Type: "Set"
Configuration:
Keep Only Set: true
Variables:
- Name: "file_name"
Value: "{{ $json.body.file_name }}"
- Name: "query"
Value: "{{ $json.body.query }}"
- Name: "sftpgo_path"
Value: "/{{ $json.body.file_name }}"
- Name: "register_path"
Value: "/Users/accusys/sftpgo_test/demo/{{ $json.body.file_name }}"
Node ③: Get SFTPGo Token (取得權杖)
Node Name: "Get SFTPGo Token"
Node Type: "HTTP Request"
Configuration:
Method: GET
URL: "http://localhost:8080/api/v2/user/token"
Authentication: "Basic Auth"
User: "demo"
Password: "demopassword123"
Output:
{
"access_token": "eyJhbGci...",
"expires_at": "2026-03-22T07:00:00Z"
}
Node ④: Upload to SFTPGo (上傳檔案)
Node Name: "Upload to SFTPGo"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:8080/api/v2/user/files"
Authentication: "Bearer Token"
Bearer Token: "{{ $json.access_token }}"
Body Content Type: "Form-Data Multipart"
Body:
path: /demo
mkdir_parents: true
filenames: @{{ $json.file_name }}
Output:
{"message":"Upload completed"}
檔案來源選項:
- Webhook 接收: 從 Webhook 的 binary data 取得
- 固定路徑: 指定本地檔案路徑
- URL 下載: 先下載遠端檔案再上傳
Node ⑤: Create Share Link (建立分享連結)
Node Name: "Create Share Link"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:8080/api/v2/user/shares"
Authentication: "Bearer Token"
Bearer Token: "{{ $json.access_token }}"
Body Content Type: "JSON"
Body:
{
"name": "{{ $json.file_name }}_share",
"paths": ["/{{ $json.file_name }}"],
"scope": 1,
"expires_at": 0
}
Output:
{
"id": "CjmQfrkXY5qDtC46WVZY2S",
"name": "Charade_share"
}
Node ⑥: Verify Upload (驗證上傳)
Node Name: "Verify Upload - List Shares"
Node Type: "HTTP Request"
Configuration:
Method: GET
URL: "http://localhost:8080/api/v2/user/shares"
Authentication: "Bearer Token"
Bearer Token: "{{ $json.access_token }}"
Output:
[
{
"id": "CjmQfrkXY5qDtC46WVZY2S",
"name": "Charade_share",
"paths": ["/Old_Time_Movie_Show_-_Charade_1963.HD.mov"]
}
]
Node ⑦: Register Video (註冊影片)
說明: 只註冊 ASR、ASRX、STORY 模組處理
Node Name: "Register Video"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/register"
Body Content Type: "JSON"
Body:
{
"path": "{{ $json.register_path }}",
"modules": "asr,asrx,story"
}
Output:
{
"uuid": "a1b10138a6bbb0cd",
"video_id": 7,
"file_name": "Old_Time_Movie_Show_-_Charade_1963.HD.mov",
"duration": 6879.33,
"width": 1920,
"height": 1080
}
可用模組:
| 模組 | 說明 |
|---|---|
asr |
語音轉文字 (Whisper) |
asrx |
說話者分離 (WhisperX) |
story |
故事線生成 (Parent-Child) |
yolo |
物體偵測 (可選) |
cut |
場景偵測 (可選) |
ocr |
文字辨識 (可選) |
face |
人臉偵測 (可選) |
pose |
姿態估計 (可選) |
Node ⑧: Wait 10 Seconds (輪詢間隔)
Node Name: "Wait 10 Seconds"
Node Type: "Wait"
Configuration:
Amount: 10
Unit: "Seconds"
Node ⑨: Check Progress (檢查進度)
Node Name: "Check Progress"
Node Type: "HTTP Request"
Configuration:
Method: GET
URL: "http://localhost:3002/api/v1/progress/{{ $('Register Video').item.json.uuid }}"
Output:
{
"uuid": "a1b10138a6bbb0cd",
"processors": [
{"name": "asr", "status": "complete", "message": "1867 segments"},
{"name": "asrx", "status": "progress", "message": "ASRX_TRANSCRIBING"},
{"name": "story", "status": "pending", "message": ""}
]
}
注意: 只有 asr、asrx、story 三個模組
Node ⑩: Log Progress (記錄進度)
Node Name: "Log Progress"
Node Type: "Code"
Configuration:
Language: "JavaScript"
Code:
```javascript
const progress = $input.first().json;
const processors = progress.processors;
const totalProcessors = processors.length;
const completedProcessors = processors.filter(p => p.status === 'complete').length;
const overallProgress = Math.round((completedProcessors / totalProcessors) * 100);
const currentProcessor = processors.find(p =>
p.status === 'progress' || p.status === 'info'
);
const progressMessage = `
═══════════════════════════════════════════════
📹 Video RAG Processing: ${overallProgress}%
UUID: ${progress.uuid}
${processors.map(p => {
const icon = p.status === 'complete' ? '✅' :
p.status === 'progress' || p.status === 'info' ? '🔄' : '⏳';
return ` ${icon} ${p.name.padEnd(6)} ${p.message || p.status}`;
}).join('\n')}
${currentProcessor ? `Current: ${currentProcessor.name}` : 'All complete!'}
═══════════════════════════════════════════════
`.trim();
console.log(progressMessage);
return {
json: {
uuid: progress.uuid,
overall_progress: overallProgress,
completed_processors: completedProcessors,
total_processors: totalProcessors,
current_processor: currentProcessor?.name || 'idle',
processors: processors,
log_message: progressMessage
}
};
Output: { "uuid": "a1b10138a6bbb0cd", "overall_progress": 33, "log_message": "📹 Video RAG Processing: 33%..." }
---
### Node ⑪: Is Complete? (判斷分支)
```yaml
Node Name: "Is Complete?"
Node Type: "IF"
Configuration:
Condition:
$json.processors.every(p => p.status === 'complete')
Connections:
TRUE (完成): → Node ⑫ Natural Language Search
FALSE (未完成): → Node ⑧ Wait 10 Seconds (Loop)
Node ⑫: Natural Language Search (RAG 搜尋)
Node Name: "Natural Language Search"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/search"
Body Content Type: "JSON"
Body:
{
"query": "{{ $('Set Variables').item.json.query }}",
"limit": 10,
"uuid": "{{ $('Register Video').item.json.uuid }}"
}
Output:
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "c_001",
"text": "Hello and welcome to the old-time movie show...",
"score": 0.92
}
]
}
Node ⑫B: Hybrid Search (Vector + BM25)
說明: 使用混合搜尋,結合向量相似度和全文檢索
Node Name: "Hybrid Search"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/search/hybrid"
Body Content Type: "JSON"
Body:
{
"query": "{{ $('Set Variables').item.json.query }}",
"limit": 10,
"uuid": "{{ $('Register Video').item.json.uuid }}",
"vector_weight": 0.7,
"bm25_weight": 0.3
}
Output:
{
"query": "What is the movie about?",
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "c_001",
"chunk_type": "sentence",
"start_time": 0.0,
"end_time": 5.0,
"text": "Hello and welcome to the old-time movie show...",
"vector_score": 0.85,
"bm25_score": 0.75,
"combined_score": 0.80
}
]
}
權重建議:
| 查詢類型 | vector_weight | bm25_weight |
|---|---|---|
| 主題查詢 | 0.8 | 0.2 |
| 事實查找 | 0.5 | 0.5 |
| 平衡查詢 | 0.7 | 0.3 |
Node ⑬: Get Media URL (取得媒體連結)
Node Name: "Get Media URL"
Node Type: "HTTP Request"
Configuration:
Method: POST
URL: "http://localhost:3002/api/v1/n8n/search"
Body Content Type: "JSON"
Body:
{
"query": "{{ $('Set Variables').item.json.query }}",
"limit": 10,
"uuid": "{{ $('Register Video').item.json.uuid }}"
}
Output:
{
"count": 10,
"hits": [
{
"id": "c_001",
"vid": "a1b10138a6bbb0cd",
"text": "Hello and welcome to the old-time movie show...",
"score": 0.92,
"media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
Node ⑭: Build Response (組合結果)
Node Name: "Build Response"
Node Type: "Set"
Configuration:
Keep Only Set: true
Variables:
- Name: "ok"
Value: true
- Name: "uuid"
Value: "{{ $('Register Video').item.json.uuid }}"
- Name: "file_name"
Value: "{{ $('Set Variables').item.json.file_name }}"
- Name: "query"
Value: "{{ $('Set Variables').item.json.query }}"
- Name: "count"
Value: "{{ $('Get Media URL').item.json.count }}"
- Name: "results"
Value: "{{ $('Get Media URL').item.json.hits }}"
- Name: "overall_progress"
Value: "{{ $('Log Progress').item.json.overall_progress }}"
Node ⑮: Respond to Webhook (回傳結果)
Node Name: "Respond to Webhook"
Node Type: "Respond to Webhook"
Configuration:
Respond With: "JSON"
Response Body:
{
"ok": true,
"uuid": "{{ $json.uuid }}",
"file_name": "{{ $json.file_name }}",
"query": "{{ $json.query }}",
"count": {{ $json.count }},
"results": {{ $json.results }},
"overall_progress": {{ $json.overall_progress }},
"message": "Video RAG completed successfully"
}
快速複製所需資訊
SFTPGo 設定
| 項目 | 值 |
|---|---|
| API Base | http://localhost:8080/api/v2 |
| Demo User | demo |
| Demo Password | demopassword123 |
| Demo Home | /Users/accusys/sftpgo_test/demo |
| Token Endpoint | /api/v2/user/token |
| Upload Endpoint | /api/v2/user/files |
| Share Endpoint | /api/v2/user/shares |
Momentry 設定
| 項目 | 值 |
|---|---|
| API Base | http://localhost:3002 |
| Register | POST /api/v1/register |
| Progress | GET /api/v1/progress/{uuid} |
| Search | POST /api/v1/search |
| n8n Search | POST /api/v1/n8n/search |
| Hybrid Search | POST /api/v1/search/hybrid |
| Media Base | https://wp.momentry.ddns.net |
Demo 測試資料
Charade (1963) Demo Video
- UUID:
a1b10138a6bbb0cd - 位置:
/Users/accusys/test_video/Old_Time_Movie_Show_-_Charade_1963.HD.mov - 時長: 6872 秒 (~1.9 小時)
已處理檔案:
| 檔案 | 大小 | 內容 |
|---|---|---|
asr.json |
210KB | 1867 語音區段 |
cut.json |
220KB | 1331 場景 |
story.json |
1.8MB | 641 父子區塊 |
transcript.txt |
40KB | 可讀文字稿 |
Output 目錄: /Users/accusys/momentry_core_0.1/output
版本歷史
| 日期 | 版本 | 變更 |
|---|---|---|
| 2026-03-22 | v1.0 | 初始建立 |
| 2026-03-22 | v1.1 | 新增 Hybrid Search (Vector + BM25) 節點 |
| 2026-03-22 | v1.2 | 簡化為只處理 ASR、ASRX、STORY 模組 |