diff --git a/.env b/.env
index 71bc619..991bd11 100644
--- a/.env
+++ b/.env
@@ -1,40 +1,3 @@
-# Database Configuration
-DATABASE_URL=postgres://accusys@localhost:5432/momentry
-
-# Redis
-# Format: redis://[username][:password]@host:port
-# Users: default (with password), accusys (custom user with password)
-REDIS_URL=redis://accusys:accusys@localhost:6379
-
-# MongoDB
-MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
-MONGODB_DATABASE=momentry
-
-# Qdrant Vector Database
-QDRANT_URL=http://localhost:6333
-QDRANT_API_KEY=Test3200Test3200Test3200
-QDRANT_COLLECTION=chunks_v3
-
-# Gitea
-GITEA_URL=http://localhost:3000
-
-# API Server (Production)
-MOMENTRY_SERVER_PORT=3002
-MOMENTRY_REDIS_PREFIX=momentry:
-API_HOST=127.0.0.1
-API_PORT=3002
-
-# Worker Configuration (Production)
-MOMENTRY_WORKER_ENABLED=true
-MOMENTRY_MAX_CONCURRENT=2
-MOMENTRY_POLL_INTERVAL=5
-
-# Watch Directories (comma separated)
-WATCH_DIRECTORIES=~/Videos,~/momentry_core_project/test_video
-
-# Ollama (for Mistral 7B LLM)
-OLLAMA_HOST=http://localhost:11434
-
-# Model Paths
-# EMBEDDING_MODEL_PATH=./models/comic-embed-text
-# LLM_MODEL_PATH=./models/mistral-7b
+DB_MAX_CONNECTIONS=50
+DB_ACQUIRE_TIMEOUT=30
+QDRANT_URL=http://127.0.0.1:6333
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 567d49a..aa65550 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,6 @@ id_*
*.swp
*.swo
*~
+
+# Documentation backups
+docs_v1.0/
diff --git a/Cargo.toml b/Cargo.toml
index b3b9486..2bc05c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,7 @@ tokio = { version = "1", features = ["full"] }
tracing = "0.1"
tracing-subscriber = "0.3"
once_cell = "1.19"
+libc = "0.2"
dotenv = "0.15"
# CLI
@@ -73,7 +74,6 @@ crossterm = "0.28"
atty = "0.2"
# System
-libc = "0.2"
[lib]
name = "momentry_core"
@@ -94,3 +94,7 @@ path = "src/player/main.rs"
[[bin]]
name = "momentry_playground"
path = "src/playground.rs"
+
+[[bin]]
+name = "fix_chunks"
+path = "src/bin/fix_chunks.rs"
diff --git a/com.momentry.api.updated.plist b/com.momentry.api.updated.plist
new file mode 100644
index 0000000..341fbd2
--- /dev/null
+++ b/com.momentry.api.updated.plist
@@ -0,0 +1,64 @@
+
+
+
+
+ Label
+ com.momentry.api
+
+ UserName
+ accusys
+
+ GroupName
+ staff
+
+ WorkingDirectory
+ /Users/accusys/momentry_core_0.1
+
+ ProgramArguments
+
+ /Users/accusys/momentry_core_0.1/target/release/momentry
+ server
+ --port
+ 3002
+
+
+ EnvironmentVariables
+
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+
+ DATABASE_URL
+ postgres://accusys@localhost:5432/momentry
+
+ DB_MAX_CONNECTIONS
+ 50
+
+ DB_ACQUIRE_TIMEOUT
+ 30
+
+ REDIS_URL
+ redis://:accusys@localhost:6379
+
+ REDIS_PASSWORD
+ accusys
+
+ OLLAMA_HOST
+ http://localhost:11434
+
+ QDRANT_URL
+ http://127.0.0.1:6333
+
+
+ RunAtLoad
+
+
+ KeepAlive
+
+
+ StandardOutPath
+ /Users/accusys/momentry/log/momentry_api.log
+
+ StandardErrorPath
+ /Users/accusys/momentry/log/momentry_api.error.log
+
+
\ No newline at end of file
diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md
index 79701db..f5dd3db 100644
--- a/docs/API_ACCESS.md
+++ b/docs/API_ACCESS.md
@@ -1,5 +1,23 @@
# Momentry Core API 存取指南
+| 項目 | 內容 |
+|------|------|
+| 版本 | V1.3 |
+| 日期 | 2026-03-25 |
+| 用途 | API 存取方式、端點與整合指南 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.3 | 2026-03-25 | 更新: n8n 搜尋回傳 `file_path` 取代 `media_url`,新增 API Key 驗證說明 | OpenCode | deepseek-reasoner |
+| V1.2 | 2026-03-24 | 更新網址與服務列表 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-23 | 初始版本 | Warren | OpenCode / MiniMax M2.5 |
+
+---
+
## 基本網址
| 環境 | URL | 說明 |
@@ -20,7 +38,16 @@
- 生產環境
## 認證
-目前為開放狀態(示範用途無需認證)。正式環境將實作 API Key。
+所有 `/api/v1/*` 端點(除了健康檢查 `/health` 與 `/health/detailed`)都需要 API Key 認證。
+
+請在請求標頭中加入:
+```
+X-API-Key: YOUR_API_KEY
+```
+
+**目前示範使用的 API Key**: `demo_api_key_12345`
+
+> **注意**: 正式環境請使用安全的 API Key 管理機制,避免在客戶端暴露 API Key。
---
@@ -91,12 +118,14 @@
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
- "media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
+> **注意**: API 現在返回 `file_path`(檔案系統路徑)而非 `media_url`(網頁 URL)。如需在網頁中播放影片,請將檔案路徑轉換為可訪問的 URL(例如透過 SFTPGo 分享連結)。
+
---
## 影片管理 API
@@ -134,7 +163,10 @@
```javascript
const response = await fetch('http://localhost:3002/api/v1/search', {
method: 'POST',
- headers: { 'Content-Type': 'application/json' },
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': 'YOUR_API_KEY' // 替換為實際的 API Key
+ },
body: JSON.stringify({ query: 'charade', limit: 5 })
});
const data = await response.json();
@@ -149,7 +181,10 @@ curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'query' => 'charade',
'limit' => 5
]));
-curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
+curl_setopt($ch, CURLOPT_HTTPHEADER, [
+ 'Content-Type: application/json',
+ 'X-API-Key: YOUR_API_KEY' // 替換為實際的 API Key
+]);
$response = curl_exec($ch);
$data = json_decode($response, true);
```
@@ -158,10 +193,12 @@ $data = json_decode($response, true);
## 影片嵌入網址
-影片可透過 SFTPGo 分享連結存取:
-```
-https://wp.momentry.ddns.net/{檔案名稱}
-```
+> **重要**: API 現在返回 `file_path`(檔案系統路徑),而非直接可訪問的網址。您需要將檔案路徑轉換為 SFTPGo 分享連結才能嵌入影片。
+
+**檔案路徑轉換為網址:**
+- API 返回的 `file_path` 範例:`/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4`
+- 對應的 SFTPGo 分享連結:`https://wp.momentry.ddns.net/demo/video.mp4`
+- 轉換方式:移除 `/Users/accusys/momentry/var/sftpgo/data/` 前綴,將剩餘路徑附加到 `https://wp.momentry.ddns.net/`
**手動建立分享連結:**
1. 開啟 SFTPGo Web UI:`http://localhost:8080`
diff --git a/docs/API_CURL_EXAMPLES.md b/docs/API_CURL_EXAMPLES.md
index 4219899..e6a6388 100644
--- a/docs/API_CURL_EXAMPLES.md
+++ b/docs/API_CURL_EXAMPLES.md
@@ -2,12 +2,23 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.2 |
-| 日期 | 2026-03-23 |
+| 版本 | V1.4 |
+| 日期 | 2026-03-26 |
| Base URL | `http://localhost:3002` |
---
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.4 | 2026-03-26 | 新增: 任務管理端點 (`/api/v1/jobs`, `/api/v1/jobs/:uuid`),更新註冊端點回應格式 | OpenCode | deepseek-reasoner |
+| V1.3 | 2026-03-25 | 更新: n8n 搜尋回傳 `file_path` 取代 `media_url`,新增 API Key 驗證說明 | OpenCode | deepseek-reasoner |
+| V1.2 | 2026-03-23 | 建立 curl 範例文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+
+---
+
> **狀態說明**:
> - ✅ **已實作**: 健康檢查、搜尋、影片管理端點
> - ⚠️ **規劃中**: API Key 管理功能
@@ -76,6 +87,20 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
---
+## API 認證
+
+所有 `/api/v1/*` 端點(除了健康檢查)都需要 API Key 認證。請在請求標頭中加入:
+
+```
+-H "X-API-Key: YOUR_API_KEY"
+```
+
+**目前示範使用的 API Key**: `demo_api_key_12345`
+
+> **注意**: 正式環境請使用安全的 API Key 管理機制。
+
+---
+
## 1. 已實作端點
### 健康檢查
@@ -161,6 +186,7 @@ curl -X GET http://localhost:3002/api/v1/api-keys/stats \
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/path/to/video.mp4"}'
```
@@ -168,30 +194,31 @@ curl -X POST http://localhost:3002/api/v1/register \
```json
{
- "id": 1,
"uuid": "a1b2c3d4e5f6g7h8",
- "file_path": "/path/to/video.mp4",
+ "video_id": 1,
+ "job_id": 123,
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
- "height": 1080
+ "height": 1080,
+ "already_exists": false
}
```
### 3.2 列出所有影片 ✅
```bash
-curl http://localhost:3002/api/v1/videos
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
```
### 3.3 查詢影片 ✅
```bash
# 依 UUID 查詢
-curl "http://localhost:3002/api/v1/lookup?uuid=a1b2c3d4e5f6g7h8"
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=a1b2c3d4e5f6g7h8"
# 依路徑查詢
-curl "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
```
### 3.4 處理影片 🔧 *(CLI - 非 API)*
@@ -209,7 +236,7 @@ cargo run --bin momentry -- process
### 3.5 取得處理進度 ✅
```bash
-curl http://localhost:3002/api/v1/progress/
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/
```
**回應範例**:
@@ -247,6 +274,67 @@ curl http://localhost:3002/api/v1/progress/
}
```
+### 3.6 任務管理 ✅
+
+```bash
+# 列出所有任務
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs
+
+# 取得特定任務詳情
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs/
+```
+
+**任務列表回應範例**:
+```json
+{
+ "jobs": [
+ {
+ "id": 123,
+ "uuid": "a1b2c3d4e5f6g7h8",
+ "status": "pending",
+ "current_processor": null,
+ "progress_current": 0,
+ "progress_total": 100,
+ "created_at": "2026-03-26 10:30:00",
+ "started_at": null
+ }
+ ]
+}
+```
+
+**任務詳情回應範例**:
+```json
+{
+ "id": 123,
+ "uuid": "a1b2c3d4e5f6g7h8",
+ "status": "processing",
+ "current_processor": "asr",
+ "progress_current": 50,
+ "progress_total": 100,
+ "processors": [
+ {
+ "processor_type": "asr",
+ "status": "complete",
+ "started_at": "2026-03-26 10:30:00",
+ "completed_at": "2026-03-26 10:35:00",
+ "duration_secs": 300.5,
+ "error_message": null
+ },
+ {
+ "processor_type": "cut",
+ "status": "pending",
+ "started_at": null,
+ "completed_at": null,
+ "duration_secs": null,
+ "error_message": null
+ }
+ ],
+ "created_at": "2026-03-26 10:30:00",
+ "started_at": "2026-03-26 10:30:00",
+ "updated_at": "2026-03-26 10:35:00"
+}
+```
+
---
## 4. 查詢與搜索
@@ -256,6 +344,7 @@ curl http://localhost:3002/api/v1/progress/
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{
"query": "測試關鍵字",
"limit": 5
@@ -286,6 +375,7 @@ curl -X POST http://localhost:3002/api/v1/search \
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{
"query": "測試關鍵字",
"limit": 5
@@ -307,7 +397,7 @@ curl -X POST http://localhost:3002/api/v1/n8n/search \
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.92,
- "media_url": "https://wp.momentry.ddns.net/video.mp4"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
@@ -318,6 +408,7 @@ curl -X POST http://localhost:3002/api/v1/n8n/search \
```bash
curl -X POST http://localhost:3002/api/v1/search/hybrid \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{
"query": "測試關鍵字",
"limit": 5
@@ -425,6 +516,8 @@ A: 需要將工作流程切換為 Active 狀態 (右上角開關)
| `/api/v1/lookup` | GET | ✅ | 查詢影片 |
| `/api/v1/videos` | GET | ✅ | 列出所有影片 |
| `/api/v1/progress/:uuid` | GET | ✅ | 處理進度 |
+| `/api/v1/jobs` | GET | ✅ | 任務列表 |
+| `/api/v1/jobs/:uuid` | GET | ✅ | 任務詳情 |
| `/api/v1/api-keys` | * | ⚠️ | API Key 管理 (規劃中) |
### C. 常見錯誤
@@ -475,11 +568,12 @@ curl -s "$API_URL/health" | jq .
echo -e "\n=== Search ==="
curl -s -X POST "$API_URL/api/v1/search" \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "test", "limit": 3}' | jq .
# 列出影片
echo -e "\n=== Videos ==="
-curl -s "$API_URL/api/v1/videos" | jq '.videos | length'
+curl -s -H "X-API-Key: YOUR_API_KEY" "$API_URL/api/v1/videos" | jq '.videos | length'
```
---
diff --git a/docs/API_ENDPOINTS.md b/docs/API_ENDPOINTS.md
index 5fc9a9c..a641fef 100644
--- a/docs/API_ENDPOINTS.md
+++ b/docs/API_ENDPOINTS.md
@@ -4,7 +4,7 @@
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
-| 文件版本 | V1.2 |
+| 文件版本 | V1.3 |
---
@@ -15,6 +15,7 @@
| V1.0 | 2026-03-18 | 創建文件 | OpenCode |
| V1.1 | 2026-03-23 | 更新端點與實際一致 | OpenCode |
| V1.2 | 2026-03-25 | 新增快取/刪除 API | OpenCode |
+| V1.3 | 2026-03-26 | 更新API回應格式 (media_url→file_path) | OpenCode |
---
@@ -81,6 +82,7 @@ curl http://localhost:3002/health
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
-d '{"query": "test", "limit": 10}'
```
@@ -88,6 +90,7 @@ curl -X POST http://localhost:3002/api/v1/search \
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
-d '{"query": "test", "limit": 10}'
```
@@ -107,13 +110,29 @@ curl -X POST http://localhost:3002/api/v1/n8n/search \
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
-d '{"path": "/path/to/video.mp4"}'
```
+**註冊回應範例**:
+```json
+{
+ "uuid": "a1b10138a6bbb0cd",
+ "video_id": 1,
+ "job_id": 10,
+ "file_name": "video.mp4",
+ "duration": 120.5,
+ "width": 1920,
+ "height": 1080,
+ "already_exists": false
+}
+```
+
**探測影片** (不註冊,只取得影片資訊):
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
-d '{"path": "./demo/video.mp4"}'
```
@@ -150,17 +169,36 @@ curl -X POST http://localhost:3002/api/v1/probe \
**列出影片**:
```bash
-curl http://localhost:3002/api/v1/videos
+curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos
```
**查詢影片**:
```bash
-curl "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
+curl -H "X-API-Key: your-api-key" "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
```
**處理進度**:
```bash
-curl http://localhost:3002/api/v1/progress/5dea6618a606e7c7
+curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/progress/5dea6618a606e7c7
+```
+
+---
+
+### 工作管理
+
+| 方法 | 端點 | 說明 |
+|------|------|------|
+| GET | `/api/v1/jobs` | 列出所有工作 |
+| GET | `/api/v1/jobs/:uuid` | 取得指定工作的詳細資訊 |
+
+**列出工作**:
+```bash
+curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/jobs
+```
+
+**取得工作詳細資訊**:
+```bash
+curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/jobs/a03485a40b2df2d3
```
---
@@ -176,6 +214,7 @@ curl http://localhost:3002/api/v1/progress/5dea6618a606e7c7
```bash
curl -X POST http://localhost:3002/api/v1/config/cache \
-H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
-d '{"enabled": true}'
```
@@ -183,6 +222,7 @@ curl -X POST http://localhost:3002/api/v1/config/cache \
```bash
curl -X POST http://localhost:3002/api/v1/unregister \
-H "Content-Type: application/json" \
+ -H "X-API-Key: your-api-key" \
-d '{"uuid": "5dea6618a606e7c7"}'
```
@@ -199,6 +239,7 @@ curl -X POST http://localhost:3002/api/v1/unregister \
| 列出影片 | ✓ | ✓ | ✓ |
| 查詢影片 | ✓ | ✓ | ✓ |
| 處理進度 | ✓ | ✓ | ✓ |
+| 工作管理 | ✓ | ✓ | ✓ |
| 快取設定 | ✓ (管理員) | ✓ (管理員) | ✓ (管理員) |
| 刪除影片 | ✓ (管理員) | ✓ (管理員) | ✓ (管理員) |
@@ -220,7 +261,7 @@ curl -X POST http://localhost:3002/api/v1/unregister \
"title": "Chunk sentence_0001",
"text": "...",
"score": 0.92,
- "media_url": "https://wp.momentry.ddns.net/video.mp4"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
diff --git a/docs/API_EXAMPLES.md b/docs/API_EXAMPLES.md
index 1abb193..b1ac92d 100644
--- a/docs/API_EXAMPLES.md
+++ b/docs/API_EXAMPLES.md
@@ -2,13 +2,22 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V2.0 |
-| 日期 | 2026-03-25 |
+| 版本 | V2.1 |
+| 日期 | 2026-03-26 |
| Base URL (本地) | `http://localhost:3002` |
| Base URL (外部) | `https://api.momentry.ddns.net` |
---
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 |
+|------|------|------|--------|
+| V2.0 | 2026-03-25 | 創建完整範例總覽 | OpenCode |
+| V2.1 | 2026-03-26 | 更新API回應格式 (media_url→file_path) 與認證標頭 | OpenCode |
+
+---
+
## 快速參考
### 環境 URL 選擇
@@ -105,16 +114,19 @@ curl http://localhost:3002/health/detailed
# 標準格式搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 5}'
# n8n 格式搜尋(推薦)
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 5}'
# 混合搜尋
curl -X POST http://localhost:3002/api/v1/search/hybrid \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 5}'
```
@@ -150,7 +162,7 @@ curl -X POST http://localhost:3002/api/v1/search/hybrid \
"title": "Chunk sentence_0001",
"text": "fun plot twists...",
"score": 0.92,
- "media_url": "https://wp.momentry.ddns.net/video.mp4"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
@@ -160,26 +172,28 @@ curl -X POST http://localhost:3002/api/v1/search/hybrid \
```bash
# 列出所有影片
-curl http://localhost:3002/api/v1/videos
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
# 查詢特定影片(依 UUID)
-curl "http://localhost:3002/api/v1/lookup?uuid=a1b10138a6bbb0cd"
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=a1b10138a6bbb0cd"
# 查詢特定影片(依路徑)
-curl "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
# 取得處理進度
-curl http://localhost:3002/api/v1/progress/a1b10138a6bbb0cd
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/a1b10138a6bbb0cd
# 探測影片(不註冊)
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/path/to/video.mp4"}'
# 註冊影片
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
- -d '{"path": "/path/to/video.mp4", "file_name": "video.mp4"}'
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"path": "/path/to/video.mp4"}'
```
### 1.4 批次測試腳本
@@ -196,10 +210,11 @@ curl -s "$API_URL/health" | jq .
echo -e "\n=== 語意搜尋 ==="
curl -s -X POST "$API_URL/api/v1/search" \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}' | jq .
echo -e "\n=== 影片列表 ==="
-curl -s "$API_URL/api/v1/videos" | jq '.videos | length'
+curl -s -H "X-API-Key: YOUR_API_KEY" "$API_URL/api/v1/videos" | jq '.videos | length'
```
### 1.5 外部 URL 範例
@@ -211,6 +226,7 @@ curl https://api.momentry.ddns.net/health
# 外部搜尋
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 5}'
```
@@ -227,11 +243,14 @@ Node: HTTP Request
├── Authentication: None
├── Send Body: ✓ (checked)
├── Content Type: JSON
-└── Body:
- {
- "query": "={{ $json.query }}",
- "limit": "={{ $json.limit || 10 }}"
- }
+├── Body:
+│ {
+│ "query": "={{ $json.query }}",
+│ "limit": "={{ $json.limit || 10 }}"
+│ }
+├── Send Headers: ✓ (checked)
+└── Header Parameters:
+ └── X-API-Key: {{ $env.MOMENTRY_API_KEY }}
```
### 2.2 基本搜尋 Workflow
@@ -460,6 +479,24 @@ searchVideos('charade', 5)
```php
['Content-Type' => 'application/json'],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ 'X-API-Key' => 'YOUR_API_KEY' // 替換為實際的 API Key
+ ],
'body' => json_encode([
'query' => $atts['query'],
'limit' => (int)$atts['limit']
@@ -492,10 +532,15 @@ add_shortcode('momentry_search', function($atts) {
$output = '';
foreach ($data['hits'] as $hit) {
+ // 注意: API 現在返回 file_path 而非 media_url
+ // 需要將文件路徑轉換為可訪問的 URL
+ $file_path = $hit['file_path'];
+ $video_url = convert_file_path_to_url($file_path); // 需要實作此函數
+
$output .= sprintf(
'- %s 播放
',
esc_html($hit['text']),
- $hit['media_url'],
+ $video_url,
$hit['start']
);
}
@@ -569,7 +614,7 @@ Body: {"query": "charade", "limit": 5}
"title": "Chunk sentence_0001",
"text": "fun plot twists...",
"score": 0.92,
- "media_url": "https://wp.momentry.ddns.net/video.mp4"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
diff --git a/docs/API_INDEX.md b/docs/API_INDEX.md
index 422c405..6467593 100644
--- a/docs/API_INDEX.md
+++ b/docs/API_INDEX.md
@@ -2,8 +2,19 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V2.2 |
-| 日期 | 2026-03-25 |
+| 建立者 | OpenCode |
+| 建立時間 | 2026-03-25 |
+| 文件版本 | V2.2 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V2.0 | 2026-03-22 | 創建 API 文件總覽 | Warren | OpenCode |
+| V2.1 | 2026-03-24 | 新增文件分類與快速選擇指南 | OpenCode | deepseek-reasoner |
+| V2.2 | 2026-03-25 | 更新 API Key 驗證說明與文件連結 | OpenCode | deepseek-reasoner |
---
diff --git a/docs/API_KEY_MANAGEMENT.md b/docs/API_KEY_MANAGEMENT.md
index a36cf5a..9dd308f 100644
--- a/docs/API_KEY_MANAGEMENT.md
+++ b/docs/API_KEY_MANAGEMENT.md
@@ -2,9 +2,23 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.2 |
-| 日期 | 2026-03-21 |
-| 狀態 | 開發中 |
+| 建立者 | Warren |
+| 建立時間 | 2026-03-21 |
+| 文件版本 | V1.2 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-20 | 新增 Key 類型與管理流程 | Warren | OpenCode |
+| V1.2 | 2026-03-21 | 更新 API Key 格式與驗證流程 | Warren | OpenCode |
+
+---
+
+**狀態**: 開發中
---
diff --git a/docs/API_N8N_GUIDE.md b/docs/API_N8N_GUIDE.md
index b5a7402..fc567b9 100644
--- a/docs/API_N8N_GUIDE.md
+++ b/docs/API_N8N_GUIDE.md
@@ -2,9 +2,22 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.0 |
-| 日期 | 2026-03-23 |
-| 用途 | 在 n8n workflow 中呼叫 Momentry API |
+| 建立者 | Warren |
+| 建立時間 | 2026-03-23 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-23 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-26 | 新增 API Key 驗證說明,更新 HTTP Request Node 設定 | OpenCode | deepseek-reasoner |
+
+---
+
+**用途**: 在 n8n workflow 中呼叫 Momentry API
---
@@ -29,6 +42,8 @@ https://api.momentry.ddns.net
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片 |
| GET | `/api/v1/progress/:uuid` | 處理進度 |
+| GET | `/api/v1/jobs` | 任務列表 |
+| GET | `/api/v1/jobs/:uuid` | 任務詳情 |
---
@@ -43,11 +58,14 @@ Node: HTTP Request
├── Authentication: None
├── Send Body: ✓ (checked)
├── Content Type: JSON
-└── Body:
- {
- "query": "={{ $json.query }}",
- "limit": "={{ $json.limit || 10 }}"
- }
+├── Body:
+│ {
+│ "query": "={{ $json.query }}",
+│ "limit": "={{ $json.limit || 10 }}"
+│ }
+├── Send Headers: ✓ (checked)
+└── Header Parameters:
+ └── X-API-Key: {{ $env.MOMENTRY_API_KEY }}
```
### 測試用(固定關鍵字)
@@ -58,11 +76,14 @@ Node: HTTP Request
├── Method: POST
├── Send Body: ✓
├── Content Type: JSON
-└── Body:
- {
- "query": "charade",
- "limit": 3
- }
+├── Body:
+│ {
+│ "query": "charade",
+│ "limit": 3
+│ }
+├── Send Headers: ✓ (checked)
+└── Header Parameters:
+ └── X-API-Key: {{ $env.MOMENTRY_API_KEY }}
```
---
@@ -174,13 +195,21 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
在終端機中測試 API:
+> **注意**: 所有 `/api/v1/*` 端點都需要 API Key 驗證。請設定環境變數或直接替換 API Key。
+
+```bash
+# 設定環境變數(使用您的 API Key)
+export MOMENTRY_API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
+```
+
```bash
# 健康檢查
curl https://api.momentry.ddns.net/health
-# 搜尋測試
+# 搜尋測試 (需要 API Key)
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: $MOMENTRY_API_KEY" \
-d '{"query":"charade","limit":3}'
```
diff --git a/docs/API_QUICK_REFERENCE.md b/docs/API_QUICK_REFERENCE.md
new file mode 100644
index 0000000..7b2998d
--- /dev/null
+++ b/docs/API_QUICK_REFERENCE.md
@@ -0,0 +1,532 @@
+# Momentry Core API 快速查詢表
+
+| 版本 | 日期 | 建立者 |
+|------|------|--------|
+| V1.0 | 2026-03-26 | OpenCode |
+
+---
+
+## 📋 快速導覽
+
+| 類別 | 端點數量 | 說明 |
+|------|----------|------|
+| 健康檢查 | 2 | 系統狀態監控 |
+| 影片管理 | 5 | 影片註冊、查詢、刪除 |
+| 搜尋功能 | 3 | 語意搜尋、混合搜尋 |
+| 任務管理 | 2 | 處理任務狀態查詢 |
+| 系統管理 | 2 | 快取設定、進度查詢 |
+
+---
+
+## 🔐 認證
+
+所有 `/api/v1/*` 端點需要 `X-API-Key` header:
+
+```bash
+curl -H "X-API-Key: YOUR_API_KEY" ...
+```
+
+**公開端點(無需認證):**
+- `GET /health`
+- `GET /health/detailed`
+
+---
+
+## 📊 端點總表
+
+### 健康檢查
+
+| 方法 | 端點 | 認證 | 描述 |
+|------|------|------|------|
+| GET | `/health` | 公開 | 基本健康檢查 |
+| GET | `/health/detailed` | 公開 | 詳細健康檢查(包含所有服務狀態) |
+
+### 影片管理
+
+| 方法 | 端點 | 認證 | 描述 |
+|------|------|------|------|
+| POST | `/api/v1/register` | 需要 | 註冊影片並開始處理 |
+| POST | `/api/v1/unregister` | 需要 | 刪除影片及其所有資料 |
+| POST | `/api/v1/probe` | 需要 | 探測影片資訊(不註冊) |
+| GET | `/api/v1/videos` | 需要 | 列出所有已註冊影片 |
+| GET | `/api/v1/lookup` | 需要 | 查詢影片資訊 |
+
+### 搜尋功能
+
+| 方法 | 端點 | 認證 | 描述 |
+|------|------|------|------|
+| POST | `/api/v1/search` | 需要 | 語意搜尋(標準格式) |
+| POST | `/api/v1/n8n/search` | 需要 | 語意搜尋(n8n 格式) |
+| POST | `/api/v1/search/hybrid` | 需要 | 混合搜尋(向量 + 關鍵字) |
+
+### 任務管理
+
+| 方法 | 端點 | 認證 | 描述 |
+|------|------|------|------|
+| GET | `/api/v1/jobs` | 需要 | 列出所有處理任務 |
+| GET | `/api/v1/jobs/:uuid` | 需要 | 取得特定任務詳情 |
+
+### 系統管理
+
+| 方法 | 端點 | 認證 | 描述 |
+|------|------|------|------|
+| GET | `/api/v1/progress/:uuid` | 需要 | 取得影片處理進度 |
+| POST | `/api/v1/config/cache` | 需要 | 切換快取功能 |
+
+---
+
+## 🔧 詳細端點說明
+
+### 1. 健康檢查
+
+#### GET /health
+**基本健康檢查**
+```bash
+curl http://localhost:3002/health
+```
+
+**回應:**
+```json
+{
+ "status": "ok",
+ "version": "0.1.0",
+ "uptime_ms": 14426558
+}
+```
+
+#### GET /health/detailed
+**詳細健康檢查**
+```bash
+curl http://localhost:3002/health/detailed
+```
+
+**回應:**
+```json
+{
+ "status": "ok",
+ "version": "0.1.0",
+ "uptime_ms": 14441362,
+ "services": {
+ "postgres": {"status": "ok", "latency_ms": 50, "error": null},
+ "redis": {"status": "ok", "latency_ms": 0, "error": null},
+ "qdrant": {"status": "ok", "latency_ms": 2, "error": null},
+ "mongodb": {"status": "ok", "latency_ms": 2, "error": null}
+ }
+}
+```
+
+### 2. 影片管理
+
+#### POST /api/v1/register
+**註冊影片並開始處理**
+```bash
+curl -X POST http://localhost:3002/api/v1/register \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"path": "/path/to/video.mp4"}'
+```
+
+**請求:**
+```json
+{
+ "path": "/path/to/video.mp4"
+}
+```
+
+**回應:**
+```json
+{
+ "uuid": "5dea6618a606e7c7",
+ "video_id": 10,
+ "job_id": 1,
+ "file_name": "video.mp4",
+ "duration": 596.458333,
+ "width": 320,
+ "height": 180,
+ "already_exists": false
+}
+```
+
+#### POST /api/v1/unregister
+**刪除影片及其所有資料**
+```bash
+curl -X POST http://localhost:3002/api/v1/unregister \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"uuid": "5dea6618a606e7c7"}'
+```
+
+**請求:**
+```json
+{
+ "uuid": "5dea6618a606e7c7"
+}
+```
+
+**回應:**
+```json
+{
+ "success": true,
+ "uuid": "5dea6618a606e7c7",
+ "message": "Video unregistered successfully"
+}
+```
+
+#### POST /api/v1/probe
+**探測影片資訊(不註冊)**
+```bash
+curl -X POST http://localhost:3002/api/v1/probe \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"path": "/path/to/video.mp4"}'
+```
+
+**請求:**
+```json
+{
+ "path": "/path/to/video.mp4"
+}
+```
+
+**回應:**
+```json
+{
+ "uuid": "5dea6618a606e7c7",
+ "file_name": "video.mp4",
+ "duration": 596.458333,
+ "width": 320,
+ "height": 180,
+ "fps": 24.0,
+ "cached": true,
+ "format": {...},
+ "streams": [...]
+}
+```
+
+#### GET /api/v1/videos
+**列出所有已註冊影片**
+```bash
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
+```
+
+**回應:**
+```json
+{
+ "videos": [
+ {
+ "uuid": "a03485a40b2df2d3",
+ "file_path": "/path/to/video.mp4",
+ "file_name": "video.mp4",
+ "duration": 596.458333,
+ "width": 320,
+ "height": 180
+ }
+ ]
+}
+```
+
+#### GET /api/v1/lookup
+**查詢影片資訊**
+```bash
+# 依 UUID 查詢
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=a03485a40b2df2d3"
+
+# 依路徑查詢
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
+```
+
+**回應:**
+```json
+{
+ "uuid": "a03485a40b2df2d3",
+ "file_path": "/path/to/video.mp4",
+ "file_name": "video.mp4",
+ "duration": 596.458333
+}
+```
+
+### 3. 搜尋功能
+
+#### POST /api/v1/search
+**語意搜尋(標準格式)**
+```bash
+curl -X POST http://localhost:3002/api/v1/search \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"query": "search term", "limit": 5}'
+```
+
+**請求:**
+```json
+{
+ "query": "search term",
+ "limit": 5
+}
+```
+
+**回應:**
+```json
+{
+ "results": [
+ {
+ "uuid": "a1b10138a6bbb0cd",
+ "chunk_id": "sentence_0001",
+ "chunk_type": "sentence",
+ "start_time": 10.5,
+ "end_time": 15.2,
+ "text": "Found text matching query",
+ "score": 0.85
+ }
+ ],
+ "query": "search term"
+}
+```
+
+#### POST /api/v1/n8n/search
+**語意搜尋(n8n 格式)**
+```bash
+curl -X POST http://localhost:3002/api/v1/n8n/search \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"query": "search term", "limit": 5}'
+```
+
+**回應:**
+```json
+{
+ "query": "search term",
+ "count": 1,
+ "hits": [
+ {
+ "id": "sentence_0001",
+ "vid": "a1b10138a6bbb0cd",
+ "start": 10.5,
+ "end": 15.2,
+ "title": "Chunk sentence_0001",
+ "text": "Found text matching query",
+ "score": 0.85,
+ "file_path": "/path/to/video.mp4"
+ }
+ ]
+}
+```
+
+#### POST /api/v1/search/hybrid
+**混合搜尋(向量 + 關鍵字)**
+```bash
+curl -X POST http://localhost:3002/api/v1/search/hybrid \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"query": "search term", "limit": 5}'
+```
+
+**請求:**
+```json
+{
+ "query": "search term",
+ "limit": 5
+}
+```
+
+**回應:** 與 `/api/v1/search` 相同格式
+
+### 4. 任務管理
+
+#### GET /api/v1/jobs
+**列出所有處理任務**
+```bash
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs
+```
+
+**回應:**
+```json
+{
+ "jobs": [
+ {
+ "id": 10,
+ "uuid": "a03485a40b2df2d3",
+ "status": "running",
+ "current_processor": null,
+ "progress_current": 0,
+ "progress_total": 0,
+ "created_at": "2026-03-26 13:39:37.830465",
+ "started_at": null
+ }
+ ]
+}
+```
+
+#### GET /api/v1/jobs/:uuid
+**取得特定任務詳情**
+```bash
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs/a03485a40b2df2d3
+```
+
+**回應:**
+```json
+{
+ "id": 10,
+ "uuid": "a03485a40b2df2d3",
+ "status": "running",
+ "current_processor": null,
+ "progress_current": 0,
+ "progress_total": 0,
+ "processors": [
+ {
+ "processor_type": "asr",
+ "status": "completed",
+ "started_at": "2026-03-26 05:39:40.275468",
+ "completed_at": "2026-03-26 07:19:43.166613",
+ "duration_secs": 6002.891145,
+ "error_message": null
+ },
+ // ... 其他處理器
+ ],
+ "created_at": "2026-03-26 13:39:37.830465",
+ "started_at": null,
+ "updated_at": "2026-03-26 07:19:16.338406"
+}
+```
+
+### 5. 系統管理
+
+#### GET /api/v1/progress/:uuid
+**取得影片處理進度**
+```bash
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/a03485a40b2df2d3
+```
+
+**回應:**
+```json
+{
+ "uuid": "a03485a40b2df2d3",
+ "user": null,
+ "group": null,
+ "file_name": "video.mp4",
+ "duration": 596.458333,
+ "overall_progress": 0,
+ "cpu_percent": 0.2,
+ "gpu_percent": null,
+ "memory_percent": 0.1,
+ "memory_mb": 16720,
+ "processors": [
+ {
+ "name": "asr",
+ "status": "pending",
+ "current": 0,
+ "total": 0,
+ "progress": 0,
+ "message": ""
+ },
+ // ... 其他處理器
+ ]
+}
+```
+
+#### POST /api/v1/config/cache
+**切換快取功能**
+```bash
+curl -X POST http://localhost:3002/api/v1/config/cache \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"enabled": true}'
+```
+
+**請求:**
+```json
+{
+ "enabled": true
+}
+```
+
+**回應:**
+```json
+{
+ "success": true,
+ "cache_enabled": true,
+ "message": "Cache enabled"
+}
+```
+
+---
+
+## 🚀 快速工作流程
+
+### 1. 註冊並處理影片
+```bash
+# 1. 註冊影片
+curl -X POST http://localhost:3002/api/v1/register \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"path": "/path/to/video.mp4"}'
+
+# 回應包含 UUID: 5dea6618a606e7c7
+
+# 2. 監控進度
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/5dea6618a606e7c7
+
+# 3. 查看任務狀態
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs/5dea6618a606e7c7
+```
+
+### 2. 搜尋影片內容
+```bash
+# 1. 列出所有影片
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
+
+# 2. 搜尋內容
+curl -X POST http://localhost:3002/api/v1/n8n/search \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"query": "charade scene", "limit": 10}'
+```
+
+### 3. 系統管理
+```bash
+# 1. 檢查系統健康
+curl http://localhost:3002/health/detailed
+
+# 2. 管理快取
+curl -X POST http://localhost:3002/api/v1/config/cache \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"enabled": false}'
+
+# 3. 刪除影片(需要時)
+curl -X POST http://localhost:3002/api/v1/unregister \
+ -H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
+ -d '{"uuid": "5dea6618a606e7c7"}'
+```
+
+---
+
+## 📝 注意事項
+
+1. **API Key 格式:**
+ - 使用完整 API Key:`muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69`
+ - 系統存儲的是 SHA256 哈希值
+
+2. **路徑格式:**
+ - 絕對路徑:`/Users/accusys/test_video/video.mp4`
+ - 相對路徑:`./demo/video.mp4`(相對於 SFTPGo 資料目錄)
+
+3. **回應時間:**
+ - 健康檢查:< 100ms
+ - 搜尋:取決於查詢複雜度,通常 100-500ms
+ - 影片註冊:立即返回,背景處理可能需要數分鐘到數小時
+
+4. **錯誤處理:**
+ - 401: 認證失敗
+ - 404: 資源不存在
+ - 500: 伺服器內部錯誤
+
+---
+
+## 🔗 相關文件
+
+- [API 參考指南](./API_REFERENCE.md) - 詳細 API 說明
+- [API 範例總覽](./API_EXAMPLES.md) - 完整使用範例
+- [API 端點列表](./API_ENDPOINTS.md) - 端點簡介
+- [Curl 範例指南](./API_CURL_EXAMPLES.md) - curl 命令範例
+- [n8n 整合指南](./API_N8N_GUIDE.md) - n8n 工作流程整合
\ No newline at end of file
diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md
index f52c417..08f53e0 100644
--- a/docs/API_REFERENCE.md
+++ b/docs/API_REFERENCE.md
@@ -4,7 +4,7 @@
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
-| 文件版本 | V1.0 |
+| 文件版本 | V1.3 |
---
@@ -15,6 +15,7 @@
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-23 | 更新端點與實際一致 | OpenCode | - |
| V1.2 | 2026-03-25 | 新增快取/刪除 API | OpenCode | - |
+| V1.3 | 2026-03-26 | 修正認證聲明與API回應格式 | OpenCode | - |
---
@@ -38,7 +39,22 @@
## Authentication
-Currently no authentication is required.
+**API Key 認證:**
+
+所有 `/api/v1/*` 端點需要 `X-API-Key` header 進行認證。
+
+**公開端點:**
+- `GET /health` - 健康檢查
+- `GET /health/detailed` - 詳細健康檢查
+
+**認證格式:**
+```bash
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
+```
+
+**API Key 管理:**
+- 使用 `/api/v1/api-keys` 端點管理 API Keys
+- 詳細說明請參考 [API Key Management Guide](../docs/API_KEY_MANAGEMENT.md)
---
@@ -65,10 +81,12 @@ Register a video file to the system.
{
"uuid": "5dea6618a606e7c7",
"video_id": 1,
+ "job_id": 10,
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
- "height": 1080
+ "height": 1080,
+ "already_exists": false
}
```
@@ -76,6 +94,7 @@ Register a video file to the system.
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/Users/accusys/test_video/BigBuckBunny_320x180.mp4"}'
```
@@ -152,7 +171,7 @@ Get real-time processing progress via Redis.
**Example:**
```bash
# Get progress for specific video
-curl http://localhost:3002/api/v1/progress/5dea6618a606e7c7
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/5dea6618a606e7c7
```
---
@@ -199,6 +218,7 @@ Search video chunks using natural language queries (RAG).
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "machine learning", "limit": 5}'
```
@@ -238,7 +258,7 @@ N8n-compatible search endpoint with standardized response format for direct work
"title": "Sunset Scene",
"text": "The sun slowly sets over the ocean...",
"score": 0.92,
- "media_url": "https://wp.momentry.ddns.net/video.mp4"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
@@ -255,12 +275,13 @@ N8n-compatible search endpoint with standardized response format for direct work
| `hits[].title` | string | Chunk title (from metadata or auto-generated) |
| `hits[].text` | string | Text content |
| `hits[].score` | number | Relevance score (0-1) |
-| `hits[].media_url` | string | Full media URL (optional) |
+| `hits[].file_path` | string | Full file path to video file |
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "sunset", "limit": 5}'
```
@@ -296,10 +317,10 @@ Lookup video UUID by path or get video details by UUID.
**Example:**
```bash
# Lookup by path
-curl "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
# Lookup by UUID
-curl "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
+curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
```
---
@@ -327,7 +348,7 @@ List all registered videos.
**Example:**
```bash
-curl http://localhost:3002/api/v1/videos
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
```
---
diff --git a/docs/API_TRAINING_MARCOM.md b/docs/API_TRAINING_MARCOM.md
index fd6d55c..6b97b3b 100644
--- a/docs/API_TRAINING_MARCOM.md
+++ b/docs/API_TRAINING_MARCOM.md
@@ -1,7 +1,7 @@
# Momentry Core API 教育訓練手冊
> **對象**: marcom 團隊
-> **版本**: V1.2 | **日期**: 2026-03-25
+> **版本**: V1.4 | **日期**: 2026-03-25
---
@@ -147,7 +147,7 @@ curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b6
| `story` | 故事線片段(父子關係) | Story 分析 |
#### POST /api/v1/n8n/search
-n8n 專用搜尋(包含完整影片網址 media_url)
+n8n 專用搜尋(包含完整影片檔案路徑 file_path)
**請求參數**: 與 `/search` 相同
@@ -385,3 +385,4 @@ GET /api/v1/jobs/{uuid}
| V1.1 | 2026-03-25 | 新增快取/刪除 API、搜尋端點文件 | OpenCode |
| V1.2 | 2026-03-25 | 新增 Chunk 欄位說明、類型、播放方式 | OpenCode |
| V1.3 | 2026-03-25 | 新增 Demo 測試帳號(SFTPGo)| OpenCode |
+| V1.4 | 2026-03-25 | 更新 n8n 搜尋回傳欄位說明 (media_url→file_path) | OpenCode |
diff --git a/docs/API_WORDPRESS_GUIDE.md b/docs/API_WORDPRESS_GUIDE.md
index f91146a..f53ab30 100644
--- a/docs/API_WORDPRESS_GUIDE.md
+++ b/docs/API_WORDPRESS_GUIDE.md
@@ -2,12 +2,21 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.0 |
-| 日期 | 2026-03-23 |
+| 版本 | V1.1 |
+| 日期 | 2026-03-25 |
| 用途 | 在 WordPress 中呼叫 Momentry API |
---
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.1 | 2026-03-25 | 更新: n8n 搜尋回傳 `file_path` 取代 `media_url`,新增 API Key 驗證說明 | OpenCode | deepseek-reasoner |
+| V1.0 | 2026-03-23 | 創建 WordPress API 指南 | Warren | OpenCode / MiniMax M2.5 |
+
+---
+
## API URL
在 WordPress 中呼叫 API,**請使用外部 URL**:
@@ -20,6 +29,20 @@ https://api.momentry.ddns.net
---
+## API 認證
+
+所有 `/api/v1/*` 端點(除了健康檢查)都需要 API Key 認證。請在請求標頭中加入:
+
+```
+'headers' => ['Content-Type' => 'application/json', 'X-API-Key' => 'YOUR_API_KEY']
+```
+
+**目前示範使用的 API Key**: `demo_api_key_12345`
+
+> **注意**: 正式環境請使用安全的 API Key 管理機制,避免在客戶端 JavaScript 中暴露 API Key。
+
+---
+
## 常用端點
| 方法 | 端點 | 說明 |
@@ -45,7 +68,7 @@ $data = [
];
$response = wp_remote_post($api_url, [
- 'headers' => ['Content-Type' => 'application/json'],
+ 'headers' => ['Content-Type' => 'application/json', 'X-API-Key' => 'YOUR_API_KEY'],
'body' => json_encode($data),
'timeout' => 30
]);
@@ -65,7 +88,10 @@ if (is_wp_error($response)) {
30]);
+$response = wp_remote_get($api_url, [
+ 'headers' => ['X-API-Key' => 'YOUR_API_KEY'],
+ 'timeout' => 30
+]);
if (!is_wp_error($response)) {
$body = json_decode(wp_remote_retrieve_body($response), true);
@@ -83,7 +109,10 @@ if (!is_wp_error($response)) {
$uuid = '5dea6618a606e7c7';
$api_url = 'https://api.momentry.ddns.net/api/v1/lookup?uuid=' . $uuid;
-$response = wp_remote_get($api_url, ['timeout' => 30]);
+$response = wp_remote_get($api_url, [
+ 'headers' => ['X-API-Key' => 'YOUR_API_KEY'],
+ 'timeout' => 30
+]);
if (!is_wp_error($response)) {
$video = json_decode(wp_remote_retrieve_body($response), true);
@@ -104,7 +133,7 @@ if (!is_wp_error($response)) {
async function searchVideos(query, limit = 10) {
const response = await fetch('https://api.momentry.ddns.net/api/v1/n8n/search', {
method: 'POST',
- headers: { 'Content-Type': 'application/json' },
+ headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' },
body: JSON.stringify({ query, limit })
});
@@ -132,6 +161,24 @@ searchVideos('charade', 5)
```php
['Content-Type' => 'application/json'],
+ 'headers' => [
+ 'Content-Type' => 'application/json',
+ 'X-API-Key' => 'YOUR_API_KEY' // 替換為實際的 API Key
+ ],
'body' => json_encode([
'query' => $atts['query'],
'limit' => (int)$atts['limit']
@@ -164,10 +214,15 @@ add_shortcode('momentry_search', function($atts) {
$output = '';
foreach ($data['hits'] as $hit) {
+ // 注意: API 現在返回 file_path 而非 media_url
+ // 需要將文件路徑轉換為可訪問的 URL
+ $file_path = $hit['file_path'];
+ $video_url = convert_file_path_to_url($file_path); // 需要實作此函數
+
$output .= sprintf(
'- %s 播放
',
esc_html($hit['text']),
- $hit['media_url'],
+ $video_url,
$hit['start']
);
}
@@ -199,7 +254,7 @@ add_action('rest_api_init', function() {
$response = wp_remote_post(
'https://api.momentry.ddns.net/api/v1/n8n/search',
[
- 'headers' => ['Content-Type' => 'application/json'],
+ 'headers' => ['Content-Type' => 'application/json', 'X-API-Key' => 'YOUR_API_KEY'],
'body' => json_encode([
'query' => $request->get_param('query'),
'limit' => $request->get_param('limit', 10)
diff --git a/docs/DEMO_MANUAL.md b/docs/DEMO_MANUAL.md
index 33f2be4..4fb683e 100644
--- a/docs/DEMO_MANUAL.md
+++ b/docs/DEMO_MANUAL.md
@@ -2,9 +2,21 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.0 |
-| 日期 | 2026-03-25 |
-| 狀態 | 完成 |
+| 建立者 | OpenCode |
+| 建立時間 | 2026-03-25 |
+| 文件版本 | V1.0 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-25 | 創建示範手冊,包含 Demo API Key 與完整範例 | OpenCode | deepseek-reasoner |
+
+---
+
+**狀態**: 完成
---
diff --git a/docs/DOCUMENT_EMBEDDING_STRATEGY.md b/docs/DOCUMENT_EMBEDDING_STRATEGY.md
index e0750c8..4d6597d 100644
--- a/docs/DOCUMENT_EMBEDDING_STRATEGY.md
+++ b/docs/DOCUMENT_EMBEDDING_STRATEGY.md
@@ -1,5 +1,21 @@
# Document Embedding Strategy - Parent-Child Chunks
+| Item | Content |
+|------|---------|
+| Author | Warren |
+| Created | 2026-03-23 |
+| Document Version | V1.0 |
+
+---
+
+## Version History
+
+| Version | Date | Purpose | Operator | Tool/Model |
+|---------|------|---------|----------|------------|
+| V1.0 | 2026-03-23 | Create document embedding strategy | Warren | OpenCode |
+
+---
+
## Overview
Momentry uses a **parent-child chunk hierarchy** for improved RAG retrieval. This document describes the embedding strategy for this hierarchy.
@@ -44,7 +60,7 @@ embedding_text = f"Summary: {parent.text_content}
Children: {child_text_1}. {child_text_2}. {child_text_3}..."
```
-**Prefix**: `search_document: ` (for documents in Qdrant)
+**Prefix**: `search_document:` (for documents in Qdrant)
**Example**:
```
@@ -58,7 +74,7 @@ embedding_text = f"[{child.chunk_type}] {child.text_content}
Parent: {parent.description}"
```
-**Prefix**: `search_document: `
+**Prefix**: `search_document:`
**Example**:
```
diff --git a/docs/INSTALL_MOMENTRY_API.md b/docs/INSTALL_MOMENTRY_API.md
index 719d99d..696dfa8 100644
--- a/docs/INSTALL_MOMENTRY_API.md
+++ b/docs/INSTALL_MOMENTRY_API.md
@@ -461,4 +461,4 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
- `docs/INSTALL_POSTGRESQL.md` - PostgreSQL 安裝
- `docs/INSTALL_REDIS.md` - Redis 安裝
- `docs/INSTALL_QDRANT.md` - Qdrant 安裝
-- `docs/PENDING_ISSUES.md` - 待解決問題
\ No newline at end of file
+- `docs/PENDING_ISSUES.md` - 待解決問題
diff --git a/docs/INSTALL_SFTPGO.md b/docs/INSTALL_SFTPGO.md
index 9861179..67b8867 100644
--- a/docs/INSTALL_SFTPGO.md
+++ b/docs/INSTALL_SFTPGO.md
@@ -527,13 +527,13 @@ SFTPGo 提供 RESTful API 用於管理用戶和組,支援自動化運維。
### API 認證方式
-1. **獲取 Access Token** (使用 Basic Auth):
+- **獲取 Access Token** (使用 Basic Auth):
```bash
TOKEN=$(curl -s -X GET http://localhost:8080/api/v2/token \
-u "admin:Test3200Test3200" | jq -r '.access_token')
```
-2. **使用 Token 調用 API**:
+- **使用 Token 調用 API**:
```bash
curl -X GET http://localhost:8080/api/v2/admins \
-H "Authorization: Bearer $TOKEN"
@@ -569,7 +569,7 @@ curl -s -X POST http://localhost:8080/api/v2/users \
}'
```
-**權限格式注意**: 必須為 map[string][]string 格式,例如:
+**權限格式注意**: 必須為 `map[string][]string` 格式,例如:
```json
{
"/": ["*"],
@@ -752,12 +752,12 @@ sftpgo serve --config-file /Users/accusys/momentry/etc/sftpgo/sftpgo.json &
### Hook 故障排除
-1. **檢查 Hook 日誌**:
+- **檢查 Hook 日誌**:
```bash
tail -f /Users/accusys/sftpgo_test/hook.log
```
-2. **手動測試 Hook 腳本**:
+- **手動測試 Hook 腳本**:
```bash
export SFTPGO_USERNAME=demo
export SFTPGO_FILEPATH="./test.txt"
@@ -766,7 +766,7 @@ export SFTPGO_ACTION=add
/Users/accusys/sftpgo_test/register_hook.sh
```
-3. **SFTPGo 錯誤日誌**:
+- **SFTPGo 錯誤日誌**:
```bash
tail -20 /Users/accusys/momentry/log/sftpgo.error.log
```
@@ -877,12 +877,12 @@ sftp> put test.txt
## 常見問題
-#### "無效的憑證" 即使密碼正確
+### "無效的憑證" 即使密碼正確
- PostgreSQL 中的密碼哈希可能不符合 SFTPGo 預期格式
- 使用 Web 面板的 **Forgot password** 功能而非直接 SQL 更新
-#### CSRF Token 錯誤
+### CSRF Token 錯誤
- 清除瀏覽器中 `localhost:8080` 的 cookies
- 使用無痕/私密瀏覽視窗
diff --git a/docs/MAC_INSTALLATION_PLAN.md b/docs/MAC_INSTALLATION_PLAN.md
index e2e9b82..e179de4 100644
--- a/docs/MAC_INSTALLATION_PLAN.md
+++ b/docs/MAC_INSTALLATION_PLAN.md
@@ -779,4 +779,4 @@ log_info "✅ 部署完成!"
**負責人**: OpenCode AI Assistant
-**最後更新**: 2026-03-23
\ No newline at end of file
+**最後更新**: 2026-03-23
diff --git a/docs/MOMENTRY_RAG_PRESENTATION.md b/docs/MOMENTRY_RAG_PRESENTATION.md
index 553da67..be7e58b 100644
--- a/docs/MOMENTRY_RAG_PRESENTATION.md
+++ b/docs/MOMENTRY_RAG_PRESENTATION.md
@@ -1,5 +1,22 @@
# Momentry Core 影片 RAG 系統說明稿
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-22 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-25 | 更新API回應格式 (media_url→file_path) 與認證標頭 | OpenCode | deepseek-reasoner |
+
+---
+
## 系統架構
```
@@ -85,22 +102,9 @@ POST http://localhost:3002/api/v1/search
}
```
-**回應:**
-```json
-{
- "results": [
- {
- "uuid": "a1b10138a6bbb0cd",
- "chunk_id": "sentence_0006",
- "chunk_type": "sentence",
- "start_time": 48.8,
- "end_time": 55.44,
- "text": "fun plot twists, Woody Dialog and charming performances...",
- "score": 0.526
- }
- ]
-}
-```
+> **注意**:
+> 1. **API 認證**: 所有 `/api/v1/*` 端點需要 `X-API-Key` 標頭
+> 2. **檔案路徑轉換**: API 現在返回 `file_path`(檔案系統路徑),需要轉換為可訪問的 URL(例如透過 SFTPGo 分享連結)
---
@@ -132,7 +136,7 @@ POST http://localhost:3002/api/v1/n8n/search
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
- "media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
@@ -187,6 +191,7 @@ POST http://localhost:3002/api/v1/n8n/search
Method: POST
URL: http://localhost:3002/api/v1/n8n/search
Body Content Type: JSON
+ Headers: X-API-Key (需設定)
```
3. Body:
@@ -215,12 +220,17 @@ const results = hits.map((hit, index) => ({
text: hit.text,
time: `${hit.start}s - ${hit.end}s`,
score: Math.round(hit.score * 100) + "%",
- url: hit.media_url + "#t=" + hit.start + "," + hit.end
+ // 注意: API 現在返回 file_path(檔案系統路徑),需要轉換為可訪問的 URL
+ url: hit.file_path + "#t=" + hit.start + "," + hit.end // 需實作檔案路徑轉換為 URL
}));
return { json: { results } };
```
+> **注意**:
+> 1. **API 認證**: 所有 `/api/v1/*` 端點需要 `X-API-Key` 標頭
+> 2. **檔案路徑轉換**: API 現在返回 `file_path`(檔案系統路徑),需要轉換為可訪問的 URL(例如透過 SFTPGo 分享連結)
+
---
### Step 4: 格式化輸出
@@ -248,18 +258,20 @@ return { json: { results } };
# 語意搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}'
# n8n 格式
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}'
# 影片列表
-curl http://localhost:3002/api/v1/videos
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
# 特定影片區塊
-curl http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
```
---
diff --git a/docs/N8N_DEMO.md b/docs/N8N_DEMO.md
index cfb47d4..eb7f720 100644
--- a/docs/N8N_DEMO.md
+++ b/docs/N8N_DEMO.md
@@ -1,11 +1,29 @@
# n8n 整合範例
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-18 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-25 | 更新API回應格式 (media_url→file_path) 與認證標頭 | OpenCode | deepseek-reasoner |
+
+---
+
## 基本設定
### API 端點
- **Base URL:** `http://localhost:3002/api/v1`
- **Method:** `POST`
- **Content-Type:** `application/json`
+- **Authentication:** `X-API-Key: YOUR_API_KEY` (所有 `/api/v1/*` 端點皆需要)
---
@@ -36,7 +54,8 @@
},
"options": {
"headers": {
- "Content-Type": "application/json"
+ "Content-Type": "application/json",
+ "X-API-Key": "YOUR_API_KEY"
}
}
}
@@ -62,7 +81,7 @@ return results.map(r => ({
## Workflow 2: n8n 專用格式
-使用 `/n8n/search` 端點(已包含 media_url)
+使用 `/n8n/search` 端點(已包含 file_path)
### HTTP Request
```json
@@ -72,6 +91,12 @@ return results.map(r => ({
"body": {
"query": "={{ $json.searchTerm }}",
"limit": 5
+ },
+ "options": {
+ "headers": {
+ "Content-Type": "application/json",
+ "X-API-Key": "YOUR_API_KEY"
+ }
}
}
```
@@ -90,12 +115,14 @@ return results.map(r => ({
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
- "media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
```
+> **注意**: API 現在返回 `file_path`(檔案系統路徑)而非 `media_url`(網頁 URL)。如需在網頁中播放影片,請將檔案路徑轉換為可訪問的 URL(例如透過 SFTPGo 分享連結)。
+
---
## Workflow 3: 訊息機器人整合
@@ -205,16 +232,18 @@ return {
# 基本搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}'
# n8n 格式
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}'
# 取得影片列表
-curl http://localhost:3002/api/v1/videos
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
# 取得特定影片的區塊
-curl http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
+curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
```
diff --git a/docs/N8N_DEMO_EXECUTION_LOG.md b/docs/N8N_DEMO_EXECUTION_LOG.md
index 9b440da..81838ae 100644
--- a/docs/N8N_DEMO_EXECUTION_LOG.md
+++ b/docs/N8N_DEMO_EXECUTION_LOG.md
@@ -1,7 +1,20 @@
# n8n Video RAG Demo - API 執行記錄
-> 建立時間: 2026-03-22
-> 目標: 完整執行 n8n Video RAG Workflow 並記錄所有 API 呼叫
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-22 |
+| 文件版本 | V1.1 |
+| 目標 | 完整執行 n8n Video RAG Workflow 並記錄所有 API 呼叫 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode |
+| V1.1 | 2026-03-26 | 更新 API 範例,新增 X-API-Key 驗證標頭 | OpenCode | deepseek-reasoner |
---
@@ -297,12 +310,13 @@ Content-Type: application/json
---
-### Step 4.2: n8n 搜尋 (含 media_url)
+### Step 4.2: n8n 搜尋 (含 file_path)
**API 呼叫:**
```bash
curl -X POST "http://localhost:3002/api/v1/n8n/search" \
-H "Content-Type: application/json" \
+ -H "X-API-Key: demo_api_key_12345" \
-d '{
"query": "What is the movie about?",
"limit": 10,
diff --git a/docs/N8N_DEMO_WORKFLOW.md b/docs/N8N_DEMO_WORKFLOW.md
index 6486120..49389f5 100644
--- a/docs/N8N_DEMO_WORKFLOW.md
+++ b/docs/N8N_DEMO_WORKFLOW.md
@@ -1,7 +1,19 @@
# n8n Video RAG Workflow - Node 設計
-> 建立時間: 2026-03-22
-> 目標: 讓 marcom 團隊能夠複製、貼上、修改使用的完整操作指南
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-22 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-25 | 更新API回應格式 (media_url→file_path) 與認證標頭 | OpenCode | deepseek-reasoner |
---
@@ -117,7 +129,7 @@
│ │ │ │
│ │ ⑫ Natural Language Search │ │
│ │ ↓ │ │
-│ │ ⑬ Get Media URL (含 media_url) │ │
+│ │ ⑬ Get File Path (含 file_path) │ │
│ │ ↓ │ │
│ │ ⑭ Build Response │ │
│ │ ↓ │ │
@@ -363,7 +375,7 @@ Output:
}
```
-**注意**: 只有 asr、asrx、story 三個模組
+> **注意**: API 現在返回 `file_path`(檔案系統路徑)而非 `media_url`(網頁 URL)。如需在網頁中播放影片,請將檔案路徑轉換為可訪問的 URL(例如透過 SFTPGo 分享連結)。
---
@@ -559,7 +571,7 @@ Output:
"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"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
@@ -642,12 +654,13 @@ Configuration:
| 項目 | 值 |
|------|-----|
| API Base | `http://localhost:3002` |
+| Authentication | `X-API-Key` header (所有 `/api/v1/*` 端點) |
| 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` |
+| Media Base | `https://wp.momentry.ddns.net` (僅供參考,API 返回 `file_path` 而非 URL) |
### Demo 測試資料
diff --git a/docs/N8N_HTTP_REQUEST_GUIDE.md b/docs/N8N_HTTP_REQUEST_GUIDE.md
index b0ec5d2..74cf856 100644
--- a/docs/N8N_HTTP_REQUEST_GUIDE.md
+++ b/docs/N8N_HTTP_REQUEST_GUIDE.md
@@ -1,5 +1,22 @@
# n8n HTTP Request Node 設定指南
+| 項目 | 內容 |
+|------|------|
+| 建立者 | OpenCode |
+| 建立時間 | 2026-03-26 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-23 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
+| V1.1 | 2026-03-26 | 新增 API Key 驗證說明,更新 curl 範例 | OpenCode | deepseek-reasoner |
+
+---
+
> **API URL 說明**:
> - **本地測試**: `http://localhost:3002`
> - **n8n workflow**: `https://api.momentry.ddns.net`
@@ -32,7 +49,9 @@ Node: HTTP Request
│ "query": "={{ $json.query }}",
│ "limit": "={{ $json.limit }}"
│ }
-└── Options: (empty)
+├── Send Headers: ✓ (checked)
+└── Header Parameters:
+ └── X-API-Key: {{ $env.MOMENTRY_API_KEY }}
```
### 方法 2: 使用 Raw Body + Headers
@@ -51,7 +70,8 @@ Node: HTTP Request
│ }
├── Send Headers: ✓ (checked)
└── Header Parameters:
- └── Content-Type: application/json
+ ├── Content-Type: application/json
+ └── X-API-Key: {{ $env.MOMENTRY_API_KEY }}
```
### 方法 3: 最簡單的 Hardcoded 測試
@@ -218,8 +238,12 @@ URL: https://api.momentry.ddns.net/api/v1/n8n/search
在終端機測試:
```bash
+# 需要 API Key 驗證 (設定環境變數或直接替換)
+export MOMENTRY_API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
+
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: $MOMENTRY_API_KEY" \
-d '{"query":"charade","limit":2}'
```
diff --git a/docs/N8N_INTEGRATION_GUIDE.md b/docs/N8N_INTEGRATION_GUIDE.md
index 2bca11b..241def5 100644
--- a/docs/N8N_INTEGRATION_GUIDE.md
+++ b/docs/N8N_INTEGRATION_GUIDE.md
@@ -2,9 +2,22 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.1 |
-| 日期 | 2026-03-23 |
-| 目標讀者 | n8n 使用者、DevOps |
+| 建立者 | Warren |
+| 建立時間 | 2026-03-23 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-22 | 創建 n8n 整合手冊 | Warren | OpenCode |
+| V1.1 | 2026-03-23 | 新增 API Key 驗證與完整工作流範例 | Warren | OpenCode |
+
+---
+
+**目標讀者**: n8n 使用者、DevOps
---
diff --git a/docs/N8N_MCP_SETUP.md b/docs/N8N_MCP_SETUP.md
index ec47dae..d00670d 100644
--- a/docs/N8N_MCP_SETUP.md
+++ b/docs/N8N_MCP_SETUP.md
@@ -1,5 +1,21 @@
# OpenCode n8n MCP 整合設定
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-23 |
+| 文件版本 | V1.0 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-23 | 創建 n8n MCP 整合設定文件 | Warren | OpenCode |
+
+---
+
> 建立時間: 2026-03-23
> 更新時間: 2026-03-23
diff --git a/docs/N8N_MCP_TEST_REPORT.md b/docs/N8N_MCP_TEST_REPORT.md
index 3c12c08..2439b5e 100644
--- a/docs/N8N_MCP_TEST_REPORT.md
+++ b/docs/N8N_MCP_TEST_REPORT.md
@@ -1,5 +1,21 @@
# n8n MCP 整合測試報告
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-23 |
+| 文件版本 | V1.0 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-23 | 創建測試報告 | Warren | OpenCode |
+
+---
+
## 測試日期
2026-03-23
diff --git a/docs/N8N_VIDEO_SEARCH_SUCCESS.md b/docs/N8N_VIDEO_SEARCH_SUCCESS.md
index 1305922..315cf00 100644
--- a/docs/N8N_VIDEO_SEARCH_SUCCESS.md
+++ b/docs/N8N_VIDEO_SEARCH_SUCCESS.md
@@ -1,7 +1,20 @@
# n8n Video Search 工作流程 - 成功設定指南
-> 建立時間: 2026-03-22
-> 適用版本: n8n 2.3.5
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-22 |
+| 文件版本 | V1.1 |
+| 適用版本 | n8n 2.3.5 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode |
+| V1.1 | 2026-03-26 | 更新 API 範例,新增 X-API-Key 驗證標頭 | OpenCode | deepseek-reasoner |
---
@@ -27,7 +40,11 @@
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
- "options": {}
+ "options": {
+ "headers": {
+ "X-API-Key": "demo_api_key_12345"
+ }
+ }
}
```
@@ -85,7 +102,11 @@
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
- "options": {}
+ "options": {
+ "headers": {
+ "X-API-Key": "demo_api_key_12345"
+ }
+ }
},
"name": "Search API",
"type": "n8n-nodes-base.httpRequest",
@@ -157,7 +178,7 @@
"title": "Chunk sentence_0006",
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526,
- "media_url": "https://wp.momentry.ddns.net/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
+ "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
@@ -203,13 +224,14 @@
```bash
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
+ -H "X-API-Key: demo_api_key_12345" \
-d '{"query":"charade","limit":3}'
```
### 驗證服務狀態
```bash
# 檢查 Momentry Core
-curl https://api.momentry.ddns.net/api/v1/videos
+curl -H "X-API-Key: demo_api_key_12345" https://api.momentry.ddns.net/api/v1/videos
# 檢查 n8n
curl http://localhost:5678/api/v1/workflows \
diff --git a/docs/PLAYGROUND_BINARY_IMPLEMENTATION.md b/docs/PLAYGROUND_BINARY_IMPLEMENTATION.md
index 66f5798..4f0bc2c 100644
--- a/docs/PLAYGROUND_BINARY_IMPLEMENTATION.md
+++ b/docs/PLAYGROUND_BINARY_IMPLEMENTATION.md
@@ -1,5 +1,21 @@
# Playground Binary Implementation Plan
+| Item | Content |
+|------|---------|
+| Author | Warren |
+| Created | 2026-03-23 |
+| Document Version | V1.0 |
+
+---
+
+## Version History
+
+| Version | Date | Purpose | Operator | Tool/Model |
+|---------|------|---------|----------|------------|
+| V1.0 | 2026-03-23 | Create implementation plan | Warren | OpenCode |
+
+---
+
## Overview
Create separate `momentry_playground` binary with distinct configuration from `momentry` (production).
diff --git a/docs/PROCESSING_PIPELINE.md b/docs/PROCESSING_PIPELINE.md
index 03eabc3..2c42463 100644
--- a/docs/PROCESSING_PIPELINE.md
+++ b/docs/PROCESSING_PIPELINE.md
@@ -1,6 +1,19 @@
# Video Processing Pipeline - 處理流程
-> 建立時間: 2026-03-22
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-22 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode |
+| V1.1 | 2026-03-26 | 更新流程圖文字 (media_url→file_path) | OpenCode | deepseek-reasoner |
---
@@ -54,7 +67,7 @@
│ │ │ │
│ │ Natural Language Query ──→ [Embedding] ──→ [Qdrant Search] │ │
│ │ ↓ │ │
-│ │ 返回結果含 media_url │ │
+│ │ 返回結果含 file_path │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
@@ -268,4 +281,3 @@ curl http://localhost:3002/api/v1/progress/{uuid}
3. **獨立 Chunk 命令** - 分離 chunk 生成
4. **獨立 Vectorize 命令** - 分離向量化流程
5. **模型管理** - 新增、選擇、預覽模型
-
diff --git a/docs/TEST_AND_BENCHMARK_PLAN.md b/docs/TEST_AND_BENCHMARK_PLAN.md
index 238ba2b..b2fa9ca 100644
--- a/docs/TEST_AND_BENCHMARK_PLAN.md
+++ b/docs/TEST_AND_BENCHMARK_PLAN.md
@@ -1,5 +1,21 @@
# Momentry 系統測試與驗證計劃
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-23 |
+| 文件版本 | V1.0 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-23 | 創建測試與驗證計劃 | Warren | OpenCode |
+
+---
+
> **計劃階段** - 僅供討論,尚未執行
> **建立時間**: 2026-03-23
> **目標**: 安裝後測試、跑分、燒機
diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md
index 69465cf..dfc525d 100644
--- a/docs/USER_MANUAL.md
+++ b/docs/USER_MANUAL.md
@@ -2,9 +2,21 @@
| 項目 | 內容 |
|------|------|
-| 版本 | V1.0 |
-| 日期 | 2026-03-21 |
-| 目標讀者 | 系統管理員、開發者 |
+| 建立者 | Warren |
+| 建立時間 | 2026-03-21 |
+| 文件版本 | V1.0 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-21 | 創建使用手冊 | Warren | OpenCode |
+
+---
+
+**目標讀者**: 系統管理員、開發者
---
diff --git a/docs/VERSION_MANAGEMENT.md b/docs/VERSION_MANAGEMENT.md
index 26e9f36..97fb59f 100644
--- a/docs/VERSION_MANAGEMENT.md
+++ b/docs/VERSION_MANAGEMENT.md
@@ -1,5 +1,21 @@
# Momentry Core 版本管理規範
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-23 |
+| 文件版本 | V1.0 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-23 | 創建版本管理規範 | Warren | OpenCode |
+
+---
+
## 1. 版本與通訊埠對照表
| 版本 | Binary | Port | Redis Prefix | 用途 |
diff --git a/docs/VIDEO_REGISTRATION.md b/docs/VIDEO_REGISTRATION.md
index 4d9cab9..9a8ccce 100644
--- a/docs/VIDEO_REGISTRATION.md
+++ b/docs/VIDEO_REGISTRATION.md
@@ -1,5 +1,22 @@
# Video Registration
+| 項目 | 內容 |
+|------|------|
+| 建立者 | Warren |
+| 建立時間 | 2026-03-25 |
+| 文件版本 | V1.1 |
+
+---
+
+## 版本歷史
+
+| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
+|------|------|------|--------|-----------|
+| V1.0 | 2026-03-25 | 創建文件 | Warren | OpenCode |
+| V1.1 | 2026-03-26 | 修正 curl 範例,新增 API Key 驗證標頭 | OpenCode | deepseek-reasoner |
+
+---
+
## 概述
影片註冊 API (`POST /api/v1/register`) 用於將影片加入 Momentry Core 系統進行處理。
@@ -139,11 +156,13 @@ SFTPgo 的用戶目錄結構:
# 使用相對路徑註冊
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}'
# 或使用多層目錄
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/movies/2024/video.mp4"}'
```
@@ -185,6 +204,7 @@ pub fn extract_user_from_relative_path(relative_path: &str) -> (String, String)
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
+ -H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}'
```
@@ -224,10 +244,3 @@ curl -X POST http://localhost:3002/api/v1/probe \
| `src/core/probe/ffprobe.rs` | ffprobe 整合 |
| `docs/SFTPGO_DEMO_USER.md` | SFTPgo 用戶設置 |
| `docs/API_ENDPOINTS.md` | API 端點總覽 |
-
-## 歷史
-
-| 日期 | 變更 |
-|------|------|
-| 2026-03-25 | 初始版本 - 新增 UUID 計算規則和重複註冊檢查 |
-| 2026-03-25 | 新增 Probe API 說明 |
diff --git a/docs/n8n_workflow_simple.json b/docs/n8n_workflow_simple.json
index 932ee43..a9e6dd2 100644
--- a/docs/n8n_workflow_simple.json
+++ b/docs/n8n_workflow_simple.json
@@ -12,7 +12,10 @@
"name": "Webhook (Simple)",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
- "position": [250, 300],
+ "position": [
+ 250,
+ 300
+ ],
"webhookId": "video-search-simple"
},
{
@@ -34,7 +37,8 @@
},
"options": {
"headers": {
- "Content-Type": "application/json"
+ "Content-Type": "application/json",
+ "X-API-Key": "demo_api_key_12345"
}
}
},
@@ -42,17 +46,23 @@
"name": "搜尋 Momentry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
- "position": [500, 300]
+ "position": [
+ 500,
+ 300
+ ]
},
{
"parameters": {
- "jsCode": "// 處理 Momentry 搜尋結果\nconst data = $input.first().json;\nconst hits = data.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n success: false,\n message: '找不到相關結果',\n query: data.query\n }\n };\n}\n\n// 格式化結果\nconst formattedResults = hits.map((hit, idx) => ({\n index: idx + 1,\n id: hit.id,\n title: hit.title,\n text: hit.text,\n startTime: hit.start,\n endTime: hit.end,\n relevance: Math.round(hit.score * 100) + '%',\n videoUrl: hit.media_url,\n videoLink: hit.media_url + '#t=' + hit.start + ',' + hit.end\n}));\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count,\n results: formattedResults\n }\n};"
+ "jsCode": "// 處理 Momentry 搜尋結果\nconst data = $input.first().json;\nconst hits = data.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n success: false,\n message: '找不到相關結果',\n query: data.query\n }\n };\n}\n\n// 格式化結果\nconst formattedResults = hits.map((hit, idx) => {\n return {\n index: idx + 1,\n id: hit.id,\n title: hit.title,\n text: hit.text,\n startTime: hit.start,\n endTime: hit.end,\n relevance: Math.round(hit.score * 100) + '%',\n file_path: hit.file_path\n };\n});\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count,\n results: formattedResults\n }\n};"
},
"id": "code-process-simple",
"name": "處理結果",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
- "position": [750, 300]
+ "position": [
+ 750,
+ 300
+ ]
},
{
"parameters": {
@@ -63,7 +73,10 @@
"name": "回傳結果",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
- "position": [1000, 300]
+ "position": [
+ 1000,
+ 300
+ ]
}
],
"connections": {
@@ -107,4 +120,4 @@
"versionId": "1",
"createdAt": "2026-03-23T00:00:00.000Z",
"updatedAt": "2026-03-23T00:00:00.000Z"
-}
+}
\ No newline at end of file
diff --git a/docs/n8n_workflow_video_rag_mcp.json b/docs/n8n_workflow_video_rag_mcp.json
index a3b6a91..59405c1 100644
--- a/docs/n8n_workflow_video_rag_mcp.json
+++ b/docs/n8n_workflow_video_rag_mcp.json
@@ -11,7 +11,10 @@
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
- "position": [250, 300]
+ "position": [
+ 250,
+ 300
+ ]
},
{
"parameters": {
@@ -21,22 +24,31 @@
"contentType": "json",
"body": "={{ JSON.stringify({query: $json.body.query || $json.body, limit: $json.body.limit || 5, uuid: $json.body.uuid}) }}",
"options": {
- "timeout": 30000
+ "timeout": 30000,
+ "headers": {
+ "X-API-Key": "demo_api_key_12345"
+ }
}
},
"name": "Search Momentry Core",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
- "position": [500, 300]
+ "position": [
+ 500,
+ 300
+ ]
},
{
"parameters": {
- "jsCode": "// Process Momentry Core search results\nconst data = $input.first().json;\nconst hits = data.hits || [];\n\nif (hits.length === 0) {\n return {\n json: {\n success: false,\n message: 'No relevant results found',\n query: data.query,\n results: []\n }\n };\n}\n\n// Format results for RAG\nconst formattedResults = hits.map((hit, idx) => ({\n index: idx + 1,\n id: hit.id || hit.chunk_id,\n title: hit.title || 'Unknown Video',\n text: hit.text || hit.content || '',\n startTime: hit.start_time || hit.start || 0,\n endTime: hit.end_time || hit.end || 0,\n relevance: Math.round((hit.score || 0) * 100) + '%',\n videoUuid: hit.video_uuid || hit.uuid,\n mediaUrl: hit.media_url || '',\n deepLink: hit.media_url ? `${hit.media_url}#t=${hit.start_time || hit.start},${hit.end_time || hit.end}` : ''\n}));\n\n// Build context for RAG\nconst context = formattedResults\n .map(r => `[${r.index}] ${r.text} (Video: ${r.title}, Time: ${r.startTime}s-${r.endTime}s)`)\n .join('\\n\\n');\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count || hits.length,\n context: context,\n results: formattedResults\n }\n};"
+ "jsCode": "// Process Momentry Core search results\nconst data = $input.first().json;\nconst hits = data.hits || [];\n\nif (hits.length === 0) {\n return {\n json: {\n success: false,\n message: 'No relevant results found',\n query: data.query,\n results: []\n }\n };\n}\n\n// Format results for RAG\nconst formattedResults = hits.map((hit, idx) => {\n return {\n index: idx + 1,\n id: hit.id || hit.chunk_id,\n title: hit.title || 'Unknown Video',\n text: hit.text || hit.content || '',\n startTime: hit.start_time || hit.start || 0,\n endTime: hit.end_time || hit.end || 0,\n relevance: Math.round((hit.score || 0) * 100) + '%',\n videoUuid: hit.video_uuid || hit.uuid,\n file_path: hit.file_path || ''\n };\n});\n\n// Build context for RAG\nconst context = formattedResults\n .map(r => \\`[\\${r.index}] \\${r.text} (Video: \\${r.title}, Time: \\${r.startTime}s-\\${r.endTime}s)\\`)\n .join('\\n\\n');\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count || hits.length,\n context: context,\n results: formattedResults\n }\n};"
},
"name": "Process RAG Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
- "position": [750, 300]
+ "position": [
+ 750,
+ 300
+ ]
},
{
"parameters": {
@@ -49,7 +61,10 @@
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
- "position": [1000, 300]
+ "position": [
+ 1000,
+ 300
+ ]
}
],
"connections": {
@@ -91,4 +106,4 @@
"executionOrder": "v1"
},
"staticData": null
-}
+}
\ No newline at end of file
diff --git a/docs/n8n_workflow_video_search.json b/docs/n8n_workflow_video_search.json
index 12b29b2..032249b 100644
--- a/docs/n8n_workflow_video_search.json
+++ b/docs/n8n_workflow_video_search.json
@@ -12,7 +12,10 @@
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
- "position": [250, 300],
+ "position": [
+ 250,
+ 300
+ ],
"webhookId": "video-search"
},
{
@@ -34,7 +37,8 @@
},
"options": {
"headers": {
- "Content-Type": "application/json"
+ "Content-Type": "application/json",
+ "X-API-Key": "demo_api_key_12345"
}
}
},
@@ -42,17 +46,23 @@
"name": "搜尋 Momentry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
- "position": [500, 300]
+ "position": [
+ 500,
+ 300
+ ]
},
{
"parameters": {
- "jsCode": "const hits = $input.first().json.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n message: '找不到相關結果',\n query: $input.first().json.query\n }\n };\n}\n\nconst results = hits.map((hit, index) => ({\n number: index + 1,\n text: hit.text,\n start: hit.start,\n end: hit.end,\n score: Math.round(hit.score * 100) + '%',\n url: hit.media_url + '#t=' + hit.start + ',' + hit.end,\n video_title: hit.title\n}));\n\nreturn {\n json: {\n query: $input.first().json.query,\n count: $input.first().json.count,\n results: results\n }\n};"
+ "jsCode": "const hits = $input.first().json.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n message: '找不到相關結果',\n query: $input.first().json.query\n }\n };\n}\n\nconst results = hits.map((hit, index) => {\n return {\n number: index + 1,\n text: hit.text,\n start: hit.start,\n end: hit.end,\n score: Math.round(hit.score * 100) + '%',\n video_title: hit.title,\n file_path: hit.file_path\n };\n});\n\nreturn {\n json: {\n query: $input.first().json.query,\n count: $input.first().json.count,\n results: results\n }\n};"
},
"id": "code-process",
"name": "處理結果",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
- "position": [750, 300]
+ "position": [
+ 750,
+ 300
+ ]
},
{
"parameters": {
@@ -77,7 +87,10 @@
"name": "Telegram 通知",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
- "position": [1000, 300],
+ "position": [
+ 1000,
+ 300
+ ],
"continueOnFail": true
}
],
@@ -122,4 +135,4 @@
"versionId": "1",
"createdAt": "2026-03-23T00:00:00.000Z",
"updatedAt": "2026-03-23T00:00:00.000Z"
-}
+}
\ No newline at end of file
diff --git a/install_worker_service.sh b/install_worker_service.sh
new file mode 100644
index 0000000..75fff1d
--- /dev/null
+++ b/install_worker_service.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e
+
+echo "Installing Momentry Worker as a system service..."
+
+# Copy worker plist to system LaunchDaemons
+sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.worker.plist /Library/LaunchDaemons/
+
+# Load the service
+sudo launchctl load /Library/LaunchDaemons/com.momentry.worker.plist
+
+echo "Worker service installed successfully."
+echo "Checking service status..."
+launchctl list | grep com.momentry.worker || echo "Service not listed in user domain; check system domain."
diff --git a/migrations/004_fix_processor_results.sql b/migrations/004_fix_processor_results.sql
new file mode 100644
index 0000000..4986e40
--- /dev/null
+++ b/migrations/004_fix_processor_results.sql
@@ -0,0 +1,61 @@
+-- ================================================================
+-- Migration 004: Fix Processor Results Schema
+-- Version: 004
+-- Date: 2026-03-26
+-- Description: Add missing output_data column and fix worker integration
+-- ================================================================
+
+-- 4.1.1: Add output_data column (JSONB) to processor_results
+ALTER TABLE processor_results ADD COLUMN IF NOT EXISTS output_data JSONB;
+
+-- 4.1.2: Update processor_results table - drop duration_secs column if exists (we'll compute it)
+ALTER TABLE processor_results DROP COLUMN IF EXISTS duration_secs;
+
+-- 4.1.3: Add computed duration column (stored as integer seconds)
+ALTER TABLE processor_results ADD COLUMN IF NOT EXISTS duration_secs INT GENERATED ALWAYS AS (
+ CASE
+ WHEN completed_at IS NOT NULL AND started_at IS NOT NULL
+ THEN EXTRACT(EPOCH FROM (completed_at - started_at))::INT
+ ELSE NULL
+ END
+) STORED;
+
+-- 4.1.4: Add check constraint for processor values
+ALTER TABLE processor_results DROP CONSTRAINT IF EXISTS chk_processor_results_processor;
+ALTER TABLE processor_results ADD CONSTRAINT chk_processor_results_processor
+ CHECK (processor IN ('asr', 'cut', 'yolo', 'ocr', 'face', 'pose', 'asrx'));
+
+-- 4.1.5: Create index on processor_results.output_data for JSON queries (optional)
+CREATE INDEX IF NOT EXISTS idx_processor_results_output_data ON processor_results USING gin (output_data);
+
+-- 4.1.6: Add foreign key from processor_results.video_id to videos.id if not exists
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM information_schema.table_constraints
+ WHERE table_name = 'processor_results'
+ AND constraint_name = 'processor_results_video_id_fkey'
+ ) THEN
+ ALTER TABLE processor_results ADD CONSTRAINT processor_results_video_id_fkey
+ FOREIGN KEY (video_id) REFERENCES videos(id) ON DELETE CASCADE;
+ END IF;
+END $$;
+
+-- 4.1.7: Update monitor_jobs table - ensure processors column is correct type
+ALTER TABLE monitor_jobs ALTER COLUMN processors TYPE VARCHAR(20)[] USING processors::VARCHAR(20)[];
+ALTER TABLE monitor_jobs ALTER COLUMN completed_processors TYPE VARCHAR(20)[] USING completed_processors::VARCHAR(20)[];
+ALTER TABLE monitor_jobs ALTER COLUMN failed_processors TYPE VARCHAR(20)[] USING failed_processors::VARCHAR(20)[];
+
+-- 4.1.8: Add default values for arrays
+ALTER TABLE monitor_jobs ALTER COLUMN processors SET DEFAULT '{"asr","cut","yolo","ocr","face","pose","asrx"}';
+ALTER TABLE monitor_jobs ALTER COLUMN completed_processors SET DEFAULT '{}';
+ALTER TABLE monitor_jobs ALTER COLUMN failed_processors SET DEFAULT '{}';
+
+-- 4.1.9: Update existing rows to have default processor array
+UPDATE monitor_jobs SET processors = '{"asr","cut","yolo","ocr","face","pose","asrx"}' WHERE processors IS NULL;
+
+-- 4.1.10: Add index on monitor_jobs.processors for faster array operations
+CREATE INDEX IF NOT EXISTS idx_monitor_jobs_processors ON monitor_jobs USING gin (processors);
+
+COMMENT ON COLUMN processor_results.output_data IS 'JSON output from processor execution';
+COMMENT ON COLUMN processor_results.duration_secs IS 'Computed duration in seconds (completed - started)';
\ No newline at end of file
diff --git a/migrations/005_change_duration_float.sql b/migrations/005_change_duration_float.sql
new file mode 100644
index 0000000..1d30e15
--- /dev/null
+++ b/migrations/005_change_duration_float.sql
@@ -0,0 +1,22 @@
+-- ================================================================
+-- Migration 005: Change duration_secs to FLOAT8
+-- Version: 005
+-- Date: 2026-03-26
+-- Description: Change processor_results.duration_secs from INT to FLOAT8
+-- to match Rust f64 type and preserve fractional seconds.
+-- ================================================================
+
+-- 5.1.1: Drop the existing generated column
+ALTER TABLE processor_results DROP COLUMN IF EXISTS duration_secs;
+
+-- 5.1.2: Re-add as double precision (float8) computed column
+ALTER TABLE processor_results ADD COLUMN duration_secs DOUBLE PRECISION GENERATED ALWAYS AS (
+ CASE
+ WHEN completed_at IS NOT NULL AND started_at IS NOT NULL
+ THEN EXTRACT(EPOCH FROM (completed_at - started_at))
+ ELSE NULL
+ END
+) STORED;
+
+-- 5.1.3: Update comment
+COMMENT ON COLUMN processor_results.duration_secs IS 'Computed duration in seconds (completed - started) as double precision';
\ No newline at end of file
diff --git a/momentry_runtime/plist/com.momentry.api.plist b/momentry_runtime/plist/com.momentry.api.plist
index 9b5e003..78a0b78 100644
--- a/momentry_runtime/plist/com.momentry.api.plist
+++ b/momentry_runtime/plist/com.momentry.api.plist
@@ -30,6 +30,12 @@
DATABASE_URL
postgres://accusys@localhost:5432/momentry
+ DB_MAX_CONNECTIONS
+ 50
+
+ DB_ACQUIRE_TIMEOUT
+ 30
+
REDIS_URL
redis://:accusys@localhost:6379
@@ -40,7 +46,7 @@
http://localhost:11434
QDRANT_URL
- http://localhost:6333
+ http://127.0.0.1:6333
RunAtLoad
diff --git a/momentry_runtime/plist/com.momentry.worker.plist b/momentry_runtime/plist/com.momentry.worker.plist.bak
similarity index 88%
rename from momentry_runtime/plist/com.momentry.worker.plist
rename to momentry_runtime/plist/com.momentry.worker.plist.bak
index a55520d..3ccb38b 100644
--- a/momentry_runtime/plist/com.momentry.worker.plist
+++ b/momentry_runtime/plist/com.momentry.worker.plist.bak
@@ -30,6 +30,12 @@
DATABASE_URL
postgres://accusys@localhost:5432/momentry
+ DB_MAX_CONNECTIONS
+ 50
+
+ DB_ACQUIRE_TIMEOUT
+ 30
+
REDIS_URL
redis://:accusys@localhost:6379
@@ -40,7 +46,7 @@
http://localhost:11434
QDRANT_URL
- http://localhost:6333
+ http://127.0.0.1:6333
RunAtLoad
diff --git a/scripts/__pycache__/redis_publisher.cpython-311.pyc b/scripts/__pycache__/redis_publisher.cpython-311.pyc
index 61e667d..c7666d6 100644
Binary files a/scripts/__pycache__/redis_publisher.cpython-311.pyc and b/scripts/__pycache__/redis_publisher.cpython-311.pyc differ
diff --git a/scripts/asr_processor.py b/scripts/asr_processor.py
old mode 100644
new mode 100755
index 9ece2cb..46fb532
--- a/scripts/asr_processor.py
+++ b/scripts/asr_processor.py
@@ -3,17 +3,65 @@ import sys
import json
import os
import argparse
+import signal
+import subprocess
from faster_whisper import WhisperModel
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from redis_publisher import RedisPublisher
+def signal_handler(signum, frame):
+ print(f"ASR: Received signal {signum}, exiting...")
+ sys.exit(1)
+
+
+def has_audio_stream(video_path):
+ """Check if video file has audio stream using ffprobe."""
+ try:
+ cmd = [
+ "ffprobe",
+ "-v",
+ "error",
+ "-select_streams",
+ "a",
+ "-show_entries",
+ "stream=codec_type",
+ "-of",
+ "csv=p=0",
+ video_path,
+ ]
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
+ return bool(result.stdout.strip())
+ except subprocess.CalledProcessError:
+ return False
+ except FileNotFoundError:
+ print("WARNING: ffprobe not found, assuming audio exists")
+ return True
+
+
def run_asr(video_path, output_path, uuid: str = ""):
+ # Set up signal handlers
+ signal.signal(signal.SIGTERM, signal_handler)
+ signal.signal(signal.SIGINT, signal_handler)
+
publisher = RedisPublisher(uuid) if uuid else None
if publisher:
publisher.info("asr", "ASR_START")
+ # Check for audio stream
+ if not has_audio_stream(video_path):
+ if publisher:
+ publisher.info("asr", "No audio stream detected, skipping transcription")
+ output = {"language": "", "language_probability": 0.0, "segments": []}
+ with open(output_path, "w") as f:
+ json.dump(output, f, indent=2)
+ if publisher:
+ publisher.complete("asr", "0 segments (no audio)")
+ sys.stderr.write("ASR: No audio stream, skipping transcription\n")
+ sys.stderr.flush()
+ sys.exit(0)
+
if publisher:
publisher.info("asr", "Loading Whisper model...")
@@ -53,6 +101,12 @@ def run_asr(video_path, output_path, uuid: str = ""):
if publisher:
publisher.complete("asr", f"{len(results)} segments")
+ sys.stderr.write(
+ f"ASR: Transcription complete, {len(results)} segments written to {output_path}\n"
+ )
+ sys.stderr.flush()
+ sys.exit(0)
+
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="ASR Transcription")
diff --git a/scripts/caption_processor.py b/scripts/caption_processor.py
old mode 100644
new mode 100755
diff --git a/scripts/ocr_processor.py b/scripts/ocr_processor.py
index 9737873..9a58c65 100755
--- a/scripts/ocr_processor.py
+++ b/scripts/ocr_processor.py
@@ -8,14 +8,24 @@ import sys
import json
import argparse
import os
+import signal
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from redis_publisher import RedisPublisher
+def signal_handler(signum, frame):
+ print(f"OCR: Received signal {signum}, exiting...")
+ sys.exit(1)
+
+
def process_ocr(video_path: str, output_path: str, uuid: str = ""):
"""Process video for OCR using EasyOCR"""
+ # Set up signal handlers
+ signal.signal(signal.SIGTERM, signal_handler)
+ signal.signal(signal.SIGINT, signal_handler)
+
publisher = RedisPublisher(uuid) if uuid else None
if publisher:
publisher.info("ocr", "OCR_START")
diff --git a/scripts/pose_processor.py b/scripts/pose_processor.py
index bd3d0de..6906f38 100755
--- a/scripts/pose_processor.py
+++ b/scripts/pose_processor.py
@@ -8,14 +8,24 @@ import sys
import json
import argparse
import os
+import signal
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from redis_publisher import RedisPublisher
+def signal_handler(signum, frame):
+ print(f"POSE: Received signal {signum}, exiting...")
+ sys.exit(1)
+
+
def process_pose(video_path: str, output_path: str, uuid: str = ""):
"""Process video for pose estimation using YOLOv8 Pose"""
+ # Set up signal handlers
+ signal.signal(signal.SIGTERM, signal_handler)
+ signal.signal(signal.SIGINT, signal_handler)
+
publisher = RedisPublisher(uuid) if uuid else None
if publisher:
publisher.info("pose", "POSE_START")
diff --git a/scripts/story_processor.py b/scripts/story_processor.py
old mode 100644
new mode 100755
diff --git a/src/api/server.rs b/src/api/server.rs
index 2c380ad..789320c 100644
--- a/src/api/server.rs
+++ b/src/api/server.rs
@@ -791,8 +791,8 @@ async fn search(
uuid: chunk.uuid.clone(),
chunk_id: chunk.chunk_id.clone(),
chunk_type: chunk.chunk_type.as_str().to_string(),
- start_time: chunk.start_time,
- end_time: chunk.end_time,
+ start_time: chunk.start_time().seconds(),
+ end_time: chunk.end_time().seconds(),
text,
score: r.score,
});
@@ -868,8 +868,8 @@ async fn n8n_search(
hits.push(N8nSearchHit {
id: chunk.chunk_id.clone(),
vid: chunk.uuid.clone(),
- start: chunk.start_time,
- end: chunk.end_time,
+ start: chunk.start_time().seconds(),
+ end: chunk.end_time().seconds(),
title: if title.is_empty() {
format!("Chunk {}", chunk.chunk_id)
} else {
diff --git a/src/bin/fix_chunks.rs b/src/bin/fix_chunks.rs
new file mode 100644
index 0000000..ba1a852
--- /dev/null
+++ b/src/bin/fix_chunks.rs
@@ -0,0 +1,82 @@
+use anyhow::Result;
+use momentry_core::core::config;
+use momentry_core::core::db::PostgresDb;
+use momentry_core::core::processor::asrx::AsrxResult;
+use momentry_core::core::processor::face::FaceResult;
+use momentry_core::core::processor::ocr::OcrResult;
+use momentry_core::core::processor::pose::PoseResult;
+use momentry_core::core::processor::yolo::{YoloPythonResult, YoloResult};
+use momentry_core::worker::processor::ProcessorPool;
+use serde_json;
+use std::fs;
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ // Initialize tracing
+ tracing_subscriber::fmt::init();
+
+ // Database connection
+ let db_url = config::DATABASE_URL.clone();
+ let db = PostgresDb::new(&db_url).await?;
+
+ let uuid = "9760d0820f0cf9a7";
+
+ // Load OCR result
+ let ocr_json =
+ fs::read_to_string("/Users/accusys/momentry/output/job_2_ocr_1774475908877.json")?;
+ let ocr_result: OcrResult = serde_json::from_str(&ocr_json)?;
+ println!("Loaded OCR result with {} frames", ocr_result.frames.len());
+
+ // Load FACE result
+ let face_json =
+ fs::read_to_string("/Users/accusys/momentry/output/job_2_face_1774475908878.json")?;
+ let face_result: FaceResult = serde_json::from_str(&face_json)?;
+ println!(
+ "Loaded FACE result with {} frames",
+ face_result.frames.len()
+ );
+
+ // Load POSE result
+ let pose_json =
+ fs::read_to_string("/Users/accusys/momentry/output/job_2_pose_1774475908880.json")?;
+ let pose_result: PoseResult = serde_json::from_str(&pose_json)?;
+ println!(
+ "Loaded POSE result with {} frames",
+ pose_result.frames.len()
+ );
+
+ // Load ASRX result
+ let asrx_json =
+ fs::read_to_string("/Users/accusys/momentry/output/job_2_asrx_1774475908887.json")?;
+ let asrx_result: AsrxResult = serde_json::from_str(&asrx_json)?;
+ println!(
+ "Loaded ASRX result with {} segments",
+ asrx_result.segments.len()
+ );
+
+ // Load YOLO result
+ let yolo_json =
+ fs::read_to_string("/Users/accusys/momentry/output/job_2_yolo_1774475908875.json")?;
+ let python_result: YoloPythonResult = serde_json::from_str(&yolo_json)?;
+ let yolo_result = python_result.to_yolo_result();
+ println!(
+ "Loaded YOLO result with {} frames",
+ yolo_result.frames.len()
+ );
+
+ // Store chunks using ProcessorPool's static methods
+ println!("Storing OCR chunks...");
+ ProcessorPool::store_ocr_chunks(&db, uuid, &ocr_result).await?;
+ println!("Storing FACE chunks...");
+ ProcessorPool::store_face_chunks(&db, uuid, &face_result).await?;
+ println!("Storing POSE chunks...");
+ ProcessorPool::store_pose_chunks(&db, uuid, &pose_result).await?;
+ println!("Storing ASRX chunks...");
+ ProcessorPool::store_asrx_chunks(&db, uuid, &asrx_result).await?;
+ println!("Storing YOLO chunks...");
+ ProcessorPool::store_yolo_chunks(&db, uuid, &yolo_result).await?;
+
+ println!("All trace chunks stored successfully!");
+
+ Ok(())
+}
diff --git a/src/core/chunk/splitter.rs b/src/core/chunk/splitter.rs
index 33a0061..196d8e1 100644
--- a/src/core/chunk/splitter.rs
+++ b/src/core/chunk/splitter.rs
@@ -20,7 +20,7 @@ impl ChunkSplitter {
while current_time < duration {
let end_time = (current_time + self.time_based_duration).min(duration);
- chunks.push(Chunk::new(
+ chunks.push(Chunk::from_seconds(
0, // file_id
uuid.to_string(),
index,
@@ -45,7 +45,7 @@ impl ChunkSplitter {
let mut chunks = Vec::new();
for (index, segment) in asr_segments.iter().enumerate() {
- chunks.push(Chunk::new(
+ chunks.push(Chunk::from_seconds(
0, // file_id
uuid.to_string(),
index as u32,
diff --git a/src/core/chunk/types.rs b/src/core/chunk/types.rs
index 92d0ea8..024b559 100644
--- a/src/core/chunk/types.rs
+++ b/src/core/chunk/types.rs
@@ -1,3 +1,4 @@
+use crate::core::time::FrameTime;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
@@ -46,10 +47,11 @@ pub struct Chunk {
pub chunk_index: u32,
pub chunk_type: ChunkType,
pub rule: ChunkRule,
- pub start_time: f64,
- pub end_time: f64,
+ /// Frames per second (can be fractional, e.g., 29.97, 23.976)
pub fps: f64,
+ /// Start frame (0-based)
pub start_frame: i64,
+ /// End frame (exclusive)
pub end_frame: i64,
pub text_content: Option,
pub content: serde_json::Value,
@@ -62,6 +64,13 @@ pub struct Chunk {
}
impl Chunk {
+ /// Creates a new chunk from frame counts.
+ ///
+ /// # Arguments
+ ///
+ /// * `start_frame` - Start frame (0-based)
+ /// * `end_frame` - End frame (exclusive)
+ /// * `fps` - Frames per second (can be fractional)
#[allow(clippy::too_many_arguments)]
pub fn new(
file_id: i32,
@@ -69,13 +78,11 @@ impl Chunk {
chunk_index: u32,
chunk_type: ChunkType,
rule: ChunkRule,
- start_time: f64,
- end_time: f64,
+ start_frame: i64,
+ end_frame: i64,
fps: f64,
content: serde_json::Value,
) -> Self {
- let start_frame = (start_time * fps) as i64;
- let end_frame = (end_time * fps) as i64;
let chunk_id = format!("{}_{:04}", chunk_type.as_str(), chunk_index);
Self {
file_id,
@@ -84,8 +91,6 @@ impl Chunk {
chunk_index,
chunk_type,
rule,
- start_time,
- end_time,
fps,
start_frame,
end_frame,
@@ -100,6 +105,95 @@ impl Chunk {
}
}
+ /// Creates a new chunk from seconds (legacy conversion).
+ ///
+ /// This is useful for migrating from older systems that store time as seconds.
+ /// The frame counts are calculated by rounding `seconds * fps`.
+ #[allow(clippy::too_many_arguments)]
+ pub fn from_seconds(
+ file_id: i32,
+ uuid: String,
+ chunk_index: u32,
+ chunk_type: ChunkType,
+ rule: ChunkRule,
+ start_time: f64,
+ end_time: f64,
+ fps: f64,
+ content: serde_json::Value,
+ ) -> Self {
+ let start_frame = (start_time * fps).round() as i64;
+ let end_frame = (end_time * fps).round() as i64;
+ Self::new(
+ file_id,
+ uuid,
+ chunk_index,
+ chunk_type,
+ rule,
+ start_frame,
+ end_frame,
+ fps,
+ content,
+ )
+ }
+
+ /// Returns the start time as a `FrameTime`.
+ pub fn start_time(&self) -> FrameTime {
+ FrameTime::from_frames(self.start_frame, self.fps)
+ }
+
+ /// Returns the end time as a `FrameTime`.
+ pub fn end_time(&self) -> FrameTime {
+ FrameTime::from_frames(self.end_frame, self.fps)
+ }
+
+ /// Returns the duration in frames.
+ pub fn duration_frames(&self) -> i64 {
+ self.end_frame - self.start_frame
+ }
+
+ /// Returns the duration in seconds.
+ pub fn duration_seconds(&self) -> f64 {
+ self.duration_frames() as f64 / self.fps
+ }
+
+ /// Formats the start time as "seconds.frame" (e.g., "123.04").
+ pub fn format_start_sec_frame(&self) -> String {
+ self.start_time().format_sec_frame()
+ }
+
+ /// Formats the end time as "seconds.frame" (e.g., "456.15").
+ pub fn format_end_sec_frame(&self) -> String {
+ self.end_time().format_sec_frame()
+ }
+
+ /// Formats the start time as "HH:MM:SS".
+ pub fn format_start_hms(&self) -> String {
+ self.start_time().format_hms()
+ }
+
+ /// Formats the end time as "HH:MM:SS".
+ pub fn format_end_hms(&self) -> String {
+ self.end_time().format_hms()
+ }
+
+ /// Formats the start time as "HH:MM:SS.FF".
+ pub fn format_start_hms_frame(&self) -> String {
+ self.start_time().format_hms_frame()
+ }
+
+ /// Formats the end time as "HH:MM:SS.FF".
+ pub fn format_end_hms_frame(&self) -> String {
+ self.end_time().format_hms_frame()
+ }
+
+ /// Returns a tuple of (start_seconds, end_seconds) for compatibility.
+ ///
+ /// This is provided for backward compatibility during migration.
+ /// Prefer using `start_time()` and `end_time()` methods.
+ pub fn time_range_seconds(&self) -> (f64, f64) {
+ (self.start_time().seconds(), self.end_time().seconds())
+ }
+
pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
self.metadata = Some(metadata);
self
diff --git a/src/core/db/mongodb_db.rs b/src/core/db/mongodb_db.rs
index 0c176f2..a087d3b 100644
--- a/src/core/db/mongodb_db.rs
+++ b/src/core/db/mongodb_db.rs
@@ -28,13 +28,15 @@ pub struct ChunkDocument {
impl From for ChunkDocument {
fn from(chunk: Chunk) -> Self {
+ let start_time = chunk.start_time().seconds();
+ let end_time = chunk.end_time().seconds();
Self {
uuid: chunk.uuid,
chunk_id: chunk.chunk_id,
chunk_index: chunk.chunk_index,
chunk_type: chunk.chunk_type.as_str().to_string(),
- start_time: chunk.start_time,
- end_time: chunk.end_time,
+ start_time,
+ end_time,
fps: chunk.fps,
start_frame: chunk.start_frame,
end_frame: chunk.end_frame,
@@ -118,8 +120,6 @@ impl MongoDb {
chunk_index: doc.chunk_index,
chunk_type,
rule: ChunkRule::Rule1,
- start_time: doc.start_time,
- end_time: doc.end_time,
fps: doc.fps,
start_frame: doc.start_frame,
end_frame: doc.end_frame,
@@ -178,8 +178,6 @@ impl MongoDb {
chunk_index: doc.chunk_index,
chunk_type,
rule: ChunkRule::Rule1,
- start_time: doc.start_time,
- end_time: doc.end_time,
fps: doc.fps,
start_frame: doc.start_frame,
end_frame: doc.end_frame,
@@ -235,8 +233,6 @@ impl MongoDb {
chunk_index: doc.chunk_index,
chunk_type,
rule: ChunkRule::Rule1,
- start_time: doc.start_time,
- end_time: doc.end_time,
fps: doc.fps,
start_frame: doc.start_frame,
end_frame: doc.end_frame,
diff --git a/src/core/db/postgres_db.rs b/src/core/db/postgres_db.rs
index 6a736f2..137f7c3 100644
--- a/src/core/db/postgres_db.rs
+++ b/src/core/db/postgres_db.rs
@@ -126,8 +126,6 @@ pub struct PreChunk {
pub source_type: String,
pub source_file: Option,
pub chunk_type: String,
- pub start_time: f64,
- pub end_time: f64,
pub start_frame: i64,
pub end_frame: i64,
pub fps: f64,
@@ -209,7 +207,7 @@ pub struct MonitorJobStats {
pub failed: i32,
}
-#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
+#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum ProcessorType {
Asr,
@@ -449,6 +447,12 @@ impl PostgresDb {
.parse::()
.unwrap_or(60);
+ tracing::info!(
+ "DB pool config: max_connections={}, acquire_timeout={}s",
+ max_connections,
+ acquire_timeout_secs
+ );
+
let pool_options = PgPoolOptions::new()
.max_connections(max_connections)
.acquire_timeout(std::time::Duration::from_secs(acquire_timeout_secs));
@@ -1770,8 +1774,8 @@ impl PostgresDb {
.bind(&chunk.chunk_id)
.bind(chunk.chunk_index as i32)
.bind(chunk.chunk_type.as_str())
- .bind(chunk.start_time)
- .bind(chunk.end_time)
+ .bind(chunk.start_time().seconds())
+ .bind(chunk.end_time().seconds())
.bind(chunk.fps)
.bind(chunk.start_frame)
.bind(chunk.end_frame)
@@ -1791,7 +1795,7 @@ impl PostgresDb {
pub async fn get_chunks_by_uuid(&self, uuid: &str) -> Result> {
let rows = sqlx::query(
- "SELECT COALESCE(file_id, 0) as file_id, uuid, chunk_id, chunk_index, chunk_type, start_time, end_time, COALESCE(fps, 24.0) as fps, COALESCE(start_frame, 0) as start_frame, COALESCE(end_frame, 0) as end_frame, text_content, content, metadata, vector_id, COALESCE(frame_count, 0) as frame_count, pre_chunk_ids, parent_chunk_id, child_chunk_ids FROM chunks WHERE uuid = $1 ORDER BY chunk_index"
+ "SELECT COALESCE(file_id, 0) as file_id, uuid, chunk_id, chunk_index, chunk_type, COALESCE(fps, 24.0) as fps, COALESCE(start_frame, 0) as start_frame, COALESCE(end_frame, 0) as end_frame, text_content, content, metadata, vector_id, COALESCE(frame_count, 0) as frame_count, pre_chunk_ids, parent_chunk_id, child_chunk_ids FROM chunks WHERE uuid = $1 ORDER BY chunk_index"
)
.bind(uuid)
.fetch_all(&self.pool)
@@ -1811,12 +1815,12 @@ impl PostgresDb {
_ => ChunkType::TimeBased,
};
- let content: serde_json::Value = r.get(11);
- let metadata: Option = r.get(12);
+ let content: serde_json::Value = r.get(9);
+ let metadata: Option = r.get(10);
- let pre_chunk_ids: Vec = r.try_get(15).unwrap_or_default();
- let parent_chunk_id: Option = r.try_get(16).ok().flatten();
- let child_chunk_ids: Vec = r.try_get(17).unwrap_or_default();
+ let pre_chunk_ids: Vec = r.try_get(13).unwrap_or_default();
+ let parent_chunk_id: Option = r.try_get(14).ok().flatten();
+ let child_chunk_ids: Vec = r.try_get(15).unwrap_or_default();
let (rule, content_data) = if content.get("rule").is_some() {
let rule_str = content
@@ -1844,8 +1848,7 @@ impl PostgresDb {
chunk_index: chunk_index as u32,
chunk_type,
rule,
- start_time: r.get("start_time"),
- end_time: r.get("end_time"),
+
fps: r.get("fps"),
start_frame: r.get("start_frame"),
end_frame: r.get("end_frame"),
@@ -1866,7 +1869,7 @@ impl PostgresDb {
pub async fn get_chunk_by_chunk_id(&self, chunk_id: &str) -> Result