17 Commits

Author SHA1 Message Date
accusys 83ae05014f fix: save probe.json to OUTPUT_DIR instead of current directory
- Changed FileManager base directory from '.' to OUTPUT_DIR
- Fixed both register and probe endpoints
- probe.json is now saved to /Users/accusys/momentry/output/
2026-03-25 12:44:24 +08:00
accusys 171c36acce feat: add v2 backup versioning system
- Add BACKUP_VERSIONING.md with comprehensive backup management
- Update backup_all.sh with v2 version marking
  - v2 naming format: {service}_{type}_v2_{date}_{time}.{ext}
  - Fixed MongoDB backup path to /opt/homebrew/var/mongodb
  - Added momentry_output backup for probe.json files
  - Added restore functions for v2 format
- Update backup_monitor.sh for v2 filename parsing
- Updated VIDEO_REGISTRATION.md with Probe API documentation
- Updated JOB_WORKER_IMPLEMENTATION_PLAN.md status to implemented
- Updated MOMENTRY_CORE_MONITORING.md with Job Worker monitoring
- Updated SERVICES.md with Momentry Playground and Job Worker info
2026-03-25 09:35:34 +08:00
accusys 02c63892b7 docs: add Momentry Playground and Job Worker to SERVICES.md
- Add Momentry Playground binary entry (port 3003, dev environment)
- Add Job Worker architecture and monitoring section
- Add Momentry API vs Playground comparison table
- Add Job Worker environment variables documentation
- Add SQL monitoring commands for jobs and processors
2026-03-25 03:38:08 +08:00
accusys 93a9f8c349 docs: update for new architecture (Probe API, Job Worker)
- VIDEO_REGISTRATION.md: Add Probe API reference and comparison table
- JOB_WORKER_IMPLEMENTATION_PLAN.md: Update status to implemented
- MOMENTRY_CORE_MONITORING.md: Add Job Worker monitoring section
  - monitor_jobs and processor_results table docs
  - Worker status check commands
  - Redis job monitoring commands
2026-03-25 03:35:36 +08:00
accusys 89fbfd6ad9 feat: add POST /api/v1/probe endpoint
- Add ProbeRequest/ProbeResponse structures
- Support relative and absolute paths
- Cache probe.json for repeated requests
- Return video metadata (uuid, duration, width, height, fps)
- Include cached flag to indicate cache hit
- Export FormatInfo and StreamInfo from probe module
- Update API_ENDPOINTS.md documentation
2026-03-25 03:26:40 +08:00
accusys 3edaf01f6c fix: update MonitorJob tests for NaiveDateTime type 2026-03-25 03:07:32 +08:00
accusys 786381ac67 feat: add job worker and duplicate registration check
Job Worker System:
- Add polling-based job worker (max 2 concurrent processors)
- Create monitor_jobs records when videos are registered
- Link videos.job_id to monitor_jobs
- Fix type mismatches (i32 vs i64) for database IDs

Duplicate Registration:
- Check if video already exists before registering
- Return existing video info with already_exists: true
- Use canonical path for UUID computation

USER_DATA_ROOT Configuration:
- Add MOMENTRY_USER_DATA_ROOT environment variable
- UUID computed from relative path (username/filename)
- Ensures consistent UUIDs when data root changes
2026-03-25 02:50:31 +08:00
accusys 2d787b7806 chore: configure Qdrant API key and update Redis key documentation
- Add QDRANT_API_KEY to .env and .env.development
- Update docs to reflect configurable Redis key prefix
- Add Qdrant configuration section to monitoring docs
2026-03-25 01:28:31 +08:00
accusys fe24906791 docs: add configurable Redis key prefix to Redis Keys and Monitoring docs
- Add new section 2 in MOMENTRY_CORE_REDIS_KEYS.md explaining configurable prefix
- Update environment variables to include MOMENTRY_REDIS_PREFIX
- Update Rust implementation examples to use REDIS_KEY_PREFIX static
- Add warning note in MOMENTRY_CORE_MONITORING.md about configurable prefix
- Update shell scripts to use REDIS_PREFIX environment variable
- Document default prefixes: momentry: (production), momentry_dev: (development)
2026-03-25 01:11:10 +08:00
accusys 1d1c60366e feat: add momentry_playground binary for development
- Add separate momentry_playground binary with distinct configuration
- Production (momentry): Port 3002, Redis prefix 'momentry:'
- Development (momentry_playground): Port 3003, Redis prefix 'momentry_dev:'
- Add SERVER_PORT and REDIS_KEY_PREFIX config via environment variables
- Replace all hardcoded Redis key prefixes with configurable values
- Create .env.development for playground environment settings
- Update .env with production defaults
- Add dotenv dependency for environment file loading

Configuration isolation allows running both binaries simultaneously
without port conflicts or Redis key collisions.
2026-03-25 00:40:31 +08:00
accusys bfc4317b88 feat: Add HTTP API for progress monitoring
- Add /api/v1/progress/:uuid endpoint for real-time progress查询
- Implement Redis Hash storage for progress persistence
- Increase DB connection pool (5->10)
- Add get_processor_status method to RedisClient
- Update DEVELOPMENT_LOG with HTTP API implementation

Test: curl http://127.0.0.1:3002/api/v1/progress/<uuid>
2026-03-18 02:14:49 +08:00
accusys 26f73ab620 docs: Add video chunk specification
- Define three chunk types: Sentence, Cut, TimeBased
- Support overlapping chunks
- Frame-accurate timestamps
- Include content and metadata structures
- Add PostgreSQL schema
- Document Rust data structures and splitter implementation
2026-03-16 15:59:15 +08:00
accusys 47b9c2f750 docs: Add JSON output specification
- Define all JSON output file types and naming conventions
- Document Probe, ASR, ASRx, OCR, YOLO, Face, Pose structures
- Include field descriptions and data types
- Add processing pipeline flow
- Document database storage schema
2026-03-16 15:48:51 +08:00
accusys e66b9501bd docs: Add version quick reference sections
- Add version summary table to SERVICES.md
- Add version quick reference to PYTHON.md
- Add version quick reference to NODEJS.md
- Update related documents references
2026-03-16 15:43:41 +08:00
accusys 9bbfaa1d06 docs: Add Python and Node.js integration specification
- Architecture overview for mixed runtime environments
- Python version management with venv
- Node.js version locking for n8n
- Isolation principles for coexisting environments
- Workflow integration patterns
- Monitoring configuration
- Troubleshooting guide
- New service decision tree
2026-03-16 15:22:39 +08:00
accusys ea2bbb9fd9 docs: Add Rust development specification
- Project structure and module design
- Code style and naming conventions
- Error handling patterns
- Async programming guidelines
- External process integration (Python scripts)
- Testing strategy
- Logging with tracing
- Performance optimization
- Security considerations
- CLI command design
- Version control guidelines
2026-03-16 15:17:31 +08:00
accusys de14bd6afa Initial commit: Momentry Core v0.1
- Rust-based digital asset management system
- Video analysis: ASR, OCR, YOLO, Face, Pose
- RAG capabilities with Qdrant vector database
- Multi-database support: PostgreSQL, Redis, MongoDB
- Monitoring system with launchd plists
- n8n workflow automation integration
2026-03-16 15:07:33 +08:00
1675 changed files with 7884 additions and 534095 deletions
+40
View File
@@ -0,0 +1,40 @@
# 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
+15 -36
View File
@@ -8,37 +8,35 @@
MOMENTRY_SERVER_PORT=3003 MOMENTRY_SERVER_PORT=3003
MOMENTRY_REDIS_PREFIX=momentry_dev: MOMENTRY_REDIS_PREFIX=momentry_dev:
# Worker Configuration (enabled for development) # Worker Configuration (disabled by default for development)
MOMENTRY_WORKER_ENABLED=true MOMENTRY_WORKER_ENABLED=false
MOMENTRY_MAX_CONCURRENT=6 MOMENTRY_MAX_CONCURRENT=1
MOMENTRY_POLL_INTERVAL=10 MOMENTRY_POLL_INTERVAL=10
MOMENTRY_WORKER_BATCH_SIZE=5 MOMENTRY_WORKER_BATCH_SIZE=5
# Database (PostgreSQL) - Schema isolation # Database (same as production, but could use separate dev database)
DATABASE_URL=postgres://accusys@localhost:5432/momentry DATABASE_URL=postgres://accusys@localhost:5432/momentry
DATABASE_SCHEMA=dev
# MongoDB - Database isolation # MongoDB
MONGODB_URL=mongodb://localhost:27017 MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
MONGODB_DATABASE=momentry_dev MONGODB_DATABASE=momentry
# Redis (already isolated via prefix) # Redis
REDIS_URL=redis://127.0.0.1:6379 REDIS_URL=redis://:accusys@localhost:6379
# REDIS_PASSWORD not set - Redis has no password configured REDIS_PASSWORD=accusys
# Qdrant Vector Database - Collection isolation # Qdrant Vector Database (same as production)
QDRANT_URL=http://localhost:6333 QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=Test3200Test3200Test3200 QDRANT_API_KEY=Test3200Test3200Test3200
QDRANT_COLLECTION=momentry_dev_rule1_v2 QDRANT_COLLECTION=chunks_v3
# Paths # Paths
MOMENTRY_OUTPUT_DIR=/Users/accusys/momentry/output_dev MOMENTRY_OUTPUT_DIR=/Users/accusys/momentry/output_dev
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry_dev MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry_dev
MOMENTRY_SFTP_ROOT=/Users/accusys/momentry/var/sftpgo/data/demo/
# Python (for processing scripts) # Python (for processing scripts)
MOMENTRY_PYTHON_PATH=/Users/accusys/momentry_core/venv/bin/python MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11
MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core/scripts MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core_0.1/scripts
# Logging # Logging
RUST_LOG=debug RUST_LOG=debug
@@ -53,29 +51,10 @@ MOMENTRY_CUT_TIMEOUT=3600
MOMENTRY_DEFAULT_TIMEOUT=7200 MOMENTRY_DEFAULT_TIMEOUT=7200
# Cache Settings # Cache Settings
MONGODB_CACHE_ENABLED=false MONGODB_CACHE_ENABLED=true
MONGODB_CACHE_TTL_VIDEOS=300 MONGODB_CACHE_TTL_VIDEOS=300
MONGODB_CACHE_TTL_SEARCH=300 MONGODB_CACHE_TTL_SEARCH=300
MONGODB_CACHE_TTL_HYBRID_SEARCH=600 MONGODB_CACHE_TTL_HYBRID_SEARCH=600
MONGODB_CACHE_TTL_VIDEO_META=3600 MONGODB_CACHE_TTL_VIDEO_META=3600
REDIS_CACHE_TTL_HEALTH=30 REDIS_CACHE_TTL_HEALTH=30
REDIS_CACHE_TTL_VIDEO_META=3600 REDIS_CACHE_TTL_VIDEO_META=3600
# 同義詞配置文件(可選)
# 取消註釋並設置為您的同義詞JSON檔案路徑以啟用同義詞擴展
# MOMENTRY_SYNONYM_FILE=/Users/accusys/momentry_core_0.1/docs/examples/custom_synonyms.json
#
# 多個同義詞檔案(逗號分隔),會覆蓋 MOMENTRY_SYNONYM_FILE
# MOMENTRY_SYNONYM_FILES=/path/to/first.json,/path/to/second.json
#
# 示例檔案:docs/examples/custom_synonyms.json
# TMDb Integration (probe phase - auto-create identities from movie metadata)
TMDB_API_KEY=e9cde52197f6f8df4d9db99da93db1fb
MOMENTRY_TMDB_PROBE_ENABLED=true
# LLM for 5W1H summary (points to M5 Gemma4)
MOMENTRY_LLM_SUMMARY_URL=http://127.0.0.1:8082/v1/chat/completions
MOMENTRY_LLM_SUMMARY_MODEL=google_gemma-4-26B-A4B-it-Q5_K_M.gguf
MOMENTRY_LLM_SUMMARY_ENABLED=true
# Embedding (ANE CoreML server)
MOMENTRY_EMBED_URL=http://localhost:11436
+58 -41
View File
@@ -1,53 +1,70 @@
# Momentry Core Environment Configuration # Momentry Core Configuration Template
# Copy this file to .env and fill in your values # Copy this file to .env and customize for your environment
# DO NOT commit .env to version control # DO NOT commit .env with real credentials to version control
# === Database === # ===========================================
DATABASE_URL=postgres://accusys@localhost:5432/momentry # Database Configuration
DATABASE_SCHEMA=dev # ===========================================
DATABASE_URL=postgres://user:password@localhost:5432/momentry
# === MongoDB === # ===========================================
MONGODB_URL=mongodb://localhost:27017 # Redis Configuration
# ===========================================
REDIS_URL=redis://user:password@localhost:6379
REDIS_PASSWORD=your_redis_password
# ===========================================
# MongoDB Configuration
# ===========================================
MONGODB_URL=mongodb://user:password@localhost:27017/admin
MONGODB_DATABASE=momentry MONGODB_DATABASE=momentry
MONGODB_CACHE_ENABLED=true
# === Redis === # ===========================================
REDIS_URL=redis://:accusys@localhost:6379 # Qdrant Configuration
REDIS_PASSWORD=accusys # ===========================================
MOMENTRY_REDIS_PREFIX=momentry_dev: QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=your_qdrant_api_key
QDRANT_COLLECTION=chunks_v3
# === Qdrant === # ===========================================
QDRANT_COLLECTION=momentry_rule1 # API Server Configuration
# ===========================================
API_HOST=127.0.0.1
API_PORT=3000
# === API Keys === # ===========================================
MOMENTRY_API_KEY=muser_your_key_here # Directory Paths
MOMENTRY_DEMO_API_KEY=muser_your_demo_key_here # ===========================================
JWT_SECRET=your_jwt_secret_here_change_in_production MOMENTRY_OUTPUT_DIR=/path/to/output
SFTPGO_BASE_URL=http://127.0.0.1:8080 MOMENTRY_BACKUP_DIR=/path/to/backup
MOMENTRY_SCRIPTS_DIR=/path/to/momentry_core/scripts
TMDB_API_KEY=your_tmdb_api_key_here
# === LLM ===
MOMENTRY_LLM_SUMMARY_URL=http://127.0.0.1:8082/v1/chat/completions
MOMENTRY_LLM_SUMMARY_MODEL=google_gemma-4-26B-A4B-it-Q5_K_M.gguf
MOMENTRY_LLM_SUMMARY_TIMEOUT=120
# === Paths ===
MOMENTRY_OUTPUT_DIR=/Users/accusys/momentry/output_dev
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup
MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core_0.1/scripts
MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11 MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11
MOMENTRY_FFMPEG=/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg
MOMENTRY_MEDIA_BASE_URL=
# === Encryption === # ===========================================
AUDIT_ENCRYPTION_KEY= # 32 bytes hex (64 hex chars) # Processor Timeouts (seconds)
# ===========================================
# === Processor Timeouts (seconds) ===
MOMENTRY_ASR_TIMEOUT=3600 MOMENTRY_ASR_TIMEOUT=3600
MOMENTRY_CUT_TIMEOUT=3600 MOMENTRY_CUT_TIMEOUT=3600
MOMENTRY_DEFAULT_TIMEOUT=7200 MOMENTRY_DEFAULT_TIMEOUT=7200
# === Server === # ===========================================
MOMENTRY_SERVER_PORT=3003 # Watch Directories (comma separated)
MOMENTRY_LOG_LEVEL=info # ===========================================
WATCH_DIRECTORIES=~/Videos,~/Downloads
# ===========================================
# Logging
# ===========================================
RUST_LOG=info
# Options: trace, debug, info, warn, error
# ===========================================
# Ollama (for LLM integration)
# ===========================================
OLLAMA_HOST=http://localhost:11434
# ===========================================
# Model Paths
# ===========================================
# EMBEDDING_MODEL_PATH=./models/embedding
# LLM_MODEL_PATH=./models/llm
+36 -16
View File
@@ -1,20 +1,40 @@
target/ # Environment - Local configs (NEVER commit these)
.DS_Store
.env .env
.env.development .env.local
*.gguf .env.*.local
*.mlpackage
*.pt # Build artifacts
*.pth target/
*.bin
*.onnx
*.zip
*.tar.gz
venv/ venv/
__pycache__/
node_modules/ # Generated files
*.log thumbnails/
/tmp/ *.asr.json
*.probe.json
test_asr.json
# Local output (machine learning results)
output/
*.pt
# Cache
.ruff_cache/
# OS files
.DS_Store
.Spotlight-V100
.Trashes
# Logs
*.log *.log
scripts/swift_processors/.build/ # SSH keys (NEVER commit)
id_*
!id_*.pub
# IDE and editor
.vscode/
.idea/
*.swp
*.swo
*~
-21
View File
@@ -1,21 +0,0 @@
{
"default": true,
"MD003": false,
"MD009": false,
"MD010": false,
"MD013": false,
"MD022": false,
"MD024": false,
"MD025": false,
"MD031": false,
"MD032": false,
"MD033": false,
"MD034": false,
"MD036": false,
"MD040": false,
"MD046": false,
"MD055": false,
"MD056": false,
"MD058": false,
"MD060": false
}
-21
View File
@@ -1,21 +0,0 @@
{
"default": true,
"MD003": false,
"MD009": false,
"MD010": false,
"MD013": false,
"MD022": false,
"MD024": false,
"MD025": false,
"MD031": false,
"MD032": false,
"MD033": false,
"MD034": false,
"MD036": false,
"MD040": false,
"MD046": false,
"MD055": false,
"MD056": false,
"MD058": false,
"MD060": false
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE dev.videos SET processing_status = $1 WHERE uuid = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Text"
]
},
"nullable": []
},
"hash": "2d61eacd106ad5144c99a85c84f070924af9b29103a507e115674d1b14b77181"
}
@@ -1,14 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE dev.jobs SET status = 'COMPLETED', processed_frames = total_frames, updated_at = NOW() WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "345d912734b063a7b30d52c066045553964d0a55453a7e26a4d8b8d758be3857"
}
@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE dev.jobs SET status = 'FAILED', error_message = $2, updated_at = NOW() WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Text"
]
},
"nullable": []
},
"hash": "60cc008705cfea3a4532b9496db8f6ed0e3023436660bdf8ee81fe78fe270971"
}
+2 -409
View File
@@ -2,192 +2,12 @@
Rust-based digital asset management system with video analysis and RAG capabilities. Rust-based digital asset management system with video analysis and RAG capabilities.
---
## ⚠️ CRITICAL: 開發隔離原則
### 絕對禁止事項
- **絕對不可修改 `/Users/accusys/wordpress/` 目錄下的任何檔案**
- **絕對不可修改 n8n 工作流或設定**
- **絕對不可修改 WordPress 或 n8n 的資料庫 table**
- **除非是 release 作業,絕對不可動 port 3002 (production)**
- **🔴 DELETE / REMOVE / DROP / CLEAR 任何資料前必須先問使用者「要刪嗎?」獲得明確同意後才能執行**
- **🔴 Qdrant collection 刪除、DB truncate、檔案刪除、資料清空 — 一律要先問**
- **🔴 不確定是否該刪 → 先問,不要自己決定**
### 開發範圍界定
| 範圍 | 狀態 | 說明 |
|------|------|------|
| `momentry_core_0.1/` | ✅ **可開發** | Momentry Core 主要開發目錄 |
| `momentry_core_0.1/portal/` | ✅ **可開發** | Tauri Portal 前端 |
| `momentry_core_0.1/src/` | ✅ **可開發** | Rust 後端程式碼 |
| `/Users/accusys/wordpress/` | ❌ **禁止修改** | WordPress/Marcom 團隊負責 |
| n8n 工作流 | ❌ **禁止修改** | 自動化流程,與 dev 無關 |
| WordPress/n8n 資料庫 table | ❌ **禁止修改** | Marcom 團隊管理,與 dev 無關 |
### 開發環境
| 服務 | Port | 用途 | 命令 |
|------|------|------|------|
| Playground | 3003 | **唯一開發環境** | `cargo run --bin momentry_playground -- server` |
| Production | 3002 | ❌ 禁止修改 | `cargo run -- server` (僅 release 時) |
| Portal (Tauri) | 1420 | 前端開發 | `npm run tauri dev` |
### 日誌與啟動
| 服務 | 日誌路徑 | 啟動方式 |
|------|----------|----------|
| Production (3002) | `logs/momentry_3002.log` | `./run-server-3002.sh` |
| Playground (3003) | `logs/momentry_3003.log` | `./run-server-3003.sh` |
| Worker / 歷史 | `logs/nohup_worker_*.log` | 由 worker 自動產生 |
> **注意**: 所有伺服器日誌統一存放於專案內 `logs/` 目錄。
> 啟動腳本會自動 kill 舊程序、重 build(若需要)、並將日誌導向 `logs/`。
## ⚠️ 交叉污染防制 (Cross-Contamination Prevention)
**每個執行前必須評估是否會汙染其他獨立作業。**
### Scope Isolation Matrix
| 執行內容 | 允許的 Scope | 禁止影響 | 檢查事項 |
|----------|-------------|----------|----------|
| M4 delivery binary | `target/release/momentry` | Playground (3003), Production (3002) | 確認舊 process 未被誤殺 |
| Playground server | `localhost:3003`, `dev.*` schema | Production (3002), `public.*` schema | `DATABASE_SCHEMA=dev` |
| Production deploy | `localhost:3002`, `public.*` schema | Playground (3003), `dev.*` schema | 先停 production,不影響 playground |
| Git commit | 只包含意圖修改的檔案 | 無關的 untracked files | `git status` 確認 stage 內容正確 |
| CI / packaged tests | 測試環境 | 正式資料 | 測試用 DB 不能連到 production |
| Doc changes | 指定文件 | 其他文件、程式碼 | `git diff --stat` 檢查 scope |
| SQL migration | 目標 schema | 其他 schema、無關 table | `WHERE` clause 要精準 |
| `sed` / `grep` / mass edit | 目標檔案集 | 非目標檔案 | 先用 `grep -c` 確認只有目標檔案匹配 |
### Recent Violations / Near-Misses
| 事件 | 問題 | 防止方式 |
|------|------|----------|
| `sed` API doc 編號 | `sed -i '' 's/.../.../g'` 改到所有行 | 先 `grep -c` 確認匹配,`git diff` 再提交 |
| 亂加 `/api/v1/register` route | 不必要的 API 別名,汙染路由表 | 角色切換:路由設計不該由實作方決定 |
| `API_WORKSPACE/` vs `GUIDES/` vs `REFERENCE/` vs `DESIGN/` vs `OPERATIONS/` vs `INTEGRATIONS/` | 文件放到錯誤分類 | API 文件改在 API_WORKSPACE/modules/ 編輯,`make deploy` 生成到 GUIDES/ |
| Build release binary in plan mode | 浪費時間,無意義 | 嚴格遵守 plan/build mode 規定 |
### ⛔ 嚴格測試隔離規則 (Strict Test Isolation)
- **所有測試 (Test) 必須在 Dev (3003) 進行**。
- **絕對禁止 (ABSOLUTELY FORBIDDEN)** 在任何測試指令、Demo 流程或 API 檢查中使用 `localhost:3002`。
- 即使是「測試 Unregister」或「檢查版本」,若未明確標示為 "Production Deployment",一律視為違規。
- **預設行為**: 所有 curl, CLI, 或程式碼測試指令,預設 URL 必須為 `http://localhost:3003`。
### 違反後果
- 修改 WordPress/n8n 可能影響 marcom 團隊工作與生產環境
- 修改 WordPress/n8n 資料庫 table 可能破壞自動化流程與資料完整性
- 修改 port 3002 可能中斷正在使用的服務 (這是非常嚴重的錯誤)
- 所有 dev 測試必須在 playground (3003) 進行
---
## AI Coding Principles (Karpathy-Inspired)
Behavioral guidelines to reduce common LLM coding mistakes.
Source: [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills) (94K stars)
**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
### 1. Think Before Coding
**Don't assume. Don't hide confusion. Surface tradeoffs.**
- State your assumptions explicitly. If uncertain, ask.
- If multiple interpretations exist, present them - don't pick silently.
- If a simpler approach exists, say so. Push back when warranted.
- If something is unclear, stop. Name what's confusing. Ask.
### 2. Simplicity First
**Minimum code that solves the problem. Nothing speculative.**
- No features beyond what was asked.
- No abstractions for single-use code.
- No "flexibility" or "configurability" that wasn't requested.
- No error handling for impossible scenarios.
- If you write 200 lines and it could be 50, rewrite it.
Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
### 3. Surgical Changes
**Touch only what you must. Clean up only your own mess.**
When editing existing code:
- Don't "improve" adjacent code, comments, or formatting.
- Don't refactor things that aren't broken.
- Match existing style, even if you'd do it differently.
- If you notice unrelated dead code, mention it - don't delete it.
When your changes create orphans:
- Remove imports/variables/functions that YOUR changes made unused.
- Don't remove pre-existing dead code unless asked.
The test: Every changed line should trace directly to the user's request.
### 4. Goal-Driven Execution
**Define success criteria. Loop until verified.**
Transform tasks into verifiable goals:
- "Add validation" -> "Write tests for invalid inputs, then make them pass"
- "Fix the bug" -> "Write a test that reproduces it, then make it pass"
- "Refactor X" -> "Ensure tests pass before and after"
For multi-step tasks, state a brief plan:
```
1. [Step] -> verify: [check]
2. [Step] -> verify: [check]
3. [Step] -> verify: [check]
```
Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
---
These guidelines are working if: fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.
---
## Terminology (V4.0)
| Term | Scope | Description | Example |
|------|-------|-------------|---------|
| **file_uuid** | Video file | Video file identifier (renamed from `video_uuid`) | `384b0ff44aaaa1f1` |
| **identity_uuid** | Global identity | Global person identity (cross-file) | `a9a90105-6d6b-46ff-92da-0c3c1a57dff4` |
| **face_id** | Single detection | Single face detection (frame-level) | `face_100` |
| **trace_id** | Face tracking | Face tracking ID (Face Tracker output) | `2` |
| **chunk_id** | Sentence chunk | Sentence chunk (from pre_chunks via rules) | `chunk_1` |
| **speaker_id** | Speaker segment | Speaker ID (from ASRX) | `SPEAKER_0` |
| **person_id** | ❌ **Deprecated** | Video-local person ID (removed in V4.0) | - |
### Architecture (V4.0)
```
Face → Identity (Two-layer, direct binding)
↓
person_identities table: REMOVED
file_identities table: ADDED (N:N relationship)
```
### Key Changes (V3.x → V4.0)
| Change | V3.x | V4.0 |
|--------|------|------|
| **video_uuid** | Used everywhere | **file_uuid** |
| **person_identities** | Required (303 records) | **Removed** |
| **person_id APIs** | 28 endpoints | **Removed** (except register/bind) |
| **Face binding** | Person → Identity | **Face → Identity** (direct) |
| **Chunk binding** | Manual | **Auto** (time alignment) |
---
## Build & Run Commands ## Build & Run Commands
```bash ```bash
# Build project (use debug builds for development/testing) # Build project
cargo build cargo build
cargo build --release
cargo build --bin momentry cargo build --bin momentry
cargo build --bin momentry_playground cargo build --bin momentry_playground
@@ -202,29 +22,8 @@ cargo run -- server --host 0.0.0.0 --port 3002
# Run playground (development binary) # Run playground (development binary)
cargo run --bin momentry_playground -- server cargo run --bin momentry_playground -- server
cargo run --bin momentry_playground -- --help cargo run --bin momentry_playground -- --help
# Start servers (recommended — auto-build & logs to logs/)
./run-server-3002.sh
./run-server-3003.sh
``` ```
### Server Logs
All runtime logs are centralized in `logs/`:
```bash
# View real-time logs
tail -f logs/momentry_3002.log
tail -f logs/momentry_3003.log
# Check recent errors
grep -i "error\|panic\|FAIL" logs/momentry_*.log | tail -20
```
### ⚠️ CRITICAL: `cargo build --release` PROHIBITION
- **NEVER run `cargo build --release` unless the user explicitly says "release the binary" or "正式 release"**
- `cargo build --release` is SLOW and only needed when producing a production binary for deployment
- For all development, testing, debugging, and linting: use `cargo build` or `cargo check`
- If uncertain, ALWAYS ask the user first
## Binaries ## Binaries
| Binary | Purpose | Port | Redis Prefix | Environment | | Binary | Purpose | Port | Redis Prefix | Environment |
@@ -383,15 +182,6 @@ src/
### Server ### Server
- `MOMENTRY_SERVER_PORT` - API server port (default: `3002` for production, `3003` for playground) - `MOMENTRY_SERVER_PORT` - API server port (default: `3002` for production, `3003` for playground)
- `MOMENTRY_REDIS_PREFIX` - Redis key prefix (default: `momentry:` for production, `momentry_dev:` for playground) - `MOMENTRY_REDIS_PREFIX` - Redis key prefix (default: `momentry:` for production, `momentry_dev:` for playground)
- `MOMENTRY_API_KEY` - API key for Player online mode testing
### Testing API Key
```bash
export MOMENTRY_API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
# Test Player online mode
cargo run --features player --bin momentry_player -- -o
```
### Database ### Database
- `DATABASE_URL` - PostgreSQL (default: `postgres://accusys@localhost:5432/momentry`) - `DATABASE_URL` - PostgreSQL (default: `postgres://accusys@localhost:5432/momentry`)
@@ -411,16 +201,6 @@ cargo run --features player --bin momentry_player -- -o
- `MOMENTRY_CUT_TIMEOUT` - CUT timeout in seconds (default: 3600) - `MOMENTRY_CUT_TIMEOUT` - CUT timeout in seconds (default: 3600)
- `MOMENTRY_DEFAULT_TIMEOUT` - Default timeout (default: 7200) - `MOMENTRY_DEFAULT_TIMEOUT` - Default timeout (default: 7200)
### TMDb Integration (Face Clustering)
- `TMDB_API_KEY` - TMDb API key for movie metadata lookup (required for `MOMENTRY_TMDB_PROBE_ENABLED=true`)
- `MOMENTRY_TMDB_PROBE_ENABLED` - Enable TMDb probe during registration (default: `false`)
- Register phase: searches TMDb by filename, creates identities with tmdb_id/tmdb_profile
- Post-process phase: matches detected faces against TMDb identities via cosine similarity
### Synonym Expansion
- `MOMENTRY_SYNONYM_FILES` - Comma-separated paths to synonym JSON files (e.g., `data/english_synonyms.json,data/llm_synonyms.json`)
- `MOMENTRY_SYNONYM_FILE` - Single synonym JSON file path (deprecated, use above)
### Logging ### Logging
- `RUST_LOG` or `MOMENTRY_LOG_LEVEL` - Log level (default: `info`) - `RUST_LOG` or `MOMENTRY_LOG_LEVEL` - Log level (default: `info`)
@@ -432,24 +212,6 @@ cargo run --features player --bin momentry_player -- -o
- Monitor directory is a separate system (not Rust) - Monitor directory is a separate system (not Rust)
- PythonExecutor provides unified script execution with timeout support - PythonExecutor provides unified script execution with timeout support
- Redis 1.0.x for improved performance - Redis 1.0.x for improved performance
- FaceNet CoreML model (`models/facenet512.mlpackage`) replaces InsightFace for embedding extraction (MIT license, ANE-accelerated)
### LLM Synonym Generation
Generate synonym database using llama.cpp (Gemma4):
```bash
# Generate full database (162 entries, ~5 minutes)
python3 scripts/generate_synonyms_llamacpp.py
# Quick test
python3 scripts/generate_synonyms_llamacpp.py --test
# Resume from existing file
python3 scripts/generate_synonyms_llamacpp.py --resume
# Output: data/llm_synonyms.json (27 Chinese + 135 English words)
```
## Task Management ## Task Management
@@ -551,85 +313,6 @@ shellcheck scripts/*.sh monitor/**/*.sh
**注意**: Hook 只檢查 error 等級的 shellcheck 問題,style 警告會顯示但不阻擋提交。 **注意**: Hook 只檢查 error 等級的 shellcheck 問題,style 警告會顯示但不阻擋提交。
## Gitea Sync
主要 sync 管道為 Gitea:`http://192.168.110.200:3000/admin/momentry_core.git`
### 產生 Access Token(首次設定)
```bash
# admin 帳號密碼為 AccusysTest!
TOKEN=$(curl -s -X POST "http://192.168.110.200:3000/api/v1/users/admin/tokens" \
-u "admin:AccusysTest!" \
-H "Content-Type: application/json" \
-d '{"name":"m5max128_push","scopes":["write:repository"]}' | jq -r '.sha1')
echo $TOKEN
```
### 設定 Remote
```bash
# 用 token 取代密碼
git remote add origin http://admin:TOKEN@192.168.110.200:3000/admin/momentry_core.git
# 同步
git pull origin main
git push origin main
```
### Token 記錄
| 機器 | Token |
|------|-------|
| M5Max128 | `c33768c4cc26c0f4c575dcce832e92e5cf192773` (write:repository + write:user) |
**注意**: Token 有 write:repository scope,勿外洩。如需新增 token 給其他機器,各自產自己的 token。
## Release Workflow
### Release 前準備
每次 release production binary 前,必須:
1. **建立 Release Tag**
```bash
git tag -a v0.X.X -m "Release vX.X.X - YYYY-MM-DD"
git push origin v0.X.X
```
2. **備份獨立 Source Code**
```bash
# 建立 release 獨立目錄
RELEASE_DIR="/Users/accusys/momentry_core_releases/v0.X.X"
mkdir -p "$RELEASE_DIR"
# 複製完整原始碼(排除不必要的檔案)
rsync -av --exclude='.git' --exclude='target' --exclude='node_modules' \
/Users/accusys/momentry_core_0.1/ "$RELEASE_DIR/"
# 記錄 release 資訊
echo "Release: v0.X.X" > "$RELEASE_DIR/RELEASE_INFO.txt"
echo "Date: $(date)" >> "$RELEASE_DIR/RELEASE_INFO.txt"
echo "Git Commit: $(git rev-parse HEAD)" >> "$RELEASE_DIR/RELEASE_INFO.txt"
echo "Binary: $(ls -la target/release/momentry)" >> "$RELEASE_DIR/RELEASE_INFO.txt"
```
3. **備份 Binary**
```bash
cp target/release/momentry "$RELEASE_DIR/momentry_v0.X.X"
cp target/release/momentry_playground "$RELEASE_DIR/momentry_playground_v0.X.X" 2>/dev/null
```
4. **記錄資料庫 Schema**
```bash
pg_dump -U accusys -d momentry --schema-only > "$RELEASE_DIR/schema_v0.X.X.sql"
```
### 重要性
- 避免 release binary 與 current source code 不一致
- 方便追蹤特定 release 的程式碼狀態
- 必要時可快速復原或比對差異
- 確保資料庫 schema 與程式碼版本對應
## Reference Documents ## Reference Documents
| 文件 | 用途 | | 文件 | 用途 |
@@ -728,93 +411,3 @@ Phase 1: marcom 建構 (現在) → Elementor 頁面建構
Phase 2: 交付審視 (TBD) → 功能確認 / 重構評估 Phase 2: 交付審視 (TBD) → 功能確認 / 重構評估
Phase 3: OpenCode 重構 → 純程式碼實作,交付無 Elementor 依賴版本 Phase 3: OpenCode 重構 → 純程式碼實作,交付無 Elementor 依賴版本
``` ```
## M4 通知規範
### 固定通知方式
通知 M4 的唯一管道:**`M4_workspace/` 下建立回覆文件 + `git commit`**。不需口頭、即時訊息、郵件。
### 命名規則
```
docs_v1.0/M4_workspace/YYYY-MM-DD_<topic>_response.md (回覆 M4 問題)
docs_v1.0/M4_workspace/YYYY-MM-DD_<topic>.md (主動通報)
docs_v1.0/M4_workspace/YYYY-MM-DD_<topic>_test_report.md (測試報告)
```
### 觸發時機
| 情境 | 動作 |
|------|------|
| M4 提交問題報告到 `M4_workspace/` | 修復後,回覆 `*_response.md` |
| 完成 M4 要求的任務 | 回覆 `*_response.md` |
| 重大變更(模型替換、架構變更) | 主動通知 `*.md` |
| 新測試包產出 | `*_test_report.md` |
### 交付檢查
1. 文件寫入 `docs_v1.0/M4_workspace/`
2. `git add` 包含該文件
3. `git commit` 含相關變更
4. M4 透過 git log 查看
詳細規範見 `docs_v1.0/M4_workspace/M4_NOTIFICATION_PROTOCOL.md`。
## UUID Naming Rule
**Never use bare `uuid` in API route paths, query params, JSON keys, or code variable names. Always qualify:**
| Context | Must use | Never |
|---------|----------|-------|
| Video/file resource | `file_uuid` | `uuid` |
| Identity resource | `identity_uuid` | `uuid` |
| Query parameter | `file_uuid=`, `identity_uuid=` | `uuid=` |
| Route path | `:file_uuid`, `:identity_uuid` | `:uuid` |
| JSON key | `"file_uuid"`, `"identity_uuid"` | `"uuid"` |
This applies to docs, code, API responses, and curl examples. Exceptions: internal database primary key names (e.g. `identities.uuid` column).
## Document Compliance Checklist
Before creating any file in `docs_v1.0/` (API_WORKSPACE, GUIDES, REFERENCE, DESIGN, OPERATIONS, INTEGRATIONS), verify all items below.
**IMPORTANT**: API functional documents are generated from `API_WORKSPACE/modules/`. Edit modules there, then run `make deploy` in `API_WORKSPACE/` to update `GUIDES/`. Never edit generated files in `GUIDES/` directly. See `DESIGN/Modular_Doc_System_V1.0.md` for the full system design.
### P0 — Mandatory (7 items)
| # | Check | Rule |
|---|-------|------|
| 1 | YAML frontmatter | `title`, `version`, `date`, `author`, `status` present |
| 2 | Version history | Table at bottom of file tracking changes |
| 3 | Top info table | scope, status, applicable to, etc. |
| 4 | PascalCase filename | e.g. `DetectorRegistry.md`, not `detector_registry.md` |
| 5 | `_` separator | Within filenames use `_`, never spaces or other chars |
| 6 | English content | Entire file in English |
| 7 | Correct directory | File must reside in appropriate directory: `API_WORKSPACE/modules/` (API endpoint modules), `GUIDES/` (user docs, generated), `REFERENCE/` (data models), `DESIGN/` (architecture), `OPERATIONS/` (infra/release), `INTEGRATIONS/` (n8n/tests) |
### P0b — UUID Naming
| # | Check | Rule |
|---|-------|------|
| 8 | `file_uuid` not bare `uuid` | All file references use `file_uuid` (see UUID Naming Rule above) |
| 9 | `identity_uuid` not bare `uuid` | All identity references use `identity_uuid` |
### P1 — Suggested (3 items)
| # | Check | Note |
|---|-------|------|
| 1 | Cross-references | Link to related docs in API_WORKSPACE/, GUIDES/, REFERENCE/, DESIGN/, OPERATIONS/ |
| 2 | Glossary terms | Define non-obvious terms inline or link glossary |
| 3 | Diagrams | Include Mermaid/ASCII diagram for complex topics |
### Exception
`M4_workspace/` files are exempt from this checklist (free-format reply documents).
---
## Delivery Procedure
完整交付程序(M4_workspace → M5 → Release → Deploy → Public)見:
`docs_v1.0/OPERATIONS/DELIVERY_PROCEDURE.md`
Generated
+273 -1057
View File
File diff suppressed because it is too large Load Diff
+11 -60
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "momentry_core" name = "momentry_core"
version = "1.0.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["Momentry Team"] authors = ["Momentry Team"]
description = "Digital asset management system with video analysis and RAG" description = "Digital asset management system with video analysis and RAG"
@@ -11,9 +11,8 @@ anyhow = "1.0"
thiserror = "1.0" thiserror = "1.0"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = "0.3"
once_cell = "1.19" once_cell = "1.19"
libc = "0.2"
dotenv = "0.15" dotenv = "0.15"
# CLI # CLI
@@ -26,42 +25,32 @@ futures-util = "0.3"
# Serialization # Serialization
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
regex = "1"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
# UUID # UUID
sha2 = "0.10" sha2 = "0.10"
hex = "0.4" hex = "0.4"
uuid = { version = "1.0", features = ["v4"] } uuid = { version = "1.0", features = ["v4"] }
mac_address = "1.1"
# Security # Security
subtle = "2.5" subtle = "2.5"
aes-gcm = "0.10" aes-gcm = "0.10"
base64 = "0.22" base64 = "0.22"
argon2 = "0.5"
jsonwebtoken = "9.3"
# Text processing
jieba-rs = "0.8.1"
ferrous-opencc = { version = "0.3.1", features = ["s2t-conversion", "t2s-conversion"] }
# Cache # Cache
moka = { version = "0.12", features = ["future"] } moka = { version = "0.12", features = ["future"] }
# Database # Database
redis = { version = "1.0", features = ["tokio-comp", "connection-manager"] } redis = { version = "1.0", features = ["tokio-comp", "connection-manager"] }
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "json", "chrono", "uuid"] } sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "json", "chrono"] }
mongodb = { version = "2", features = ["tokio-runtime"] } mongodb = { version = "2", features = ["tokio-runtime"] }
bson = { version = "2", features = ["chrono-0_4"] } bson = { version = "2", features = ["chrono-0_4"] }
qdrant-client = "1.7" qdrant-client = "1.7"
reqwest = { version = "0.12", features = ["json", "gzip"] } reqwest = { version = "0.12", features = ["json"] }
pgvector = { version = "0.3", features = ["sqlx"] }
# HTTP Server # HTTP Server
axum = { version = "0.7", features = ["multipart"] } axum = "0.7"
tower = "0.4" tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "fs"] }
# API Documentation # API Documentation
utoipa = { version = "4", features = ["axum_extras", "chrono", "uuid"] } utoipa = { version = "4", features = ["axum_extras", "chrono", "uuid"] }
@@ -82,9 +71,9 @@ crossterm = "0.28"
# Terminal # Terminal
atty = "0.2" atty = "0.2"
tokio-util = { version = "0.7.18", features = ["io"] }
# System # System
libc = "0.2"
[lib] [lib]
name = "momentry_core" name = "momentry_core"
@@ -92,20 +81,12 @@ path = "src/lib.rs"
[features] [features]
default = [] default = []
player = ["sdl2"] player = []
[dependencies.sdl2]
version = "0.35"
optional = true
[[bin]] [[bin]]
name = "momentry" name = "momentry"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
name = "momentry-cli"
path = "src/bin/cli.rs"
[[bin]] [[bin]]
name = "momentry_player" name = "momentry_player"
path = "src/player/main.rs" path = "src/player/main.rs"
@@ -113,33 +94,3 @@ path = "src/player/main.rs"
[[bin]] [[bin]]
name = "momentry_playground" name = "momentry_playground"
path = "src/playground.rs" path = "src/playground.rs"
[[bin]]
name = "fix_chunks"
path = "src/bin/fix_chunks.rs"
[[bin]]
name = "migrate_chinese_text"
path = "src/bin/migrate_chinese_text.rs"
[[bin]]
name = "test_bm25_simple"
path = "src/bin/test_bm25_simple.rs"
[[bin]]
name = "integrated_player"
path = "src/bin/integrated_player.rs"
[[bin]]
name = "release"
path = "src/bin/release.rs"
[[bin]]
name = "service"
path = "src/bin/service.rs"
[build-dependencies]
chrono = "0.4"
[dev-dependencies]
tempfile = "3"
-277
View File
@@ -1,277 +0,0 @@
# Identity Best-Face API
**狀態:** 規劃中
**提出日期:** 2026-06-01
**提出者:** WordPress Portal 前端團隊
---
## 1. 背景
WordPress Portal 的 People 頁面需要在 identity detail view 與 grid card 中顯示代表臉部縮圖。目前前端作法:
1. `GET /identity/{uuid}/traces` → 取得所有 trace 列表(含 `avg_confidence`)
2. 對每個 trace 載入第一幀 thumbnail → `GET /file/{uuid}/trace/{tid}/thumbnail`
3. 從有 thumbnail 的 trace 中,選 `avg_confidence` 最高者作為代表圖
### 現有問題
- **品質不佳**:trace thumbnail 固定取第一幀,不一定是該 trace 內最清晰或正面的臉部畫面
- **浪費頻寬**:前端需發送大量並行請求(最多 20 trace × thumbnail),多數 thumbnail 最終不會被使用
- **無快取**:每次進入 detail view 都要重複載入所有 thumbnail
- **不一致**:同樣 identity 在 grid card 與 detail view 可能顯示不同代表圖
---
## 2. 目標
後端新增一個 endpoint,對指定 identity **跨所有 trace** 選出品質最佳(最清晰)的臉部畫面,並提供可直接使用的縮圖 URL,支援 disk cache。
---
## 3. API 規格
### `GET /api/v1/identity/:identity_uuid/best-face`
無 query parameter。
#### 成功回應 `200`
```json
{
"success": true,
"identity_uuid": "a6fb22eebefaef17e62af874997c5944",
"name": "Audrey Hepburn",
"source": "fresh",
"best": {
"file_uuid": "a6fb22eebefaef17e62af874997c5944",
"trace_id": 42,
"frame_number": 3120,
"timestamp_secs": 124.8,
"bbox": {
"x": 240,
"y": 180,
"width": 120,
"height": 160
},
"confidence": 0.97,
"quality_score": 18624.0,
"blur_score": 2.1,
"thumbnail_url": "/api/v1/file/a6fb22eebefaef17e62af874997c5944/trace/42/thumbnail"
}
}
```
#### 無可用臉部 `200`
```json
{
"success": true,
"identity_uuid": "a6fb22eebefaef17e62af874997c5944",
"name": "Audrey Hepburn",
"source": "fresh",
"best": null
}
```
#### 欄位說明
| 欄位 | 型態 | 說明 |
|------|------|------|
| `success` | boolean | 請求是否成功 |
| `identity_uuid` | string | identity UUID(32字元無連字號) |
| `name` | string | identity 名稱 |
| `source` | string | `"fresh"`(即時計算)或 `"cache"`(來自 disk cache) |
| `best` | object/null | 最佳臉部資訊,無可用臉部時為 `null` |
| `best.file_uuid` | string | 該臉部所屬檔案 UUID |
| `best.trace_id` | int | 該臉部所屬 trace ID |
| `best.frame_number` | int | 代表臉的影格編號 |
| `best.timestamp_secs` | float | 代表臉的時間戳(秒) |
| `best.bbox` | object | 臉部 bounding box `{x, y, width, height}` |
| `best.confidence` | float | 該臉部的 detection confidence |
| `best.quality_score` | float | 品質分數 = `(width * height) * confidence` |
| `best.blur_score` | float | 模糊度分數(ffmpeg blurdetect),越低越清晰 |
| `best.thumbnail_url` | string | 縮圖 URL(相對路徑,可直接用於瀏覽器) |
---
## 4. 實作建議
### 4.1 建議放置位置
**選項 A(建議):** `src/api/trace_agent_api.rs`
- 原因:核心邏輯重用 `select_rep_face()`(目前為 `pub(crate)`,位於同一檔案),無需修改既有的 function visibility
- 在 `trace_agent_routes()` 中新增路由
**選項 B:** `src/api/identity_binding.rs`
- 需將 `select_rep_face` 改為 `pub` 才能跨檔案呼叫
- 路由語意上更接近 identity 操作
### 4.2 演算法
```
1. DISK CACHE CHECK
路徑:{OUTPUT_DIR}/identities/{uuid}/best_face.json
讀取 identity.json 的 updated_at,與 cache 中記錄的版本比較
若 cache 未過期 → 直接回傳(source: "cache")
若無 cache 或已過期 → 繼續計算
2. QUERY IDENTITY
SELECT id, name FROM identities
WHERE REPLACE(uuid::text, '-', '') = $1
3. QUERY TOP N TRACES
SELECT fd.file_uuid, fd.trace_id,
AVG(fd.confidence)::float8 AS avg_conf
FROM {schema}.face_detections fd
WHERE fd.identity_id = $1
AND fd.confidence > 0.7
AND (fd.metadata->>'qc_ok' IS NULL
OR (fd.metadata->>'qc_ok')::boolean = true)
GROUP BY fd.file_uuid, fd.trace_id
ORDER BY avg_conf DESC
LIMIT 5
4. FOR EACH TRACE (並行)
select_rep_face(pool, file_uuid, trace_id, err_fn)
 → 回傳該 trace 內 blur_score 最低(最清晰)的臉
失敗則 skip(log warning)
5. SELECT BEST AMONG RESULTS
主排序:blur_score ASC(越低越清晰)
次排序:quality_score DESC(blur_score 差距 < 0.5 時)
全部失敗 → best = null
6. WRITE DISK CACHE
路徑:{OUTPUT_DIR}/identities/{uuid}/best_face.json
內容:best 欄位 + 計算時間 + identity updated_at
7. RESPONSE
```
### 4.3 效能參數
| 參數 | 值 | 說明 |
|------|----|------|
| TOP N | 5 | 只對 confidence 最高的 5 個 trace 做 blurdetect |
| confidence 門檻 | > 0.7 | 同既有的 `select_rep_face` 邏輯 |
| QC 過濾 | qc_ok = true/null | 同既有邏輯 |
| ffmpeg timeout | inherit from Command | 每個 trace 約 1-3s |
| cache TTL | 直到下一次 bind/unbind/merge | 事件驅動失效 |
### 4.4 快取策略
**寫入時機:** `get_identity_best_face` 計算完成後
**失效時機(刪除 `best_face.json`):**
| 觸發 operation | 所在檔案 | 備註 |
|---------------|---------|------|
| `bind_trace` (POST) | `identity_binding.rs` | 新增 face 關聯 |
| `unbind` (POST) | `identity_binding.rs` | 移除 face 關聯 |
| `mergeinto` (POST) | `identity_binding.rs` | source + target 雙雙清除 |
| `profile-image` (POST) | `identity_api.rs` | 使用者上傳新大頭照 |
**Cache 驗證機制:** 儲存計算時的 `identity.updated_at`,每次請求時比對:
- 若 identity 的 `updated_at` 未變 → cache 有效
- 若已變 → 重新計算
### 4.5 建議的新增/修改檔案
| 檔案 | 動作 | 說明 |
|------|------|------|
| `src/api/trace_agent_api.rs` | **新增** handler + struct + route | ~+130 行 |
| `src/api/identity_binding.rs` | **修改** 3 處 + cache invalidation helper | ~+25 行 |
| `src/api/identity_api.rs` | **修改** 1 處(profile-image POST) | ~+5 行 |
### 4.6 需要的新 struct
**`src/api/trace_agent_api.rs`**(或獨立檔案 `src/core/identity_best_face.rs`):
```rust
#[derive(Debug, Serialize, Deserialize)]
pub struct BestFaceResponse {
pub success: bool,
pub identity_uuid: String,
pub name: String,
pub source: String,
pub best: Option<BestFaceResult>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BestFaceResult {
pub file_uuid: String,
pub trace_id: i32,
pub frame_number: i64,
pub timestamp_secs: f64,
pub bbox: RepFaceBbox,
pub confidence: f64,
pub quality_score: f64,
pub blur_score: f64,
pub thumbnail_url: String,
}
```
### 4.7 Cache Invalidation Helper Function
```rust
async fn invalidate_best_face_cache(output_dir: &str, uuid_clean: &str) {
let path = format!("{}/identities/{}/best_face.json", output_dir, uuid_clean);
let _ = tokio::fs::remove_file(path).await;
}
```
---
## 5. 前端整合參考(供後端團隊理解使用情境)
WP snippet 72 (`ms-people.js`) 的 `loadPersonDetail` 中,優先使用新 endpoint:
```js
async function loadPersonDetail(person) {
if (person.thumb && person._hasProfileImage) return;
try {
const res = await apiFetch('/identity/' + person.id + '/best-face');
if (res?.success && res?.best) {
const b = res.best;
person.thumb = `${API_BASE}/file/${b.file_uuid}/trace/${b.trace_id}/thumbnail?api_key=${API_KEY}`;
person._hasProfileImage = true;
updateDetailAvatar(person);
return;
}
} catch (e) { /* fallback to legacy */ }
// 原邏輯:traces → thumbnails → confidence sort
}
```
同樣可用於 grid card 的代表圖載入(`loadGridThumbnails`):
```js
// 一次性載入所有 pending identity 的 best-face
const results = await Promise.allSettled(
persons.map(p => apiFetch('/identity/' + p.id + '/best-face'))
);
```
---
## 6. 驗收標準
1. `GET /api/v1/identity/{uuid}/best-face` → `200` + valid JSON
2. 有 trace 的 identity → `best` 不為 null,且 `blur_score` 為該 identity 所有 trace 中最低
3. 無 trace 的 identity → `best: null`
4. 短時間內重複請求同一 identity → `source: "cache"`,回應時間 < 10ms
5. 綁定新 trace 後再次請求 → `source: "fresh"`(cache 已正確失效)
6. `thumbnail_url` 可直接用於 `<img>` 顯示
---
## 7. 風險與注意事項
- **首次請求延遲**:對有大量 trace 的 identity(如主角),首次請求可能需 5-15 秒。建議前端顯示 loading state
- **ffmpeg 資源**:同時多個請求可能導致高 CPU 使用。可考慮加入 per-identity lock 避免重複計算
- **邊界案例**:trace 內的 faces 全部 confidence ≤ 0.7 或 qc_ok=false,則該 trace 被跳過,可能導致 `best: null`
-3
View File
@@ -1,3 +0,0 @@
# momentry_core
Digital asset management system with video analysis and RAG - Production version with API Key authentication
-78
View File
@@ -1,78 +0,0 @@
# Sync Notes 2026-05-21
## M5Max128 收到後需要做的事
```bash
cd ~/momentry_core
git pull origin main # 拉取所有變更
cat SYNC_V1.1.md # 閱讀此文件
# 資料庫變更(必須先執行,否則 worker 會 fail)
psql -U accusys -d momentry -c "ALTER TABLE public.pre_chunks ALTER COLUMN coordinate_index SET DEFAULT 0;"
# 重建 + 重啟
cargo build --release --bin momentry
./run-server-3002.sh
```
---
## Bugs Fixed (13)
| # | 問題 | 根因 | 修復 |
|---|------|------|------|
| 1 | `GET /identity/:uuid/files` 空資料 | SQL 缺 `REPLACE(uuid)` + 缺 `JOIN videos` | 改用 `REPLACE(uuid::text...)` + JOIN videos + `frame_number/fps` |
| 2 | `GET /identity/:uuid/faces` crash + 空 | `i64`/`INT4` 型別不符 + 硬編碼 NULL/0 | `id::bigint`、`confidence::float8` + 真實欄位 |
| 3 | `GET /identity/:uuid` crash | `IdentityDetailRecord.id` 是 `i64` 但 DB 是 `INT4` | `id::bigint as id` |
| 4 | `GET /file/:uuid/identities` 空 | 雙重 stub(handler + DB 都 `Vec::new()`) | 完整實作 + 正確 total count |
| 5 | `GET /identities/search?q=Louis` 500 | `c.text_content` NULL 但 Rust tuple 用 `String` | 改 `Option<String>` |
| 6 | `POST /search/universal` person type first/last_time null | `search_persons_internal` 用 `timestamp_secs` | 改 `frame_number/fps` + JOIN videos |
| 7 | faces/files/chunks total 不正確 | `total: data.len()` | 獨立 COUNT 查詢 |
| 8 | `GET /identity/:uuid/traces` 無分頁 | 缺 page/page_size | 新增 `TracesQuery` + LIMIT/OFFSET |
| 9 | 身分比對 frame-level 不穩定 | frame-level Qdrant | 改 **trace-level**(AVG embedding per trace) |
| 10 | Charade face embedding 不在 Qdrant | 沒跑 `sync_face_embeddings` | match API 自動 push + ANN search |
| 11 | 無眼睛 face 推入 Qdrant | 無 QC 過濾 | `face_landmark_qc.py --apply` + Qdrant sync 過濾 `qc_ok` |
| 12 | TMDb 比對 dev/prod 不一致 | Qdrant ANN 不同 collection | trace-level 改善穩定性 |
| 13 | `faces/files/chunks total` 顯示 page_size | `total: data.len()` | 改為獨立 COUNT 查詢 |
## ✨ 新功能 (6)
| # | 功能 | 說明 |
|---|------|------|
| 1 | `POST /api/v1/tmdb/fetch` | 從 TMDb 下載 cast → 建立 identity + json + jpg + Qdrant |
| 2 | `POST /api/v1/agents/tmdb/match/:file_uuid` | 推 face → Qdrant ANN search → bind identity |
| 3 | `GET /api/v1/identity/:uuid/status` | 檢查 identity.json + profile.jpg 是否存在 |
| 4 | `/health` 新增 watcher/worker/時區 | `watcher_running`、`worker_running`、`system_timezone` |
| 5 | `SYSTEM_TIMEZONE` config | 自動偵測系統時區,可 `MOMENTRY_TIMEZONE` 覆蓋 |
| 6 | `GET /identity/:uuid/traces` 分頁 | `?page=1&page_size=20` |
## 🔧 資料庫變更
```sql
-- 必須執行(否則 worker 的 CUT processor 會失敗)
ALTER TABLE public.pre_chunks ALTER COLUMN coordinate_index SET DEFAULT 0;
-- 選擇性(face_landmark_qc.py --apply 需要)
ALTER TABLE public.face_detections ADD COLUMN metadata jsonb DEFAULT '{}'::jsonb;
```
## 🗑️ 清理
- 刪除 2,769 個孤兒 `person_xxx` identity(無 face_detections)
- `person_identities` + `person_appearances` table 已 DROP
## 📂 主要檔案變更
| 檔案 | 說明 |
|------|------|
| `src/api/identity_api.rs` | identity detail/files/faces total 修正 + status endpoint |
| `src/api/identity_binding.rs` | traces 分頁(新增 `page`/`page_size`/`total`) |
| `src/api/server.rs` | health 新增 watcher/worker/system_timezone |
| `src/api/tmdb_api.rs` | **新檔案** — tmdb/fetch + match 端點 |
| `src/api/universal_search.rs` | person search 改 frame_number/fps |
| `src/core/config.rs` | 新增 SYSTEM_TIMEZONE |
| `src/core/db/qdrant_db.rs` | search_face_collection + sync_trace_embeddings + batch upsert |
| `src/core/db/postgres_db.rs` | get_identity_files/faces 修正 + get_file_identities 實作 |
| `src/core/tmdb/probe.rs` | extract_movie_name 改進(只取 `(` 前) |
| `scripts/face_landmark_qc.py` | 新增 `--apply` + `--schema` 參數 |
| `Cargo.toml` | reqwest 加 `gzip` feature |
-81
View File
@@ -1,81 +0,0 @@
use std::collections::BTreeMap;
use std::path::Path;
fn main() {
let version = std::env::var("CARGO_PKG_VERSION").unwrap_or_else(|_| "unknown".to_string());
let git_hash = std::process::Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
let timestamp = std::process::Command::new("date")
.args(["-u", "+%Y-%m-%dT%H:%M:%SZ"])
.output()
.ok()
.and_then(|o| String::from_utf8(o.stdout).ok())
.map(|s| s.trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=BUILD_VERSION={}", version);
println!("cargo:rustc-env=BUILD_GIT_HASH={}", git_hash);
println!("cargo:rustc-env=BUILD_TIMESTAMP={}", timestamp);
// ── Schema migration manifest ──
// Scan release/migrate_*.sql, compute SHA256, embed as JSON string
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
let release_dir = Path::new(&manifest_dir).join("release");
let mut migrations = BTreeMap::new(); // sorted by filename
if let Ok(entries) = std::fs::read_dir(&release_dir) {
for entry in entries.flatten() {
let path = entry.path();
let fname = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
if fname.starts_with("migrate_") && fname.ends_with(".sql") {
if let Ok(content) = std::fs::read(&path) {
let hash = sha256_hex(&content);
migrations.insert(fname.to_string(), hash);
}
}
}
}
// Encode as comma-separated: name1:hash1,name2:hash2,...
let manifest: String = migrations
.iter()
.map(|(name, hash)| format!("{}:{}", name, hash))
.collect::<Vec<_>>()
.join(",");
println!("cargo:rustc-env=REQUIRED_MIGRATIONS={}", manifest);
println!(
"cargo:info=Embedded {} migration checksums",
migrations.len()
);
}
fn sha256_hex(data: &[u8]) -> String {
use std::io::Write;
use std::process::{Command, Stdio};
if let Ok(mut child) = Command::new("shasum")
.arg("-a")
.arg("256")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(data);
}
if let Ok(out) = child.wait_with_output() {
if let Ok(s) = String::from_utf8(out.stdout) {
if let Some(hash) = s.split(' ').next() {
return hash.to_string();
}
}
}
}
"unknown".to_string()
}
-22
View File
@@ -1,22 +0,0 @@
# Port Registry - Momentry Core
# Each port must have exactly one owner.
# Before adding a service: pick a free port, add a row here, then configure.
#
# Port Service Owner Config Key Default Source
22 ssh sshd - - macOS
80 http Caddy - - Caddyfile
443 https Caddy - - Caddyfile
2019 caddy-admin Caddy - - Caddyfile (internal)
3000 gitea gitea - 3000 start_momentry.sh
3002 production momentry MOMENTRY_SERVER_PORT 3002 run-server-3002.sh
3003 playground momentry_playground MOMENTRY_SERVER_PORT 3003 start_momentry.sh
3200 dashboard Caddy - - Caddyfile
3306 mariadb mariadbd - 3306 start_momentry.sh
5432 postgresql postgres DATABASE_URL postgres://...:5432 start_momentry.sh
6379 redis redis-server REDIS_URL redis://...:6379 start_momentry.sh
6333 qdrant qdrant QDRANT_URL http://...:6333 start_momentry.sh
8081 wordpress Caddy - - Caddyfile
8082 llm llama-server MOMENTRY_LLM_CHAT_URL http://...:8082 start_momentry.sh
9000 php-fpm php-fpm - 9000 brew services
11434 ollama ollama MOMENTRY_OLLAMA_URL http://...:11434 start_momentry.sh
11436 embedding embeddinggemma MOMENTRY_EMBED_URL http://...:11436 start_momentry.sh
1 # Port Registry - Momentry Core
2 # Each port must have exactly one owner.
3 # Before adding a service: pick a free port, add a row here, then configure.
4 #
5 # Port Service Owner Config Key Default Source
6 22 ssh sshd - - macOS
7 80 http Caddy - - Caddyfile
8 443 https Caddy - - Caddyfile
9 2019 caddy-admin Caddy - - Caddyfile (internal)
10 3000 gitea gitea - 3000 start_momentry.sh
11 3002 production momentry MOMENTRY_SERVER_PORT 3002 run-server-3002.sh
12 3003 playground momentry_playground MOMENTRY_SERVER_PORT 3003 start_momentry.sh
13 3200 dashboard Caddy - - Caddyfile
14 3306 mariadb mariadbd - 3306 start_momentry.sh
15 5432 postgresql postgres DATABASE_URL postgres://...:5432 start_momentry.sh
16 6379 redis redis-server REDIS_URL redis://...:6379 start_momentry.sh
17 6333 qdrant qdrant QDRANT_URL http://...:6333 start_momentry.sh
18 8081 wordpress Caddy - - Caddyfile
19 8082 llm llama-server MOMENTRY_LLM_CHAT_URL http://...:8082 start_momentry.sh
20 9000 php-fpm php-fpm - 9000 brew services
21 11434 ollama ollama MOMENTRY_OLLAMA_URL http://...:11434 start_momentry.sh
22 11436 embedding embeddinggemma MOMENTRY_EMBED_URL http://...:11436 start_momentry.sh
-123
View File
@@ -1,123 +0,0 @@
# Momentry Core Production Configuration
# Version: 1.0.0
# Effective: 2025-03-27
[server]
host = "0.0.0.0"
port = 3002
workers = 4
log_level = "info"
max_connections = 1000
keep_alive = 75
[database]
url = "postgres://accusys@localhost:5432/momentry"
pool_size = 20
idle_timeout = 300
max_lifetime = 1800
[redis]
url = "redis://:accusys@localhost:6379"
prefix = "momentry:"
pool_size = 50
connection_timeout = 5
read_timeout = 3
write_timeout = 3
[storage]
output_dir = "/Users/accusys/momentry/output"
backup_dir = "/Users/accusys/momentry/backup"
max_file_size = "10GB"
[processors]
asr_timeout = 7200 # 2 hours for long videos
ocr_timeout = 3600 # 1 hour
yolo_timeout = 14400 # 4 hours
face_timeout = 3600 # 1 hour
pose_timeout = 7200 # 2 hours
asrx_timeout = 10800 # 3 hours for speaker diarization
cut_timeout = 7200 # 2 hours for scene detection
caption_timeout = 3600 # 1 hour for captioning
story_timeout = 3600 # 1 hour for story generation
default_timeout = 7200
max_concurrent = 2 # Limit to prevent overload
[asr]
model_size = "medium"
device = "cpu"
language = "auto"
task = "transcribe"
beam_size = 5
best_of = 5
[ocr]
languages = "en"
confidence = 0.7
gpu = false
model_path = "~/.EasyOCR/model"
[yolo]
model_size = "yolov8n.pt"
confidence = 0.25
iou = 0.45
gpu = false
auto_save_interval = 30
auto_save_frames = 300
classes = "" # empty = all classes
[face]
method = "haar"
confidence = 0.5
min_size = 30
max_size = 300
scale_factor = 1.1
min_neighbors = 3
gpu = false
gpu_backend = "cpu" # cpu, cuda, mps, rocm
enable_mps = false
[pose]
model_size = "yolov8n-pose.pt"
confidence = 0.25
iou = 0.45
gpu = false
keypoint_confidence = 0.5
max_persons = 10
[asrx]
model_size = "medium"
device = "cpu"
language = "en"
batch_size = 16
diarization = true
min_speakers = 1
max_speakers = 10
[cut]
method = "content"
threshold = 27.0
min_scene_length = 0.5
show_progress = true
[caption]
model = "gpt-4"
max_tokens = 1000
temperature = 0.7
[story]
model = "gpt-4"
max_tokens = 2000
temperature = 0.8
[audit]
enabled = true
log_file = "/Users/accusys/momentry/logs/audit.log"
retention_days = 90
[monitoring]
enabled = true
metrics_port = 9090
health_check_interval = 30
alert_threshold_cpu = 80
alert_threshold_memory = 85
alert_threshold_disk = 90
-516
View File
@@ -1,516 +0,0 @@
<!-- module: identity -->
<!-- description: Global identities — CRUD, detail, files, faces, bind, unbind, search -->
<!-- depends: 01_auth -->
## Global Identities
### `GET /api/v1/identities`
**Auth**: Required
**Scope**: identity-level
List all registered identities with pagination.
#### Example
```bash
curl -s "$API/api/v1/identities?page=1&page_size=20" -H "X-API-Key: $KEY" | jq '{count, identities: [.identities[] | {name}]}'
```
---
### `GET /api/v1/identity/:identity_uuid`
**Auth**: Required
**Scope**: identity-level
Get detailed information for a specific identity, including metadata and TMDb references.
#### Example
```bash
curl -s "$API/api/v1/identity/$IDENTITY_UUID" -H "X-API-Key: $KEY"
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
"name": "Cary Grant",
"identity_type": "people",
"source": "tmdb",
"status": "confirmed",
"tmdb_id": 112,
"tmdb_profile": "{output}/identities/{identity_uuid}/profile.jpg",
"metadata": {},
"reference_data": {},
"created_at": "2026-05-16T12:00:00Z",
"updated_at": null
}
```
| Field | Type | Description |
|-------|------|-------------|
| `identity_uuid` | string | Identity identifier |
| `name` | string | Identity name |
| `identity_type` | string | `"people"` or null |
| `source` | string | `.json`, `auto`, `tmdb`, `user_defined`, or `merged` |
| `status` | string | `"confirmed"`, `"pending"`, or `"inactive"` |
| `tmdb_id` | integer | TMDb person ID (only if source = tmdb) |
| `tmdb_profile` | string | Local profile image path (`{output}/identities/{uuid}/profile.jpg`) |
| `metadata` | object | Metadata JSON (tmdb_character, cast_order, etc.) |
| `created_at` | string | Creation timestamp |
---
### `DELETE /api/v1/identity/:identity_uuid`
**Auth**: Required
**Scope**: identity-level
Delete an identity permanently.
---
### `PATCH /api/v1/identity/:identity_uuid`
**Auth**: Required
**Scope**: identity-level
Partially update an identity. Only provided fields are modified. The `name` field is a display label and may repeat across identities. Aliases for multilingual display are stored in `metadata.aliases` (see BCP 47 reference below).
#### Request (JSON, all fields optional)
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | New display name |
| `metadata` | object | Merged into existing metadata. Use `"aliases"` key for locale-tagged names |
| `status` | string | `"confirmed"`, `"pending"`, or `"skipped"` |
| `identity_type` | string | `"people"`, `"brand"`, `"object"`, `"concept"`, etc. |
#### Example
```bash
curl -s -X PATCH "$API/api/v1/identity/$IDENTITY_UUID" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "John Smith",
"metadata": {
"aliases": [
{"locale": "en", "name": "John Smith"},
{"locale": "zh-TW", "name": "約翰·史密斯"},
{"locale": "ja", "name": "ジョン・スミス"}
]
}
}'
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
"updated_fields": ["name", "metadata"]
}
```
#### Error Responses
| HTTP | When |
|------|------|
| `400` | No fields to update or invalid UUID format |
| `404` | Identity not found |
---
### `GET /api/v1/identity/:identity_uuid/files`
**Auth**: Required
**Scope**: identity-level
Get all files where this identity appears. Returns per-file summary including face count, confidence, and appearance time range.
#### Example
```bash
curl -s "$API/api/v1/identity/$IDENTITY_UUID/files" -H "X-API-Key: $KEY"
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "c3545906c82d4b66aa1d150bc02decce",
"total": 1,
"page": 1,
"page_size": 20,
"data": [
{
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"file_name": "Charade (1963) Cary Grant & Audrey Hepburn.mp4",
"file_path": "/path/to/videos/Charade.mp4",
"status": "completed",
"face_count": 19695,
"speaker_count": 0,
"first_appearance": 206.76,
"last_appearance": 6756.68,
"confidence": 0.803
}
]
}
```
#### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `file_uuid` | string | File identifier (full 32-char hex) |
| `file_name` | string | Video file name |
| `file_path` | string | Absolute path to video file |
| `status` | string | Video processing status (`"completed"`, `"processing"`, etc.) |
| `face_count` | int | Total face detections for this identity in this file |
| `speaker_count` | int | Speaker segments (reserved, always `0`) |
| `first_appearance` | float | First appearance time in seconds (computed from `frame_number / fps`) |
| `last_appearance` | float | Last appearance time in seconds |
| `confidence` | float | Average detection confidence |
---
### `GET /api/v1/identity/:identity_uuid/faces`
**Auth**: Required
**Scope**: identity-level
Get all face detection records associated with this identity.
#### Example
```bash
curl -s "$API/api/v1/identity/$IDENTITY_UUID/faces?page=1&page_size=20" -H "X-API-Key: $KEY"
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "c3545906c82d4b66aa1d150bc02decce",
"total": 19695,
"page": 1,
"page_size": 20,
"data": [
{
"id": 655704,
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"frame_number": 5169,
"timestamp_secs": 206.76,
"face_id": "5169_0",
"bbox": {
"x": 706,
"y": 469,
"width": 618,
"height": 618
},
"confidence": 0.855
}
]
}
```
#### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | int64 | Face detection record ID |
| `file_uuid` | string | File where face was detected |
| `frame_number` | int64 | Frame number (primary coordinate) |
| `timestamp_secs` | float | Time in seconds (computed as `frame_number / fps`) |
| `face_id` | string | Face ID (format: `{frame_number}_{detection_index}`) |
| `bbox` | object | Bounding box |
| `bbox.x` | float | Left coordinate |
| `bbox.y` | float | Top coordinate |
| `bbox.width` | float | Width in pixels |
| `bbox.height` | float | Height in pixels |
| `confidence` | float | Detection confidence (0.0–1.0) |
---
### `GET /api/v1/identity/:identity_uuid/chunks`
**Auth**: Required
**Scope**: identity-level
Get all text chunks (sentences) spoken while this identity's face was on screen. Useful for finding what a person said.
#### Example
```bash
curl -s "$API/api/v1/identity/$IDENTITY_UUID/chunks" -H "X-API-Key: $KEY"
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
"data": [
{
"id": 0,
"file_uuid": "bd80fec92b0b6963d177a2c55bf713e2",
"chunk_id": "bd80fec92b0b6963d177a2c55bf713e2_2",
"chunk_type": "sentence",
"start_frame": 5103,
"end_frame": 5127,
"fps": 24.0,
"start_time": 212.64,
"end_time": 213.64,
"text_content": "[213s-214s] Cary Grant: \"Olá!\""
}
]
}
```
| Field | Type | Description |
|-------|------|-------------|
| `file_uuid` | string | File identifier |
| `chunk_id` | string | Sentence chunk identifier |
| `start_frame` | integer | Frame-accurate start position |
| `end_frame` | integer | Frame-accurate end position |
| `fps` | float | Frames per second |
| `start_time` | float | Start time in seconds |
| `end_time` | float | End time in seconds |
| `text_content` | string | Spoken text content |
---
### `POST /api/v1/identity/:identity_uuid/bind`
**Auth**: Required
**Scope**: identity-level
Bind a face detection to an identity. Associates the face trace with the identity for future search and recognition.
#### Request Parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `file_uuid` | string | Yes | File where face is detected |
| `face_id` | string | Yes | Face ID (format: `{frame}_{idx}`) |
#### Example
```bash
curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"file_uuid": "'"$FILE_UUID"'", "face_id": "1_5"}'
```
---
### `POST /api/v1/identity/:identity_uuid/unbind`
**Auth**: Required
**Scope**: identity-level
Unbind a face detection from an identity. Removes the identity association from the face record.
---
### `GET /api/v1/identities/search`
**Auth**: Required
**Scope**: identity-level
Search identities by name (ILIKE search). Returns matching identity records.
#### Example
```bash
curl -s "$API/api/v1/identities/search?q=Cary" -H "X-API-Key: $KEY"
```
| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Identity name |
| `source` | string | Identity source |
| `tmdb_id` | integer | TMDb ID (if source = tmdb) |
| `file_uuid` | string | Associated file |
---
---
### `POST /api/v1/identity/upload`
**Auth**: Required
**Scope**: identity-level
Upload an identity.json file to create or update an identity. Accepts the same format as the identity.json files stored on disk.
If an identity with the same `identity_uuid` already exists, it will be updated with the new values.
#### Request
The request body is an `IdentityFile` object:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `identity_uuid` | string | Yes | Identity identifier |
| `name` | string | Yes | Identity display name |
| `identity_type` | string | No | `"people"` or null |
| `source` | string | No | `.json`, `auto`, `tmdb`, `user_defined`, or `merged` |
| `status` | string | No | `"confirmed"`, `"pending"`, or `"inactive"` |
| `tmdb_id` | integer | No | TMDb person ID |
| `tmdb_profile` | string | No | TMDb profile image URL |
| `metadata` | object | No | Arbitrary metadata JSON |
| `file_bindings` | array | No | Array of `{ file_uuid, trace_ids, face_count }` (informational) |
#### Example
```bash
curl -s -X POST "$API/api/v1/identity/upload" \
-H "X-API-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"version": 1,
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
"name": "Cary Grant",
"identity_type": "people",
"source": ".json",
"status": "confirmed",
"metadata": {},
"file_bindings": []
}'
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
"name": "Cary Grant",
"message": "Identity uploaded successfully"
}
```
---
---
### `POST /api/v1/identity/:identity_uuid/profile-image`
**Auth**: Required
**Scope**: identity-level
Upload a profile image (JPEG or PNG) for an identity. The image is saved to `{output}/identities/{uuid}/profile.{ext}`.
Uses `multipart/form-data` with field name `image`.
#### Example
```bash
curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/profile-image" \
-H "X-API-Key: $KEY" \
-F "image=@/path/to/photo.jpg"
```
#### Response (200)
```json
{
"success": true,
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
"path": "/path/to/output/identities/.../profile.jpg",
"message": "Profile image saved: profile.jpg"
}
```
#### Error Responses
| HTTP | When |
|------|------|
| `400` | Missing image field or unsupported format |
| `404` | Identity not found |
| `415` | Unsupported image type (use JPEG or PNG) |
---
### `GET /api/v1/identity/:identity_uuid/profile-image`
**Auth**: Required
**Scope**: identity-level
Retrieve the profile image for an identity. Returns the raw image data with appropriate Content-Type header.
```bash
curl -s "$API/api/v1/identity/$IDENTITY_UUID/profile-image" \
-H "X-API-Key: $KEY" -o profile.jpg
```
| Response Header | Value |
|----------------|-------|
| `content-type` | `image/jpeg` or `image/png` |
---
## Alias System (BCP 47 Locale Tags)
Identity aliases support multilingual display names. Aliases are stored in `metadata.aliases` as an array of `{locale, name}` objects.
### BCP 47 Locale Tags Reference
| Locale | Tag | Example |
|--------|-----|---------|
| English | `en` | John Smith |
| Traditional Chinese | `zh-TW` | 約翰·史密斯 |
| Simplified Chinese | `zh-CN` | 约翰·史密斯 |
| Japanese | `ja` | ジョン・スミス |
| Korean | `ko` | 존 스미스 |
| Cantonese | `yue` | 約翰·史密夫 |
| French | `fr` | John Smith (French spelling) |
| Spanish | `es` | Juan Smith |
| Arabic | `ar` | جون سميث |
| Russian | `ru` | Джон Смит |
| Thai | `th` | จอห์น สมิธ |
BCP 47 is the IETF standard for language tags. Format: `language` (e.g. `en`, `ja`) or `language-Region` (e.g. `zh-TW`, `zh-CN`).
### Frontend Display Logic
```javascript
function getDisplayName(identity, preferredLocale) {
const match = identity.metadata?.aliases?.find(a => a.locale === preferredLocale);
if (match) return match.name;
const lang = preferredLocale.split('-')[0];
const langMatch = identity.metadata?.aliases?.find(a => a.locale.startsWith(lang));
if (langMatch) return langMatch.name;
return identity.name;
}
```
### Updating Aliases via PATCH
```json
PATCH /api/v1/identity/:identity_uuid
{
"metadata": {
"aliases": [
{"locale": "en", "name": "John Smith"},
{"locale": "zh-TW", "name": "約翰·史密斯"}
]
}
}
```
---
*Updated: 2026-05-22*
-317
View File
@@ -1,317 +0,0 @@
<!-- module: media -->
<!-- description: Video streaming & frame extraction -->
<!-- depends: 01_auth -->
## Video Streaming & Frame Extraction
All video streaming endpoints support the following common query parameters:
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `mode` | string | No | `normal` | `normal` or `debug` (draws detection overlays) |
| `audio` | string | No | `on` | `on` or `off` |
---
### `GET /api/v1/file/:file_uuid/video`
Stream the full video file with range support for seeking.
**Auth**: Required
**Scope**: file-level
#### Response
- **200**: Video stream (`Content-Type` based on file extension)
- **206**: Partial content (range request)
- Supports `Range` header for seeking
---
### `GET /api/v1/file/:file_uuid/trace/:trace_id/video`
Stream video with highlights for a specific face trace (follows a single person across frames with bounding box overlay).
**Auth**: Required
**Scope**: file-level
---
### `GET /api/v1/file/:file_uuid/trace/:trace_id/representative-face`
Find the best single face to represent this trace. Uses a two-stage selection: SQL (area × confidence → top 10) then FFmpeg `blurdetect` (sharpness → pick the least blurry).
**Auth**: Required
**Scope**: file-level
#### Example
```bash
curl -s "$API/api/v1/file/$FILE_UUID/trace/1939/representative-face" \
-H "X-API-Key: $KEY"
```
#### Response (200)
```json
{
"success": true,
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"trace_id": 1939,
"face_count": 538,
"representative": {
"frame_number": 68193,
"timestamp_secs": 2727.72,
"bbox": { "x": 347, "y": 378, "width": 427, "height": 427 },
"confidence": 0.760,
"quality_score": 138516,
"blur_score": 9.46
}
}
```
#### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `trace_id` | integer | Face trace ID |
| `face_count` | integer | Total face detections in this trace |
| `representative.frame_number` | integer | Frame number of the selected face (primary coordinate) |
| `representative.timestamp_secs` | float | Time in seconds (derived from `frame_number / fps`) |
| `representative.bbox` | object | Bounding box `{x, y, width, height}` |
| `representative.confidence` | float | Detection confidence (0.0–1.0) |
| `representative.quality_score` | float | Pre-selection score (`area × confidence`) |
| `representative.blur_score` | float | FFmpeg blurdetect result (lower = sharper) |
#### Error Responses
---
### `GET /api/v1/file/:file_uuid/trace/:trace_id/thumbnail`
Extract the best face image for a trace as JPEG (320×320). Internally selects the face using the same two-stage algorithm as `representative-face`, then crops via FFmpeg. The result is cacheable for 24 hours.
**Auth**: Required
**Scope**: file-level
#### Example
```bash
curl -s "$API/api/v1/file/$FILE_UUID/trace/1939/thumbnail" \
-H "X-API-Key: $KEY" -o trace_1939_face.jpg
```
#### Response
- **200**: `image/jpeg` binary data (320×320 cropped face)
- **404**: File, trace not found, or no suitable face
- **500**: FFmpeg or database error
---
### `GET /api/v1/file/:file_uuid/identities/:identity_uuid_a/co-occur-with/:identity_uuid_b`
Find the first frame where two identities appear together, with representative face thumbnails for both.
**Auth**: Required
**Scope**: file-level
#### Example
```bash
# Audrey Hepburn & Cary Grant 第一次同框
curl -s "$API/api/v1/file/$FILE_UUID/identities/$AUDREY_UUID/co-occur-with/$CARY_UUID" \
-H "X-API-Key: $KEY" | jq '{identity_a: .identity_a.name, identity_b: .identity_b.name, first_frame: .first_cooccurrence.frame_number}'
```
#### Response (200)
```json
{
"success": true,
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"identity_a": {
"identity_uuid": "c3545906-c82d-4b66-aa1d-150bc02decce",
"name": "Audrey Hepburn",
"trace_id": 920
},
"identity_b": {
"identity_uuid": "2b0ddefe-e2a9-4533-9308-b375594604d5",
"name": "Cary Grant",
"trace_id": 919
},
"first_cooccurrence": {
"frame_number": 38165,
"timestamp_secs": 1526.60,
"total_cooccurrence_frames": 3136,
"representative_face_a": {
"frame_number": 38199,
"bbox": { "x": 122, "y": 339, "width": 176, "height": 176 },
"confidence": 0.832,
"thumbnail_url": "/api/v1/file/aeed71342.../trace/920/thumbnail"
},
"representative_face_b": {
"frame_number": 38291,
"bbox": { "x": 511, "y": 315, "width": 192, "height": 192 },
"confidence": 0.791,
"thumbnail_url": "/api/v1/file/aeed71342.../trace/919/thumbnail"
}
}
}
```
#### Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `identity_a.name` | string | First identity name |
| `identity_b.name` | string | Second identity name |
| `first_cooccurrence.frame_number` | int | First frame where both appear |
| `first_cooccurrence.timestamp_secs` | float | Time in seconds |
| `first_cooccurrence.total_cooccurrence_frames` | int | Total frames with both present |
| `first_cooccurrence.representative_face_a/b` | object | Best face thumbnail data for each identity |
#### Error Responses
| HTTP | When |
|------|------|
| `404` | File or identity not found |
| `404` | The two identities never co-occur in this file |
| `500` | Database or FFmpeg error |
### `GET /api/v1/file/:file_uuid/video/bbox`
Stream video with bounding box overlay for all detected objects/faces.
**Auth**: Required
**Scope**: file-level
Uses a built-in 5×7 bitmap font renderer to draw labels directly on video frames via FFmpeg `drawtext` filter.
---
### `GET /api/v1/file/:file_uuid/thumbnail`
Extract a single frame from a video as JPEG image. Uses FFmpeg `select` filter.
**Auth**: Required
**Scope**: file-level
#### Query Parameters
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `frame` | integer | Yes | — | Zero-based frame number to extract |
| `x` | integer | No | — | Crop start X (left edge). Requires `y`, `w`, `h`. |
| `y` | integer | No | — | Crop start Y (top edge). Requires `x`, `w`, `h`. |
| `w` | integer | No | — | Crop width in pixels. Requires `x`, `y`, `h`. |
| `h` | integer | No | — | Crop height in pixels. Requires `x`, `y`, `w`. |
All four crop params (`x`, `y`, `w`, `h`) must be provided together or omitted.
#### Example
```bash
# Extract frame 1000 (full frame)
curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000" \
-H "Authorization: Bearer $JWT" -o frame_1000.jpg
# Extract and crop face region (x=320, y=240, w=160, h=160)
curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000&x=320&y=240&w=160&h=160" \
-H "Authorization: Bearer $JWT" -o face_crop.jpg
```
#### Response
- **200**: `image/jpeg` binary data
- **404**: File not found
- **500**: FFmpeg error (e.g., frame number exceeds video duration)
### `GET /api/v1/file/:file_uuid/clip`
Extract a video clip (time range) as MPEG-TS stream. Uses FFmpeg `-ss` fast seek.
**Auth**: Required
**Scope**: file-level
#### Query Parameters
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `start_frame` | integer | No* | — | Start frame (zero-based). **Frame-accurate** — use this for precision. |
| `end_frame` | integer | No* | — | End frame (zero-based, inclusive). Requires `start_frame`. |
| `start_time` | float | No* | — | Start time in seconds. Approximate (FPS-dependent). Fallback if frames not given. |
| `end_time` | float | No* | — | End time in seconds. Approximate (FPS-dependent). Fallback if frames not given. |
| `fps` | float | No | video FPS | Override frames-per-second for frame↔time calculation. Defaults to video's detected FPS. |
| `mode` | string | No | `normal` | `normal` or `debug` (draws "CLIP" overlay) |
| `audio` | string | No | `on` | `on` or `off` |
Either (`start_frame`+`end_frame`) OR (`start_time`+`end_time`) must be provided.
#### Example
```bash
# Clip by frame range (primary)
curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_frame=0&end_frame=47" \
-H "Authorization: Bearer $JWT" -o clip.ts
# Clip by time range (fallback)
curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_time=30&end_time=45" \
-H "Authorization: Bearer $JWT" -o clip.ts
```
#### Response
- **200**: `video/mp2t` MPEG-TS stream
- **400**: Missing/invalid range parameters
- **404**: File not found
- **500**: FFmpeg error
#### Technical Notes
| Detail | Value |
|--------|-------|
| **Backend** | FFmpeg (`ffmpeg-full`) |
| **Seek** | `-ss` before `-i` (fast keyframe seek) |
| **Format** | MPEG-TS (`mpegts` muxer, pipe-safe) |
| **Codec** | H.264 + AAC |
| **Cache** | `Cache-Control: public, max-age=86400` (24h) |
### Video vs Clip: Quality & Format Comparison
Both endpoints support time range extraction, but serve different use cases:
| Feature | `/video` | `/clip` |
|---------|----------|---------|
| **No params** | Streams full file (Range seek) | Returns 400 (params required) |
| **HTTP Range** | ✅ Supported | ❌ Not supported |
| **Encoding** | `-c copy` (zero encoding) | `-c:v libx264 -c:a aac` (re-encode) |
| **Quality** | Original (bit-exact, zero loss) | Compressed (default CRF ≈ 23) |
| **Format** | `video/mp4` | `video/mp2t` (MPEG-TS) |
| **Speed** | Fast (no computation) | Slower (encoding required) |
| **Frame control** | Time-based (`dur = (ef-sf)/fps`) | Precise (`-vframes`) |
| **Debug mode** | ❌ | ✅ `mode=debug` overlay |
| **Cache** | ❌ | ✅ `max-age=86400` |
#### Usage Recommendation
| Scenario | Use |
|----------|-----|
| Full video streaming / player seek | `/video` |
| Quick preview clip (zero quality loss) | `/video?start_frame=...&end_frame=...` |
| Debug frame verification / text overlay | `/clip?mode=debug` |
| Precise frame count control | `/clip` |
| CDN cacheable clip | `/clip` |
---
| Detail | Value |
|--------|-------|
| **Backend** | FFmpeg (`ffmpeg-full`) |
| **Filter** | `select=eq(n\,FRAME)` to select frame, optional `crop=W:H:X:Y` |
| **Output** | Single JPEG via pipe (`image2pipe`, `mjpeg` codec) |
| **Cache** | `Cache-Control: public, max-age=86400` (24h) |
| **Frame number** | Zero-based (`frame=0` = first frame of video) |
---
*Updated: 2026-05-19 12:49:24*
-224
View File
@@ -1,224 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "doc_wasm"
version = "0.1.0"
dependencies = [
"pulldown-cmark",
"serde",
"serde_json",
"wasm-bindgen",
]
[[package]]
name = "getopts"
version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
dependencies = [
"bitflags",
"getopts",
"memchr",
"pulldown-cmark-escape",
"unicase",
]
[[package]]
name = "pulldown-cmark-escape"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "wasm-bindgen"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
dependencies = [
"unicode-ident",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
-18
View File
@@ -1,18 +0,0 @@
[package]
name = "doc_wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
pulldown-cmark = "0.11"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[profile.release]
lto = true
opt-level = "s"
strip = true
-29
View File
@@ -1,29 +0,0 @@
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn render_markdown(md: &str) -> String {
let parser = pulldown_cmark::Parser::new(md);
let mut html = String::new();
pulldown_cmark::html::push_html(&mut html, parser);
// wrap tables
html = html.replace("<table>", "<table class=\"table\">");
html
}
#[wasm_bindgen]
pub fn module_list() -> String {
serde_json::to_string(&[
("01_auth", "安全認證", "Authentication"),
("02_health", "健康檢查", "Health"),
("03_register", "檔案註冊", "File Registration"),
("04_lookup", "檔案屬性查詢", "File Lookup"),
("05_process", "處理流程", "Processing"),
("06_search", "搜尋功能", "Search"),
("07_identity", "身份識別", "Identity"),
("08_identity_agent", "智能身份綁定", "Smart Identity Binding"),
("08_media", "串流與截圖", "Streaming & Thumbnails"),
("09_tmdb", "TMDb 整合", "TMDb Integration"),
("10_pipeline", "生產線", "Pipeline"),
("12_agent", "智慧代理", "AI Agents"),
]).unwrap_or_default()
}
@@ -2,20 +2,8 @@
| 項目 | 內容 | | 項目 | 內容 |
|------|------| |------|------|
| 建立者 | Warren | | 版本 | V1.1 |
| 建立時間 | 2026-03-18 | | 日期 | 2026-03-25 |
| 文件版本 | V1.3 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| 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 |
--- ---
@@ -28,34 +16,9 @@
--- ---
## 認證
除健康檢查端點外,所有 API 端點都需要 API Key。
### Header 方式
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos
```
### 響應
- `401 Unauthorized` - 缺少或無效的 API Key
- `200 OK` - 認證成功
### 取得 API Key
使用 CLI 建立:
```bash
./target/release/momentry api-key create "My API Key" --key-type user
```
---
## 端點列表 ## 端點列表
### 健康檢查(公開) ### 健康檢查
| 方法 | 端點 | 說明 | | 方法 | 端點 | 說明 |
|------|------|------| |------|------|------|
@@ -82,7 +45,6 @@ curl http://localhost:3002/health
```bash ```bash
curl -X POST http://localhost:3002/api/v1/search \ curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"query": "test", "limit": 10}' -d '{"query": "test", "limit": 10}'
``` ```
@@ -90,7 +52,6 @@ curl -X POST http://localhost:3002/api/v1/search \
```bash ```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \ curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"query": "test", "limit": 10}' -d '{"query": "test", "limit": 10}'
``` ```
@@ -110,29 +71,13 @@ curl -X POST http://localhost:3002/api/v1/n8n/search \
```bash ```bash
curl -X POST http://localhost:3002/api/v1/register \ curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"path": "/path/to/video.mp4"}' -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 ```bash
curl -X POST http://localhost:3002/api/v1/probe \ curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"path": "./demo/video.mp4"}' -d '{"path": "./demo/video.mp4"}'
``` ```
@@ -169,61 +114,17 @@ curl -X POST http://localhost:3002/api/v1/probe \
**列出影片**: **列出影片**:
```bash ```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos curl http://localhost:3002/api/v1/videos
``` ```
**查詢影片**: **查詢影片**:
```bash ```bash
curl -H "X-API-Key: your-api-key" "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7" curl "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
``` ```
**處理進度**: **處理進度**:
```bash ```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/progress/5dea6618a606e7c7 curl 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
```
---
### 系統管理
| 方法 | 端點 | 說明 |
|------|------|------|
| POST | `/api/v1/config/cache` | 切換快取功能(管理員) |
| POST | `/api/v1/unregister` | 刪除影片及其所有資料(管理員) |
**快取設定**:
```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}'
```
**刪除影片**:
```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"}'
``` ```
--- ---
@@ -239,9 +140,6 @@ curl -X POST http://localhost:3002/api/v1/unregister \
| 列出影片 | ✓ | ✓ | ✓ | | 列出影片 | ✓ | ✓ | ✓ |
| 查詢影片 | ✓ | ✓ | ✓ | | 查詢影片 | ✓ | ✓ | ✓ |
| 處理進度 | ✓ | ✓ | ✓ | | 處理進度 | ✓ | ✓ | ✓ |
| 工作管理 | ✓ | ✓ | ✓ |
| 快取設定 | ✓ (管理員) | ✓ (管理員) | ✓ (管理員) |
| 刪除影片 | ✓ (管理員) | ✓ (管理員) | ✓ (管理員) |
--- ---
@@ -261,7 +159,7 @@ curl -X POST http://localhost:3002/api/v1/unregister \
"title": "Chunk sentence_0001", "title": "Chunk sentence_0001",
"text": "...", "text": "...",
"score": 0.92, "score": 0.92,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4" "media_url": "https://wp.momentry.ddns.net/video.mp4"
} }
] ]
} }
@@ -315,7 +213,5 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
## 相關文件 ## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點) - [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點)
- [API_EXAMPLES.md](./API_EXAMPLES.md) - **完整範例總覽(curl / n8n / WordPress)** - [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 詳細指南 - [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 使用範例
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 詳細指南
- [API_CURL_EXAMPLES.md](./API_CURL_EXAMPLES.md) - curl 範例
-133
View File
@@ -1,133 +0,0 @@
# ASR Model Selection Report
**Date:** 2026-05-10
**Video:** Charade (1963), 113min
**Test setup:** faster-whisper on M5 MacBook Pro (Apple Silicon, CPU int8)
## Test Clips
| Clip | Time range | Duration | Characteristics |
|------|-----------|----------|-----------------|
| A — Rapid | 25:40–28:40 | 3 min | Fast back-and-forth dialogue, Cary & Audrey |
| B — Normal | 10:00–13:00 | 3 min | Normal conversation pace |
| C — Complex | 73:20–76:20 | 3 min | Multi-person scene, background audio |
## Test Matrix
| Variable | Values |
|----------|--------|
| Model | tiny, base, small, medium, large-v3 |
| VAD min_silence | 200ms, 500ms |
| Beam size | 5 (fixed) |
## Results Summary
### Clip A — Rapid Dialogue
| Model | VAD | Segments | Chars | Runtime | Δ chars vs best |
|-------|-----|----------|-------|---------|-----------------|
| tiny | 200 | **55** | **1618** | **4.8s** | — |
| tiny | 500 | **59** | 1582 | **4.8s** | −36 |
| base | 200 | 50 | 1543 | 9.7s | −75 |
| base | 500 | 51 | 1547 | 11.6s | −71 |
| small | 200 | 47 | 1538 | 15.0s | −80 |
| small | 500 | 47 | 1538 | 14.5s | −80 |
| medium | 200 | 45 | 1241 | 34.0s | −377 |
| medium | 500 | 45 | 1241 | 34.9s | −377 |
| large-v3 | 200 | 14 | 916 | 42.1s | −702 |
| large-v3 | 500 | 14 | 916 | 42.0s | −702 |
**Winner: tiny** — 55–59 segments, most text captured, 4.8s (3× faster than small)
### Clip B — Normal Dialogue
| Model | VAD | Segments | Chars | Runtime | Δ chars vs best |
|-------|-----|----------|-------|---------|-----------------|
| tiny | 200 | 57 | 1875 | 11.9s | −40 |
| tiny | 500 | **59** | 1801 | 10.9s | −114 |
| base | 200 | 23 | 1695 | **5.1s** | −220 |
| base | 500 | 23 | 1695 | **5.1s** | −220 |
| small | 200 | **62** | 1731 | 15.7s | −184 |
| small | 500 | **62** | 1731 | 16.4s | −184 |
| medium | 200 | 59 | 1758 | 44.9s | −157 |
| medium | 500 | 59 | 1758 | 44.8s | −157 |
| large-v3 | 200 | 32 | **1915** | 95.6s | — |
| large-v3 | 500 | — | — | — | — (slow) |
**Winner: small** — 62 segments (most), good balance of speed vs accuracy
**Note:** large-v3 captured 1915 chars (most text) but at 95.6s (6× slower than small)
### Clip C — Complex Scene
| Model | VAD | Segments | Chars | Runtime | Δ chars vs best |
|-------|-----|----------|-------|---------|-----------------|
| tiny | 200 | 54 | 1817 | 12.2s | −336 |
| tiny | 500 | 52 | 1788 | 10.5s | −365 |
| base | 200 | 51 | 2018 | 10.1s | −135 |
| base | 500 | 51 | 2006 | 9.2s | −147 |
| small | 200 | **64** | 1902 | 22.5s | −251 |
| small | 500 | 61 | **2041** | 21.2s | −112 |
| medium | 200 | 57 | 2044 | 999.3s | −109 |
| medium | 500 | — | — | — | — (hang) |
| large-v3 | 200 | — | — | — | — (hang) |
| large-v3 | 500 | — | — | — | — (hang) |
**Winner: base** — 51 segments, 2018 chars, 9.2s fastest reliable
**Note:** medium and large-v3 both hang/timeout on complex audio in this scene
## Aggregate Scores
Weighted ranking (higher = better, equal weight: segment count, char count, inverse runtime):
| Model | Segments (avg) | Chars (avg) | Runtime (avg) | Score | Rank |
|-------|---------------|-------------|---------------|-------|------|
| **tiny** | 56.0 | 1730 | **9.2s** | **8.5** | 🥇 |
| **small** | 54.7 | 1704 | 17.6s | **7.8** | 🥈 |
| base | 41.5 | 1751 | 10.1s | 7.0 | 🥉 |
| medium | 51.5 | 1627 | 339.6s | 3.5 | 4 |
| large-v3 | 20.0 | 1249 | 68.8s | 2.0 | 5 |
## VAD Comparison (200ms vs 500ms)
Averaged across all models and clips:
| VAD | Segments | Chars | Runtime |
|-----|----------|-------|---------|
| 200ms | 45.9 | 1683 | 86.1s |
| 500ms | 46.6 | 1685 | 69.2s |
**Difference:** Negligible. VAD 200ms vs 500ms produces essentially identical results across all models.
## Conclusions
### 1. Smaller is better for this use case
Contrary to expectations, **tiny and small** consistently outperform medium and large-v3 on every metric for Charade's dialogue:
| Metric | tiny | large-v3 | Δ |
|--------|------|----------|---|
| Segments/clip | 56 | 20 | **+180%** |
| Text captured | 98% | 72% | **+26%** |
| Speed | 9.2s | 68.8s | **7.5× faster** |
### 2. Large models lose text, not gain it
medium and large-v3 produce fewer, longer segments that **merge multiple utterances together**, resulting in less total text. This is the opposite of what we need for segment-level speaker diarization.
### 3. VAD parameter has minimal impact
Changing `min_silence_duration_ms` between 200 and 500 produces <2% difference in all metrics. The current default (500ms) is fine.
### 4. Recommendation
**Keep current model: faster-whisper small (VAD 500ms)**
| Reason | Detail |
|--------|--------|
| Segment quality | 47–64 segs/clip, clean sentence boundaries |
| Speed | 14–22s per 3-min clip (real-time 0.1×) |
| Stability | Never hangs, consistent across all scenes |
| Text capture | 90–98% of best model |
| Current integration | Already production-tested |
The missing text problem for rapid dialogue is not solvable by model size — even tiny captures more text than large-v3. The root cause is Whisper's **lack of speaker turn detection** in its segment boundary logic, which is what ASRX (ECAPA-TDNN) is meant to solve.
-133
View File
@@ -1,133 +0,0 @@
# ASR Segmentation Enhancement Report
**Date:** 2026-05-10
**Movie:** Charade (1963), 113 min
**Goal:** Fix merged-speaker segments in ASR output by detecting speaker change points within ASR segments.
## Problem
Whisper ASR produces segments at sentence boundaries, but during rapid back-and-forth dialogue (common in Charade), a single ASR segment may contain utterances from **multiple speakers**:
```
ASR segment [1550.0-1554.0] (4.0s):
"What's she saying now?"
Actual dialogue:
1552.7: Audrey: "What's she saying now?"
1553.4: Cary: "That she's innocent."
```
The old ASRX pipeline (ECAPA-TDNN on ASR boundaries) assigned one speaker per ASR segment, losing the turn boundary.
## Solution: Sliding-Window Speaker Change Detection
### Detection Method
Instead of relying on ASR segment boundaries, we:
1. **Slide a 1.5s window (0.75s stride)** across the entire audio
2. **Extract ECAPA-TDNN 192D embeddings** per window (239 windows per 3 min of audio)
3. **Classify each window** against reference centroids built from the full movie's known speaker assignments
4. **Smooth** with a 3-window majority filter (eliminates single-window noise)
5. **Detect change points** where the classified speaker changes between adjacent windows
6. **Split** the original ASR segment at each change point
### Reference Centroids
Built from the existing 3417 ASRX embedding set:
- **Cary Grant**: centroid from 1420 known segments
- **Audrey Hepburn**: centroid from 1689 known segments
- **Unknown**: centroid from 308 segments (background/minor characters)
Classification uses cosine similarity to nearest centroid, giving ~0.8+ similarity for main characters.
### Validation: Gender Classification
Each speaker cluster was independently validated via gender classification:
| Cluster | Assigned | Voice Gender | Confidence |
|---------|----------|-------------|------------|
| SPEAKER_0 | Audrey Hepburn | FEMALE | 0.71 |
| SPEAKER_1 | Cary Grant | MALE | 0.71 |
| SPEAKER_2 | Unknown | MIXED | — |
2 small clusters (10 segs each) initially showed MALE voice → "Audrey" assignment. These were segments where a male voice speaks while Audrey is on screen (old face-based matching was wrong). The fine-grained segmentation correctly resolves these.
### Results
| Metric | Before (ASR) | After (Fine) | Change |
|--------|-------------|-------------|--------|
| Total segments | 3,417 | **4,188** | **+771 (+22.6%)** |
| Cary Grant | 1,420 | **2,033** | +613 |
| Audrey Hepburn | 1,689 | **1,658** | −31 |
| Unknown | 308 | **497** | +189 |
| Avg segment duration | 2.0s | **1.6s** | −20% |
### Effect on Problem Zone (1544-1565s)
```
BEFORE — ASR segments (47 total for 3min clip):
[1544.0-1546.0] "Who's that with the hat?" → single speaker
[1546.0-1548.0] "That's the policeman." → single speaker
[1548.0-1550.0] "He wants to arrest Judy for Punch." → single speaker
[1550.0-1554.0] "What's she saying now?" → merged! multiple speakers
[1554.0-1557.5] "That she's innocent. She didn't do it." → merged
[1557.5-1560.7] "Oh, she did it all right." → merged
...
AFTER — Fine segments (64 total for 3min clip):
[1550.3-1551.0] "He wants to arrest Judy..." → Audrey Hepburn
[1552.7-1553.4] "What's she saying now?" → Audrey Hepburn
[1553.4-1554.2] "now? That" → Cary Grant
[1554.2-1559.3] "That she's innocent. She didn't..." → Cary Grant
[1559.3-1560.5] "Oh, she did it all right." → Audrey Hepburn
[1560.5-1561.6] "right. I" → Cary Grant
[1561.6-1562.8] "I believe her." → Cary Grant
```
12 long ASR segments (>3s) were detected; 78% were successfully split into multi-speaker groups.
### Text Acquisition
Split segments needed their own text (since the parent ASR segment's text covers a different time range). Three approaches were tested:
1. **Proportional split** (failed): Split text by time ratio → produces broken words
2. **Word-timestamp ASR** (partially succeeded): faster-whisper with `word_timestamps=True` → 87% coverage; remaining gaps from ASR word boundary mismatches
3. **Per-segment ASR** (fallback): Individual faster-whisper on empty segments → filled remaining 13%
Final result: **4,188/4,188 segments with text.**
### Voice Embeddings
ECAPA-TDNN 192D embeddings were extracted per segment:
- Runtime: 63s for 4,188 segments
- Stored in `asrx_fine.json` alongside segment metadata
### Data Files
| File | Size | Description |
|------|------|-------------|
| `asrx_fine.json` | ~45 MB | 4,188 fine segments + 4,188 embeddings |
| `asrx_fine.json → segments[].speaker_name` | — | Centroid-matched identity |
| `asrx_fine.json → segments[].speaker_id` | — | SPEAKER_0/1/2 |
| `asrx_fine.json → segments[].text` | — | ASR text (word-timestamp mapped) |
| `asrx_fine.json → embeddings[]` | — | 192D ECAPA-TDNN per segment |
### Continued Limitations
1. **Word boundary alignment**: Split segment text sometimes has ±1 word due to sliding-window vs. ASR boundary mismatch (cosmetic, not semantic)
2. **ASR merge in silence zones**: Very short utterances (<0.5s) merged into adjacent segments
3. **Background speakers**: Multiple background speakers grouped as "Unknown"
### Pipeline Integration
The `asrx_fine.json` file serves as the new ASRX output. The original `asr.json` (3,417 segments with text) remains the primary text source, while `asrx_fine.json` provides superior speaker diarization at 4,188 segments.
Speaker assignments in DB `dev.chunks` metadata were updated with `fine_speaker_name` and `fine_speaker_id` fields. Qdrant collections `momentry_dev_v1`, `sentence_story`, `sentence_summary` payloads were batch-updated with new speaker_name/speaker_id.
### Hardware & Performance
- Machine: M5 MacBook Pro, 48GB, Apple Silicon
- Model: faster-whisper small (int8 CPU)
- Embedding: ECAPA-TDNN via SpeechBrain
- Total processing time: ~5 min for the full 113-min movie
@@ -1,21 +1,3 @@
---
document_type: "operation_doc"
service: "MOMENTRY_CORE"
title: "Momentry 備份版本管理規範"
date: "2026-03-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "備份版本管理規範"
ai_query_hints:
- "查詢 Momentry 備份版本管理規範 的內容"
- "Momentry 備份版本管理規範 的主要目的是什麼?"
- "如何操作或實施 Momentry 備份版本管理規範?"
---
# Momentry 備份版本管理規範 # Momentry 備份版本管理規範
| 項目 | 內容 | | 項目 | 內容 |
-255
View File
@@ -1,255 +0,0 @@
# Charade 臉部匹配經驗總結
## 背景
Charade (1963) 影片 `a6fb22eebefaef17e62af874997c5944` 有 62,298 個人臉偵測結果,分布在 4,378 個 trace 中(TKG face tracker 輸出)。目標是將每張臉匹配到正確的 TMDb 演員 identity。
## 問題
### 1. Rust Pipeline (`face_agent.rs`) 的 Snowball 效應
原始 pipeline 透過多輪 propagation 來匹配:
- Seed embedding 匹配 → propagation rounds (2-10 輪)
- 每輪把已匹配的 face 當作新 seed 繼續擴散
- 結果:**Antonio Passalia 被匹配 18,821 張臉**(實際應 < 50)
- 原因:propagation 會放大初始匹配中的假陽性
### 2. Dev 資料庫污染
`dev` schema 的 `identity_bindings` 表:
- 所有 trace-type binding 的 `file_uuid` 都是 NULL(12,828 行)
- 這些 binding 只對應已刪除的 CCBN 檔案 (`63acd3bb`)
- **完全無法用於 sync 到 public schema**
### 3. TMDb Seed Embedding 品質不均
22/23 個 TMDb identity 有 face_embedding(Thomas Chelimsky 因無 TMDb 照片而缺少)。但這些 seed 來自單一 TMDb 照片,品質差異大:
| Identity | Seed 品質 | 問題 |
|----------|:---------:|:----:|
| Audrey Hepburn | ✅ 高 | 特徵明顯,易區分 |
| Cary Grant | ✅ 中 | 但 Charade 造型與 seed 照片有差異 |
| Walter Matthau | ❌ 低 | Seed 照片與 Charade 形象差異大 |
| Bernard Musson | ❌ 泛用 | 「典型白人男性」— seed 太泛用 |
| Antonio Passalia | ❌ 泛用 | 同上 |
## 解決方案演進
### V1:直接 pgvector 比對 (threshold 0.50)
```sql
CROSS JOIN LATERAL (
SELECT i.id FROM identities i
WHERE 1 - (embedding <=> i.face_embedding) >= 0.50
ORDER BY 1 - (embedding <=> i.face_embedding) DESC LIMIT 1
)
```
**結果**:17,066 匹配 (27.4%)
- ✅ Audrey 9,550 (正確)
- ✅ Antonio 降為 151 (不再 snowball)
- ❌ Bernard Musson 847/Paul Bonifas 273 — generic seed 假陽性
- ❌ trace-level 衝突(同一 trace 多個 identity)
- ❌ Walter Matthau 僅 535(seed 不準導致 recall 低)
### V2:Trace Conflict Cleanup
在 V1 之後,對每個 conflict trace 做多數決 → 清除 minority identity。
**結果**:移除 836 個污染臉
- ✅ trace-level 衝突降為 0
- ❌ Bernard Musson 仍保留 847(trace 內獨佔)
- ❌ 無法解決 generic seed 的根本問題
### V3:雙階段 Centroid Matching
設計:
```
Phase 1: Seed matching @ 0.55 (stricter) → 乾淨 base set
Phase 2: Centroid matching @ 0.45 → 用電影內平均臉擴張 recall
```
**結果**:27,375 匹配 (43.9%) → trace cleanup → 24,286 (39.0%)
- ✅ Audrey 11,347 (+19%)
- ✅ Cary Grant 3,107 (+56%)
- ✅ Walter Matthau 1,200 (+124%) — centroid 修正 seed!
- ❌ **Bernard Musson 2,903 (+243%)** — centroid 放大 generic seed
- ❌ **Antonio Passalia 898 (+642%)** — 同上
**教訓**:Generic seed 的 centroid 更泛用。Phase 2 的低 threshold 讓問題惡化。
### V4:雙重驗證 (Dual Gate)
在 V3 的 Phase 2 加上 seed_sim >= 0.40 條件:
```
centroid_sim >= 0.45 AND seed_sim >= 0.40
```
**結果**:23,023 匹配 → gap cleanup → trace cleanup → **22,548 (36.2%)**
- ✅ Bernard / Paul / Antonio / Michel / Clément / Raoul / Roger 仍偏高但 avg_seed_sim 改善
### V5(最終版):排除 7 個 Generic Identity
核心洞察:**與其過濾假陽性,不如不讓 generic seed 參賽**。
只保留 11 個可靠的 TMDb identity,排除 7 個:
- 排除:Bernard Musson · Paul Bonifas · Michel Thomass · Antonio Passalia · Clément Harari · Raoul Delfosse · Roger Trapp
- 保留:Audrey · Cary · James Coburn · Jacques Marin · Walter Matthau · George Kennedy · Dominique Minot · Monte Landis · Stanley Donen · Ned Glass · Louis Viret
流程:
```
1. Clear all assignments
2. Phase 1 @ 0.55 — only against 11 identities
3. Compute centroids
4. Phase 2 — centroid>=0.45 AND seed>=0.40 (11 centroids)
5. Ambiguity gate (top2 gap < 0.04 → NULL)
6. Trace conflict cleanup
```
**最終結果**:
| Identity | 最終 faces | traces | fpt | avg_sim |
|----------|:----------:|:------:|:---:|:-------:|
| Audrey Hepburn | 11,325 | 438 | 25.9 | 0.608 |
| Cary Grant | **5,101** ≪ 大幅增加 | 269 | 19.0 | 0.497 |
| James Coburn | 1,508 | 92 | 16.4 | 0.588 |
| Jacques Marin | 1,438 | 84 | 17.1 | 0.631 |
| Walter Matthau | 1,250 | 55 | 22.7 | 0.494 |
| George Kennedy | 869 | 60 | 14.5 | 0.590 |
| 排除的 7 個 | **0** ✅ | — | — | — |
| Unassigned | 39,750 | — | — | — |
**Cary Grant 從 3,107→5,101 (+64%)**:之前被 Bernard/Antonio 攔截的臉全部釋放。
## 關鍵教訓
### 1. Generic Seed 辨識
可以透過以下指標辨識 generic seed:
- **Phase 1 faces / traces 比例低**(< 5 fpt)
- **被分配到大量短 trace**(表示非連續場景)
- **avg_seed_sim 偏低但 face count 異常高**
### 2. Propagation 是雙面刃
Rust pipeline 的 propagation 可以增加 recall,但前提是 seed 要夠純。Generic seed + propagation = snowball。
### 3. Seed 數量 vs 品質
> 不是 identity 越多越好。11 個好 seed 勝過 22 個(含 7 個壞的)。
壞 seed 會攔截好 seed 的配對。排除壞 seed 後,那些臉自然會配到正確的人。
### 4. Centroid Matching 的適用條件
Centroid matching 只有在以下情況才有效:
- Centroid 來自高信賴的 Phase 1 配對(threshold >= 0.55)
- Centroid 的 Phase 1 base set > 200 faces
- 搭配 seed_sim dual gate 防止 centroid 飄移
### 5. Trace Context 的重要性
- 一個 trace = 同一人(face tracker 保證)
- Trace-level conflict cleanup 是必要的後處理
- 但無法解決 trace 層級以下(同一 trace 內)的 contamination
## 可改進的方向
### 短期
1. **手動檢查 Cary Grant 的 5,101 faces**:avg_sim 0.497 偏低,部分可能是假陽性
2. **補回已被排除的 identity**:對 Bernard Musson 等用更高 threshold(如 0.60 seed)只看能否 match 到少數高信賴臉
3. **降低 Ambiguity Gate threshold**:從 0.04 降到 0.03 可再清除一批邊緣配對
### 中期
4. **多 seed 策略**:對每個 identity 用 3-5 張 TMDb 照片,取 centroid 作為 seed
5. **場景約束**:利用 shot boundary 資訊限制跨場景的 identity 分配
6. **雙向驗證**:同時用 face→identity 和 identity→trace 兩種方向互相驗證
### 長期
7. **取代 pgvector face-level matching**:改用 trace-level embedding(同一 trace 的所有 face 取平均),再對 trace 做 identity 匹配,減少 single-frame noise
## SQL 核心語法
### pgvector Nearest Neighbor
```sql
SELECT fd.id, m.identity_id
FROM eligible fd
CROSS JOIN LATERAL (
SELECT i.id FROM identities i
WHERE 1 - (fd.embedding::vector <=> i.face_embedding) >= {threshold}
ORDER BY 1 - (fd.embedding::vector <=> i.face_embedding) DESC
LIMIT 1
) m
```
### Centroid 計算
```sql
CREATE TABLE centroids AS
SELECT identity_id, AVG(embedding::vector) as centroid
FROM face_detections
WHERE file_uuid = '{uuid}' AND identity_id IS NOT NULL
GROUP BY identity_id
HAVING COUNT(*) >= 5;
```
### Trace Conflict Cleanup
```sql
WITH conflict_traces AS (
SELECT trace_id FROM face_detections
WHERE file_uuid = '{uuid}' AND identity_id IS NOT NULL
GROUP BY trace_id HAVING COUNT(DISTINCT identity_id) > 1
),
trace_majority AS (
SELECT DISTINCT ON (ct.trace_id) ct.trace_id, fd.identity_id
FROM conflict_traces ct
JOIN face_detections fd ON fd.trace_id = ct.trace_id
WHERE fd.file_uuid = '{uuid}' AND fd.identity_id IS NOT NULL
GROUP BY ct.trace_id, fd.identity_id
ORDER BY ct.trace_id, COUNT(*) DESC
)
UPDATE face_detections fd SET identity_id = NULL
FROM trace_majority tm
WHERE fd.file_uuid = '{uuid}' AND fd.trace_id = tm.trace_id
AND fd.identity_id != tm.identity_id;
```
### Ambiguity Gate
```sql
WITH all_sims AS (
SELECT fd.id, c.identity_id,
1 - (fd.embedding::vector <=> c.centroid) as sim
FROM face_detections fd
CROSS JOIN centroids c
WHERE fd.file_uuid = '{uuid}' AND fd.identity_id IS NOT NULL
),
ranked AS (
SELECT id, sim, LEAD(sim) OVER (PARTITION BY id ORDER BY sim DESC) as sim2
FROM all_sims
),
ambiguous AS (
SELECT id FROM ranked
WHERE rn = 1 AND sim - COALESCE(sim2, 0) < 0.04
)
UPDATE face_detections fd SET identity_id = NULL
FROM ambiguous a WHERE fd.id = a.id;
```
## 資料庫備份
每次關鍵操作都有備份:
| Backup | Rows | 內容 |
|--------|:----:|:------|
| `fd_charade_bak` | 62,298 | 原始無 identity 的 Charade face_detections |
| `fd_state_bak2` | 24,286 | V5 執行前的 assignment snapshot |
| `wp_snippets_backup_20260601_11940.sql` | — | WordPress snippets 備份 |
@@ -1,40 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Video Chunk 切分規範"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "chunk"
- "video"
- "切分規範"
ai_query_hints:
- "查詢 Video Chunk 切分規範 的內容"
- "Video Chunk 切分規範 的主要目的是什麼?"
- "如何操作或實施 Video Chunk 切分規範?"
---
# Video Chunk 切分規範 # Video Chunk 切分規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 系統中影片 chunks 的切分原則與資料結構。 本文檔定義 Momentry Core 系統中影片 chunks 的切分原則與資料結構。
--- ---
@@ -614,518 +579,7 @@ TimeBased Chunks (4 個, 重疊 2秒):
--- ---
## 10. 資料庫儲存 ## 10. 相關文件
### 10.1 PostgreSQL 儲存
#### Table Schema
```sql
CREATE TABLE chunks (
id BIGSERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
start_frame BIGINT NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
end_frame BIGINT NOT NULL,
fps VARCHAR(16) NOT NULL,
fps_value DOUBLE PRECISION NOT NULL,
content JSONB NOT NULL,
metadata JSONB,
vector_id VARCHAR(64),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(uuid, chunk_id)
);
-- 索引
CREATE INDEX idx_chunks_uuid ON chunks(uuid);
CREATE INDEX idx_chunks_type ON chunks(chunk_type);
CREATE INDEX idx_chunks_time ON chunks(start_time, end_time);
CREATE INDEX idx_chunks_uuid_type ON chunks(uuid, chunk_type);
CREATE INDEX idx_chunks_vector_id ON chunks(vector_id);
```
#### 儲存範例
```rust
pub async fn store_chunk_to_postgres(db: &PostgresDb, chunk: &Chunk) -> Result<()> {
sqlx::query!(
r#"
INSERT INTO chunks (
uuid, chunk_id, chunk_index, chunk_type,
start_time, start_frame, end_time, end_frame,
fps, fps_value, content, metadata, vector_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (uuid, chunk_id) DO UPDATE SET
content = EXCLUDED.content,
metadata = EXCLUDED.metadata,
vector_id = EXCLUDED.vector_id,
updated_at = NOW()
"#,
chunk.uuid,
chunk.chunk_id,
chunk.chunk_index as i32,
chunk.chunk_type.as_str(),
chunk.start_time,
chunk.start_frame,
chunk.end_time,
chunk.end_frame,
chunk.fps,
chunk.fps_value,
serde_json::to_value(&chunk.content)?,
serde_json::to_value(&chunk.metadata)?,
chunk.vector_id,
)
.execute(&db.pool)
.await?;
Ok(())
}
```
---
### 10.2 MongoDB 儲存
#### Collection Schema
```javascript
// chunks collection
{
_id: ObjectId,
uuid: "1636719dc31f78ac",
chunk_id: "sentence_0001",
chunk_index: 1,
chunk_type: "sentence",
start_time: 10.5,
start_frame: 252,
end_time: 15.75,
end_frame: 378,
fps: "24/1",
fps_value: 24.0,
content: {
text: "Hello world, this is a test",
text_normalized: "hello world this is a test",
word_count: 7,
char_count: 34
},
metadata: {
source: "asr",
confidence: 0.95,
language: "en"
},
vector_id: "vec_sentence_0001",
created_at: ISODate("2026-03-16T10:00:00Z"),
updated_at: ISODate("2026-03-16T10:00:00Z")
}
// 索引
db.chunks.createIndex({ uuid: 1 })
db.chunks.createIndex({ chunk_type: 1 })
db.chunks.createIndex({ start_time: 1, end_time: 1 })
db.chunks.createIndex({ vector_id: 1 })
db.chunks.createIndex({ uuid: 1, chunk_type: 1 })
```
#### 儲存範例
```rust
pub async fn store_chunk_to_mongodb(db: &MongoDb, chunk: &Chunk) -> Result<()> {
let doc = bson::doc! {
"uuid": chunk.uuid,
"chunk_id": chunk.chunk_id,
"chunk_index": chunk.chunk_index,
"chunk_type": chunk.chunk_type.as_str(),
"start_time": chunk.start_time,
"start_frame": chunk.start_frame,
"end_time": chunk.end_time,
"end_frame": chunk.end_frame,
"fps": chunk.fps,
"fps_value": chunk.fps_value,
"content": serde_json::to_value(&chunk.content)?,
"metadata": serde_json::to_value(&chunk.metadata)?,
"vector_id": chunk.vector_id,
"created_at": chrono::Utc::now(),
"updated_at": chrono::Utc::now()
};
let collection = db.database("momentry").collection("chunks");
collection.update_one(
doc! { "uuid": &chunk.uuid, "chunk_id": &chunk.chunk_id },
doc! { "$set": doc },
UpdateOptions::builder().upsert(true).build(),
).await?;
Ok(())
}
```
---
## 11. 向量儲存設計
### 11.1 設計原則
**統一向量 ID 格式**,確保 Qdrant 與 PostgreSQL 相容:
```
{chunk_type}_{chunk_index:04}
範例:
sentence_0001
cut_0002
time_based_0015
```
### 11.2 Qdrant Collection
#### 建立 Collection
```bash
# 使用 Qdrant client 建立 collection
curl -X PUT http://localhost:6333/collections/chunks \
-H "Content-Type: application/json" \
-H "api-key: Test3200Test3200Test3200" \
-d '{
"vectors": {
"size": 768,
"distance": "Cosine"
}
}'
```
#### Point 結構
```json
{
"id": "sentence_0001",
"vector": [0.123, -0.456, ...],
"payload": {
"uuid": "1636719dc31f78ac",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"chunk_index": 1,
"start_time": 10.5,
"end_time": 15.75,
"text": "Hello world, this is a test",
"metadata": {
"confidence": 0.95,
"language": "en"
}
}
}
```
#### Rust 結構
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorPoint {
pub id: String,
pub vector: Vec<f32>,
pub payload: VectorPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorPayload {
pub uuid: String,
pub chunk_id: String,
pub chunk_type: String,
pub chunk_index: u32,
pub start_time: f64,
pub end_time: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scene_id: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub segment_number: Option<i32>,
pub metadata: Option<serde_json::Value>,
}
```
### 11.3 PostgreSQL Vector 儲存
#### Table Schema
```sql
-- 使用 pgvector 擴展
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE chunk_vectors (
id BIGSERIAL PRIMARY KEY,
vector_id VARCHAR(64) NOT NULL UNIQUE,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
chunk_index INTEGER NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
embedding vector(768) NOT NULL,
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
FOREIGN KEY (uuid, chunk_id) REFERENCES chunks(uuid, chunk_id)
);
-- 向量檢索索引 (IVFFlat)
CREATE INDEX idx_chunk_vectors_embedding
ON chunk_vectors
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 查詢索引
CREATE INDEX idx_chunk_vectors_uuid ON chunk_vectors(uuid);
CREATE INDEX idx_chunk_vectors_type ON chunk_vectors(chunk_type);
```
#### 儲存範例
```rust
pub async fn store_vector_to_postgres(db: &PostgresDb, point: &VectorPoint) -> Result<()> {
sqlx::query!(
r#"
INSERT INTO chunk_vectors (
vector_id, uuid, chunk_id, chunk_type, chunk_index,
start_time, end_time, embedding, metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (vector_id) DO UPDATE SET
embedding = EXCLUDED.embedding,
metadata = EXCLUDED.metadata
"#,
point.id,
point.payload.uuid,
point.payload.chunk_id,
point.payload.chunk_type,
point.payload.chunk_index as i32,
point.payload.start_time,
point.payload.end_time,
point.vector,
serde_json::to_value(&point.payload.metadata)?,
)
.execute(&db.pool)
.await?;
Ok(())
}
```
---
## 12. 查詢範例
### 12.1 語義搜尋 (Semantic Search)
#### 查詢類型 1: 相似文字搜尋
```rust
// 搜尋與問句相似的 chunks
pub async fn semantic_search(
qdrant: &QdrantDb,
query: &str,
limit: usize,
) -> Result<Vec<SearchResult>> {
// 1. 將問句向量化
let query_vector = embed_text(query).await?;
// 2. 搜尋 Qdrant
let results = qdrant.search(
"chunks",
&query_vector,
limit,
Some(&Filter::must([
Condition::Match("chunk_type", "sentence"),
])),
).await?;
Ok(results)
}
// 使用範例
let results = semantic_search(&qdrant, "找出有人在說話的片段", 10).await?;
for r in results {
println!("{}: {:.3}", r.payload.chunk_id, r.score);
println!(" Time: {}s - {}s", r.payload.start_time, r.payload.end_time);
println!(" Text: {:?}", r.payload.text);
}
```
#### 查詢類型 2: 語音/文字混合搜尋
```sql
-- PostgreSQL: 搜尋特定文字的 chunks
SELECT
c.chunk_id,
c.chunk_type,
c.start_time,
c.end_time,
c.content->>'text' as text,
v.embedding <=> query_embedding('找出開車的場景') as similarity
FROM chunks c
LEFT JOIN chunk_vectors v ON c.chunk_id = v.chunk_id
WHERE c.chunk_type = 'sentence'
AND c.content->>'text' ILIKE '%car%'
ORDER BY v.embedding <=> query_embedding('找出開車的場景')
LIMIT 10;
```
### 12.2 時間範圍搜尋
#### 查詢類型 3: 特定時間範圍
```rust
// 找出 30-60 秒之間的所有 chunks
pub async fn search_by_time_range(
db: &PostgresDb,
uuid: &str,
start: f64,
end: f64,
) -> Result<Vec<Chunk>> {
let chunks = sqlx::query_as!(
Chunk,
r#"
SELECT * FROM chunks
WHERE uuid = $1
AND start_time < $3
AND end_time > $2
ORDER BY chunk_type, chunk_index
"#,
uuid, start, end
)
.fetch_all(&db.pool)
.await?;
Ok(chunks)
}
// 使用範例
let chunks = search_by_time_range(&db, "1636719dc31f78ac", 30.0, 60.0).await?;
```
```javascript
// MongoDB: 時間範圍查詢
db.chunks.find({
uuid: "1636719dc31f78ac",
start_time: { $lt: 60 },
end_time: { $gt: 30 }
}).sort({ chunk_type: 1, chunk_index: 1 })
```
### 12.3 混合搜尋 (Hybrid Search)
#### 查詢類型 4: 文字關鍵詞 + 向量相似度
```rust
// 結合關鍵詞匹配與向量相似度
pub async fn hybrid_search(
db: &PostgresDb,
qdrant: &QdrantDb,
query: &str,
keywords: &[&str],
limit: usize,
) -> Result<Vec<HybridResult>> {
// 1. 向量搜尋
let query_vector = embed_text(query).await?;
let vector_results = qdrant.search("chunks", &query_vector, limit * 2, None).await?;
// 2. 關鍵詞過濾
let keyword_filter: Vec<_> = keywords.iter()
.map(|k| format!("%{}%", k))
.collect();
let filtered: Vec<_> = vector_results.into_iter()
.filter(|r| {
if let Some(text) = &r.payload.text {
keyword_filter.iter().any(|k| text.contains(k.as_str()))
} else {
false
}
})
.take(limit)
.collect();
Ok(filtered)
}
```
### 12.4 場景搜尋
#### 查詢類型 5: 找出特定場景
```sql
-- PostgreSQL: 找出特定場景 ID 的 chunks
SELECT * FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'cut'
AND (content->>'scene_id')::int = 5;
-- 找出包含轉場效果的 chunks
SELECT * FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'cut'
AND content->>'transition_type' = 'dissolve';
```
### 12.5 影片摘要
#### 查詢類型 6: 產生影片摘要
```sql
-- 合併影片所有語句
SELECT
string_agg(content->>'text', ' ' ORDER BY start_time) as full_transcript
FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'sentence'
AND content->>'text' IS NOT NULL;
-- 按場景聚合文字
SELECT
content->>'scene_id' as scene,
string_agg(content->>'text', ' ' ORDER BY start_time) as scene_text
FROM chunks
WHERE uuid = '1636719dc31f78ac'
AND chunk_type = 'cut'
GROUP BY content->>'scene_id'
ORDER BY MIN(start_time);
```
### 12.6 常見查詢模式
| 查詢類型 | 描述 | 資料庫 | SQL/程式碼 |
|----------|------|--------|-------------|
| 語義搜尋 | 找相似內容 | Qdrant | `search(vector, limit)` |
| 關鍵詞搜尋 | 精確文字匹配 | PostgreSQL | `ILIKE '%keyword%'` |
| 時間範圍 | 特定時段 | Both | `start_time < end AND end_time > start` |
| 場景搜尋 | 特定鏡頭 | PostgreSQL | `scene_id = N` |
| 混合搜尋 | 向量+關鍵詞 | Both |結合以上兩種 |
| 摘要產生 | 合併文字 | PostgreSQL | `string_agg()` |
---
## 13. 資料庫選擇建議
### 13.1 儲存策略
| 資料類型 | 主要儲存 | 備份/查詢 | 說明 |
|----------|----------|-----------|------|
| **Chunk 元數據** | PostgreSQL | MongoDB | 結構化查詢為主 |
| **向量資料** | Qdrant | PostgreSQL | 向量搜尋為主 |
| **全文檢索** | PostgreSQL | - | 關鍵詞搜尋 |
| **日誌/歷史** | MongoDB | - | 靈活性為主 |
### 13.2 讀寫模式
| 場景 | 寫入 | 讀取 |
|------|------|------|
| **影片處理** | PostgreSQL + Qdrant | - |
| **語義搜尋** | - | Qdrant |
| **時間軸瀏覽** | - | PostgreSQL |
| **系統分析** | MongoDB | MongoDB |
---
## 14. 相關文件
- [JSON_OUTPUT_SPEC.md](./JSON_OUTPUT_SPEC.md) - JSON 輸出規範 - [JSON_OUTPUT_SPEC.md](./JSON_OUTPUT_SPEC.md) - JSON 輸出規範
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範 - [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
@@ -1,40 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core 開發日誌"
date: "2026-03-18"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發日誌"
- "momentry"
- "core"
ai_query_hints:
- "查詢 Momentry Core 開發日誌 的內容"
- "Momentry Core 開發日誌 的主要目的是什麼?"
- "如何操作或實施 Momentry Core 開發日誌?"
---
# Momentry Core 開發日誌 # Momentry Core 開發日誌
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
> **文檔維護開始**:2026-03-18 > **文檔維護開始**:2026-03-18
> **⚠️ 補充說明**:事後補記(2026-03-18 以前),僅供參考。未來紀錄將即時記錄,參考價值較高。 > **⚠️ 補充說明**:事後補記(2026-03-18 以前),僅供參考。未來紀錄將即時記錄,參考價值較高。
@@ -185,7 +150,7 @@ TUI 顯示:
- job:{uuid}:processor:{name} - job:{uuid}:processor:{name}
### 備份系統整合 ### 備份系統整合
參考 `docs_v1.0/IMPLEMENTATION/SERVICE_ADDITION_GUIDE.md` 設計規範,規劃 OutputDir 模組: 參考 `docs/SERVICE_ADDITION_GUIDE.md` 設計規範,規劃 OutputDir 模組:
1. **環境變數**: 1. **環境變數**:
- `MOMENTRY_OUTPUT_DIR` - JSON 輸出目錄 - `MOMENTRY_OUTPUT_DIR` - JSON 輸出目錄
@@ -238,8 +203,8 @@ TUI 顯示:
| 日期 | 檔案 | 說明 | | 日期 | 檔案 | 說明 |
|------|------|------| |------|------|------|
| 2026-03-18 | `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 | | 2026-03-18 | `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 |
| 2026-03-18 | `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md` | 監控規範(暫定) | | 2026-03-18 | `docs/MOMENTRY_CORE_MONITORING.md` | 監控規範(暫定) |
| 2026-03-18 | `scripts/redis_publisher.py` | Redis 訊息發布模組 | | 2026-03-18 | `scripts/redis_publisher.py` | Redis 訊息發布模組 |
### 更新檔案 ### 更新檔案
@@ -248,7 +213,7 @@ TUI 顯示:
|------|------|------| |------|------|------|
| 2026-03-17 | `Cargo.toml` | 新增 player feature | | 2026-03-17 | `Cargo.toml` | 新增 player feature |
| 2026-03-17 | `src/lib.rs` | 新增 ui module exports | | 2026-03-17 | `src/lib.rs` | 新增 ui module exports |
| 2026-03-18 | `docs_v1.0/REFERENCE/PENDING_ISSUES.md` | 新增問題 #2, #3 | | 2026-03-18 | `docs/PENDING_ISSUES.md` | 新增問題 #2, #3 |
| 2026-03-18 | `src/core/storage/output_dir.rs` | 預設改為 `./output` | | 2026-03-18 | `src/core/storage/output_dir.rs` | 預設改為 `./output` |
| 2026-03-18 | `scripts/yolo_processor.py` | 新增 --uuid 參數 + Redis | | 2026-03-18 | `scripts/yolo_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/cut_processor.py` | 新增 Redis | | 2026-03-18 | `scripts/cut_processor.py` | 新增 Redis |
@@ -293,7 +258,7 @@ TUI 顯示:
### 問題 #3: Redis Message Bus 尚未實作 ### 問題 #3: Redis Message Bus 尚未實作
- 狀態:已解決 - 狀態:已解決
- 詳細設計:參考 `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` - 詳細設計:參考 `docs/MOMENTRY_CORE_REDIS_KEYS.md`
- 進度:Python 端 + Rust 端均已完成 - 進度:Python 端 + Rust 端均已完成
--- ---
@@ -457,103 +422,3 @@ cargo run --bin momentry -- process <uuid>
# 查詢進度 # 查詢進度
curl http://127.0.0.1:3002/api/v1/progress/<uuid> curl http://127.0.0.1:3002/api/v1/progress/<uuid>
``` ```
---
## 2026-03-18 (Dashboard)
### Web Dashboard 實作
**目標**:建立 Web 介面監控 momentry_core 處理進度
**技術選擇**:Static HTML + JavaScript (非 WASM)
**實作內容**:
| 元件 | 檔案 | 說明 |
|------|------|------|
| Dashboard | `momentry_dashboard/dist/index.html` | 靜態 HTML 頁面 |
| API 代理 | Caddyfile port 3200 | 反向代理到 API server |
**功能**:
- 影片列表顯示
- 即時進度條 (每 5 秒自動刷新)
- 搜尋功能
- 處理器狀態 (ASR/CUT/YOLO/OCR/Face/Pose)
**訪問**:
- Dashboard: http://localhost:3200
- API: http://localhost:3200/api/v1/*
---
## 發生問題記錄
### HTTP API 問題
1. **語法錯誤** (main.rs)
- 位置:lines 297-322
- 原因:重複的程式碼區塊
- 解決:移除重複區塊
2. **DB 連線池耗盡**
- 原因:預設 5 個連線不足
- 解決:增加到 10 個連線
3. **PostgreSQL shutdown 狀態**
- 原因:共享記憶體未釋放
- 解決:殺掉 stale 連線
### WASM Dashboard 問題
1. **Yew 版本問題**
- 嘗試:yew 0.21 → 0.23
- 問題:feature 名稱變更 (`web-sys` → `web_sys` → `csr`)
- 解決:放棄 WASM,改用靜態 HTML
2. **編譯錯誤**
- `wasm32-unknown-unknown` target 未安裝
- 解決:`rustup target add wasm32-unknown-unknown`
3. **Yew 0.23 API 變更**
- Properties 需要 PartialEq derive
- 多處 API 語法變更
- 放棄 WASM 方案
### Gitea Push 問題
1. **Remote URL 錯誤**
- 原因:使用 localhost:3000 而非 gitea.momentry.ddns.net
- 解決:建立新 repo `momentry_core_0_1`
2. **認證問題**
- SSH key 未授權
- 密碼認證成功推送
### Caddy 設定問題
1. **API 代理順序**
- 問題:try_files 在 reverse_proxy 之前導致 API 回傳 HTML
- 解決:使用 `handle` 區塊明確定義順序
```caddyfile
:3200 {
handle /api/* {
reverse_proxy localhost:3002
}
handle {
root * /Users/accusys/momentry_dashboard/dist
try_files {path} /index.html
file_server
}
}
```
---
## 未來工作
- [ ] 修復 WASM Dashboard (Yew 0.23 相容性)
- [ ] 新增影片播放器整合
- [ ] WebSocket 實時推送
- [ ] 移動端響應式設計
-45
View File
@@ -1,45 +0,0 @@
# 槍枝檢測模型 Charade 評估報告
**Date:** 2026-05-10
**模型:** YOLOv8n fine-tuned on Roboflow gun dataset (905 images)
**Classes:** grenade (0), knife (1), pistol (2), rifle (3)
**Weights:** `models/gun/gun_detector/weights/best.pt` (6MB)
## 訓練
- **Dataset**: 905 images, Roboflow CC BY 4.0
- **Validation mAP50**: 0.813
- **問題**: 訓練資料全為近距離槍枝特寫,與 Charade 電影中的中遠景畫面分布完全不同
## Charade 測試結果
### 系統掃描(24 取樣點 @ 每 300s)
| 時間 | 類別 | 信心 | 判定 |
|------|------|------|------|
| t=600s | pistol×2, rifle | 0.16–0.30 | ❌ FP |
| t=1200s | knife | 0.37 | ❌ FP |
| t=1800s | pistol | 0.19 | ❌ FP |
| t=2400s | knife | 0.18 | ❌ FP |
| t=3000s | pistol | 0.16 | ❌ FP |
| t=5400s | pistol×2 | 0.45, 0.17 | ❌ FP(郵票被誤判為槍) |
| t=6600s | grenade | 0.22 | ❌ FP |
### 密集掃描(ASR trigger)
在 ASR dialogue 提到 "gun" 的時間點附近跑 gun detector,找到 5 個 pistol/gun 觸發(3188s / 5461s / 6309s / 6377s / 6479s),confidence 0.300-0.387。
**結果:全部為 false positive。** 訓練效果非常不好 — 模型在電影中遠景畫面完全失效。
## 結論
1. 訓練資料與推論場景 distribution mismatch 嚴重
2. 905 張 Roboflow 近距離特寫 → Charade 的中遠景手持/部分遮蔽槍枝 → 模型無法泛化
3. 建議:收集電影真實槍枝畫面(200-500 張動作片片段)重新訓練
4. 在此之前,槍枝搜尋只能靠 ASR dialogue keyword matching + 人工確認
## 相關檔案
- `models/gun/gun_detector/weights/best.pt` — 模型權重(效果不佳)
- `output_dev/gun_detections/` — 偵測截圖(全部 FP)
- `scripts/object_search_agent.py` — 整合搜尋 agent(gun detector 偵測結果僅供參考)
-73
View File
@@ -1,73 +0,0 @@
# Gun Detector Scan Report — YOLOv8n on Charade (1963)
**Date:** 2026-05-10
**Model:** `models/gun/gun_detector/weights/best.pt`
**Base:** YOLOv8n fine-tuned on Roboflow gun dataset (905 images)
**Classes:** grenade, knife, pistol, rifle
**Scan script:** `scripts/gun_detector_scan.py`
## Scan Method
- **121 scan points**: 2 ASR "gun" mentions + 114 fixed intervals (60s) + 5 original hit timestamps
- **Per point**: scan ±30 frames at every 3rd frame = ~20 frames per point
- **Total frames processed**: ~2,420
- **Runtime**: ~2 min
## Results
| Class | Detections | Top Confidence |
|-------|-----------|---------------|
| pistol | **82** | 0.887 |
| rifle | 55 | 0.822 |
| grenade | 35 | 0.797 |
| knife | 38 | 0.810 |
| **Total** | **210** (after dedup) | — |
## Original 5 Pistol Timestamps
| Timestamp | Original | This Scan | Delta |
|-----------|----------|-----------|-------|
| 3188s (53:08) | pistol 0.387 | ✅ **0.474** | +22% |
| 5461s (91:01) | pistol 0.355 | ✅ **0.346** | −3% |
| 6309s (1:45:09) | pistol 0.374 | ❌ Not found | — |
| 6377s (1:46:17) | gun 0.316 | ✅ **0.757** | +140% |
| 6479s (1:47:59) | pistol 0.300 | ✅ **0.815** | +172% |
## Top Pistol Detections
| Time | Confidence | Image |
|------|-----------|-------|
| 84:00 (5040s) | **0.887** | `5040s_pistol_0.887.jpg` |
| 90:00 (5400s) | **0.816** | `5400s_pistol_0.816.jpg` |
| 108:00 (6480s) | **0.815** | `6480s_pistol_0.815.jpg` |
| 48:59 (2939s) | **0.805** | `2939s_pistol_0.805.jpg` |
| 53:07 (3187s) | **0.474** | `3187s_pistol_0.474.jpg` |
| 91:00 (5459s) | **0.346** | `5459s_pistol_0.346.jpg` |
## Analysis
### Model Performance
Compared to the original evaluation (May 7, 24 sample points, all FP):
- This scan found **significantly more detections** (210 vs 7)
- Confidence values are **much higher** (0.887 vs 0.45 max)
- 4/5 original pistol timestamps recovered
### Cautions
1. **Training data mismatch**: Model was trained on 905 close-up gun photos, NOT movie frames. High confidence ≠ real gun.
2. **Stamp false positive confirmed**: t=5400s (identified in original eval as stamp → pistol) continues to fire at 0.816
3. **Pattern suggests overconfidence**: Many detections at regular intervals (every 60s, same objects) suggest the model is detecting non-gun objects with high confidence
### Verified Findings
The original 5 pistol images from the gun_detections/ directory (3188s, 5461s, 6309s, 6377s, 6479s) were all produced by the same YOLOv8n model. The user previously stated that none of these have been confirmed as real guns.
## Files
| File | Description |
|------|-------------|
| `output_dev/gun_detections/gun_detections.json` | All 210 deduped detections |
| `output_dev/gun_detections/*.jpg` | Annotated screenshots (one per detection) |
| `scripts/gun_detector_scan.py` | Scan script (reproducible) |
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "CADDY"
title: "Caddy 安裝指南 (本地部署)"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "caddy"
- "reverse-proxy"
- "web-server"
- "macos"
ai_query_hints:
- "如何安裝 Caddy 反向代理?"
- "Caddy 配置檔案路徑在哪裡?"
- "如何配置 Caddy 開機自動啟動?"
---
# Caddy 安裝指南 (本地部署) # Caddy 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 Caddy Web Server,配置為本地部署,作為反向代理伺服器。 本文檔說明如何在 macOS 上安裝 Caddy Web Server,配置為本地部署,作為反向代理伺服器。
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "GITEA"
title: "Gitea 安裝指南 (本地部署)"
date: "2026-03-15"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "gitea"
- "git-server"
- "version-control"
- "macos"
ai_query_hints:
- "如何安裝 Gitea 本地 Git 伺服器?"
- "Gitea 數據目錄路徑在哪裡?"
- "如何配置 Gitea 開機自動啟動?"
---
# Gitea 安裝指南 (本地部署) # Gitea 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 Gitea Git 服務,配置為本地部署。 本文檔說明如何在 macOS 上安裝 Gitea Git 服務,配置為本地部署。
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "MARIADB"
title: "MariaDB 安裝指南 (本地部署)"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "mariadb"
- "database"
- "macos"
- "sql"
ai_query_hints:
- "如何安裝 MariaDB 資料庫?"
- "MariaDB 連線資訊為何?"
- "如何備份與恢復 MariaDB 數據?"
---
# MariaDB 安裝指南 (本地部署) # MariaDB 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 MariaDB,配置為本地部署,支援遠端訪問。 本文檔說明如何在 macOS 上安裝 MariaDB,配置為本地部署,支援遠端訪問。
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "MONGODB"
title: "MongoDB 安裝指南 (本地部署)"
date: "2026-03-15"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "mongodb"
- "nosql"
- "database"
- "macos"
ai_query_hints:
- "如何安裝 MongoDB 資料庫?"
- "MongoDB 連線資訊為何?"
- "如何配置 MongoDB 開機自動啟動?"
---
# MongoDB 安裝指南 (本地部署) # MongoDB 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 MongoDB Community Edition,配置為本地部署,支援遠端訪問。 本文檔說明如何在 macOS 上安裝 MongoDB Community Edition,配置為本地部署,支援遠端訪問。
@@ -47,8 +11,8 @@ ai_query_hints:
| 項目 | 狀態 | | 項目 | 狀態 |
|------|------| |------|------|
| MongoDB (mongodb-community) | ✅ 已安裝 v8.2.6 | | MongoDB (mongodb-community) | ✅ 已安裝 v8.2.6 |
| 數據目錄 | /opt/homebrew/var/mongodb | | 數據目錄 | 保留 (/Users/accusys/momentry/var) - 共用 |
| 日誌目錄 | /Users/accusys/momentry/log | | 日誌目錄 | 保留 (/Users/accusys/momentry/log) - 共用 |
--- ---
@@ -76,9 +40,9 @@ sudo launchctl list | grep mongo
### Step 2: 數據目錄 (已存在 - 共用) ### Step 2: 數據目錄 (已存在 - 共用)
數據目錄使用 homebrew 預設位置: 數據目錄已存在,無需建立:
- 數據目錄: `/opt/homebrew/var/mongodb` - 數據目錄: `/Users/accusys/momentry/var`
- 配置目錄: `/opt/homebrew/etc/mongod.conf` - 配置目錄: `/Users/accusys/momentry/etc/mongodb`
- 日誌目錄: `/Users/accusys/momentry/log` - 日誌目錄: `/Users/accusys/momentry/log`
**建立配置目錄和日誌文件**: **建立配置目錄和日誌文件**:
@@ -97,19 +61,15 @@ chown -R accusys:staff /Users/accusys/momentry
--- ---
### Step 3: 使用 LaunchAgent 啟動 (開機自動) ### Step 3: 啟動 MongoDB (後台執行)
```bash ```bash
# 複製 plist 到 LaunchDaemons 目錄 (開機自動需要 root 權限) nohup /opt/homebrew/bin/mongod \
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.mongodb.plist \ --dbpath /Users/accusys/momentry/var \
/Library/LaunchDaemons/ --logpath /Users/accusys/momentry/log/mongodb.log \
--port 27017 \
# 載入並啟動 --bind_ip 0.0.0.0 \
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist > /Users/accusys/momentry/log/mongodb.log 2>&1 &
# 驗證
launchctl list | grep mongodb
pgrep -a mongod
``` ```
--- ---
@@ -130,20 +90,14 @@ db.createUser({
--- ---
### Step 4: 驗證安裝 ### Step 5: 使用 plist 開機自動啟動
```bash ```bash
# 檢查進程 # 複製 plist 到 LaunchDaemons 目錄
pgrep -a mongod sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.mongodb.plist /Library/LaunchDaemons/
# 檢查端口 # 載入並啟動
lsof -i :27017 sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
# 測試連線
mongosh --eval "db.adminCommand('ping')"
# 檢查 LaunchAgent
launchctl list | grep mongodb
``` ```
--- ---
@@ -311,11 +265,12 @@ tail -20 /Users/accusys/momentry/log/mongodb.error.log
### 啟動/停止 ### 啟動/停止
```bash ```bash
# 使用 LaunchAgent (開機自動 - LaunchDaemons 目錄) # 停止
sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist # 啟動 pkill mongod
sudo launchctl unload /Library/LaunchDaemons/com.momentry.mongodb.plist # 停止 # 或
kill <PID>
# 手動啟動 (僅除錯用) # 啟動 (後台)
nohup /opt/homebrew/bin/mongod \ nohup /opt/homebrew/bin/mongod \
--dbpath /Users/accusys/momentry/var \ --dbpath /Users/accusys/momentry/var \
--logpath /Users/accusys/momentry/log/mongodb.log \ --logpath /Users/accusys/momentry/log/mongodb.log \
@@ -323,8 +278,8 @@ nohup /opt/homebrew/bin/mongod \
--bind_ip 0.0.0.0 \ --bind_ip 0.0.0.0 \
> /Users/accusys/momentry/log/mongodb.log 2>&1 & > /Users/accusys/momentry/log/mongodb.log 2>&1 &
# 強制停止 # 使用 plist (開機自動啟動)
pkill mongod sudo launchctl load /Library/LaunchDaemons/com.momentry.mongodb.plist
``` ```
--- ---
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "N8N"
title: "n8n 安裝指南 (本地部署)"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "n8n"
- "workflow"
- "automation"
- "macos"
ai_query_hints:
- "如何安裝 n8n 自動化平台?"
- "n8n Webhook 配置方式為何?"
- "如何匯出匯入 n8n Workflow?"
---
# n8n 安裝指南 (本地部署) # n8n 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 n8n 工作流自動化平台,配置為本地部署,使用 Queue 模式。 本文檔說明如何在 macOS 上安裝 n8n 工作流自動化平台,配置為本地部署,使用 Queue 模式。
@@ -46,22 +10,13 @@ ai_query_hints:
| 項目 | 狀態 | | 項目 | 狀態 |
|------|------| |------|------|
| n8n | ✅ 已安裝 v2.12.3 | | n8n | ✅ 已安裝 v2.3.5 |
| 數據目錄 | /Users/accusys/momentry/var/n8n/ | | 數據目錄 | /Users/accusys/momentry/var/n8n/ |
| 日誌目錄 | /Users/accusys/momentry/log/ | | 日誌目錄 | /Users/accusys/momentry/log/ |
| Main Plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist | | Main Plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist |
| Worker Plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist | | Worker Plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist |
| 數據庫 | PostgreSQL (n8n) | | 數據庫 | PostgreSQL (n8n) |
| 隊列 | Redis | | 隊列 | Redis |
| Launchd 狀態 | ✅ Main + Worker 已註冊 |
| RunAtLoad | ✅ 已設定 |
| KeepAlive | ✅ 已設定 |
### 重要更新 (2026-03-24)
1. **n8n Main + Worker**: 兩個服務都使用自定義 plist
2. **Runner 禁用**: 為避免端口衝突,Main 服務設定 `N8N_RUNNERS_ENABLED=false`
3. **Worker 端口**: Worker 使用 5681, 5682, 5690, 5691 端口
--- ---
@@ -77,7 +32,7 @@ brew install n8n
**驗證**: **驗證**:
```bash ```bash
n8n --version n8n --version
# 2.12.3 # 2.3.5
``` ```
--- ---
@@ -262,9 +217,9 @@ ps aux | grep "n8n.*worker" | grep -v grep && echo " ✗ 仍在運行" || echo
echo "3. Port 8085:" echo "3. Port 8085:"
lsof -i :8085 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放" lsof -i :8085 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. Port 5679 (Worker) # 3. Port 5690-5691
echo "4. Port 5679 (Worker):" echo "4. Port 5690-5691:"
lsof -i :5679 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放" lsof -i :5690 > /dev/null 2>&1 && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 4. n8n 命令 # 4. n8n 命令
echo "5. n8n 命令:" echo "5. n8n 命令:"
@@ -332,7 +287,8 @@ ps aux | grep n8n | grep -v grep
# 2. 檢查 Port # 2. 檢查 Port
lsof -i :5678 lsof -i :5678
lsof -i :5679 lsof -i :5690
lsof -i :5691
# 3. 測試連線 # 3. 測試連線
curl http://localhost:5678/ curl http://localhost:5678/
@@ -369,7 +325,8 @@ sudo launchctl list | grep n8n
| 服務 | Port | | 服務 | Port |
|------|------| |------|------|
| Main | 5678 | | Main | 5678 |
| Task Broker (Worker 連接) | 5679 | | Worker Broker | 5690 |
| Worker Health Check | 5691 |
--- ---
@@ -502,7 +459,7 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
- 版本: 2.3.5 - 版本: 2.3.5
- Main Port: 5678 - Main Port: 5678
- Task Broker (Worker): 5679 - Worker Ports: 5690-5691
- 數據目錄: /Users/accusys/momentry/var/n8n/ - 數據目錄: /Users/accusys/momentry/var/n8n/
- 日誌目錄: /Users/accusys/momentry/log/ - 日誌目錄: /Users/accusys/momentry/log/
- 數據庫: PostgreSQL n8n - 數據庫: PostgreSQL n8n
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "OLLAMA"
title: "Ollama 安裝指南 (本地部署)"
date: "2026-03-15"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "ollama"
- "llm"
- "ai-inference"
- "macos"
ai_query_hints:
- "如何安裝 Ollama 本地 LLM 推理引擎?"
- "如何下載 Ollama 模型?"
- "Ollama API 端點為何?"
---
# Ollama 安裝指南 (本地部署) # Ollama 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 Ollama,配置為本地部署,用於運行大型語言模型 (LLM)。 本文檔說明如何在 macOS 上安裝 Ollama,配置為本地部署,用於運行大型語言模型 (LLM)。
@@ -48,7 +12,7 @@ ai_query_hints:
|------|------| |------|------|
| Ollama | ✅ 已安裝 v0.13.5 | | Ollama | ✅ 已安裝 v0.13.5 |
| Port | 11434 | | Port | 11434 |
| Models 目錄 | /Users/accusys/momentry/var/ollama/models | | Models 目錄 | /Users/accusys/.ollama/models/ |
| 日誌目錄 | /Users/accusys/momentry/log/ | | 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.ollama.plist | | Plist | /Library/LaunchDaemons/com.momentry.ollama.plist |
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "PHP"
title: "PHP 安裝指南 (本地部署)"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "php"
- "web-server"
- "wordpress"
- "macos"
ai_query_hints:
- "如何安裝 PHP 環境?"
- "PHP 配置優化建議為何?"
- "如何配置 PHP-FPM 與 Nginx/Caddy?"
---
# PHP 安裝指南 (本地部署) # PHP 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 PHP 及 PHP-FPM,配置為本地部署。 本文檔說明如何在 macOS 上安裝 PHP 及 PHP-FPM,配置為本地部署。
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "POSTGRESQL"
title: "PostgreSQL 安裝指南 (本地部署)"
date: "2026-03-15"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "postgresql"
- "database"
- "macos"
- "sql"
ai_query_hints:
- "如何安裝 PostgreSQL 資料庫?"
- "PostgreSQL 數據目錄路徑在哪裡?"
- "如何卸載 PostgreSQL 並保留數據?"
---
# PostgreSQL 安裝指南 (本地部署) # PostgreSQL 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 PostgreSQL,配置為本地部署,支援遠端訪問。 本文檔說明如何在 macOS 上安裝 PostgreSQL,配置為本地部署,支援遠端訪問。
@@ -47,18 +11,9 @@ ai_query_hints:
| 項目 | 狀態 | | 項目 | 狀態 |
|------|------| |------|------|
| PostgreSQL | ✅ 已安裝 v18.1 | | PostgreSQL | ✅ 已安裝 v18.1 |
| 數據目錄 | /Users/accusys/momentry/var/postgresql | | 數據目錄 | /Users/accusys/momentry/var/postgresql/ |
| 日誌目錄 | /Users/accusys/momentry/log/ | | 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.postgresql.plist | | Plist | /Library/LaunchDaemons/com.momentry.postgresql.plist |
| Launchd 狀態 | ✅ 已註冊 |
| RunAtLoad | ✅ 已設定 |
| KeepAlive | ✅ 已設定 |
### 重要更新 (2026-03-24)
1. **資料目錄已變更**: 從 `/opt/homebrew/var/postgresql@18` 遷移到 `/Users/accusys/momentry/var/postgresql`
2. **統一管理**: 所有 Momentry 服務現在都使用 `/Library/LaunchDaemons/` 下的自定義 plist
3. **避免衝突**: 刪除了 homebrew plist,避免 reboot 後使用舊資料目錄
--- ---
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "QDRANT"
title: "Qdrant 安裝指南 (本地部署)"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "qdrant"
- "vector-database"
- "ai-search"
- "macos"
ai_query_hints:
- "如何安裝 Qdrant 向量資料庫?"
- "Qdrant 連線資訊為何?"
- "如何配置 Qdrant Collection?"
---
# Qdrant 安裝指南 (本地部署) # Qdrant 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 Qdrant Vector Database,配置為本地部署,支援遠端訪問。 本文檔說明如何在 macOS 上安裝 Qdrant Vector Database,配置為本地部署,支援遠端訪問。
@@ -1,43 +1,5 @@
---
document_type: "installation_guide"
service: "REDIS"
title: "Redis 安裝指南 (本地部署)"
date: "2026-03-15"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "redis"
- "cache"
- "key-value"
- "macos"
ai_query_hints:
- "如何安裝 Redis 快取伺服器?"
- "Redis 連線資訊為何?"
- "如何配置 Redis 持久化?"
---
# Redis 安裝指南 (本地部署) # Redis 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 更新 rust redis crate 版本至 0.32.7 | OpenCode | - |
| V1.2 | 2026-03-21 | 添加 Redis 用戶配置說明 | OpenCode | - |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 Redis,配置為本地部署,支援遠端訪問。 本文檔說明如何在 macOS 上安裝 Redis,配置為本地部署,支援遠端訪問。
@@ -49,7 +11,7 @@ ai_query_hints:
| 項目 | 狀態 | | 項目 | 狀態 |
|------|------| |------|------|
| Redis | ✅ 已安裝 v8.4.0 | | Redis | ✅ 已安裝 v8.4.0 |
| 數據目錄 | /opt/homebrew/var/db/redis/ | | 數據目錄 | /Users/accusys/momentry/var/redis/ |
| 日誌目錄 | /Users/accusys/momentry/log/ | | 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.redis.plist | | Plist | /Library/LaunchDaemons/com.momentry.redis.plist |
@@ -398,104 +360,8 @@ sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
## 版本資訊 ## 版本資訊
| 項目 | 值 | - 版本: 8.4.0
|------|-----| - Port: 6379
| Redis Server | 8.4.0 | - Password: accusys
| Rust redis crate | 0.32.7 | - 數據目錄: /Users/accusys/momentry/var/redis/
| Port | 6379 | - 日誌目錄: /Users/accusys/momentry/log/
| Password | accusys |
| 數據目錄 | /Users/accusys/momentry/var/redis/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
---
## Rust redis crate 版本
Cargo.toml 中的 redis 依賴:
```toml
redis = { version = "0.32", features = ["tokio-comp"] }
```
### 版本歷史
| 版本 | 日期 | 變更 |
|------|------|-------|
| 0.25.4 | - | 原始版本(有未來相容性警告) |
| 0.32.7 | 2026-03-21 | **升級** - 修復 Rust 2024 never type 回退問題 |
### 升級說明
升級到 0.32.x 的優點:
- 修復 Rust 2024 edition 未來相容性問題
- API 完全向後相容
- 無需修改現有程式碼
---
## Redis 用戶配置說明
### 當前狀態
| 項目 | 狀態 |
|------|------|
| 用戶類型 | 僅有 `default` 用戶 |
| 自訂用戶 | ❌ 未配置 |
| ACL 持久化 | ❌ 未配置 |
### Redis ACL 狀態
```bash
# 查看 ACL
redis-cli -a accusys ACL LIST
# 輸出:
# user default on sanitize-payload #hash ~* &* +@all
```
### 連線格式說明
| 格式 | 狀態 | 說明 |
|------|------|------|
| `redis://:accusys@localhost:6379` | ✅ 正確 | 使用默認用戶 + 密碼 |
| `redis://accusys:accusys@localhost:6379` | ❌ 失敗 | 用戶 `accusys` 不存在 |
### 為何用戶名不可用
1. **Redis 啟動方式**:使用 `--requirepass` 參數,僅設定默認用戶密碼
2. **無 ACL 配置文件**:未指定 `--aclfile` 參數
3. **動態建立用戶**:手動建立的用戶不會持久化(重啟後消失)
### 解決方案
#### 方案 A:使用默認用戶(現行)
```env
REDIS_URL=redis://:accusys@localhost:6379
```
**適用於**:單一應用、簡單部署
#### 方案 B:建立 ACL 配置文件
```bash
# 1. 建立 ACL 文件
cat > /Users/accusys/momentry/etc/redis/users.acl << 'EOF'
user default on sanitize-payload ~* &* +@all >accusys
user accusys on sanitize-payload ~* &* +@all >accusys
EOF
# 2. 修改 plist (添加 --aclfile 參數)
# --aclfile /Users/accusys/momentry/etc/redis/users.acl
# 3. 重啟 Redis
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
**適用於**:多應用、需要用戶隔離
### 參考
- 問題追蹤:`docs_v1.0/REFERENCE/PENDING_ISSUES.md` 問題 #5
- 測試結果:2026-03-21 Redis 認證測試
@@ -1,41 +1,5 @@
---
document_type: "installation_guide"
service: "RUSTDESK"
title: "RustDesk 安裝指南 (本地部署)"
date: "2026-03-15"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "Warren"
tags:
- "rustdesk"
- "remote-desktop"
- "screen-sharing"
- "macos"
ai_query_hints:
- "如何安裝 RustDesk 遠端桌面?"
- "RustDesk 伺服器配置方式為何?"
- "如何配置 RustDesk 中繼伺服器?"
---
# RustDesk 安裝指南 (本地部署) # RustDesk 安裝指南 (本地部署)
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-15 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明如何在 macOS 上安裝 RustDesk 遠端桌面服務,配置為本地部署。 本文檔說明如何在 macOS 上安裝 RustDesk 遠端桌面服務,配置為本地部署。
+360
View File
@@ -0,0 +1,360 @@
# SFTPGo 安裝指南 (本地部署)
## 概述
本文檔說明如何在 macOS 上安裝 SFTPGo,配置為本地部署,用於 SFTP/FTP/WebDAV 檔案傳輸服務。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| SFTPGo | ✅ 已安裝 v2.7.0 |
| Port | 8080 (HTTP), 2022 (SFTP) |
| 配置目錄 | /Users/accusys/momentry/etc/sftpgo/ |
| 日誌目錄 | /Users/accusys/momentry/log/ |
| Plist | /Library/LaunchDaemons/com.momentry.sftpgo.plist |
---
## 安裝步驟
### Step 1: 安裝 SFTPGo (使用 brew)
```bash
# 安裝 SFTPGo
brew install sftpgo
```
**驗證**:
```bash
sftpgo --version
# SFTPGo 2.7.0
```
---
### Step 2: 建立目錄
```bash
# 建立配置目錄
mkdir -p /Users/accusys/momentry/etc/sftpgo
# 建立日誌目錄
mkdir -p /Users/accusys/momentry/log
# 建立工作目錄
mkdir -p /Users/accusys/workspace/sftpgo
# 建立日誌文件
touch /Users/accusys/momentry/log/sftpgo.log
touch /Users/accusys/momentry/log/sftpgo.error.log
# 設定權限
chown -R accusys:staff /Users/accusys/momentry/etc/sftpgo
chown -R accusys:staff /Users/accusys/momentry/log
chown -R accusys:staff /Users/accusys/workspace/sftpgo
```
---
### Step 3: 建立設定檔
建立 `/Users/accusys/momentry/etc/sftpgo/sftpgo.json`:
```json
{
"common": {
"idle_timeout": 15,
"upload_mode": 0,
"max_per_host_connections": 20
},
"users": [
{
"username": "accusys",
"password": "",
"public_keys": [],
"home_dir": "/Users/accusys/workspace/sftpgo",
"uid": 501,
"gid": 20,
"permissions": {
"/": ["*"]
}
}
],
"httpd": {
"bind_port": 8080,
"bind_address": "0.0.0.0"
},
"ftpd": {
"bind_port": 21,
"bind_address": "0.0.0.0"
},
"sftpd": {
"bind_port": 2022,
"bind_address": "0.0.0.0"
},
"webdavd": {
"bind_port": 0,
"bind_address": ""
}
}
```
---
### Step 4: 使用 plist 開機自動啟動
```bash
# 複製 plist 到 LaunchDaemons 目錄
sudo cp /Users/accusys/momentry_core_0.1/momentry_runtime/plist/com.momentry.sftpgo.plist /Library/LaunchDaemons/
# 載入並啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
---
## 監控配置
### 添加到監控配置
在 `monitor/config/monitor_config.yaml` 中添加:
```yaml
service:
services:
- name: "sftpgo"
type: "http"
port: 8080
host: "localhost"
check_url: "http://localhost:8080/api/v2/info"
timeout: 5
enabled: true
```
---
## 卸載步驟
### 重要: 路徑說明
| 路徑 | 類型 | 說明 |
|------|------|------|
| `/Users/accusys/momentry/etc/sftpgo/` | 配置 | **不要刪除** - SFTPGo 配置 |
| `/Users/accusys/momentry/log/` | 日誌 | **不要刪除** - 日誌目錄 |
| `/Users/accusys/workspace/sftpgo/` | 數據 | **不要刪除** - 上傳檔案目錄 |
| `/opt/homebrew/opt/sftpgo/` | 安裝 | **刪除** - SFTPGo 安裝目錄 |
### Step 1: 停止 SFTPGo
```bash
# 找到 SFTPGo 進程
ps aux | grep sftpgo | grep -v grep
# 停止 SFTPGo
pkill sftpgo
# 確認停止
ps aux | grep sftpgo | grep -v grep || echo "SFTPGo 已停止"
```
---
### Step 2: 卸載 SFTPGo
```bash
# 卸載 SFTPGo
brew uninstall sftpgo
# 移除 plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.sftpgo.plist
sudo rm /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
---
### Step 3: 刪除專屬檔案
```bash
# 刪除配置目錄 (可選)
rm -rf /Users/accusys/momentry/etc/sftpgo
# 刪除日誌 (可選)
rm -f /Users/accusys/momentry/log/sftpgo.log
rm -f /Users/accusys/momentry/log/sftpgo.error.log
```
**注意: 不要刪除以下目錄**:
```bash
# 這些是重要的,不要刪除!
# /Users/accusys/momentry/etc/sftpgo
# /Users/accusys/momentry/log
# /Users/accusys/workspace/sftpgo
```
---
### Step 4: 卸載後檢查清單
```bash
echo "=== SFTPGo 卸載後檢查 ==="
# 1. 檢查 SFTPGo 進程
echo "1. SFTPGo 進程:"
ps aux | grep sftpgo | grep -v grep && echo " ✗ 仍在運行" || echo " ✓ 已停止"
# 2. Port 8080/2022
echo "2. Port 8080/2022:"
(lsof -i :8080 > /dev/null 2>&1 || lsof -i :2022 > /dev/null 2>&1) && echo " ✗ 仍被佔用" || echo " ✓ 已釋放"
# 3. sftpgo 命令
echo "3. sftpgo 命令:"
which sftpgo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 4. brew 安裝
echo "4. brew 安裝:"
brew list sftpgo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 5. launchctl 服務
echo "5. launchctl 服務:"
sudo launchctl list | grep sftpgo > /dev/null 2>&1 && echo " ✗ 仍存在" || echo " ✓ 已移除"
# 6. 配置目錄 (可選刪除)
echo "6. 配置目錄:"
[ -d "/Users/accusys/momentry/etc/sftpgo" ] && echo " ✓ 保留" || echo " ✗ 已刪除"
```
---
## 手動檢查命令
```bash
# 1. 檢查進程
ps aux | grep sftpgo | grep -v grep
# 2. 檢查 Port
lsof -i :8080
lsof -i :2022
# 3. 測試連線
curl http://localhost:8080/
# 4. 查看版本
sftpgo --version
# 5. 驗證配置
sftpgo validate --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 6. 查看日誌
tail -20 /Users/accusys/momentry/log/sftpgo.log
```
---
## 連線資訊
| 項目 | 值 |
|------|-----|
| HTTP/WebDAV | http://localhost:8080 |
| SFTP | localhost:2022 |
| FTP | localhost:21 |
| Admin API | http://localhost:8080/api/v2/info |
---
## 環境變數
在 `.env` 中:
```env
SFTPGO_CONFIG=/Users/accusys/momentry/etc/sftpgo/sftpgo.json
SFTPGO_DATA_DIR=/Users/accusys/workspace/sftpgo
```
---
## 故障排除
### SFTPGo 無法啟動
```bash
# 檢查日誌
tail -f /Users/accusys/momentry/log/sftpgo.log
# 驗證配置語法
sftpgo validate --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 檢查目錄權限
ls -la /Users/accusys/momentry/etc/sftpgo/
# 重新設定權限
chown -R $(whoami):staff /Users/accusys/momentry/etc/sftpgo
```
### Port 被佔用
```bash
# 檢查哪個程序佔用 port
lsof -i :8080
lsof -i :2022
# 終止佔用程序
kill <PID>
```
### 需要重新載入 plist
```bash
# 卸載舊服務 (如果存在)
sudo launchctl unload /Library/LaunchDaemons/com.momentry.sftpgo.plist 2>/dev/null
# 載入新服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.sftpgo.plist
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| 安裝 | `/opt/homebrew/opt/sftpgo/` | SFTPGo 安裝目錄 |
| 執行檔 | `/opt/homebrew/opt/sftpgo/bin/sftpgo` | SFTPGo 執行檔 |
| 配置 | `/Users/accusys/momentry/etc/sftpgo/sftpgo.json` | 設定檔 |
| 日誌 | `/Users/accusys/momentry/log/sftpgo.log` | 執行日誌 |
| 錯誤日誌 | `/Users/accusys/momentry/log/sftpgo.error.log` | 錯誤日誌 |
| 工作目錄 | `/Users/accusys/workspace/sftpgo/` | 上傳檔案目錄 |
| plist | `/Library/LaunchDaemons/com.momentry.sftpgo.plist` | 開機啟動 |
| 備份 | `/Users/accusys/momentry/var/sftpgo_backup/sftpgo.json` | 配置備份 |
---
## 常用指令
```bash
# 驗證配置
sftpgo validate --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
# 查看版本
sftpgo --version
# 查看可用命令
sftpgo --help
# 重載配置 (熱重載)
sftpgo reload --config /Users/accusys/momentry/etc/sftpgo/sftpgo.json
```
---
## 版本資訊
- 版本: 2.7.0
- HTTP Port: 8080
- SFTP Port: 2022
- FTP Port: 21
- 配置: /Users/accusys/momentry/etc/sftpgo/sftpgo.json
- 工作目錄: /Users/accusys/workspace/sftpgo
- 日誌目錄: /Users/accusys/momentry/log/
@@ -1,30 +1,10 @@
---
document_type: "architecture_design"
service: "MOMENTRY_CORE"
title: "Job Worker 實作計畫"
date: "2026-04-27"
version: "V1.2"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "實作計畫"
- "worker"
- "processing_status"
ai_query_hints:
- "查詢 Job Worker 實作計畫 的內容"
- "Job Worker 實作計畫 的主要目的是什麼?"
- "如何操作或實施 Job Worker 實作計畫?"
- "processing_status 字段設計"
---
# Job Worker 實作計畫 # Job Worker 實作計畫
| 項目 | 內容 | | 項目 | 內容 |
|------|------| |------|------|
| 建立者 | Warren / OpenCode | | 建立者 | Warren / OpenCode |
| 建立時間 | 2026-03-24 | | 建立時間 | 2026-03-24 |
| 文件版本 | V1.2 | | 文件版本 | V1.1 |
| 狀態 | ✅ 已實作 | | 狀態 | ✅ 已實作 |
--- ---
@@ -35,7 +15,6 @@ ai_query_hints:
|------|------|------|--------| |------|------|------|--------|
| V1.0 | 2026-03-24 | 建立實作計畫 | OpenCode | | V1.0 | 2026-03-24 | 建立實作計畫 | OpenCode |
| V1.1 | 2026-03-25 | 實作完成,更新狀態 | OpenCode | | V1.1 | 2026-03-25 | 實作完成,更新狀態 | OpenCode |
| V1.2 | 2026-04-27 | 添加 processing_status 字段設計說明 | OpenCode |
--- ---
@@ -653,11 +632,11 @@ export REDIS_URL=redis://:accusys@localhost:6379
| 文件 | 用途 | | 文件 | 用途 |
|------|------| |------|------|
| `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md` | 監控系統規範 | | `docs/MOMENTRY_CORE_MONITORING.md` | 監控系統規範 |
| `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計 | | `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計 |
| `docs_v1.0/ARCHITECTURE/PROCESSING_PIPELINE.md` | 處理流程 | | `docs/PROCESSING_PIPELINE.md` | 處理流程 |
| `docs_v1.0/ARCHITECTURE/CHUNK_DESIGN.md` | 資料庫設計 | | `docs/CHUNK_DESIGN.md` | 資料庫設計 |
| `docs_v1.0/REFERENCE/API_REFERENCE.md` | API 參考 | | `docs/API_REFERENCE.md` | API 參考 |
--- ---
@@ -692,117 +671,6 @@ export REDIS_URL=redis://:accusys@localhost:6379
| `completed` | 所有處理完成 | | `completed` | 所有處理完成 |
| `failed` | 處理失敗 | | `failed` | 處理失敗 |
### B.1 videos 表 processing_status 欄位
| 值 | 說明 | 適用場景 |
|------|------|----------|
| `REGISTERED` | 已註冊 | 新註冊的視頻,尚未觸發處理 |
| `PENDING` | 等待處理 | 已觸發處理,等待作業分配 |
| `PROBING` | 探測中 | ffprobe 分析執行中 |
| `ASR` | ASR 處理中 | ASR 作業執行中 |
| `OCR` | OCR 處理中 | OCR 作業執行中 |
| `YOLO` | YOLO 處理中 | YOLO 作業執行中 |
| `FACE` | 人臉偵測中 | Face 作業執行中 |
| `POSE` | 姿態估計中 | Pose 作業執行中 |
| `CUT` | 分塊處理中 | Cut 作業執行中 |
| `ASRX` | 說話者分離中 | ASRX 作業執行中 |
| `COMPLETED` | 完成 | 所有處理完成 |
| `FAILED` | 失敗 | 處理失敗 |
| `PAUSED` | 暫停 | 斷點續傳暫停狀態 |
| `RESUMING` | 恢復中 | 斷點續傳恢復中 |
#### B.1.1 status 與 processing_status 的關係
| status | processing_status | 說明 |
|--------|-------------------|------|
| `pending` | `REGISTERED` | 新註冊,Portal顯示「已註冊」(藍色) |
| `processing` | `PENDING` | 已觸發,Portal顯示「等待處理」(黃色) |
| `processing` | `PROBING`/`ASR`/... | 各處理器執行中,Portal顯示處理器名稱(靛藍) |
| `completed` | `COMPLETED` | 完成,Portal顯示「已完成」(綠色) |
| `failed` | `FAILED` | 失敗,Portal顯示「處理失敗」(紅色) |
#### B.1.2 Portal顯示優先級
Portal 優先使用 `processing_status`(詳細狀態),Fallback 使用 `status`(基本狀態)。
#### B.1.3 processing_status JSONB 結構(V1.2 起)
從 V1.2 起,`processing_status` 改為 **JSONB** 格式,支持多層級進度追蹤。
詳細規範請參考: `REFERENCE/PROCESSING_STATUS_JSONB_SPEC.md`
##### JSONB 主要字段
| 字段 | 類型 | 說明 |
|------|------|------|
| `phase` | String | 當前階段(PROCESSING, COMPLETED, FAILED) |
| `active_processors` | Array[String] | 正在執行的處理器列表(大寫) |
| `total_frames` | Integer | 影片總帧數 |
| `processing_summary` | Object | 處理器完成狀態總覽 |
| `pre_chunks_summary` | Object | pre_chunks 表絕計(按處理器) |
| `chunks_summary` | Object | chunks 表絕計(按 Rule) |
| `agents` | Object | Agent 任務狀態(5W1H, Translation) |
| `vectorization_summary` | Object | 向量化絕計 |
| `progress` | Object | 各處理器詳細進度 |
##### JSONB 範例(處理中)
```json
{
"phase": "PROCESSING",
"active_processors": ["YOLO", "OCR"],
"total_frames": 412343,
"progress": {
"YOLO": {
"current_frame": 25000,
"percentage": 6.0,
"status": "running"
}
}
}
```
##### JSONB 範例(完成)
```json
{
"phase": "COMPLETED",
"active_processors": [],
"pre_chunks_summary": {
"total_records": 25000,
"by_processor": {
"asr": {"records": 1466},
"yolo": {"records": 11000}
}
},
"chunks_summary": {
"total_chunks": 2798,
"by_rule": {
"rule_1": {"chunks_count": 1466},
"rule_3": {"chunks_count": 1332}
}
},
"agents": {
"5w1h": {"status": "completed"}
}
}
```
##### SQL 查詢範例
```sql
-- 取得 phase
SELECT processing_status->>'phase' FROM videos WHERE uuid = 'xxx';
-- 取得 active_processors
SELECT processing_status->'active_processors' FROM videos WHERE uuid = 'xxx';
-- 取得 pre_chunks 絕計
SELECT processing_status->'pre_chunks_summary'->>'total_records' FROM videos;
```
---
### C. processor_results 表 status 欄位 ### C. processor_results 表 status 欄位
| 值 | 說明 | | 值 | 說明 |
@@ -1,40 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry JSON 輸出檔案規範"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "json"
- "輸出檔案規範"
ai_query_hints:
- "查詢 Momentry JSON 輸出檔案規範 的內容"
- "Momentry JSON 輸出檔案規範 的主要目的是什麼?"
- "如何操作或實施 Momentry JSON 輸出檔案規範?"
---
# Momentry JSON 輸出檔案規範 # Momentry JSON 輸出檔案規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。 本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。
--- ---
-50
View File
@@ -1,50 +0,0 @@
# M4 / M5 協作協議
## 核心原則:檔案是 source of truth
所有 processor 的產出是 `{uuid}.{processor}.json` 檔案。
**檔案存在 = 處理完成**,優先於 DB 或 Redis 的任何狀態記錄。
## 絕對禁止
### 1. 不可刪除已存在的輸出檔
- 任何 `{uuid}.{processor}.*` 檔案,無論是 `.json`、`.json.tmp`、`.json.partial`、`.json.err`
- 一律不允許 `rm`、`unlink`、`delete`
- 唯一例外:明確的人工指令 `rm` / `Delete this file`
### 2. 不可覆蓋已存在的輸出檔
- 重新執行 processor 前,必須先 **copy(非 rename)** 加上時間戳備份
- 備份命名:`{uuid}.{processor}.{timestamp}.{original_extension}`
- 若備份名已存在,跳過(不覆蓋不 counter)
- 原檔保留不動
### 3. 不可跨域操作
- M4 只能在 M4 機器(Mac Mini)上操作
- M5 只能在 M5 機器(MacBook Pro)上操作
- 禁止任何跨機器的檔案操作或 cleanup
## 重跑 processor 的正確流程
1. Worker 檢查 `{uuid}.{processor}.json` 是否存在
2. **存在 → 跳過**(無論 DB/Redis 狀態)
3. 不存在 → copy 備份既有 `{uuid}.{processor}.*` → 執行 processor
4. Processor 輸出寫入 `.tmp` → 完成後 rename 為 `.json`
## 例外處理
| 狀態 | 行為 |
|------|------|
| `.json` 存在 | 跳過,視為完成 |
| `.json.tmp` 存在(無 `.json`) | 視為未完成,備份後重跑 |
| `.json.partial` 存在(無 `.json`) | 視為未完成,備份後重跑 |
| `.json.err` 存在(無 `.json`) | 視為未完成,備份後重跑 |
| Process 被 kill(SIGKILL) | partial 存為 `.json.partial`(非 `.json`) |
## 違規後果
2026-05-09 事故:M4 release 打包未含 .json → 跨域操作 → M5 cleanup 誤刪 asr.json
→ 導致 ASR 需重跑(完整電影約 1.5hr)
→ YOLO 需重跑
→ 損失已完成的 pipeline 進度
此類違規不可再發生。
-31
View File
@@ -1,31 +0,0 @@
# M4 Release Incident — 2026-05-09
## Summary
M4 在進行 release 打包作業時,未依照計畫包含 output `.json` 檔案,僅在 database 中保留 records。此外 M4 違反操作邊界進入 M5 管轄範圍,M5 執行 cleanup 時將已完成的 `asr.json` 一併刪除。
## Impact
| 檔案 | 狀態 | 說明 |
|------|------|------|
| `{uuid}.asr.json` | ❌ 遺失 | 已完成的 ASR 輸出被 M5 cleanup 誤刪 |
| `{uuid}.yolo.json` | ❌ 損毀 | JSON parse error,需重跑 |
| DB records | ⚠️ 不一致 | processor_results 狀態與實際檔案不符 |
## Root Cause
1. **M4 release 打包遺漏**: Release 流程未將 `.json` 輸出檔納入打包範圍,只保留了 DB。
2. **M4 越界操作**: M4 在 M5 的目錄/範圍內執行操作,違反開發隔離原則。
3. **M5 cleanup 誤刪**: M5 的 cleanup 機制未預期 M4 的產出,將 `asr.json` 視為無用檔案清除。
## 處理
- ASR: 重跑中(asr_processor.py,完整電影約 6780s)
- YOLO: 重跑中(yolo_processor.py)
- 已修改 worker 邏輯:開機後以 `.json` 檔案存在為 source of truth,不再僅依賴 DB/Redis 狀態
## 預防措施
- Release 流程需明確定義 deliverables 包含 `.json` 檔案
- M4/M5 操作邊界需嚴格遵守,禁止跨域操作
- Cleanup 機制應先確認檔案是否為有效 processor output
-77
View File
@@ -1,77 +0,0 @@
# M4 vs M5 Max Comparison
## Hardware
| Spec | M4 (Mac Mini) | M5 (MacBook Pro) |
|------|--------------|-------------------|
| **Model** | Mac Mini (M4) | MacBook Pro (M5 Max) |
| **Hostname** | `accusys-Mac-mini-M4-2.local` | `Accusyss-MacBook-Pro.local` |
| **macOS** | 26.4.1 (Sequoia) | 26.4.1 (Sequoia) |
| **RAM** | 16 GB | **48 GB** |
| **CPU Cores** | 10 | **18** |
| **Disk** | 2TB (est.) | **1.8TB (12GB used, 97% free)** |
| **Network** | 192.168.110.210, 192.168.110.200 | 192.168.110.201, 192.168.31.182 |
## Installed Services
| Service | M4 | M5 |
|---------|-----|------|
| **PostgreSQL** | 18.1 (Homebrew) | **18.3 (Source build)** |
| **pgvector** | Homebrew | **0.8.2 (Source build)** |
| **Redis** | 8.4.0 (Homebrew) | **7.4.3 (Source build)** |
| **Qdrant** | Homebrew/pre-built | **1.17.1 (Source build, `cargo`)** |
| **MongoDB** | Homebrew | 8.2.7 (Homebrew) |
| **MariaDB** | ✗ via brew | **12.2.2 (Homebrew, for WordPress)** |
| **PHP** | ✗ via brew | **8.5.5 (Homebrew, WordPress ext. ✅)** |
| **SFTPGo** | Pre-built binary | **2.7.1 (Source build, patched dep)** |
| **FFmpeg** | 8.1 (Homebrew) | **8.1.1 (Homebrew)** |
| **OpenCode** | 1.14.39 | **1.14.39** |
| **Gemma4 LLM** | ✗ (not enough RAM) | **31B Q5_K_M @ 8081** |
## Build Approach
| Aspect | M4 | M5 |
|--------|-----|-----|
| **PostgreSQL** | `brew install postgresql@18` | `./configure && make && make install` |
| **Redis** | `brew install redis` | `make && cp src/redis-server ~/redis/bin/` |
| **Qdrant** | `brew install qdrant` | `cargo build --release --bin qdrant` (from GitHub) |
| **SFTPGo** | `brew install sftpgo` | `git clone && go build` (patched `go-m1cpu`) |
| **Philosophy** | Mixed (Homebrew + binary) | **Source-first** (GitHub source, checksums recorded) |
## Data Migration (M4 → M5)
| Data | Size | Status |
|------|------|--------|
| **Database (dev schema)** | 837MB dump | ✅ Restored (16 tables) |
| **Video file** | 2.2GB | ✅ Transferred |
| **output_dev JSON** | 2.9GB (462 files) | ✅ Transferred |
| **output JSON** | 65MB (2523 files) | ✅ Transferred |
| **Configs** | small | ✅ Transferred |
## Database Row Counts (M5)
| Table | Rows |
|-------|------|
| `pre_chunks` | 494,339 |
| `face_detections` | 6,211 |
| `tkg_nodes` | 2,414 |
| `identity_bindings` | 2,347 |
| `tkg_edges` | 1,320 |
## Key Differences
### 1. RAM (16GB vs 48GB)
- **M4 (16GB)**: Cannot run Gemma4 31B LLM locally. Memory pressure during concurrent pipeline processing.
- **M5 (48GB)**: Can run Gemma4 31B (Q5_K_M, ~20GB) + databases + playground simultaneously.
### 2. Build Philosophy
- **M4**: Quick setup via Homebrew bottles (pre-compiled).
- **M5**: **Source-first** — every service built from GitHub/official source. `SHA256` checksums recorded. Dependencies patched as needed (SFTPGo `go-m1cpu`).
### 3. Unique M5 Services
- **MariaDB + PHP**: Installed for WordPress/marcom portal development.
- **Gemma4 LLM**: Running on port 8081, accessible for RAG/identity clustering.
- **OpenCode**: Configured with Gemma4 provider for AI-assisted development.
### 4. Data Freshness
- M5 is a **snapshot** of M4's state at 2026-05-06 (commit `bac6c2d`). Changes made on M4 after sync date must be re-synced.
-259
View File
@@ -1,259 +0,0 @@
# M5 Dev Environment Setup Log
**Machine**: M5 MacBook Pro (MacOS 26.4.1, Apple M5 Max, 48GB)
**User**: accusys (admin group, sudo with password)
**Date**: 2026-05-06
**Setup by**: OpenCode
---
## 1. Source Code
| Item | Detail |
|------|--------|
| Repo | `https://gitea.momentry.ddns.net/warren/momentry_core.git` |
| Branch | `main` |
| Commit | `bac6c2d` (feat: identity clustering V3.0) |
| Sync method | rsync from M4 (192.168.110.210) |
| Path | `~/momentry_core_0.1/` |
---
## 2. Installed Services
### 2.1 PostgreSQL 18.3
| Field | Value |
|-------|-------|
| **Source** | [https://ftp.postgresql.org/pub/source/v18.3/postgresql-18.3.tar.gz](https://ftp.postgresql.org/pub/source/v18.3/postgresql-18.3.tar.gz) |
| **GitHub** | [https://github.com/postgresql/postgresql](https://github.com/postgresql/postgresql) |
| **Build method** | Manual `./configure && make && make install` |
| **Prefix** | `~/pgsql/18.3/` |
| **Data dir** | `~/pgsql/data/` |
| **Port** | 5432 |
| **Version** | PostgreSQL 18.3 |
| **SHA256** | `ab04939aafdb9e8487c2f13dda91e6a4a7f4c83368f5bedd23ee4ad1fda64afb` |
| **Start command** | `pg_ctl -D ~/pgsql/data -l ~/pgsql/pg.log start` |
| **Configure flags** | `--prefix=$HOME/pgsql/18.3 --with-uuid=e2fs --with-icu --with-openssl` |
| **Build date** | 2026-05-06 |
| **Notes** | `--with-uuid=e2fs` used (requires Homebrew `e2fsprogs`). macOS built-in UUID not detected by configure. |
### 2.2 pgvector 0.8.2
| Field | Value |
|-------|-------|
| **Source** | [https://github.com/pgvector/pgvector](https://github.com/pgvector/pgvector) |
| **Version** | v0.8.2 |
| **Build method** | `git clone && make && make install` |
| **SHA256** | `65dec31ec078d60ee9d8e1dac59be8a41edf8c79bf380cd0093691b0afd257a8` |
| **Build date** | 2026-05-06 |
| **Notes** | Built against PostgreSQL 18.3 source installation |
### 2.3 Redis 7.4.3
| Field | Value |
|-------|-------|
| **Source** | [https://github.com/redis/redis/archive/refs/tags/7.4.3.tar.gz](https://github.com/redis/redis/archive/refs/tags/7.4.3.tar.gz) |
| **GitHub** | [https://github.com/redis/redis](https://github.com/redis/redis) |
| **Version** | 7.4.3 |
| **Build method** | `make -j$(sysctl -n hw.ncpu)` |
| **Binary path** | `~/redis/bin/redis-server` |
| **Port** | 6379 |
| **SHA256** | `87b6a9ea145c56c1ace724acbb9906b7be4abddd44041545adf44ce9f4d0a615` |
| **Start command** | `redis-server --daemonize yes --port 6379` |
| **Build date** | 2026-05-06 |
### 2.4 Qdrant 1.17.1
| Field | Value |
|-------|-------|
| **Source** | [https://github.com/qdrant/qdrant.git](https://github.com/qdrant/qdrant.git) |
| **Version** | v1.17.1 |
| **Build method** | `cargo build --release --bin qdrant` |
| **Binary path** | `~/momentry_core_0.1/services/qdrant/target/release/qdrant` |
| **Storage dir** | `~/qdrant_storage` |
| **Port** | 6333 (HTTP), 6334 (gRPC) |
| **SHA256** | `8f8aa63840a0f948b43f9b95f784ace69595892de5dc581bb66bd62fd86d6c66` |
| **Build date** | 2026-05-06 |
| **Config** | `~/qdrant_config.yaml` |
| **Start command** | `qdrant --config-path ~/qdrant_config.yaml &` |
| **Build deps** | protoc (Homebrew protobuf), cmake |
### 2.5 MongoDB 8.2.7
| Field | Value |
|-------|-------|
| **Source** | Homebrew `mongodb/brew/mongodb-community` |
| **Version** | 8.2.7 |
| **Port** | 27017 |
| **Start command** | `brew services start mongodb/brew/mongodb-community` |
| **Install date** | 2026-05-06 |
### 2.6 MariaDB 12.2.2
| Field | Value |
|-------|-------|
| **Source** | Homebrew `mariadb` |
| **Version** | 12.2.2-MariaDB |
| **Port** | 3306 |
| **Start command** | `brew services start mariadb` |
| **Install date** | 2026-05-06 |
### 2.7 PHP 8.5.5
| Field | Value |
|-------|-------|
| **Source** | Homebrew `php` |
| **Version** | 8.5.5 |
| **WordPress extensions** | mysqli, pdo_mysql, gd, xml, mbstring, curl, zip, json, intl, bcmath, gmp, openssl |
| **Start command** | `brew services start php` |
| **Install date** | 2026-05-06 |
### 2.8 FFmpeg / FFprobe 8.1.1
| Field | Value |
|-------|-------|
| **Source** | Homebrew `ffmpeg` |
| **Version** | 8.1.1 |
| **SHA256** | `00d01197255300c02122c783dd0126a9e7f47d6c6a19faafae2e6610efd071d3` |
| **Install date** | 2026-05-06 |
### 2.9 SFTPGo 2.7.1
| Field | Value |
|-------|-------|
| **Source** | [https://github.com/drakkan/sftpgo.git](https://github.com/drakkan/sftpgo.git) |
| **Version** | v2.7.1 |
| **Build method** | `git clone && go build -o sftpgo_bin ./` |
| **Binary path** | `~/momentry_core_0.1/services/sftpgo_bin` |
| **SHA256** | `550b6653f8f2cd7c58620e128e85be571a6702c79cf374824ad9b420ca039db1` |
| **Build date** | 2026-05-06 |
| **Patch** | Upgraded `go-m1cpu` from v0.2.0 → v0.2.1 to fix SIGTRAP crash on macOS 26.4.1 |
| **Notes** | Pre-built binary from GitHub releases crashed with `go-m1cpu` cgo compatibility issue. Source build with patched dependency resolved. |
### 2.10 OpenCode 1.14.39
| Field | Value |
|-------|-------|
| **Source** | [https://opencode.ai/install](https://opencode.ai/install) |
| **Version** | 1.14.39 |
| **Binary path** | `~/.opencode/bin/opencode` |
| **SHA256** | `def4a786c257bd6a965e46a2b069802496681b9eea20261d7d1b55629af3d1da` |
| **Install date** | 2026-05-06 |
### 2.11 Python 3.11 + Packages
| Field | Value |
|-------|-------|
| **Source** | Homebrew `python@3.11` |
| **Version** | 3.11.15 |
| **Path** | `/opt/homebrew/bin/python3.11` |
| **Key packages** | coremltools, opencv-python, numpy, psycopg2, torch, transformers, whisperx, etc. |
| **Requirements** | `~/momentry_core_0.1/requirements.txt` |
| **Install date** | 2026-05-06 |
| **FaceNet model** | `models/facenet512.mlpackage` (512D CoreML, loads OK) |
### 2.12 Build Tools
| Tool | Version | Source |
|------|---------|--------|
| Rust | 1.95.0 | rustup (pre-installed) |
| Go | 1.26.2 | Homebrew `go` |
| cmake | 4.3.2 | Homebrew `cmake` |
| pkg-config | - | Homebrew `pkg-config` |
---
## 3. Momentry Configuration
### 3.1 Environment Files
| File | Purpose |
|------|---------|
| `.env` | Production config (port 3002) |
| `.env.development` | Development config (port 3003) |
Key settings:
- `DATABASE_URL=postgres://accusys@localhost:5432/momentry`
- `REDIS_URL=redis://:accusys@localhost:6379`
- `DATABASE_SCHEMA=dev`
- `MOMENTRY_SERVER_PORT=3003` (dev) / `3002` (prod)
- `MOMENTRY_API_KEY=muser_test_apikey`
- `MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11`
- `MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core_0.1/scripts`
### 3.2 Database Tables Created
| Table | Created by |
|-------|-----------|
| `dev.videos` | Manual SQL |
| `dev.chunks` | Manual SQL |
| `dev.monitor_jobs` | Manual SQL |
| `dev.processor_results` | Manual SQL |
| `dev.talents` | Manual SQL |
| `dev.identity_bindings` | Manual SQL |
| `dev.api_keys` | Manual SQL |
### 3.3 API Key
- Key: `muser_test_apikey`
- Hash (SHA256): `3f2fa16e44ff74267786fdf979b9c33dac0cad515282e4937a0776756a61e821`
- Status: active
---
## 4. Running Services (Verified)
| Service | Port | Status |
|---------|------|--------|
| PostgreSQL | 5432 | ✅ |
| Redis | 6379 | ✅ |
| Qdrant | 6333 | ✅ |
| MongoDB | 27017 | ✅ |
| MariaDB | 3306 | ✅ |
| Momentry Playground | 3003 | ✅ |
| Gemma4 LLM | 8081 | ✅ (pre-installed) |
---
## 5. PATH Configuration
`.zshrc`:
```zsh
export PATH="/opt/homebrew/bin:/opt/homebrew/opt/postgresql@18/bin:$HOME/.opencode/bin:$PATH"
```
Also available:
- `$HOME/pgsql/18.3/bin` — source-built PostgreSQL tools
- `$HOME/redis/bin` — source-built Redis
- `$HOME/.cargo/bin` — Rust/Cargo tools
---
## 6. M5 End-to-End Test Results (Charade Full Movie)
Run date: 2026-05-06 20:38-20:57
| Stage | Time | Result |
|-------|------|--------|
| **Swift_face** (Vision ANE detection) | 867s (14.5 min) | 3999 frames (interval=30) |
| **CoreML FaceNet** (512D embedding) | 271s (4.5 min) | 6186 face embeddings |
| **Face tracker** (scene-cut aware) | ~30s | 1538 traces |
| **DB store** | ~5s | 6186 detections in `dev.face_detections` |
| **Total** | ~19 min | 1 long video (412k frames, 2.2GB) |
**Scene-cut effect**: 1538 traces (vs 379 without scene-cut reset in M4 data). Scene boundaries correctly split traces.
**Models used**:
- Face detection: Apple Vision (ANE) via `swift_face`
- Face embedding: CoreML FaceNet 512D via `facenet512.mlpackage`
- Text embedding: `mxbai-embed-large` (1024D) via Ollama
---
## 7. Known Issues
1. **Momentry API status `degraded`**: Expected on fresh setup. Some cache/processing dependencies not fully initialized.
2. **SFTPGo startup requires config**: Binary built from source, needs config file for production use.
3. **Migration scripts not all run**: Base tables created manually. Some migration files (017+) reference tables/columns that need verification.
4. **OpenCode config**: `~/.config/opencode/config.json` not yet configured for M5 Gemma4 provider.
@@ -1,22 +1,3 @@
---
document_type: "operation_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core 監控規範 (暫定)"
date: "2026-03-17"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "監控規範"
ai_query_hints:
- "查詢 Momentry Core 監控規範 (暫定) 的內容"
- "Momentry Core 監控規範 (暫定) 的主要目的是什麼?"
- "如何操作或實施 Momentry Core 監控規範 (暫定)?"
---
# Momentry Core 監控規範 (暫定) # Momentry Core 監控規範 (暫定)
| 項目 | 內容 | | 項目 | 內容 |
@@ -660,20 +641,20 @@ redis-cli HGETALL momentry:job:{uuid}
### 已完成實作 (2026-03-22) ### 已完成實作 (2026-03-22)
**監控系統**: **監控系統**:
- 完整健康檢查腳本設計: `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md` - 完整健康檢查腳本設計: `docs/MOMENTRY_CORE_MONITORING.md`
- 多層次服務監控 (Layer 2: Service, Layer 7: Backup) - 多層次服務監控 (Layer 2: Service, Layer 7: Backup)
- Redis Job 監控腳本: `monitor/service/redis_job_monitor.sh` - Redis Job 監控腳本: `monitor/service/redis_job_monitor.sh`
- SFTPGo 特殊監控 (API 認證檢查) - SFTPGo 特殊監控 (API 認證檢查)
**SFTPGo 管理**: **SFTPGo 管理**:
- 備份還原機制: `backup_all.sh` (第 325-546 行) - 備份還原機制: `backup_all.sh` (第 325-546 行)
- API 管理用戶與組 (完整文件於 `docs_v1.0/IMPLEMENTATION/INSTALL_SFTPGO.md`) - API 管理用戶與組 (完整文件於 `docs/INSTALL_SFTPGO.md`)
- Hook 自動註冊流程: `/Users/accusys/sftpgo_test/register_hook.sh` - Hook 自動註冊流程: `/Users/accusys/sftpgo_test/register_hook.sh`
- Demo 用戶與組完整測試環境 - Demo 用戶與組完整測試環境
**文檔更新**: **文檔更新**:
- `docs_v1.0/IMPLEMENTATION/INSTALL_SFTPGO.md`: 新增備份還原、API管理、Hook配置章節 - `docs/INSTALL_SFTPGO.md`: 新增備份還原、API管理、Hook配置章節
- `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md`: 完善監控規範 - `docs/MOMENTRY_CORE_MONITORING.md`: 完善監控規範
### 待驗證功能 ### 待驗證功能
@@ -689,5 +670,5 @@ redis-cli HGETALL momentry:job:{uuid}
- [監控系統總覽](../monitor/MONITORING.md) - [監控系統總覽](../monitor/MONITORING.md)
- [備份規範](./SERVICE_ADDITION_GUIDE.md) - [備份規範](./SERVICE_ADDITION_GUIDE.md)
- [SFTPGo 安裝與管理指南](./INSTALL_SFTPGO.md) - [SFTPGo 安裝與管理指南](./INSTALL_SFTPGO.md)
- [API 參考文件](../docs_v1.0/REFERENCE/API_REFERENCE.md) - [API 參考文件](../docs/API_REFERENCE.md)
- [n8n 整合指南](./N8N_INTEGRATION_GUIDE.md) - [n8n 整合指南](./N8N_INTEGRATION_GUIDE.md)
@@ -1,23 +1,3 @@
---
document_type: "reference_doc"
service: "REDIS"
title: "Momentry Core Redis Key 設計規範"
date: "2026-03-17"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "redis"
- "設計規範"
ai_query_hints:
- "查詢 Momentry Core Redis Key 設計規範 的內容"
- "Momentry Core Redis Key 設計規範 的主要目的是什麼?"
- "如何操作或實施 Momentry Core Redis Key 設計規範?"
---
# Momentry Core Redis Key 設計規範 # Momentry Core Redis Key 設計規範
| 項目 | 內容 | | 項目 | 內容 |
@@ -1,38 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Node.js 開發指南"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發指南"
ai_query_hints:
- "查詢 Node.js 開發指南 的內容"
- "Node.js 開發指南 的主要目的是什麼?"
- "如何操作或實施 Node.js 開發指南?"
---
# Node.js 開發指南 # Node.js 開發指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述 ## 概述
本文檔說明 Momentry 專案中 Node.js 環境的配置、管理與監控。 本文檔說明 Momentry 專案中 Node.js 環境的配置、管理與監控。
@@ -208,8 +175,7 @@ psql -U accusys -h localhost -d momentry -c "SELECT * FROM node_version_baseline
| 應用 | Node.js 版本 | 執行路徑 | Port | 狀態 | 說明 | | 應用 | Node.js 版本 | 執行路徑 | Port | 狀態 | 說明 |
|------|-------------|----------|------|------|------| |------|-------------|----------|------|------|------|
| n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5679 | ✅ 執行中 | 工作流自動化平台 | | n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5690 | ✅ 執行中 | 工作流自動化平台 |
| markdownlint-cli | 25.x | /opt/homebrew/bin/npm | - | ✅ 已安裝 | Markdown lint 工具 |
| - | - | - | - | - | 新增應用請填入此表 | | - | - | - | - | - | 新增應用請填入此表 |
--- ---
-94
View File
@@ -1,94 +0,0 @@
# Non-Human Sound Detection — Tool Selection Report
**Date:** 2026-05-10
**Movie:** Charade (1963), 113 min
**Audio:** 16kHz mono WAV
**Goal:** Detect non-human sound events (gunshots, impacts, doors, music, etc.)
## Tested Approaches
### Approach A: AST AudioSet (HuggingFace)
| Item | Detail |
|------|--------|
| Model | `MIT/ast-finetuned-audioset-10-10-0.4593` |
| Method | Audio Spectrogram Transformer, fine-tuned on AudioSet-2M (527 classes) |
| Dependencies | `transformers`, `torch` ✅ (no torchcodec needed) |
| Load time | ~1s on M5 |
| Inference time | ~0.5s per 3-second clip (805k params, float32) |
| Accuracy | Good — correctly distinguishes speech vs. door vs. music |
**Test results on Charade:**
| Time | Energy-based said | AST AudioSet said | Verdict |
|------|------------------|-------------------|---------|
| 0:10 | — | Environmental noise (26%) | Background noise, plausible |
| 10:32 | Gunshot candidate (43x) | **Speech (76%)** | ✅ AST correct |
| 57:00 | Gunshot candidate (49x) | **Door (62%) + Slam (5%)** | ✅ AST correct |
| 65:13 | Gunshot candidate (50x) | **Speech (58%)** | ✅ AST correct |
| 85:12 | Gunshot candidate (39x) | **Speech (68%)** | ✅ AST correct |
**Conclusion**: Energy-based impulse detection has **100% false positive rate** for gunshot detection. AST AudioSet correctly classifies all candidates as non-gunshot.
### Approach B: Custom Energy + Spectral Features
| Item | Detail |
|------|--------|
| Method | RMS energy + spectral centroid + sub-band energy ratios |
| Speed | ~3s for full 113-min movie (every 10th window) |
| Accuracy | Poor — cannot distinguish gunshot from speech, door, music |
| Result | 1 "gunshot_candidate" from 453 test windows; all false positives on verification |
**Conclusion**: Useful as a **coarse pre-filter** (Stage 1), not as a standalone classifier.
## Two-Stage Design
```
Stage 1 (Energy filter, ~1 min):
Full audio → sliding window RMS + centroid → ~200 candidate windows
|
v
Stage 2 (AST classifier, ~2 min):
Extract 3-sec audio for each candidate → AST AudioSet classification
|
v
Non-speech events: gunshot, explosion, door slam, music, etc.
```
Estimated processing: ~3 min for full movie (vs. 75 min for full AST scan)
## Key AudioSet Classes Relevant to Charade
| Class | AudioSet ID | Relevance |
|-------|-------------|-----------|
| Gunshot, gunfire | 402 | **Primary target** |
| Explosion | 400 | Hand grenade in plot |
| Door slams | 404 | Scenes at hotel, apartment |
| Music | 130-133 | Background score |
| Speech | 0-3 | Already handled by ASR |
| Vehicle | 100-110 | Car sounds in Paris chase |
| Glass break | 424 | Window breaking scene |
## Actor-voice gender mismatches (resolved by fine-grained ASRX)
During the speaker mapping work, 20 segments where the old face→TMDb assignment said "Audrey Hepburn" but the new ASRX voice embedding clearly said "MALE". These segments were verified via video clips and confirmed to be scenes where:
1. A male speaker (Cary Grant or other) is speaking while Audrey Hepburn's face is on screen
2. The old pipeline incorrectly assigned the speaker name based on face identity
3. The fine-grained sliding window approach correctly resolves these
The 20 segments were from SPEAKER_5 (10 segs) and SPEAKER_9 (10 segs), both of which mapped to MALE voice clusters. These were re-assigned to "Cary Grant" or "Unknown" as appropriate.
## Recommendations
| Approach | Speed | Accuracy | Best for |
|----------|-------|----------|----------|
| Energy pre-filter | ✅ 1 min | ❌ Low | Stage 1: candidate selection |
| AST AudioSet | ⚠️ 2 min | ✅ High | Stage 2: event classification |
| Full AST scan | ❌ 75 min | ✅ High | N/A — two-stage is better |
**Design**: Two-stage pipeline: energy pre-filter → AST classifier
**Implementation path**:
1. Write `scripts/non_human_sound_detector.py` with the two-stage design
2. Output `{uuid}.sound_events.json` with typed events
3. Integrate into the sound_event_detector framework
-150
View File
@@ -1,150 +0,0 @@
# Phase 1 Completion Report — v2 (fine-grained ASRX)
**File**: Charade (1963) Cary Grant & Audrey Hepburn
**UUID**: `aeed71342a899fe4b4c57b7d41bcb692`
**Date**: 2026-05-10
**System**: M5 (MacBook Pro, 48GB, Apple Silicon)
---
## 1. Processor Outputs
| File | Size | Description |
|------|------|-------------|
| `asr.json` | 413KB | 3,417 segments, full movie coverage (Whisper small) |
| `asrx.json` | **18MB** | **4,188 segments** (fine-grained, ECAPA-TDNN) |
| `asrx_fine.json` | 45MB | 4,188 fine segments + voice embeddings (intermediate) |
| `cut.json` | 329KB | 2,260 scenes |
| `yolo.json` | 181MB | 169,625 frames with object detections |
| `face.json` | **106MB** | 4,550 frames, 5,910 faces @ 8Hz (CoreML 512D) |
| `face_traced.json` | 110MB | Traced faces with 423 identity traces |
| `lip.json` | 492KB | Lip openness analysis |
| `ocr.json` | 277KB | 606 OCR frames |
| `pose.json` | 26MB | 4,211 pose frames |
| `scene.json` | 403B | Scene classification |
## 2. Pipeline 8-Stage Checklist
| Stage | Status | Detail |
|-------|--------|--------|
| ASR | ✅ | 3,417 segments, last end 6,773s (100%) |
| ASRX | ✅ | **4,188 segments** (fine-grained, 10→3 speakers mapped) |
| Sentence Chunks | ✅ | **4,188 sentence chunks** with yolo_objects + face_ids |
| Vectorization | ✅ | 4,188 Qdrant (768D), all 3 collections updated |
| Face Trace | ✅ | 423 traces, 11,820 detections @ 8Hz |
| TKG Graph | ✅ | 498 nodes, 1,617 edges |
| Trace Chunks | ✅ | 423 trace chunks |
| Phase 1 Release | ✅ | 3.0GB package |
## 3. Speaker Identification
### ASRX Enhancement (3417 → 4188 segments)
The original Whisper ASR merges rapid back-and-forth dialogue into single segments. A sliding-window ECAPA-TDNN approach was developed to detect speaker change points within each ASR segment:
1. **Sliding window**: 1.5s window, 0.75s stride across full audio
2. **ECAPA-TDNN 192D embedding** per window
3. **Classification** against reference centroids (Cary Grant, Audrey Hepburn, Unknown)
4. **Majority-vote smoothing** over 3 adjacent windows
5. **Change point detection** where classified speaker changes
6. **Split** original ASR segment at each change point
**Result**: 3,417 → **4,188 segments** (+771, +22.6%). Validated via gender classification (ECAPA-TDNN → 92.3% agreement with character identity).
### Speaker Mapping (Centroid-based)
| Speaker ID | Name | Segments | Duration | Voice Gender |
|------------|------|----------|----------|-------------|
| SPEAKER_0 | Audrey Hepburn | 1,658 | 2,786s | FEMALE |
| SPEAKER_1 | Cary Grant | 2,033 | 3,962s | MALE |
| SPEAKER_2 | Unknown (minor) | 497 | 806s | MIXED |
Method: Reference centroids built from 3,107 known segments (1,420 Cary + 1,689 Audrey). Each fine segment classified by cosine similarity to nearest centroid. No cross-contamination between speaker clusters.
### Gender Validation
Two small clusters (SPEAKER_5: 10 segs, SPEAKER_9: 10 segs) initially showed MALE voice → Audrey assignment. Video clip verification confirmed these are segments where a male voice speaks while Audrey is on screen (old face-based matching was incorrect). The fine-grained segmentation correctly resolves these.
## 4. Sentence Chunks — Full Migration
All 4,188 fine segments were written to `dev.chunks` with complete data per chunk:
| Chunk Field | Value | Source |
|-------------|-------|--------|
| `start_time`/`end_time` | Fine segment boundaries | `asrx_fine.json` |
| `start_frame`/`end_frame` | time × 25fps | Calculated |
| `content` | `{data: {text, text_normalized}, rule: rule_1}` | ASR text |
| `metadata.yolo_objects` | Dedup class names in frame range | `pre_chunks(yolo)` |
| `metadata.face_ids` | Trace IDs in frame range | `face_detections` |
| `metadata.speaker_name` | Centroid-matched identity | `asrx_fine.json` |
- 4,158/4,188 chunks have YOLO objects (avg 3-5 object classes)
- 398/4,188 chunks have face IDs (face data covers first ~12 min only)
### Parent/Story Chunks
| Metric | Before (v1) | After (v2) |
|--------|-------------|------------|
| Children per parent | 15 (fixed) | 15 (fixed) |
| Total parents | 228 | **280** |
| LLM summaries | 228 (Gemma4) | **280** (Gemma4, regenerated) |
| Qdrant stories | 456 pts | **560 pts** |
## 5. Qdrant Vector Collections
| Collection | Dims | Points | Content | Status |
|-----------|------|--------|---------|--------|
| `momentry_dev_v1` | 768 | **4,188** | Sentence chunk embeddings (EmbeddingGemma) | ✅ |
| `momentry_dev_stories` | 768 | **560** | 280 dialogue + 280 LLM summary | ✅ |
| `momentry_dev_faces` | 512 | 5,910 | Face embeddings (8Hz CoreML) | ✅ |
| `momentry_dev_voice` | 192 | **4,188** | Voice embeddings (ECAPA-TDNN) | ✅ |
| `sentence_story` | 768 | **4,188** | Sentence template with speaker | ✅ |
| `sentence_summary` | 768 | **4,188** | Context-aware LLM sentence summary | ✅ |
## 6. ASR Model Selection
A comprehensive benchmark (5 models × 2 VAD settings × 3 test clips = 30 runs) showed:
| Model | Segments | Chars | Runtime | Verdict |
|-------|----------|-------|---------|---------|
| tiny | 56 avg | 1,730 | **9.2s** | Most segments, best text capture |
| **small** | **55 avg** | **1,704** | **17.6s** | **Best balance (current)** |
| base | 42 avg | 1,751 | 10.1s | Good but fewer segments |
| medium | 52 avg | 1,627 | 339.6s | Slow, loses text |
| large-v3 | 20 avg | 1,249 | 68.8s | **Worst**: merges utterances, loses 26% text |
**Conclusion**: Keep `faster-whisper small (VAD 500ms)`. The missing-text problem is not solvable by model size — even tiny captures more text than large-v3. Root cause is Whisper's lack of speaker turn detection in segment boundary logic, which is solved by the sliding-window ASRX approach above.
## 7. Release Package
| Component | Size |
|-----------|------|
| `output_json/` | 13 processor files |
| `chunks.csv` | 3.2MB |
| `vectors.csv` | 58MB |
| `identities.csv` | 1MB |
| `schema.sql` | 30KB |
| Qdrant snapshots (5 collections) | ~3GB |
| `RELEASE_INFO.txt` | Metadata |
| **Total** | **~3.0GB** |
## 8. Key Technical Decisions
| Decision | Rationale |
|----------|-----------|
| Sliding window 1.5s/0.75s | Optimal balance: captures turn boundaries without over-splitting |
| Centroid-based classification | 0.8+ similarity, no retraining needed, 100% consistent |
| Word-timestamp ASR for text | Re-run with `word_timestamps=True`, 87% coverage; remaining 13% → per-segment ASR fallback |
| Fixed 15 children/parent | Maintains Phase 1 design consistency |
| `yolo_objects` dedup | Only class names stored per chunk (not per-frame) |
| `face_ids` via `trace_id` | `face_id` column is NULL in DB; `trace_id` is the actual identifier |
| Keep ASR small model | Benchmarked 5 models; larger models lose text, not gain it |
| `app.run(threaded=True)` | Dashboard v2: single-threaded Flask was blocking on subprocess calls |
## 9. Phase 2 Preparation
Pending for Phase 2:
- Rule 3 scene chunking (cut-based parent chunks)
- 5W1H Agent (LLM-generated scene summaries)
- Full pipeline + 5W1H release packaging
- Source separation (Demucs/HPSS) for overlapping speech scenarios
-63
View File
@@ -1,63 +0,0 @@
# Phase 1 Release Checklist
**UUID**: `aeed71342a899fe4b4c57b7d41bcb692`
**Model**: v2 (fine-grained ASRX, 4,188 segments)
**Date**: 2026-05-10
## 1. Processor Outputs
- [x] `asr.json` — faster-whisper small, 3,417 segments
- [x] `asrx.json` — ECAPA-TDNN fine-grained, 4,188 segments
- [x] `cut.json` — 2,260 scene cuts
- [x] `yolo.json` — 169,625 frames, object detections
- [x] `face.json` — 4,550 frames, 5,910 faces @ 8Hz
- [x] `face_traced.json` — 423 traced identities
- [x] `lip.json` — Lip openness per ASRX segment
- [x] `ocr.json` — 606 OCR frames
- [x] `pose.json` — 4,211 pose frames
- [x] `scene.json` — Scene classification
## 2. Pipeline Stages
- [x] ASR: 3,417 segments, full movie
- [x] ASRX: 4,188 segments (fine-grained), 3 speakers
- [x] Sentence chunks: 4,188 in `dev.chunks`
- [x] Vectorization: 4,188 in Qdrant `momentry_dev_v1`
- [x] Face trace: 423 traces, 11,820 detections
- [x] TKG: 498 nodes, 1,617 edges
- [x] Trace chunks: 423 in `dev.chunks`
- [x] All 8 stages passing
## 3. Qdrant Collections
- [x] `momentry_dev_v1` — 4,188 pts, 768D (EmbeddingGemma)
- [x] `momentry_dev_stories` — 560 pts, 768D (280 dialogue + 280 summary)
- [x] `momentry_dev_faces` — 5,910 pts, 512D (CoreML FaceNet)
- [x] `momentry_dev_voice` — 4,188 pts, 192D (ECAPA-TDNN)
- [x] `sentence_story` — 4,188 pts, 768D (sentence template)
- [x] `sentence_summary` — 4,188 pts, 768D (context-aware LLM)
## 4. Database (dev.chunks)
- [x] Sentence chunks: 4,188 with speaker_name, speaker_id
- [x] Story chunks: 280 with LLM summaries
- [x] Cut chunks: 1,130
- [x] Trace chunks: 423
- [x] YOLO objects in metadata: 4,158/4,188
- [x] Face IDs in metadata: 398/4,188
- [x] Parent-child relationships set
## 5. Speaker Mapping
- [x] SPEAKER_0 → Audrey Hepburn (1,658 segs, gender FEMALE ✅)
- [x] SPEAKER_1 → Cary Grant (2,033 segs, gender MALE ✅)
- [x] SPEAKER_2 → Unknown (497 segs, minor characters)
- [x] Voice embeddings validated via gender classification
## 6. Release Package
- [x] Phase 1 release packaged at `release/phase1/latest/`
- [x] Qdrant snapshots for all 5 collections
- [x] `chunks.csv`, `vectors.csv`, `identities.csv` exported
- [x] `schema.sql` from PostgreSQL
- [x] Dashboard v2 running at port 5050
@@ -1,41 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Playground Binary Implementation Plan"
date: "2026-03-23"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "binary"
- "plan"
- "implementation"
- "playground"
ai_query_hints:
- "查詢 Playground Binary Implementation Plan 的內容"
- "Playground Binary Implementation Plan 的主要目的是什麼?"
- "如何操作或實施 Playground Binary Implementation Plan?"
---
# Playground Binary Implementation Plan # 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 ## Overview
Create separate `momentry_playground` binary with distinct configuration from `momentry` (production). Create separate `momentry_playground` binary with distinct configuration from `momentry` (production).
@@ -399,8 +363,8 @@ momentry_dev:worker:job:{uuid}:processor:{name}
| Document | Purpose | | Document | Purpose |
|----------|---------| |----------|---------|
| `docs_v1.0/REFERENCE/SERVICES.md` | Port allocations | | `docs/SERVICES.md` | Port allocations |
| `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` | Redis key design | | `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis key design |
| `AGENTS.md` | Code style and conventions | | `AGENTS.md` | Code style and conventions |
--- ---
-134
View File
@@ -1,134 +0,0 @@
# Processor 產出機制檢討
## 三層機制定義
### 1. 中斷接續(Interruption Resume)
Process 被殺掉後,重啟時能接續進度。
**現狀**: 大部分 processor 有 `.tmp` → `.partial` 保護,但重跑時從頭開始。
### 2. 補充機制(Supplement)
完成度不足時,只補沒做完的部分,不重跑整個。
**現狀**: 全部從頭跑,無補充。
### 3. 糾錯機制(Error Correction)
輸出檔損毀時能自動偵測並修復。
**現狀**: file-existence check 只檢查檔案存在,不檢查內容是否有效。
---
## Processor 逐一檢討
### ASR
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ `.tmp` → `.partial`(executor) | ✅ OK |
| 補充機制 | ❌ 每次從頭跑 | 若跑到 50% 被殺,下次從 0% 開始 |
| 糾錯機制 | ❌ 不驗證內容 | file-existence check 看到 `.json` 存在就跳過,不管內容 |
| Pipe | ✅ executor.run() | ✅ |
| Timeout | ✅ 已移除(None) | ✅ |
**改善方案**:
- 補充:ASR 重跑時掃描 existing `.json` 或 `.partial`,找出最後 segment 的 `end_time`,傳入 `--resume-from` 給 Python script
- 糾錯:file-existence check 對 `.json` 做 `serde_json::from_str` 驗證,無效 → 視為不存在
### ASRX
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ❌ **不用 executor**,直接寫 `.json` | 被殺掉時留下壞檔 |
| 補充機制 | ❌ 同 ASR | 依賴 ASR,ASR 不完整 ASRX 也不能跑 |
| 糾錯機制 | ❌ 不驗證內容 | 同上 |
| Pipe | ❌ **raw Command**,沒有 `.tmp` 保護 | 緊急 |
| Timeout | ⚠️ 7200s hardcode | 應改為 None(同 ASR) |
**改善方案**:
- **最優先**: 改為使用 `executor.run()`,獲得 `.tmp` 保護
- 其他同 ASR
### YOLO
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ executor `.tmp` | ✅ |
| 補充機制 | ❌ 從頭跑 | 若跑到 frame 100,000 被殺,下次從 frame 0 |
| 糾錯機制 | ❌ 不驗證內容 | yolo.json 之前就是壞的但 file check 跳過 |
**改善方案**:
- 補充:掃描 `.partial` 的最後 frame,傳入 `--resume-frame` 給 Python script
- 糾錯:file-existence check 對 `.json` 做 JSON parse 驗證
### FACE / POSE / OCR
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ executor `.tmp` | ✅ |
| 補充機制 | ❌ 從頭跑 | 同 YOLO |
| 糾錯機制 | ❌ 不驗證內容 | 同 YOLO |
**改善方案**: 同 YOLO
### CUT
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ executor `.tmp` | ✅ |
| 補充機制 | ✅ register 階段已完成,直接載入 | ✅ |
| 糾錯機制 | ❌ 不驗證內容 | 同 YOLO |
**改善方案**: 糾錯即可
### SCENE
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ **最完整**:檢查 `.err`/`.json`/`.tmp` 三種狀態 | ✅ |
| 補充機制 | ❌ 從頭跑 | ✅(scene 很快) |
| 糾錯機制 | ⚠️ 有檢查 `.err` | ✅ |
### VISUAL_CHUNK
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ executor `.tmp` | ✅ |
| 補充機制 | ❌ | ❌ |
| 糾錯機制 | ❌ **錯誤被吞掉**(回傳空結果) | 應回報 error 而非靜默失敗 |
**改善方案**: 不要吞錯誤,讓 error 往上傳
### STORY
| 面向 | 現狀 | 問題 |
|------|------|------|
| 中斷接續 | ✅ executor `.tmp` | ✅ |
| 補充機制 | ❌ | ❌ |
| 糾錯機制 | ❌ | ❌ |
---
## 優先級
### P0 — 立即修復
1. **ASRX 改用 executor.run()**
- 檔案:`src/core/processor/asrx.rs`
- 獲得 `.tmp` 保護、SIGKILL process group、`.partial` 保留
- 移除 hardcode timeout
### P1 — 糾錯機制
2. **File-existence check 加入 JSON 驗證**
- 檔案:`src/worker/job_worker.rs`
- 在 `output_path.exists()` 之後,對 `.json` 做 `serde_json::from_str::<Value>`
- 若 parse 失敗 → 不 skip,當作檔案不存在繼續跑
- 若 parse 成功但內容空(無 segments/frames)→ 當不完整
### P2 — 補充機制
3. **ASR resume-from 補充**
- 檔案:`src/core/processor/asr.rs` + `scripts/asr_processor.py`
- Rust 端發現 `.partial` 存在,讀取最後 segment 的 end_time
- 傳入 `--resume-from {time}` 給 Python script
- Python script 跳過 `--resume-from` 之前的音訊
4. **YOLO/Face/Pose resume-frame 補充**
- 檔案:各 processor.rs + 對應 Python script
- 掃描 `.partial` 中的最後 frame_number
- 傳入 `--resume-frame {frame}` 給 Python script
### P3 — 其他
5. **VisualChunk 不吞錯誤**
6. **Executor SIGTERM → SIGKILL 兩段式關閉**
@@ -1,40 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Python 開發規範"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發規範"
- "python"
ai_query_hints:
- "查詢 Python 開發規範 的內容"
- "Python 開發規範 的主要目的是什麼?"
- "如何操作或實施 Python 開發規範?"
---
# Python 開發規範 # Python 開發規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 新增 RedisPublisher API 文檔 | OpenCode | - |
---
## 概述 ## 概述
本文檔定義 Momentry 專案中 Python 程式碼的開發標準與最佳實踐。 本文檔定義 Momentry 專案中 Python 程式碼的開發標準與最佳實踐。
@@ -264,63 +229,6 @@ Pillow>=10.0.0
--- ---
## RedisPublisher 進度發布
### 概述
`redis_publisher.py` 提供統一的進度發布介面,用於 Python 處理器向 Rust 端的 TUI 即時回報進度。
### 基本用法
```python
#!/opt/homebrew/bin/python3.11
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from redis_publisher import RedisPublisher
def process_video(video_path: str, uuid: str):
pub = RedisPublisher(uuid)
pub.info("asr", "Starting ASR processing")
pub.progress("asr", current=50, total=100, message="Processing segment")
pub.complete("asr", "Transcription complete")
```
### API 參考
| 方法 | 說明 | 範例 |
|------|------|------|
| `info(proc, msg)` | 發布資訊訊息 | `pub.info("asr", "Model loaded")` |
| `progress(proc, cur, tot, msg)` | 發布進度 | `pub.progress("asr", 50, 100, "...")` |
| `complete(proc, msg)` | 發布完成 | `pub.complete("asr", "Done")` |
| `error(proc, msg)` | 發布錯誤 | `pub.error("asr", "Failed")` |
| `warning(proc, msg)` | 發布警告 | `pub.warning("asr", "Retry...")` |
| `percentage(proc, pct, msg)` | 發布百分比 | `pub.percentage("asr", 50.5, "50%")` |
### 結構化訊息格式
```python
from redis_publisher import MessageType, ProgressContext
# 使用 Context Manager
with ProgressContext(pub, "asr"):
# 自動發布開始/完成/錯誤
run_asr()
# 帶 extra 資料
pub.progress("asr", current=50, total=100, message="...",
extra={"fps": 30.5, "model": "tiny"})
```
### 環境變數
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `REDIS_URL` | `redis://:accusys@localhost:6379` | Redis 連線 URL |
| `REDIS_PASSWORD` | `accusys` | Redis 密碼 |
---
## 程式碼規範 ## 程式碼規範
### Import 排序 ### Import 排序
-81
View File
@@ -1,81 +0,0 @@
# Release Packaging Design
三類包:**開發系統升級包** + **生產系統升級包** + **檔案內容包**,完全獨立。
## 1. 開發系統升級包 (System/Dev)
給 playground(port 3003, dev schema)使用。
```
release/system/dev/{version}/
├── RELEASE_INFO.txt
├── source.tar.gz ← Rust + scripts source code
├── .env.development ← DATABASE_SCHEMA=dev, port 3003
├── schema_dev.sql ← dev schema DDL
├── scripts/
│ ├── pipeline_status.py
│ ├── generate_asr1.py
│ ├── apply_asr_corrections.py
│ ├── clean_sentence_text.py
│ └── import_file_package.py ← 匯入檔案內容包
├── test/
│ └── api_test.sh
└── migration/
└── {prev}_to_{version}.sql
```
升級:覆蓋 code + 執行 migration → `cargo build --bin momentry_playground` → 重啟 3003
## 2. 生產系統升級包 (System/Prod)
給 production(port 3002, public schema)使用。
```
release/system/prod/{version}/
├── RELEASE_INFO.txt
├── source.tar.gz ← Rust + scripts source code
├── .env ← DATABASE_SCHEMA=public, port 3002
├── schema_public.sql ← public schema DDL
├── scripts/ (same as dev)
├── test/
│ └── api_test.sh
└── migration/
└── {prev}_to_{version}.sql
```
## 3. 檔案內容包 (File)
一個影片的完整資料,開發與生產環境共用。
```
release/files/{file_uuid}/{version}/
├── metadata.json ← Registration info
├── RELEASE_INFO.txt
├── processors/ ← output_dev/{uuid}.*.json
│ ├── asr.json
│ ├── asrx.json
│ ├── asr-1.json
│ ├── yolo.json
│ ├── face.json
│ ├── pose.json
│ ├── ocr.json
│ ├── cut.json
│ └── scene.json
├── face_detections.csv ← 該檔案的所有 face detections
├── identities.csv ← 關聯的 identities
├── tkg_nodes.csv ← TKG nodes
├── tkg_edges.csv ← TKG edges
├── qdrant/ ← Qdrant snapshots for this file
│ ├── momentry_dev_v1.snapshot
│ ├── sentence_story.snapshot
│ └── ...
└── RELEASE_INFO.txt
```
### 匯入流程
```
1. POST /api/v1/files/register → 取得 file_uuid
2. python3 scripts/import_file_package.py --uuid {uuid} --package path/
3. 檔案狀態更新為「已註冊已處理」
```
-240
View File
@@ -1,240 +0,0 @@
# Momentry Model — 分階段交付
## 核心架構
```
Pipeline (training)
│ 每個 processor 產出 .json
│ Rule 1/3 Ingestion → chunks + embeddings
▼
momentry model for {video} ← 每部影片 = 一個 model
│ release/phase1/latest/
│ release/phase2/latest/
▼
momentry core (inference engine) ← Rust API server
│ momentry_playground (dev)
│ momentry (production)
▼
Search / Query / Identity APIs
```
- **Pipeline** = training phase:影片 → processor output → chunks → embeddings
- **Model** = 每部影片的產出 package(output_json + chunks + vectors)
- **Engine** = momentry core,吃 model 提供 API(search, trace, identity)
每個影片可有多個 model 版本,命名保留升級空間:
| Model 版本 | Qdrant Collection | 內容 | 觸發時機 |
|-----------|------------------|------|---------|
| `{uuid}_v1` | `momentry_dev_v1` | sentence chunk embedding(base) | ASR + ASRX + Rule 1 完成 |
| `{uuid}_v2` | `momentry_dev_v2` | 完整 pipeline + 5W1H | 全部完成 |
| `{uuid}_v3` | `momentry_dev_v3` | object identity + custom detector | v2 + object instance matching 完成 |
各版本共存不覆蓋。
## 階段劃分
### Phase 1:Sentence Chunk Embedding(base model)
**觸發時機**: ASR + ASRX 完成 + Rule 1 Ingestion + vectorize 完成
**交付內容**:
- `{uuid}.asr.json`
- `{uuid}.asrx.json`
- chunks(chunk_type = 'sentence')
- chunk_vectors(sentence embedding)
**用途**: 終端使用者可進行語意搜尋
### Phase 2:完整 Pipeline(v2 model)
**觸發時機**: 全部 processor 完成 + Rule 3 Ingestion + 5W1H Agent
**交付內容**:
- Phase 1 全部內容
- 所有 `{uuid}.*.json`(cut, yolo, face, pose, ocr, ...)
- chunks(chunk_type = 'cut', 'visual', 'trace', 'story')
- chunk_vectors(summary embedding)
- identities / identity_bindings / face_detections
**用途**: 完整搜尋 + 摘要 + 人物識別
---
## Worker Pipeline
```
ASR 完成 → ASRX 完成
↓
Rule 1 Ingestion (sentence chunks)
↓
vectorize_chunks (sentence embedding)
↓
📦 Phase 1 release ───→ release/phase1/latest/ (base model)
↓
其他 processors 繼續 (yolo, face, pose, ocr, ...)
↓
Rule 3 Ingestion + 5W1H Agent
↓
📦 Phase 2 release ───→ release/phase2/latest/ (full model)
```
## 產出目錄結構
```
release/
├── phase1/
│ ├── {version}_{timestamp}/
│ │ ├── output_json/ ← 所有已完成的 .json
│ │ ├── chunks.csv ← sentence chunks
│ │ ├── vectors.csv ← sentence embeddings
│ │ ├── schema.sql ← chunks table DDL
│ │ └── RELEASE_INFO.txt
│ └── latest → {version}_{timestamp}
│
└── phase2/
├── {version}_{timestamp}/
│ ├── output_json/ ← 所有 .json
│ ├── chunks.csv ← 所有 chunks
│ ├── vectors.csv ← 所有 embeddings
│ ├── identities.csv ← 人物身分
│ ├── schema.sql ← 完整 schema
│ └── RELEASE_INFO.txt
└── latest → {version}_{timestamp}
```
## momentry model vs momentry core
| | momentry model | momentry core |
|---|---|---|
| 類比 | 訓練好的 weights | inference engine |
| 內容 | `.json` + chunks + vectors | Rust binary |
| 生命週期 | 每部影片產出一個 | 一個 binary 服務所有影片 |
| 版本 | `{uuid}_v1`(base) / `{uuid}_v2` / `{uuid}_v3` | `momentry_playground` / `momentry` |
| 交付對象 | 終端使用者 | 部署工程師 |
---
## Wiki 機制:每個 model 都可被調整
每個 momentry model(`{uuid}_v1` / `v2` / `v3`)不只是唯讀的產出,而是可透過 wiki 機制持續改善。
### 與傳統 RAG 的區別
| | 傳統 RAG | momentry wiki |
|---|---|---|
| 知識儲存 | vector DB(ephemeral) | model package(permanent) |
| 修正方式 | query 時 LLM 決定是否採用 | 使用者/Agent 直接編輯 |
| 修正持久性 | ❌ 下次 query 就消失 | ✅ 寫入 model,版本化保存 |
| 模型改進 | 無(僅改變 prompt) | 下次 version bump 時合併為 ground truth |
| 協作方式 | 單向(retrieve → generate) | 雙向(編輯 → 合併 → 改進) |
| 離線可用 | ❌ 需 vector DB + LLM | ✅ 離線查閱 wiki 目錄 |
**momentry wiki 不是 RAG 的替代品,而是 model 的生命週期管理機制。**
### 概念
```
momentry model (release package)
├── output_json/ ← 唯讀,processor 產出
├── chunks.csv ← 唯讀,ingestion 產出
├── vectors.csv ← 唯讀,embedding 產出
└── wiki/ ← 可編輯,使用者貢獻知識
├── identities.json ← "trace 5 = Audrey Hepburn"
├── objects.json ← "object 42 = 郵票 #1"
├── corrections.json ← "ASR 'Hello' → 'Halo'"
└── changelog.json ← 編輯歷史
```
### 資料流向
```
使用者/Agent 編輯 wiki
↓
DB wiki_entries + wiki_revisions 寫入
↓
下次 release 打包時 merge 進 model
↓
TKG label 更新 (tkg_nodes.label)
↓
新版 model version bump
```
### 與 TKG 的關係
wiki 的 identity 和 object 標註會回寫到 TKG node label:
```
(face_trace:5) label="Audrey Hepburn" ← wiki 編輯
(object_instance:42) label="郵票 #1" ← wiki 編輯
```
這些編輯累積後,可做為下一版 model training 的 ground truth。
### 實作方向
**DB 層** — 新 table `wiki_entries` + `wiki_revisions`:
```sql
wiki_entries (target_type, target_id, title, body, summary, status, version, file_uuid)
wiki_revisions (entry_id, version, title, body, summary, change_summary, edited_by)
```
**API 層** — CRUD + 版本歷史:
```
GET /api/v1/wiki/{target_type}/{target_id}
PUT /api/v1/wiki/{target_type}/{target_id}
GET /api/v1/wiki/{target_type}/{target_id}/revisions
POST /api/v1/wiki/search
```
**打包層** — `release_pack.py` 加入 wiki 匯出,與 model 共存
---
## Phase 3:Object Identity(v3 model)
### 目標
從影片中提取關鍵物體(郵票、手槍、信封、放大鏡...),對同類物體做 instance-level 的跨畫面追蹤與辨識,達到類似 face trace 的效果 — 不只是 detect class,還能區分「這一張郵票」vs「那一張郵票」。
### 現狀問題
1. **COCO 80 類不包含關鍵物體** — 郵票、手槍、信封、放大鏡等不在 COCO 資料集中
2. **YOLOv5nano 偵測率低** — 即使是 COCO 類別(knife, cell phone)在 nano 模型上 recall 不足
3. **無 object instance matching** — 目前只有 frame-level detection,沒有跨 frame 的物體追蹤
### 技術方向
```
YOLOv8m/OWL-ViT → 改善 detection coverage
↓
Object Tracker (IoU + embedding,類似 face tracker)
↓
object_trace → TKG CO_OCCURS_WITH edges
↓
object identity → 同物體跨場景辨識
```
| 方向 | 方法 | 效果 |
|------|------|------|
| Model upgrade | `yolov5nu` → `yolov8s.pt` / `yolov8m.pt` | COCO recall 提升 |
| Custom fine-tune | 收集 stamps/guns 資料 fine-tune YOLO | 可偵測非 COCO 物件 |
| Zero-shot | OWL-ViT / Grounding DINO by text prompt | 不用 training,但速度慢 |
| Object trace | IoU + embedding 跨 frame 匹配 | instance-level 追蹤 |
| Object identity | clustering 跨場景辨識同一物體 | 可在全片搜尋「這把槍」 |
### 與 TKG 整合
```
face_trace -[:CO_OCCURS_WITH]-> object_instance:5 (這把槍)
face_trace -[:CO_OCCURS_WITH]-> object_instance:42 (這張郵票)
查詢: "Audrey Hepburn 拿這把槍的畫面"
→ face_trace:5 -[:SPEAKS_AS]-> SPEAKER_0
→ face_trace:5 -[:CO_OCCURS_WITH]-> object_instance:5
```
### 交付順序
1. YOLO model upgrade(低難度,立即見效)
2. Object tracker(中難度,參考 face tracker 實作)
3. Custom fine-tune / zero-shot(高難度,需資料或新模型)
@@ -1,42 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Rust 開發規範 - Momentry Core"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發規範"
- "rust"
- "core"
- "momentry"
ai_query_hints:
- "查詢 Rust 開發規範 - Momentry Core 的內容"
- "Rust 開發規範 - Momentry Core 的主要目的是什麼?"
- "如何操作或實施 Rust 開發規範 - Momentry Core?"
---
# Rust 開發規範 - Momentry Core # Rust 開發規範 - Momentry Core
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 新增 PythonExecutor 模組說明 | OpenCode | - |
---
本規範定義 Momentry Core 專案的 Rust 開發標準,確保程式碼品質與一致性。 本規範定義 Momentry Core 專案的 Rust 開發標準,確保程式碼品質與一致性。
## 1. 專案結構 ## 1. 專案結構
@@ -64,7 +27,6 @@ src/
│ │ └── qdrant_db.rs │ │ └── qdrant_db.rs
│ ├── processor/ # 影片處理器 │ ├── processor/ # 影片處理器
│ │ ├── mod.rs │ │ ├── mod.rs
│ │ ├── executor.rs # Python 腳本統一執行器 (含超時控制)
│ │ ├── asr.rs # 語音識別 │ │ ├── asr.rs # 語音識別
│ │ ├── asrx.rs # 說話者分離 │ │ ├── asrx.rs # 說話者分離
│ │ ├── ocr.rs # 文字辨識 │ │ ├── ocr.rs # 文字辨識
@@ -311,47 +273,6 @@ for line in stderr.lines() {
} }
``` ```
### 5.3 PythonExecutor 統一執行器
使用 `PythonExecutor` 封裝 Python 腳本執行邏輯:
```rust
use momentry_core::core::processor::{PythonExecutor, validate_python_env};
// 驗證 Python 環境
fn init() -> Result<()> {
validate_python_env()?;
Ok(())
}
// 使用 Executor 執行腳本
async fn run_script() -> Result<()> {
let executor = PythonExecutor::new()?;
executor.run(
"asr_processor.py",
&["/path/to/video.mp4", "/path/to/output.json"],
Some("job-uuid"),
"ASR",
Some(Duration::from_secs(3600)), // 1小時超時
).await?;
Ok(())
}
```
#### Processor 超時設定
| Processor | 超時 | 說明 |
|----------|------|------|
| ASR | 1 小時 | 語音識別 |
| ASRx | 2 小時 | 說話者分離 |
| YOLO | 2 小時 | 物件偵測 |
| OCR | 2 小時 | 文字辨識 |
| Face | 2 小時 | 人臉偵測 |
| Pose | 2 小時 | 姿態估計 |
| Cut | 1 小時 | 場景偵測 |
--- ---
## 6. Python 與 Node.js 混用規範 ## 6. Python 與 Node.js 混用規範
@@ -423,7 +344,7 @@ let output = Command::new(venv_python)
#### 6.3.1 版本鎖定 #### 6.3.1 版本鎖定
參考 `docs_v1.0/REFERENCE/NODEJS.md`: 參考 `docs/NODEJS.md`:
| 版本 | 用途 | 路徑 | | 版本 | 用途 | 路徑 |
|------|------|------| |------|------|------|
@@ -458,7 +379,7 @@ let output = Command::new(venv_python)
| **獨立路徑** | Python 用 venv 路徑,Node.js 用 node@22 路徑 | | **獨立路徑** | Python 用 venv 路徑,Node.js 用 node@22 路徑 |
| **獨立環境** | n8n 服務使用 launchd plist,不與 Rust 共享環境 | | **獨立環境** | n8n 服務使用 launchd plist,不與 Rust 共享環境 |
| **明確版本** | 所有腳本明確指定直譯器路徑 | | **明確版本** | 所有腳本明確指定直譯器路徑 |
| **PORT 分配** | n8n: 5678/5679, API: 另行分配 | | **PORT 分配** | n8n: 5678/5690, API: 另行分配 |
#### 6.4.2 環境變數隔離 #### 6.4.2 環境變數隔離
@@ -607,7 +528,7 @@ lsof -i :5678 # n8n
1. 更新本文檔的版本表格 1. 更新本文檔的版本表格
2. 建立對應的監控腳本 2. 建立對應的監控腳本
3. 如需 launchd plist,建立並加入 `momentry_runtime/plist/` 3. 如需 launchd plist,建立並加入 `momentry_runtime/plist/`
4. 更新 `docs_v1.0/REFERENCE/NODEJS.md` 或 `docs_v1.0/REFERENCE/PYTHON.md` 4. 更新 `docs/NODEJS.md` 或 `docs/PYTHON.md`
### 5.2 進度回報 ### 5.2 進度回報
@@ -1,21 +1,3 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry 系統服務安裝與管理指南"
date: "2026-03-18"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "系統服務安裝與管理指南"
ai_query_hints:
- "查詢 Momentry 系統服務安裝與管理指南 的內容"
- "Momentry 系統服務安裝與管理指南 的主要目的是什麼?"
- "如何操作或實施 Momentry 系統服務安裝與管理指南?"
---
# Momentry 系統服務安裝與管理指南 # Momentry 系統服務安裝與管理指南
| 項目 | 內容 | | 項目 | 內容 |
@@ -1,22 +1,4 @@
# Momentry 服務添加規範 v2.1 # Momentry 服務添加規範 v2.0
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 更新時間 | 2026-03-24 |
| 文件版本 | V2.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V2.1 | 2026-03-24 | 更新 launchctl 命令,使用 bootstrap | OpenCode | OpenCode / big-pickle |
---
## 一、概述 ## 一、概述
@@ -283,44 +265,24 @@ EOF
### 8.1 基本操作 ### 8.1 基本操作
```bash ```bash
# 啟動服務 (使用 launchctl bootstrap) # 啟動服務
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.{service}.plist sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 停止服務 (使用 launchctl bootout) # 停止服務
sudo launchctl bootout system/com.momentry.{service}.plist sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
# 重新載入服務 # 重啟服務
sudo launchctl bootout system/com.momentry.{service}.plist sudo launchctl unload /Library/LaunchDaemons/com.momentry.{service}.plist
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.{service}.plist sudo launchctl load /Library/LaunchDaemons/com.momentry.{service}.plist
# 查看服務狀態 # 查看服務狀態
launchctl list | grep com.momentry launchctl list | grep momentry
# 查看特定服務狀態
launchctl list | grep com.momentry.{service}
# 查看服務日誌 # 查看服務日誌
tail -f /Users/accusys/momentry/log/{service}.log tail -f /Users/accusys/momentry/log/{service}.log
tail -f /Users/accusys/momentry/log/{service}.error.log tail -f /Users/accusys/momentry/log/{service}.error.log
``` ```
### 8.2 批量管理
```bash
# 啟動所有 Momentry 服務
for plist in /Library/LaunchDaemons/com.momentry.*.plist; do
sudo launchctl bootstrap system "$plist"
done
# 停止所有 Momentry 服務
for svc in $(launchctl list | grep com.momentry | awk '{print $3}'); do
sudo launchctl bootout system/$svc 2>/dev/null
done
# 查看所有 Momentry 服務狀態
launchctl list | grep com.momentry
```
### 8.2 故障排除 ### 8.2 故障排除
```bash ```bash
@@ -695,4 +657,3 @@ EOF
| 1.0 | 2026-03-15 | 初始版本 | | 1.0 | 2026-03-15 | 初始版本 |
| 2.0 | 2026-03-15 | 統一 Plist 位置、移除 root/用戶區分、加入運行方式分類 | | 2.0 | 2026-03-15 | 統一 Plist 位置、移除 root/用戶區分、加入運行方式分類 |
| 2.1 | 2026-03-15 | 新增服務備份作業、服務完整刪除作業 | | 2.1 | 2026-03-15 | 新增服務備份作業、服務完整刪除作業 |
| 2.1 | 2026-03-24 | 更新 launchctl 命令,使用 `bootstrap`/`bootout` 替代 `load`/`unload` | |
-101
View File
@@ -1,101 +0,0 @@
# Trace Search API 設計
## 概念
trace 是一種 chunk。
現有的 chunk_type: `cut`, `sentence`, `visual`, `story`
新增 chunk_type: `trace`
每個 trace(人物跨 frame 追蹤軌跡)就是一個時間區間 + 區間內的 ASR text。
跟其他 chunk 完全一樣,只是切分維度不同:
- cut chunk = 鏡頭切換
- sentence chunk = 語句邊界
- visual chunk = 畫面物體組合
- **trace chunk = 人物出現區間 + 當下 spoken text**
這樣 trace 可以直接放進現有的 `chunks` 表,共用 embedding、搜尋、Qdrant sync 整套機制,不需要任何新 table。
## chunks 表現有結構
```sql
chunks (
id, file_uuid, chunk_type, -- 'trace' 新增
start_frame, end_frame, start_time, end_time,
text_content, -- trace 區間的 ASR text
embedding, -- text_content 的 pgvector
metadata JSONB, -- { trace_id, face_count, identity_id, identity_name }
...
)
```
## 資料產生流程(worker 擴充)
在 face processing + `store_traced_faces.py` 完成後:
1. 查詢 `face_detections` 聚合每個 trace 的 `MIN(frame)`, `MAX(frame)`, `COUNT(*)`
2. 對每個 trace,查詢 `pre_chunks WHERE processor_type='asr'` 中與 trace time range 重疊的 text
3. 彙整 text → EmbeddingGemma 產生 `embedding`
4. 寫入 `chunks`(`chunk_type='trace'`),metadata 含 `trace_id`, `face_count`, `identity_id`
5. embedding 自動進 Qdrant(與既有 chunk 同一 collection)
## Search API 擴充
Universal Search 的 `types` 原本就支援 `"chunk"`。
在 chunk 搜尋中過濾 `chunk_type = 'trace'` 即可。
**Request**:
```json
{
"query": "open the door",
"types": ["chunk"],
"filters": { "chunk_type": "trace" },
"uuid": "aeed71342a899fe4b4c57b7d41bcb692",
"page": 1,
"page_size": 20
}
```
**Response**(與既有 Chunk result 相同):
```json
{
"type": "chunk",
"chunk_id": "chunk_42",
"chunk_type": "trace",
"start_frame": 45200, "end_frame": 45900,
"start_time": 1808.0, "end_time": 1836.0,
"score": 0.87,
"text": "Open the door. Come on, hurry up.",
"metadata": {
"trace_id": 5,
"face_count": 42,
"identity_name": "Audrey Hepburn"
}
}
```
完全沿用既有的 `SearchResult::Chunk` variant,不用新增 enum variant。
### 搜尋語法
```sql
SELECT c.*
FROM dev.chunks c
WHERE c.file_uuid = $1
AND c.chunk_type = 'trace'
AND c.embedding IS NOT NULL
ORDER BY c.embedding <=> $2
LIMIT $3;
```
## 總結
| 項目 | 作法 |
|------|------|
| 新 table | ❌ 不需要 |
| 新 enum variant | ❌ 不需要 |
| SearchResult 改動 | ❌ 不需要 |
| chunk_type 新增 | ✅ `'trace'` |
| worker 擴充 | ✅ 產生 trace chunk (face done 後) |
| SearchFilters 擴充 | ✅ 加 `chunk_type` filter |
| Qdrant | ✅ 自動(既有 chunk collection) |
@@ -1,40 +1,5 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Video Registration"
date: "2026-03-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "video"
- "registration"
ai_query_hints:
- "查詢 Video Registration 的內容"
- "Video Registration 的主要目的是什麼?"
- "如何操作或實施 Video Registration?"
---
# Video Registration # 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 系統進行處理。 影片註冊 API (`POST /api/v1/register`) 用於將影片加入 Momentry Core 系統進行處理。
@@ -174,13 +139,11 @@ SFTPgo 的用戶目錄結構:
# 使用相對路徑註冊 # 使用相對路徑註冊
curl -X POST http://localhost:3002/api/v1/register \ curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}' -d '{"path": "./demo/video.mp4"}'
# 或使用多層目錄 # 或使用多層目錄
curl -X POST http://localhost:3002/api/v1/register \ curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/movies/2024/video.mp4"}' -d '{"path": "./demo/movies/2024/video.mp4"}'
``` ```
@@ -222,7 +185,6 @@ pub fn extract_user_from_relative_path(relative_path: &str) -> (String, String)
```bash ```bash
curl -X POST http://localhost:3002/api/v1/probe \ curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}' -d '{"path": "./demo/video.mp4"}'
``` ```
@@ -260,5 +222,12 @@ curl -X POST http://localhost:3002/api/v1/probe \
| `src/core/storage/uuid.rs` | UUID 計算邏輯 | | `src/core/storage/uuid.rs` | UUID 計算邏輯 |
| `src/api/server.rs` | 註冊與 Probe API 實現 | | `src/api/server.rs` | 註冊與 Probe API 實現 |
| `src/core/probe/ffprobe.rs` | ffprobe 整合 | | `src/core/probe/ffprobe.rs` | ffprobe 整合 |
| `docs_v1.0/IMPLEMENTATION/SFTPGO_DEMO_USER.md` | SFTPgo 用戶設置 | | `docs/SFTPGO_DEMO_USER.md` | SFTPgo 用戶設置 |
| `docs_v1.0/REFERENCE/API_ENDPOINTS.md` | API 端點總覽 | | `docs/API_ENDPOINTS.md` | API 端點總覽 |
## 歷史
| 日期 | 變更 |
|------|------|
| 2026-03-25 | 初始版本 - 新增 UUID 計算規則和重複註冊檢查 |
| 2026-03-25 | 新增 Probe API 說明 |
-201
View File
@@ -1,201 +0,0 @@
# Momentry Eye API Reference
**Vision Agent** — Multi-model zero-shot object detection service.
Port: `5052` | Resource IDs: `eye-gdino`, `eye-paligemma`
---
## Models
| Model | ID | Params | Size | Confidence | Speed | License |
|-------|-----|--------|------|------------|-------|---------|
| Grounding DINO | `grounding-dino` | 232M | 891MB | ✅ 0-1 score | ~340ms | Apache 2.0 |
| PaliGemma 3B | `paligemma` | 2,923M | ~3GB | ❌ no score | ~80ms | Gemma license |
## Endpoints
### `GET /health`
System status and loaded models.
```bash
curl localhost:5052/health
```
Response:
```json
{
"status": "ok",
"models_loaded": ["grounding-dino"],
"models_available": ["grounding-dino", "paligemma"],
"device": "mps",
"port": 5052
}
```
### `GET /models`
List available models with specs.
```bash
curl localhost:5052/models
```
### `POST /detect`
Detect objects in a single video frame.
```bash
curl localhost:5052/detect \
-H "Content-Type: application/json" \
-d '{"time":5461, "prompt":"gun", "model":"grounding-dino"}'
```
**Parameters:**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `uuid` | string | `aeed71342a...` | Video file UUID |
| `time` | float | `0` | Timestamp in seconds |
| `prompt` | string | `"gun"` | Object to detect |
| `model` | string | `"grounding-dino"` | Model: `grounding-dino`, `paligemma`, or `fusion` |
| `threshold` | float | `0.1` | Minimum confidence (GDINO only) |
| `weights` | object | — | Fusion weights, e.g. `{"grounding-dino":0.6,"paligemma":0.4}` |
**Fusion mode** runs both models and combines results with weighted scoring. Default weights: GDINO 0.6, PaliGemma 0.4.
```bash
# Fusion: run both models, combine results
curl localhost:5052/detect \
-d '{"time":206, "prompt":"water gun", "model":"fusion"}'
# Custom fusion weights
curl localhost:5052/detect \
-d '{"time":206, "prompt":"gun", "model":"fusion",
"weights":{"grounding-dino":0.5,"paligemma":0.5}}'
```
**Response:**
```json
{
"model": "grounding-dino",
"detections": [
{"bbox": [726.2, 567.4, 969.0, 694.6], "score": 0.476, "label": "gun"},
{"bbox": [686.7, 567.0, 969.6, 918.3], "score": 0.262, "label": "gun"}
],
"time_ms": 345.2,
"n_detections": 2,
"shot_url": "/shots/aeed7134_5461s_gun_grounding-dino.jpg"
}
```
**Fusion response** also includes `per_model` (detections per model) and `fusion` (deduplicated combined list with `fused_score`).
### `POST /search`
Search across a time range.
```bash
# Natural language query
curl localhost:5052/search \
-d '{"query":"find the gun", "range":"5400-5600", "interval":10}'
```
**Parameters:**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `query` | string | `"find the gun"` | Natural language query (parsed to extract object) |
| `target` | string | — | `file_uuid:chunk_id` or `file_uuid:trace_id` — resolves to time range |
| `range` | string | `"0-6780"` | Manual time range |
| `interval` | int | `30` | Scan interval in seconds |
| `model` | string | `"grounding-dino"` | Detection model |
| `threshold` | float | `0.15` | Minimum confidence |
**Target resolution:**
| Format | Example | Resolves to |
|--------|---------|-------------|
| `file_uuid:chunk_id` | `uuid:uuid_story_90` | Chunk's time range |
| `file_uuid:trace_id` | `uuid:trace_5` | Trace's time range |
| `file_uuid:chunk_index` | `uuid:500` | Chunk index 500's range |
```bash
# Using target
curl localhost:5052/search \
-d '{"target":"aeed71342...:aeed71342..._story_90", "query":"gun"}'
# Using trace
curl localhost:5052/search \
-d '{"target":"aeed71342...:trace_5", "query":"person"}'
```
### `POST /multimodal`
Multi-modal search across sentence chunks — combines ASR text match + visual confirmation.
```bash
# Search for Jean-Louis: ASR match + GDINO child detection
curl localhost:5052/multimodal \
-d '{"keyword":"Jean-Louis", "prompt":"child"}'
# Search trace chunks visually (no ASR)
curl localhost:5052/multimodal \
-d '{"keyword":"", "prompt":"person", "chunk_type":"trace", "range":"3500-4000"}'
```
**Parameters:**
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `keyword` | string | — | ASR keyword to search in sentence text |
| `prompt` | string | same as keyword | Visual prompt for GDINO |
| `chunk_type` | string | `"sentence"` | `sentence`, `trace`, `story`, `cut` |
| `target` | string | — | Specific chunk target |
| `range` | string | `"0-6780"` | Time range (for non-sentence chunks) |
| `threshold` | float | `0.15` | Visual detection threshold |
### `GET /shots/<filename>`
Retrieve annotated detection images.
```bash
curl -o result.jpg localhost:5052/shots/aeed7134_5461s_gun_grounding-dino.jpg
```
## Object Detection Performance Summary
| Object type | Size in frame | GDINO | PaliGemma | Best prompt |
|-------------|--------------|-------|-----------|-------------|
| Gun (realistic) | 15-30% | ✅ 0.36-0.67 | ✅ | `pistol` / `handgun` |
| Water gun (toy) | 15-31% | ❌ 0 | ✅ | `water gun` (PaliGemma) |
| Child (Jean-Louis) | 30-60% | ⚠️ 0.3-0.9 | ❌ | `child` (high FP on adults) |
| Stamp | <5% | ❌ FP | ❌ | — |
| Passport | <10% | ❌ FP | ❌ | — |
| Magnifying glass | <5% | ❌ FP | ❌ | — |
| Cup / Bottle | 5-15% | ✅ 0.3-0.5 | — | `cup` / `bottle` |
| Cell phone | 5-10% | ✅ 0.3-0.5 | — | `cell phone` |
## Resource Registration
On startup, the agent auto-registers as resources in `dev.resources`:
| Resource ID | Type | Status |
|-------------|------|--------|
| `eye-gdino` | `vision_model` | `online` |
| `eye-paligemma` | `vision_model` | `online` |
Heartbeat updates every 60 seconds. Discover via:
```sql
SELECT * FROM dev.resources WHERE resource_type = 'vision_model';
```
## Files
| File | Description |
|------|-------------|
| `scripts/vision_agent.py` | Vision Agent server (port 5052) |
| `output_dev/vision_shots/` | Annotated detection screenshots |
| `docs/ZERO_SHOT_DETECTION_RESEARCH.md` | Full model research report |
-190
View File
@@ -1,190 +0,0 @@
# Zero-Shot Object Detection Model Research Report
**Date:** 2026-05-10
**Goal:** Evaluate models for detecting arbitrary objects in Charade (1963)
**System:** M5 MacBook Pro (Apple Silicon MPS, 48GB)
---
## Tested Models
| Model | Params | Size | Resolution | Type | License |
|-------|--------|------|------------|------|---------|
| YOLOv8n fine-tune (gun) | 3.2M | 6MB | 640px | Closed-set (4 classes) | AGPL-3.0 |
| OWL-ViT base | 109M | 586MB | 384px | Zero-shot | Apache 2.0 |
| **Grounding DINO Base** | **232M** | **891MB** | **384px** | **Zero-shot** | **Apache 2.0** |
| Grounding DINO Large | 232M | 895MB | 384px | Zero-shot | Apache 2.0 |
| Florence-2 Base | 231M | ~3GB | 384px | Zero-shot (generative) | MIT |
| Florence-2 Large | 776M | ~6GB | 384px | Zero-shot (generative) | MIT |
| PaliGemma 3B mix-224 | 2,923M | ~3GB | 224px | Zero-shot (generative) | Gemma license |
| PaliGemma 3B mix-448 | 2,923M | ~6GB | 448px | Zero-shot (generative) | Gemma license |
## Detection Performance on Charade
### Large Objects (gun)
| Model | 8 timepoints | Best confidence | Runtime |
|-------|-------------|----------------|---------|
| YOLOv8n fine-tune | ❌ 0/5 (all FP) | 0.45 (stamp→pistol) | 0.03s |
| OWL-ViT | ❌ 2/8 | 0.054 | 3.4s |
| **Grounding DINO Base** | **✅ 8/8** | **0.499** | **0.33s** |
| PaliGemma 3B mix-224 | ✅ 3/8 (gun), 3/8 overall | 0.499 | 0.5-3s |
### Small Objects (stamp, passport, magnifying glass)
| Model | Stamp | Passport | Magnifying glass |
|-------|-------|----------|-----------------|
| Grounding DINO Base | ❌ FP (~0.3) | ❌ FP (~0.4) | ❌ FP (~0.3-0.5) |
| PaliGemma 3B mix-224 | ❌ no det | ❌ no det | not tested |
| PaliGemma 3B mix-448 | ❌ (not tested) | ❌ (not tested) | ❌ (not tested) |
**All models fail on objects smaller than ~50px at native 1920x1080 resolution.**
### Other Objects
| Object | YOLO COCO | Grounding DINO | Notes |
|--------|-----------|----------------|-------|
| knife | ✅ 368 frames | ✅ 84 hits | Small but detectable |
| cup | ✅ | ✅ 13 hits | Moderate size |
| bottle | ✅ | ✅ 12 hits | Moderate size |
| cell phone | ✅ | ✅ 5 hits | Hand-held |
| book | ✅ | ✅ 3 hits | Hand-held |
| car | ✅ | ✅ 9 hits | Large object |
| tie | ✅ | ✅ 139 hits | On-person (worn, not held) |
## Detailed Model Analysis
### Grounding DINO Base (Recommended)
**Scores:** Detection confidence 0.1-0.5 (typical for zero-shot)
**Timing per frame (MPS):**
| Component | Time | % of total |
|-----------|------|------------|
| Processor (text+image) | 17ms | 5% |
| Model inference | 310ms | 93% |
| Post-processing | 5ms | 2% |
| **Total** | **331ms** | **100%** |
**Multi-prompt batching:** 8 prompts in 335ms (42ms/prompt vs 309ms single)
**Memory:** ~1GB (MPS)
**License:** Apache 2.0 — fully commercial, no restrictions
### Grounding DINO Large
**Result:** Identical weights to Base. The GitHub "7-dataset" checkpoint is the same 3-dataset version as HuggingFace. The actual 7-dataset version (56.7 AP) was never released.
**Verdict: Do not use.** Base is identical and simpler.
### OWL-ViT
**Result:** Almost useless for this task. Max confidence 0.054. Detect only 2/8 timepoints.
**Verdict: Do not use.**
### Florence-2
**Issue:** `prepare_inputs_for_generation` bug in current transformers version. Cannot run inference without patching model code.
**Task format:** Uses task tokens (`<OD>`) instead of arbitrary text prompts. Cannot do "detect gun" directly — uses generic object detection.
**Verdict: Cannot use in current environment.**
### PaliGemma
**Result:** Works for gun detection (3/8) but misses small objects entirely.
**Key limitation:** No confidence score output (generative model). Either outputs bbox or nothing.
**Issues:**
- 224px variant: Too low resolution for small objects
- 448px variant: 6GB download, suspected better for detail but untested
- Gemma license may restrict commercial use vs Apache 2.0
**Verdict: Inferior to Grounding DINO for this use case.**
### YOLOv8n Fine-tune (Gun Detector)
| Dataset | 905 images (Roboflow CC BY 4.0) |
| Classes | grenade, knife, pistol, rifle |
| Validation mAP50 | 0.813 |
| Charade FP rate | **100%** (all false positives) |
**Root cause:** Training images are close-up gun photos; Charade has distant/partial guns. Distribution mismatch makes this model unusable.
**Verdict: Requires completely new training dataset.**
## Root Cause Analysis: Small Object Failure
### Grounding DINO's Resolution Limit
Grounding DINO processes images at **384×384px**. At this resolution:
```
1920px frame → 384px input (5:1 reduction)
A 50×50px object → 10×10px at 384px → only ~1 patch token
```
For comparison:
- **Gun** at 200×200px (close-up) → 40×40px → still detectable
- **Stamp** at 30×30px → 6×6px → lost in downsampling
- **Passport** at 80×120px → 16×24px → barely visible
- **Magnifying glass** at 40×40px → 8×8px → lost
### Potential Solutions
| Solution | Pros | Cons | Feasibility |
|----------|------|------|-------------|
| **Crop + zoom** on person region | Leverages existing YOLO person detections | Requires two-stage pipeline | ✅ High |
| **PaliGemma 448px** | 448px native (36% more detail) | 6GB, requires download | ⚠️ Medium |
| **YOLO fine-tune on stamps** | Fast inference (6MB) | Need 200+ training images | ⚠️ Medium |
| **Grounding DINO + tiling** | Split image into tiles, run per tile | 4-9x slower | ⚠️ Medium |
| **Florence-2 448px** | Higher resolution | Bug in transformers | ❌ Low |
## Hand-Held Object Detection Feasibility
### Available Data Sources
| Source | Type | Coverage | Usefulness |
|--------|------|----------|------------|
| YOLO `pre_chunks` | Object detections | 169,625 frames | ✅ Every frame |
| Pose `pre_chunks` | Body keypoints (left_wrist, right_wrist) | 4,269 frames | ✅ Hand location |
| Grounding DINO | Zero-shot classification | On-demand | ✅ Object ID |
| ASR dialogue | Text mentions | 4,188 chunks | ✅ "holding a gun" |
### Approach: YOLO + Pose + Grounding DINO
```
Frame
→ YOLO: Find person + objects
→ Pose: Find wrist keypoints
→ Check: Object bbox overlaps with hand region (wrist ±100px)
→ Grounding DINO: Verify object class
```
### Known Limitations
1. **Pose frame alignment:** Pose data (4,269 frames) doesn't always overlap with YOLO data at the same frame
2. **Object proximity ≠ holding:** YOLO objects near hands may be background, not held
3. **Small object blind spot:** Stamps, magnifying glasses at hand positions are too small to detect
## Recommendations
| Priority | Action | Rationale |
|----------|--------|-----------|
| 1 | Use Grounding DINO Base (Apache 2.0) | Best zero-shot detector, proven on guns, clean license |
| 2 | Two-stage pipeline for small objects | YOLO person box → crop → upscale → Grounding DINO |
| 3 | Pose wrist alignment for hand-held confirmation | Reduce false positives by requiring hand proximity |
| 4 | Replace Grounding DINO "Large" ref with Base | Large is identical weights, no benefit |
## Appendix: License Summary
| Model | License | Commercial Use | Requires |
|-------|---------|---------------|----------|
| Grounding DINO | **Apache 2.0** | ✅ Yes | NOTICE file |
| OWL-ViT | Apache 2.0 | ✅ Yes | NOTICE file |
| PaliGemma | Gemma license | ⚠️ Needs review | Google ToS |
| Florence-2 | MIT | ✅ Yes | Copyright notice |
| YOLOv8 | AGPL-3.0 | ⚠️ Needs license | Open source or paid |
-49
View File
@@ -1,49 +0,0 @@
# Zero-Shot Gun Detection Test Plan
**Date:** 2026-05-10
**Goal:** Compare OWL-ViT vs Grounding DINO for detecting guns in Charade (1963)
## Models
| Model | Source | Type |
|-------|--------|------|
| `google/owlvit-base-patch32` | HuggingFace | Zero-shot object detection |
| `IDEA-Research/grounding-dino-base` | HuggingFace | Zero-shot object detection |
## Test Timepoints (8)
| Time | Label | Source |
|------|-------|--------|
| 2646s (44:06) | 2646s | ASR: "He has a gun" |
| 3188s (53:08) | 3188s | Original detection |
| 3697s (61:37) | 3697s | ASR: "Where's your gun" |
| 5341s (89:01) | 5341s | ASR: "He already killed 3 men" |
| 5461s (91:01) | 5461s | Original detection |
| 6309s (1:45:09) | 6309s | Original detection |
| 6377s (1:46:17) | 6377s | Original detection |
| 6479s (1:47:59) | 6479s | Original detection |
## Prompts
`"gun"`, `"pistol"`, `"rifle"`, `"weapon"`
## Matrix
8 timepoints × 2 models × 4 prompts = 64 inferences
## Output
| File | Description |
|------|-------------|
| `output_dev/zero_shot_test/*.jpg` | Annotated screenshots |
| `output_dev/zero_shot_test/zero_shot_results.json` | Detection results |
| `scripts/zero_shot_gun_test.py` | Test script |
## Success Criteria
| Level | Criteria |
|-------|----------|
| Excellent | Finds real gun with confidence > 0.5 |
| Good | Finds real gun with confidence < 0.5 |
| Limited | Finds guns but many false positives |
| Failed | All false positives |
-67
View File
@@ -1,67 +0,0 @@
# Zero-Shot Gun Detection Test Report
**Date:** 2026-05-10
**Goal:** Compare OWL-ViT vs Grounding DINO for detecting guns in Charade (1963)
## Test Setup
| Model | Prompts | Timepoints | Total inferences |
|-------|---------|------------|-----------------|
| `google/owlvit-base-patch32` | gun, pistol, rifle, weapon | 8 | 32 |
| `IDEA-Research/grounding-dino-base` | gun, pistol, rifle, weapon | 8 | 32 |
## Results
| Model | Timepoints with detections | Total detections | Best confidence | Runtime |
|-------|---------------------------|-----------------|-----------------|---------|
| OWL-ViT | 2/8 | 2 | 0.054 | 1.5s |
| **Grounding DINO** | **8/8** | **109** | **0.186** | 11.5s |
## Grounding DINO — Per Timepoint
| Time | Source | Best prompt | Best confidence | Found? |
|------|--------|-------------|-----------------|--------|
| 2646s (44:06) | ASR: "He has a gun" | gun | 0.082 | ✅ |
| **3188s (53:08)** | **Original pistol** | **gun** | **0.149** | **✅** |
| 3697s (61:37) | ASR: "Where's your gun" | gun | 0.159 | ✅ |
| 5341s (89:01) | ASR: "He already killed 3 men" | gun | 0.074 | ✅ |
| **5461s (91:01)** | **Original pistol** | **gun** | **0.186** | **✅** |
| **6309s (1:45:09)** | **Original pistol** | **gun** | **0.077** | **✅** |
| **6377s (1:46:17)** | **Original gun** | **weapon** | **0.118** | **✅** |
| **6479s (1:47:59)** | **Original pistol** | **gun** | **0.060** | **✅** |
### Original 5 Pistol Frames
| Frame | OWL-ViT | Grounding DINO | Verdict |
|-------|---------|----------------|---------|
| 3188s | Not found | ✅ Found (0.149) | ✅ |
| 5461s | Not found | ✅ Found (0.186) | ✅ |
| 6309s | Not found | ✅ Found (0.077) | ✅ |
| 6377s | Not found | ✅ Found (0.118) | ✅ |
| 6479s | Not found | ✅ Found (0.060) | ✅ |
## Analysis
### OWL-ViT
- Almost completely failed: only 2 detections at 0.05 confidence
- Not suitable for this task
### Grounding DINO
- **Found all 8 timepoints**, including all 5 original pistol frames
- Best prompt is consistently `"gun"` (6/8 timepoints)
- Confidence range: 0.060 - 0.186 (typical for zero-shot detection)
- Higher confidence correlates with user-confirmed detections
### Key Finding
The 5 original pistol frames were produced by **Grounding DINO** (not YOLOv8n). The model was downloaded from HuggingFace at 15:43-15:44 on May 9, and the screenshots were generated at 15:49 — confirming OWL-ViT was tested first (failed) and then Grounding DINO was tested (succeeded).
## Integration
Grounding DINO has been integrated into `object_search_agent.py` as `--source zero_shot`:
```
python3 scripts/object_search_agent.py --keyword gun --source zero_shot
```
## Screenshots
All 64 annotated screenshots saved to `output_dev/zero_shot_test/*.jpg`
-115
View File
@@ -1,115 +0,0 @@
# Zero-Shot vs Fine-Tune 物件偵測模型選型報告
**Date:** 2026-05-10
**Goal:** 在 Charade (1963) 中搜尋非 COCO 物件(槍枝、郵票、信封等)
**System:** M5 MacBook Pro (Apple Silicon MPS)
## 動機
YOLOv8 COCO 只有 80 類,不包含 gun、stamp、envelope 等 Charade 核心物件。需要找到能在電影中搜尋任意物件的方法。
## 候選方案
| 方案 | 方法 | 訓練資料 | 開發成本 |
|------|------|---------|---------|
| A. YOLOv8n fine-tune | Fine-tune on gun dataset | 需收集 500+ 張標註圖片 | 高 |
| B. OWL-ViT zero-shot | Vision-language pretraining | 無須訓練 | 低 |
| C. Grounding DINO zero-shot | Vision-language pretraining | 無須訓練 | 低 |
## 模型大小與效能
| Model | 磁碟 | 參數 | 推論時間 (MPS) | 單幀能耗 | 模型類別 |
|-------|------|------|---------------|---------|---------|
| YOLOv8n | **6MB** | **3.2M** | **0.03s** | **~0.5J** | 封閉集(80 類) |
| OWL-ViT | 586MB | 109M | 3.4s | ~50J | 開放集(zero-shot) |
| **Grounding DINO** | **891MB** | **172M** | **4.3s** | **~65J** | **開放集(zero-shot)** |
## Charade 實測結果
| Model | 8 時間點命中 | 5 個原始 pistol | 最佳 confidence | 推論時間 | 模型大小 |
|-------|-------------|-----------------|----------------|---------|---------|
| YOLOv8n COCO | ❌ N/A(無 gun class) | — | — | 0.03s | 6MB |
| YOLOv8n fine-tune | 7/7 FP | ❌ 全部 FP | 0.45(郵票誤判) | 0.03s | 6MB |
| OWL-ViT | 2/8 | ❌ 0/5 | 0.054 | 3.4s | 586MB |
| **Grounding DINO Base** | **31/32** | **✅ 5/5** | **0.672** | **11.6s** | **891MB** |
| **Grounding DINO Large** | **32/32** | **✅ 5/5** | **1.000** | **50.1s** | **895MB** |
### Base vs Large 比較
| 指標 | Base (3 datasets) | Large (7 datasets) |
|------|------------------|-------------------|
| 平均最佳 confidence | 0.384 | **1.000** |
| 總偵測數 | 333 | **28,800** |
| COCO zero-shot AP | 48.4 | **56.7** |
| 推論時間 (MPS) | 11.6s | 50.1s |
| Edge 部署 | 較可行 | 較困難 |
### 結論
**效能優先選擇:Grounding DINO Large** — 所有 8 個時間點 confidence 1.000,零漏檢。犧牲推論速度但 detection 品質大幅超越 Base 版。
**Edge 部署選擇:Grounding DINO Base** — 體積相近但推論快 4.3x,適合資源受限裝置。
### 關鍵結論
1. **YOLOv8n fine-tune 完全失敗** — 905 張 Roboflow 近距離特寫與 Charade 中遠景畫面分布 mismatch,訓練無法泛化
2. **OWL-ViT 幾乎無效** — 對電影中的小物體辨識能力不足
3. **Grounding DINO 成功** — 5/5 找回 pistol frames,所有 ASR gun mention 時間點也命中
## Grounding DINO 優缺點
### 優點
- **零樣本搜尋**:任何 COCO 以外的物件直接用文字 prompt 搜尋
- **延伸性**:同一模型可搜尋 gun、stamp、envelope、knife、hat 等任意物件
- **無須訓練**:不需要收集標註資料或 fine-tune
- **Apache 2.0 License**:可商用
### 缺點
- **體積大**:891MB(vs YOLOv8n 的 6MB)
- **推論慢**:4.3s/frame(vs YOLOv8n 的 0.03s)
- **不適合 real-time**:edge device 上無法做即時偵測,只適合離線掃描
## Edge AI 部署考量
| 項目標題 | YOLOv8n | Grounding DINO |
|---------|---------|---------------|
| 模型大小 | 6MB ✅ | 891MB ⚠️ |
| RAM 需求 | ~100MB | ~2.5GB |
| 推論時間 | 30ms | 4.3s |
| 單幀能耗 | ~0.5J | ~65J |
| 搜尋類別數 | 80(固定) | 無限(文字 prompt) |
| 電池影響(1000 幀) | ~500J | ~65,000J |
### 建議策略
```
離線掃描(Server/Gateway):
用 Grounding DINO 對全片建立物件索引
→ 耗時但可接受(113 min 電影約 2-3 小時)
即時查詢(Edge Device):
查詢時只跑 Grounding DINO 在該 timepoint → 4s/次
→ 查詢體驗還可接受
```
## 整合狀態
- ✅ Grounding DINO 測試通過
- ✅ 整合進 `scripts/object_search_agent.py`(`--source zero_shot`)
- ✅ 測試計畫:`docs/ZERO_SHOT_GUN_TEST_PLAN.md`
- ✅ 測試報告:`docs/ZERO_SHOT_GUN_TEST_REPORT.md`
## License 聲明
Grounding DINO 採用 Apache 2.0 License,可商用。
產品若 bundle 此模型,需附 `NOTICE` 檔案:
```
Momentry
Copyright 2026 Accusys
This product includes software developed by IDEA Research:
- Grounding DINO (https://github.com/IDEA-Research/GroundingDINO)
Copyright 2023 IDEA Research
Licensed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
```
@@ -1,177 +0,0 @@
# API Dictionary v1.0.0
58 endpoints across 10 modules. Auth: `X-API-Key` header or `Authorization: Bearer <key>`.
## API Design Principle
Every path segment after the resource ID is a **verb** — an action on that resource.
```
/api/v1/{entity}/{id}/{action}
↑ ↑ ↑
實體 ID 動作
```
**Primary entities**: `file`/`files`, `identity`/`identities`
```
/api/v1/file/:file_uuid ← 檔案資源
/video → 播放影片(動詞)
/video/bbox → 播放含框(動詞)
/thumbnail → 取縮圖(動詞)
/process → 啟動處理(動詞)
/probe → 探測(動詞)
/chunks → 列出段落(動詞)
/identities → 列出身分(動詞)
/face_trace/sortby → 列出追蹤/排序(動詞)
/trace/:trace_id/faces → 列出偵測(動詞)
/api/v1/identity/:identity_uuid
/bind → 綁定(動詞)
/unbind → 解綁(動詞)
/files → 列出檔案(動詞)
/chunks → 列出段落(動詞)
/api/v1/search/universal → 搜尋(動詞)
/api/v1/search/smart → 智慧搜尋(動詞)
```
**Naming conventions**:
- 全域唯一資源 ID → `uuid`(`file_uuid`, `identity_uuid`)
- 單一實體下唯一 ID → `id`(`trace_id`, `chunk_id`, `face_id`)
- 路徑尾端 → 動詞(`/video`, `/chunks`, `/bind`)
- 集合列表 → **複數**(`/files`, `/identities`, `/resources`, `/faces`)
- 單一資源操作 → **單數**(`/file/:file_uuid`, `/identity/:identity_uuid`)
## Legend
- `→` direction of data flow
- `POST` typically requires JSON body
- All endpoints return JSON unless noted
---
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 1 | GET | `/health` | Server health (ok/degraded) |
| 2 | GET | `/health/detailed` | Per-service health + latency |
| 3 | POST | `/api/v1/auth/login` | Username/password → API key |
| 4 | POST | `/api/v1/auth/logout` | Invalidate session |
| 5 | GET | `/api/v1/stats/ingest` | Ingest statistics |
| 6 | GET | `/api/v1/stats/sftpgo` | SFTPGo service status |
| 7 | GET | `/api/v1/stats/inference` | LLM/embedding health |
| 8 | POST | `/api/v1/files/register` | Register video file → file_uuid |
| 9 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match on `file_path`+`pattern` |
| 10 | GET | `/api/v1/files/scan` | Scan directory for new files |
| 11 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
| 12 | POST | `/api/v1/file/:file_uuid/process` | Start processing pipeline |
| 13 | GET | `/api/v1/file/:file_uuid/chunks` | List pre-chunks for file |
| 14 | GET | `/api/v1/progress/:file_uuid` | Processing progress |
| 15 | GET | `/api/v1/jobs` | List monitor jobs (filterable by status) |
| 16 | POST | `/api/v1/config/cache` | Toggle Redis cache |
| 17 | POST | `/api/v1/config/auto-pipeline` | Toggle auto-pipeline on register |
| 18 | POST | `/api/v1/config/watcher-auto-register` | Toggle watcher auto-register |
| 17 | POST | `/api/v1/search/visual` | Search visual chunks |
| 18 | POST | `/api/v1/search/visual/class` | Search by object class |
| 19 | POST | `/api/v1/search/visual/density` | Search by spatial density |
| 20 | POST | `/api/v1/search/visual/combination` | Combined visual search |
| 21 | POST | `/api/v1/search/visual/stats` | Visual chunk statistics |
## File/Identity (identity_api.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 22 | GET | `/api/v1/files` | List registered files (paginated) |
| 23 | GET | `/api/v1/file/:file_uuid` | Single file detail |
| 24 | GET | `/api/v1/file/:file_uuid/identities` | Identities in this file |
| 25 | GET | `/api/v1/identities` | List all identities |
| 26 | POST | `/api/v1/identity` | Register new identity |
| 27 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
| 28 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
| 29 | GET | `/api/v1/identity/:identity_uuid/files` | Files for an identity |
| 30 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for an identity |
| 31 | POST | `/api/v1/resource/register` | Register processing resource |
| 32 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
| 33 | GET | `/api/v1/resources` | List all resources |
## Identity Binding (identity_binding.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 34 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
| 35 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
| 36 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge identity :identity_uuid → target |
## Face Candidates (identities.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 37 | GET | `/api/v1/faces/candidates` | Unbound face gallery (paginated) |
## Search (search.rs + universal_search.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 38 | POST | `/api/v1/search/smart` | Semantic search (EmbeddingGemma + pgvector) |
| 39 | POST | `/api/v1/search/universal` | BM25 keyword search (requires file_uuid) |
| 40 | POST | `/api/v1/search/frames` | Frame-level search |
## Trace (trace_agent_api.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 41 | POST | `/api/v1/file/:file_uuid/face_trace/sortby` | List traces (sorted/filtered) |
| 42 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Single trace detections + interpolation |
## Media (media_api.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 43 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (optional crop via `?frame=&x=&y=&w=&h=`) |
| 44 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream (`?start_time=&end_time=` in seconds) |
| 45 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay video (`?start_frame=&end_frame=&duration=` frame numbers) |
| 46 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (`?mode=normal\|debug&padding=`) |
## Identity Delete
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 47 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity + unbind all faces |
## Agents (agent_api.rs + five_w1h_agent_api.rs + identity_agent_api.rs)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 48 | POST | `/api/v1/agents/translate` | AI text translation |
| 49 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk 5W1H analysis |
| 50 | POST | `/api/v1/agents/5w1h/batch` | Batch 5W1H analysis |
| 51 | GET | `/api/v1/agents/5w1h/status` | 5W1H job status |
| 52 | POST | `/api/v1/agents/identity/analyze` | Identity analysis |
| 53 | GET | `/api/v1/agents/identity/status` | Identity job status |
| 54 | POST | `/api/v1/agents/identity/suggest` | Identity suggestions |
| 55 | POST | `/api/v1/agents/suggest/merge` | Suggest identity merge |
| 56 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
## Identity Search (identity_api.rs, new in V4.1)
| # | Method | Route | Description |
|---|--------|-------|-------------|
| 57 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunk results |
| 58 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
---
## Summary
| Module | Routes | File |
|--------|--------|------|
| Core | 21 | `server.rs` |
| File/Identity | 14 | `identity_api.rs` (+2 search endpoints) |
| Binding | 3 | `identity_binding.rs` |
| Faces | 1 | `identities.rs` |
| Search | 3 | `search.rs`, `universal_search.rs` |
| Trace | 2 | `trace_agent_api.rs` |
| Media | 4 | `media_api.rs` |
| Identity Delete | 1 | `identity_api.rs` |
| Agents | 9 | `agent_api.rs`, `five_w1h_agent_api.rs`, `identity_agent_api.rs` |
| **Total** | **58** | |
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,381 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core Release API Reference v1.0.0"
date: "2026-05-25"
version: "V4.2"
status: "active"
owner: "Warren"
---
# Momentry Core API Reference v1.0.0
55 endpoints across 10 categories, with real curl examples and responses.
## Base
| Environment | URL |
|-------------|-----|
| Production | `http://localhost:3002` or `https://api.momentry.ddns.net` |
| Development | `http://localhost:3003` |
| Auth | Header `X-API-Key: <key>` (login endpoint unprotected) |
> **Note**: All examples below use production port 3002. For dev testing, replace `3002` with `3003`.
---
## 1. System
| # | Method | Path | Description |
|---|--------|------|-------------|
| 1 | GET | `/health` | Server status (ok/degraded) |
| 2 | GET | `/health/detailed` | Per-service health + latency |
| 3 | GET | `/health/consistency` | Data consistency check |
| 4 | POST | `/api/v1/auth/login` | Username/password → API key |
| 5 | POST | `/api/v1/auth/logout` | Invalidate session |
| 6 | GET | `/api/v1/stats/sftpgo` | SFTPGo status |
| 7 | POST | `/api/v1/config/cache` | Toggle Redis cache |
| 8 | POST | `/api/v1/config/auto-pipeline` | Toggle auto-pipeline on register |
| 9 | POST | `/api/v1/config/watcher-auto-register` | Toggle watcher auto-register |
```bash
curl http://localhost:3002/health
```
```json
{
"status": "ok",
"version": "1.0.0",
"build_git_hash": "de88fd4e",
"build_timestamp": "2026-05-25",
"uptime_ms": 7052517
}
```
| # | Method | Path | Description |
|---|--------|------|-------------|
| 2a | GET | `/health/detailed` | Per-service health + resources + pipeline |
```bash
curl -X POST http://localhost:3002/api/v1/files/register \
-H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
-H "Content-Type: application/json" \
-d '{"file_path":"/path/to/video.mp4","content_hash":"optional-sha256-of-file"}'
```
```json
{"success":true,"file_uuid":"3abeee81d94597629ed8cb943f182e94","duration":5954.0}
```
Supports all file types (video, image, document, audio). SHA256 content_hash computed automatically if not provided.
```json
{
"status": "ok",
"build_git_hash": "de88fd4e",
"build_timestamp": "2026-05-25",
"services": {
"postgres": {"status": "ok", "latency_ms": 6},
"redis": {"status": "ok", "latency_ms": 0},
"qdrant": {"status": "ok", "latency_ms": 1},
"mongodb": {"status": "ok", "latency_ms": 0}
},
"resources": {
"cpu_used_percent": 50.0,
"cpu_idle_percent": 50.0,
"memory_available_mb": 8028,
"memory_total_mb": 16384,
"memory_used_percent": 51.0,
"gpu_available": false,
"gpu_utilization": null,
"gpu_memory_used_pct": null
},
"pipeline": {
"scripts": true,
"models": true,
"ffmpeg": true,
"embedding_server": {"status": "ok", "latency_ms": 0},
"gdino_api": {"status": "error", "latency_ms": 0, "error": "..."},
"llm": {"status": "ok", "latency_ms": 0}
}
}
```
---
## 2. File Management
| # | Method | Path | Description |
|---|--------|------|-------------|
| 10 | POST | `/api/v1/files/register` | Register file → file_uuid. Body: `{"file_path":"...", "content_hash":"optional"}` |
| 11 | GET | `/api/v1/files/lookup?file_name=` | Pre-upload name conflict check. Returns matches + `next_name` for auto-rename |
| 12 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match (`file_path`+`pattern`) |
| 13 | GET | `/api/v1/files/scan` | Scan directory for new files |
| 14 | GET | `/api/v1/files` | List files (paginated) |
| 15 | GET | `/api/v1/file/:file_uuid` | Single file detail |
| 16 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
| 17 | POST | `/api/v1/file/:file_uuid/process` | Start pipeline |
| 18 | POST | `/api/v1/file/:file_uuid/chunk/:chunk_id` | Single chunk detail (V1.0.2+) |
| 19 | POST | `/api/v1/progress/:file_uuid` | Processing progress |
| 20 | POST | `/api/v1/jobs` | Monitor jobs (filterable) |
```bash
curl -X POST http://localhost:3002/api/v1/files/register -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_path":"/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"}'
```
```json
{"success":true,"file_uuid":"3abeee81d94597629ed8cb943f182e94","duration":5954.0}
```
Modes:
- By `file_uuid`: unregister a single file
- By `file_path` + `pattern` regex: unregister all matching files in a directory
```bash
# By file_uuid
curl -X POST http://localhost:3002/api/v1/unregister \
-H "X-API-Key: muser_..." -H "Content-Type: application/json" \
-d '{"file_uuid":"53e3a229bf68878b7a799e811e097f9c"}'
# By pattern (unregister all .mp4 files in directory)
curl -X POST http://localhost:3002/api/v1/unregister \
-H "X-API-Key: muser_..." -H "Content-Type: application/json" \
-d '{"file_path":"/data/demo","pattern":"\\.mp4$"}'
```
```json
{"success":true,"file_uuid":"53e3a229bf68878b7a799e811e097f9c","message":"File unregistered successfully"}
```
```bash
curl "http://localhost:3002/api/v1/files?page=1&page_size=2" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"data":[{"file_uuid":"aeed7134...","file_name":"Charade (1963)...","status":"ready"}],"total":0,"page":1,"page_size":2}
```
---
## 3. Search
| # | Method | Path | Description |
|---|--------|------|-------------|
| 21 | POST | `/api/v1/search/visual` | Visual chunk search |
| 22 | POST | `/api/v1/search/visual/class` | By object class |
| 23 | POST | `/api/v1/search/visual/density` | By spatial density |
| 24 | POST | `/api/v1/search/visual/combination` | Combined visual search |
| 25 | POST | `/api/v1/search/visual/stats` | Visual stats |
| 26 | POST | `/api/v1/search/smart` | Semantic (EmbeddingGemma + pgvector) |
| 27 | POST | `/api/v1/search/universal` | BM25 keyword (requires file_uuid) |
| 28 | POST | `/api/v1/search/frames` | Frame-level search |
```bash
curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"query":"name","limit":2,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
```json
{"query":"name","results":[{"chunk_id":"100","text":"What's your name?","start_time":258.68,"score":0.90}],"total":5,"page":1,"page_size":20,"took_ms":42}
```
```bash
curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"query":"friends","limit":2,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
```json
{"query":"friends","results":[{"chunk_id":"104","text":"You won't find it difficult to make some new friends.","start_time":272.38,"score":0.90}],"total":3,"page":1,"page_size":20,"took_ms":38}
```
---
## 4. Face Trace
| # | Method | Path | Description |
|---|--------|------|-------------|
| 29 | POST | `/api/v1/file/:file_uuid/traces` | List traces (sorted/filtered) |
| 30 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Trace detections (+ interpolation) |
### traces — list traces
Parameters:
- `sort_by`: `face_count` | `duration` | `first_appearance`
- `min_faces`, `min_confidence`, `max_confidence`: filters
- `limit`: max results
```bash
curl -X POST "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/traces" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":2}'
```
```json
{"success":true,"total_traces":6892,"total_faces":108204,"traces":[
{"trace_id":3128,"face_count":1109,"avg_confidence":0.779},
{"trace_id":3126,"face_count":743,"avg_confidence":0.758}
]}
```
### trace/:trace_id/faces — individual detections
Parameters:
- `limit`, `offset`: pagination
- `interpolate`: boolean (fills sparse gaps with lerp bbox)
```bash
curl "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2/faces?limit=2&interpolate=true" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"trace_id":2,"fps":25.0,"total":1,"faces":[
{"id":12399,"start_frame":4620,"end_frame":4620,"start_time":184.8,"end_time":184.8,"x":787,"y":582,"width":225,"height":225,"confidence":0.666,"interpolated":false}
]}
```
---
## 5. Media
| # | Method | Path | Description |
|---|--------|------|-------------|
| 31 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
| 32 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream. Dual input: `?start_time=&end_time=` (seconds) or `?start_frame=&end_frame=` (frames). |
| 33 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay. `?start_frame=&end_frame=&face_uuid=&duration=` (all frame numbers). Dual input via `start_time`/`end_time`. |
| 34 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?mode=&padding=&audio=) |
All video endpoints support:
- `mode=normal|debug` (default: `normal`)
- `audio=on|off` (default: `on`)
`mode=normal`: raw clip, `-c copy`, no overlay.
`mode=debug`: re-encoded with top-left text info + green bboxes (trace labels at actual frames with thickness=4, interpolated at first known position with thickness=1).
```bash
# Normal mode
curl -o trace.mp4 "http://localhost:3002/api/v1/file/{file_uuid}/trace/42/video?mode=normal"
# Debug mode
curl -o trace_debug.mp4 "http://localhost:3002/api/v1/file/{file_uuid}/trace/42/video?mode=debug"
```
Debug overlay shows at bottom-left:
```
Frame {n} {pts}s
Cut: {id}
{file_uuid}
Trace {id}: start={frame} {name}
...
```
Green bbox per face detection: actual frames `thickness=4`, interpolated `thickness=1`.
---
## 6. Identities
| # | Method | Path | Description |
|---|--------|------|-------------|
| 35 | GET | `/api/v1/identities` | List all identities |
| 36 | GET | `/api/v1/file/:file_uuid/identities` | Identities in a file |
| 37 | POST | `/api/v1/identity` | Register new identity |
| 38 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
| 39 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
| 40 | GET | `/api/v1/identity/:identity_uuid/files` | Files for identity |
| 41 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for identity |
| 42 | GET | `/api/v1/faces/candidates` | Unbound face gallery |
| 43 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunks |
| 44 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
```bash
curl "http://localhost:3002/api/v1/identities?page=1&page_size=3" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"count":3852,"page":1,"page_size":3,"identities":[
{"id":18299,"identity_uuid":"76f85ee6-bc47-4a1a-9878-1beb67851ec5","name":"PERSON_aeed7134_390","metadata":{}},
{"id":18298,"identity_uuid":"f4d4ccbf-fccb-4f62-8806-2b7f4a706edb","name":"PERSON_aeed7134_389","metadata":{}},
{"id":18297,"identity_uuid":"e8a1b2c3-d4e5-4f67-8901-23456789abcd","name":"PERSON_aeed7134_388","metadata":{}}
]}
```
### GET /api/v1/file/:file_uuid/identities — identities with frame/time ranges
```bash
curl "http://localhost:3002/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/identities?limit=2" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"file_uuid":"aeed71342a899fe4b4c57b7d41bcb692","fps":25.0,"total":20,"page":1,"page_size":20,"data":[
{"identity_id":18276,"identity_uuid":"77d895cc-bc2e-4f5a-84b3-3c1f0e2a5b6a","name":"PERSON_aeed7134_367","face_count":86,"start_frame":150744,"end_frame":152895,"start_time":6029.76,"end_time":6115.8,"confidence":0.855},
{"identity_id":18179,"identity_uuid":"90fc04cd-003b-4a1b-9f7d-8c3e1d2f4a5b","name":"PERSON_aeed7134_270","face_count":13,"start_frame":77418,"end_frame":77454,"start_time":3096.72,"end_time":3098.16,"confidence":0.851}
]}
```
```bash
curl "http://localhost:3002/api/v1/faces/candidates?page=1&page_size=2" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"total":42,"candidates":[{"frame_number":30,"confidence":0.85},...]}
```
---
## 7. Identity Binding
| # | Method | Path | Description |
|---|--------|------|-------------|
| 45 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
| 46 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
| 47 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge into another identity |
```bash
curl -X POST "http://localhost:3002/api/v1/identity/a9a90105-6d6b-46ff-92da-0c3c1a57dff4/bind" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_uuid":"3abeee81d94597629ed8cb943f182e94","face_id":"face_42"}'
```
```json
{"success":true}
```
---
## 8. Resources
| # | Method | Path | Description |
|---|--------|------|-------------|
| 48 | POST | `/api/v1/resource/register` | Register processing resource |
| 49 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
| 50 | GET | `/api/v1/resources` | List all resources |
```bash
curl "http://localhost:3002/api/v1/resources" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"data":[{"resource_id":"mxbai-embed-large-v1","resource_type":"embedding_model"}],"message":"OK"}
```
---
## 9. Agents — 5W1H
| # | Method | Path | Description |
|---|--------|------|-------------|
| 51 | POST | `/api/v1/agents/translate` | AI text translation |
| 52 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk analysis |
| 53 | POST | `/api/v1/agents/5w1h/batch` | Batch analysis |
| 54 | GET | `/api/v1/agents/5w1h/status` | Job status |
```bash
curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"text":"Hello world","target_language":"zh-TW"}'
```
```json
{"success":true,"translated_text":"你好世界"}
```
---
## 10. Agents — Identity
| # | Method | Path | Description |
|---|--------|------|-------------|
| 55 | POST | `/api/v1/agents/identity/match-from-photo` | Match face from photo |
| 56 | POST | `/api/v1/agents/identity/match-from-trace` | Match face from trace |
| 57 | POST | `/api/v1/agents/suggest/merge` | Suggest merge |
| 58 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| V4.2 | 2026-05-25 | Removed phantom routes (stats/ingest, stats/inference, agents/identity/status); fixed HTTP methods (chunk, progress, jobs → POST); renamed endpoints (face_trace/sortby → traces, analyze → match-from-photo, suggest → match-from-trace); added config endpoints (consistency, auto-pipeline, watcher-auto-register); updated git hash to de88fd4e |
| V4.1 | 2026-05-14 | Added `build_timestamp` + `resources` + `pipeline` to health APIs; identity search endpoints; trace debug rework (green bbox, text overlay, all traces listed) |
## Related
- `API_DICTIONARY_V1.0.0.md` — Quick reference (55 endpoints)
- `API_DOCUMENTATION_v1.0.0.md` — Detailed spec with examples
- `TRACE/TRACE_API_REFERENCE_V1.0.0.md` — Trace-specific reference
@@ -1,381 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core Release API Reference v1.0.0"
date: "2026-05-25"
version: "V4.2"
status: "active"
owner: "Warren"
---
# Momentry Core API Reference v1.0.0
55 endpoints across 10 categories, with real curl examples and responses.
## Base
| Environment | URL |
|-------------|-----|
| Production | `http://localhost:3002` or `https://api.momentry.ddns.net` |
| Development | `http://localhost:3003` |
| Auth | Header `X-API-Key: <key>` (login endpoint unprotected) |
> **Note**: All examples below use production port 3002. For dev testing, replace `3002` with `3003`.
---
## 1. System
| # | Method | Path | Description |
|---|--------|------|-------------|
| 1 | GET | `/health` | Server status (ok/degraded) |
| 2 | GET | `/health/detailed` | Per-service health + latency |
| 3 | GET | `/health/consistency` | Data consistency check |
| 4 | POST | `/api/v1/auth/login` | Username/password → API key |
| 5 | POST | `/api/v1/auth/logout` | Invalidate session |
| 6 | GET | `/api/v1/stats/sftpgo` | SFTPGo status |
| 7 | POST | `/api/v1/config/cache` | Toggle Redis cache |
| 8 | POST | `/api/v1/config/auto-pipeline` | Toggle auto-pipeline on register |
| 9 | POST | `/api/v1/config/watcher-auto-register` | Toggle watcher auto-register |
```bash
curl http://localhost:3002/health
```
```json
{
"status": "ok",
"version": "1.0.0",
"build_git_hash": "de88fd4e",
"build_timestamp": "2026-05-25",
"uptime_ms": 7052517
}
```
| # | Method | Path | Description |
|---|--------|------|-------------|
| 2a | GET | `/health/detailed` | Per-service health + resources + pipeline |
```bash
curl -X POST http://localhost:3002/api/v1/files/register \
-H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
-H "Content-Type: application/json" \
-d '{"file_path":"/path/to/video.mp4","content_hash":"optional-sha256-of-file"}'
```
```json
{"success":true,"file_uuid":"3abeee81d94597629ed8cb943f182e94","duration":5954.0}
```
Supports all file types (video, image, document, audio). SHA256 content_hash computed automatically if not provided.
```json
{
"status": "ok",
"build_git_hash": "de88fd4e",
"build_timestamp": "2026-05-25",
"services": {
"postgres": {"status": "ok", "latency_ms": 6},
"redis": {"status": "ok", "latency_ms": 0},
"qdrant": {"status": "ok", "latency_ms": 1},
"mongodb": {"status": "ok", "latency_ms": 0}
},
"resources": {
"cpu_used_percent": 50.0,
"cpu_idle_percent": 50.0,
"memory_available_mb": 8028,
"memory_total_mb": 16384,
"memory_used_percent": 51.0,
"gpu_available": false,
"gpu_utilization": null,
"gpu_memory_used_pct": null
},
"pipeline": {
"scripts": true,
"models": true,
"ffmpeg": true,
"embedding_server": {"status": "ok", "latency_ms": 0},
"gdino_api": {"status": "error", "latency_ms": 0, "error": "..."},
"llm": {"status": "ok", "latency_ms": 0}
}
}
```
---
## 2. File Management
| # | Method | Path | Description |
|---|--------|------|-------------|
| 10 | POST | `/api/v1/files/register` | Register file → file_uuid. Body: `{"file_path":"...", "content_hash":"optional"}` |
| 11 | GET | `/api/v1/files/lookup?file_name=` | Pre-upload name conflict check. Returns matches + `next_name` for auto-rename |
| 12 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match (`file_path`+`pattern`) |
| 13 | GET | `/api/v1/files/scan` | Scan directory for new files |
| 14 | GET | `/api/v1/files` | List files (paginated) |
| 15 | GET | `/api/v1/file/:file_uuid` | Single file detail |
| 16 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
| 17 | POST | `/api/v1/file/:file_uuid/process` | Start pipeline |
| 18 | POST | `/api/v1/file/:file_uuid/chunk/:chunk_id` | Single chunk detail (V1.0.2+) |
| 19 | POST | `/api/v1/progress/:file_uuid` | Processing progress |
| 20 | POST | `/api/v1/jobs` | Monitor jobs (filterable) |
```bash
curl -X POST http://localhost:3002/api/v1/files/register -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_path":"/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"}'
```
```json
{"success":true,"file_uuid":"3abeee81d94597629ed8cb943f182e94","duration":5954.0}
```
Modes:
- By `file_uuid`: unregister a single file
- By `file_path` + `pattern` regex: unregister all matching files in a directory
```bash
# By file_uuid
curl -X POST http://localhost:3002/api/v1/unregister \
-H "X-API-Key: muser_..." -H "Content-Type: application/json" \
-d '{"file_uuid":"53e3a229bf68878b7a799e811e097f9c"}'
# By pattern (unregister all .mp4 files in directory)
curl -X POST http://localhost:3002/api/v1/unregister \
-H "X-API-Key: muser_..." -H "Content-Type: application/json" \
-d '{"file_path":"/data/demo","pattern":"\\.mp4$"}'
```
```json
{"success":true,"file_uuid":"53e3a229bf68878b7a799e811e097f9c","message":"File unregistered successfully"}
```
```bash
curl "http://localhost:3002/api/v1/files?page=1&page_size=2" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"data":[{"file_uuid":"aeed7134...","file_name":"Charade (1963)...","status":"ready"}],"total":0,"page":1,"page_size":2}
```
---
## 3. Search
| # | Method | Path | Description |
|---|--------|------|-------------|
| 21 | POST | `/api/v1/search/visual` | Visual chunk search |
| 22 | POST | `/api/v1/search/visual/class` | By object class |
| 23 | POST | `/api/v1/search/visual/density` | By spatial density |
| 24 | POST | `/api/v1/search/visual/combination` | Combined visual search |
| 25 | POST | `/api/v1/search/visual/stats` | Visual stats |
| 26 | POST | `/api/v1/search/smart` | Semantic (EmbeddingGemma + pgvector) |
| 27 | POST | `/api/v1/search/universal` | BM25 keyword (requires file_uuid) |
| 28 | POST | `/api/v1/search/frames` | Frame-level search |
```bash
curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"query":"name","limit":2,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
```json
{"query":"name","results":[{"chunk_id":"100","text":"What's your name?","start_time":258.68,"score":0.90}],"total":5,"page":1,"page_size":20,"took_ms":42}
```
```bash
curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"query":"friends","limit":2,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
```json
{"query":"friends","results":[{"chunk_id":"104","text":"You won't find it difficult to make some new friends.","start_time":272.38,"score":0.90}],"total":3,"page":1,"page_size":20,"took_ms":38}
```
---
## 4. Face Trace
| # | Method | Path | Description |
|---|--------|------|-------------|
| 29 | POST | `/api/v1/file/:file_uuid/traces` | List traces (sorted/filtered) |
| 30 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Trace detections (+ interpolation) |
### traces — list traces
Parameters:
- `sort_by`: `face_count` | `duration` | `first_appearance`
- `min_faces`, `min_confidence`, `max_confidence`: filters
- `limit`: max results
```bash
curl -X POST "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/traces" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":2}'
```
```json
{"success":true,"total_traces":6892,"total_faces":108204,"traces":[
{"trace_id":3128,"face_count":1109,"avg_confidence":0.779},
{"trace_id":3126,"face_count":743,"avg_confidence":0.758}
]}
```
### trace/:trace_id/faces — individual detections
Parameters:
- `limit`, `offset`: pagination
- `interpolate`: boolean (fills sparse gaps with lerp bbox)
```bash
curl "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2/faces?limit=2&interpolate=true" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"trace_id":2,"fps":25.0,"total":1,"faces":[
{"id":12399,"start_frame":4620,"end_frame":4620,"start_time":184.8,"end_time":184.8,"x":787,"y":582,"width":225,"height":225,"confidence":0.666,"interpolated":false}
]}
```
---
## 5. Media
| # | Method | Path | Description |
|---|--------|------|-------------|
| 31 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
| 32 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream. Dual input: `?start_time=&end_time=` (seconds) or `?start_frame=&end_frame=` (frames). |
| 33 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay. `?start_frame=&end_frame=&face_uuid=&duration=` (all frame numbers). Dual input via `start_time`/`end_time`. |
| 34 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?mode=&padding=&audio=) |
All video endpoints support:
- `mode=normal|debug` (default: `normal`)
- `audio=on|off` (default: `on`)
`mode=normal`: raw clip, `-c copy`, no overlay.
`mode=debug`: re-encoded with top-left text info + green bboxes (trace labels at actual frames with thickness=4, interpolated at first known position with thickness=1).
```bash
# Normal mode
curl -o trace.mp4 "http://localhost:3002/api/v1/file/{file_uuid}/trace/42/video?mode=normal"
# Debug mode
curl -o trace_debug.mp4 "http://localhost:3002/api/v1/file/{file_uuid}/trace/42/video?mode=debug"
```
Debug overlay shows at bottom-left:
```
Frame {n} {pts}s
Cut: {id}
{file_uuid}
Trace {id}: start={frame} {name}
...
```
Green bbox per face detection: actual frames `thickness=4`, interpolated `thickness=1`.
---
## 6. Identities
| # | Method | Path | Description |
|---|--------|------|-------------|
| 35 | GET | `/api/v1/identities` | List all identities |
| 36 | GET | `/api/v1/file/:file_uuid/identities` | Identities in a file |
| 37 | POST | `/api/v1/identity` | Register new identity |
| 38 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
| 39 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
| 40 | GET | `/api/v1/identity/:identity_uuid/files` | Files for identity |
| 41 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for identity |
| 42 | GET | `/api/v1/faces/candidates` | Unbound face gallery |
| 43 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunks |
| 44 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
```bash
curl "http://localhost:3002/api/v1/identities?page=1&page_size=3" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"count":3852,"page":1,"page_size":3,"identities":[
{"id":18299,"identity_uuid":"76f85ee6-bc47-4a1a-9878-1beb67851ec5","name":"PERSON_aeed7134_390","metadata":{}},
{"id":18298,"identity_uuid":"f4d4ccbf-fccb-4f62-8806-2b7f4a706edb","name":"PERSON_aeed7134_389","metadata":{}},
{"id":18297,"identity_uuid":"e8a1b2c3-d4e5-4f67-8901-23456789abcd","name":"PERSON_aeed7134_388","metadata":{}}
]}
```
### GET /api/v1/file/:file_uuid/identities — identities with frame/time ranges
```bash
curl "http://localhost:3002/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/identities?limit=2" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"file_uuid":"aeed71342a899fe4b4c57b7d41bcb692","fps":25.0,"total":20,"page":1,"page_size":20,"data":[
{"identity_id":18276,"identity_uuid":"77d895cc-bc2e-4f5a-84b3-3c1f0e2a5b6a","name":"PERSON_aeed7134_367","face_count":86,"start_frame":150744,"end_frame":152895,"start_time":6029.76,"end_time":6115.8,"confidence":0.855},
{"identity_id":18179,"identity_uuid":"90fc04cd-003b-4a1b-9f7d-8c3e1d2f4a5b","name":"PERSON_aeed7134_270","face_count":13,"start_frame":77418,"end_frame":77454,"start_time":3096.72,"end_time":3098.16,"confidence":0.851}
]}
```
```bash
curl "http://localhost:3002/api/v1/faces/candidates?page=1&page_size=2" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"total":42,"candidates":[{"frame_number":30,"confidence":0.85},...]}
```
---
## 7. Identity Binding
| # | Method | Path | Description |
|---|--------|------|-------------|
| 45 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
| 46 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
| 47 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge into another identity |
```bash
curl -X POST "http://localhost:3002/api/v1/identity/a9a90105-6d6b-46ff-92da-0c3c1a57dff4/bind" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_uuid":"3abeee81d94597629ed8cb943f182e94","face_id":"face_42"}'
```
```json
{"success":true}
```
---
## 8. Resources
| # | Method | Path | Description |
|---|--------|------|-------------|
| 48 | POST | `/api/v1/resource/register` | Register processing resource |
| 49 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
| 50 | GET | `/api/v1/resources` | List all resources |
```bash
curl "http://localhost:3002/api/v1/resources" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
```
```json
{"success":true,"data":[{"resource_id":"mxbai-embed-large-v1","resource_type":"embedding_model"}],"message":"OK"}
```
---
## 9. Agents — 5W1H
| # | Method | Path | Description |
|---|--------|------|-------------|
| 51 | POST | `/api/v1/agents/translate` | AI text translation |
| 52 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk analysis |
| 53 | POST | `/api/v1/agents/5w1h/batch` | Batch analysis |
| 54 | GET | `/api/v1/agents/5w1h/status` | Job status |
```bash
curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"text":"Hello world","target_language":"zh-TW"}'
```
```json
{"success":true,"translated_text":"你好世界"}
```
---
## 10. Agents — Identity
| # | Method | Path | Description |
|---|--------|------|-------------|
| 55 | POST | `/api/v1/agents/identity/match-from-photo` | Match face from photo |
| 56 | POST | `/api/v1/agents/identity/match-from-trace` | Match face from trace |
| 57 | POST | `/api/v1/agents/suggest/merge` | Suggest merge |
| 58 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| V4.2 | 2026-05-25 | Removed phantom routes (stats/ingest, stats/inference, agents/identity/status); fixed HTTP methods (chunk, progress, jobs → POST); renamed endpoints (face_trace/sortby → traces, analyze → match-from-photo, suggest → match-from-trace); added config endpoints (consistency, auto-pipeline, watcher-auto-register); updated git hash to de88fd4e |
| V4.1 | 2026-05-14 | Added `build_timestamp` + `resources` + `pipeline` to health APIs; identity search endpoints; trace debug rework (green bbox, text overlay, all traces listed) |
## Related
- `API_DICTIONARY_V1.0.0.md` — Quick reference (55 endpoints)
- `API_DOCUMENTATION_v1.0.0.md` — Detailed spec with examples
- `TRACE/TRACE_API_REFERENCE_V1.0.0.md` — Trace-specific reference
@@ -1,218 +0,0 @@
# Momentry API 使用指南
## 認證流程
```mermaid
sequenceDiagram
actor User
participant API as Momentry API
participant Auth as Auth Service
User->>API: POST /api/v1/auth/login
API->>Auth: 驗證 username/password
Auth-->>API: API Key
API-->>User: { "api_key": "muser_xxx..." }
Note over User: 後續請求帶入 Header
User->>API: GET /api/v1/files<br/>X-API-Key: muser_xxx...
API-->>User: { files: [...] }
```
**demo 帳號**: `demo` / `demo`
---
## 註冊 + 處理流程
```mermaid
flowchart LR
A[上傳影片] --> B[POST /files/register]
B --> C[取得 file_uuid]
C --> D[POST /file/:file_uuid/process]
...
F --> M[GET /progress/:file_uuid]
G --> M
H --> M
I --> M
J --> M
K --> M
L --> M
M --> N[completed]
```
---
## 臉部追蹤架構
```mermaid
graph TB
subgraph Detection
A[Face Processor] --> B[face_detections]
B --> C[Store Traced Faces]
end
subgraph Tracing
C --> D[face_traces]
D --> E[Trace Aggregation]
end
subgraph API
E --> F[POST /face_trace/sortby]
E --> G[GET /trace/:id/faces]
E --> H[GET /trace/:id/video]
end
subgraph Display
F --> I[Face Thumbnail Timeline V1]
F --> J[Identity Swimlane V2]
G --> K[Interpolation POC]
H --> L[MP4 with BBOX]
end
```
---
## 搜尋三模式
```mermaid
flowchart TD
Q[使用者輸入查詢] --> M{選擇模式}
M -->|BM25| A[POST /search/universal]
A --> B[PostgreSQL ILIKE]
B --> C[關鍵字比對 text_content]
M -->|Vector| D[POST /search/smart]
D --> E[EmbeddingGemma 768D]
E --> F[pgvector 相似度搜尋]
M -->|Hybrid| G[內部組合]
G --> H[Vector Search]
G --> I[BM25 Rerank]
H --> J[Reranked Results]
I --> J
C --> K[結果回傳]
F --> K
J --> K
```
---
## 資料模型關聯
```mermaid
erDiagram
VIDEOS ||--o{ FACE_DETECTIONS : contains
VIDEOS ||--o{ CHUNKS : contains
VIDEOS ||--o{ PRE_CHUNKS : contains
FACE_DETECTIONS ||--o{ FACE_TRACES : belongs_to
FACE_TRACES }o--|| IDENTITIES : identifies
IDENTITIES ||--o{ IDENTITY_BINDINGS : binds
CHUNKS ||--o{ PARENT_CHUNKS : groups
VIDEOS {
string file_uuid PK
string file_name
float duration
int width
int height
float fps
}
FACE_DETECTIONS {
int id PK
string file_uuid FK
int trace_id
int frame_number
int x
int y
float confidence
}
IDENTITIES {
int id PK
string name
string file_uuid
int tmdb_id
}
```
---
## 端點路徑總覽
```mermaid
mindmap
root((api.momentry.ddns.net))
System
GET /health
POST /auth/login
GET /stats/ingest
Files
POST /files/register
GET /files
GET /file/:file_uuid
POST /file/:file_uuid/process
Traces
POST /face_trace/sortby
GET /trace/:trace_id/faces
GET /trace/:trace_id/video
GET /thumbnail
Search
POST /search/universal
POST /search/smart
POST /search/visual
Identities
GET /identities
POST /identity
POST /identity/:identity_uuid/bind
Agents
POST /agents/translate
POST /agents/5w1h/analyze
POST /agents/identity/suggest
```
---
## 互動範例
### 1. 登入 → 取得檔案列表
```mermaid
sequenceDiagram
actor Dev
Dev->>API: POST /api/v1/auth/login<br/>{ "username": "demo", "password": "demo" }
API-->>Dev: { "api_key": "muser_test_001..." }
Dev->>API: GET /api/v1/files<br/>X-API-Key: muser_test_001...
API-->>Dev: { "files": [...], "total": 37 }
```
### 2. 查看臉部追蹤 → 播放影片
```mermaid
sequenceDiagram
actor Dev
Dev->>API: POST /api/v1/file/{file_uuid}/face_trace/sortby<br/>{ "sort_by": "face_count", "limit": 3 }
API-->>Dev: { "total_traces": 6892, "traces": [...] }
Dev->>API: GET /api/v1/file/{file_uuid}/trace/3128/video
API-->>Dev: MP4 binary
Note over Dev: Browser opens video with bbox
```
### 3. 身分識別
```mermaid
sequenceDiagram
actor Dev
Dev->>API: GET /api/v1/identities?page=560&page_size=5
API-->>Dev: { "identities": [<br/> {"name":"Cary Grant"},<br/> {"name":"Audrey Hepburn"}<br/>] }
```
---
## 快速參考
| 用途 | 指令 |
|------|------|
| 登入取得 Key | `curl -X POST https://api.momentry.ddns.net/api/v1/auth/login -H "Content-Type: application/json" -d '{"username":"demo","password":"demo"}'` |
| 列出檔案 | `curl https://api.momentry.ddns.net/api/v1/files -H "X-API-Key: muser_test_001"` |
| Top Traces | `curl -X POST https://api.momentry.ddns.net/api/v1/file/{file_uuid}/face_trace/sortby -H "X-API-Key: muser_test_001" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":3}'` |
| BM25 搜尋 | `curl -X POST https://api.momentry.ddns.net/api/v1/search/universal -H "X-API-Key: muser_test_001" -H "Content-Type: application/json" -d '{"query":"friends","mode":"bm25","uuid":"{file_uuid}"}'` |
| 身分列表 | `curl https://api.momentry.ddns.net/api/v1/identities?page=1&page_size=5 -H "X-API-Key: muser_test_001"` |
@@ -1,136 +0,0 @@
{
"title": "Momentry Core 展示 v1.0.0",
"version": "1.0",
"language": "zh_TW",
"server": "https://api.momentry.ddns.net",
"setup": "KEY=\"X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69\"; BASE=https://api.momentry.ddns.net; FILE=3abeee81d94597629ed8cb943f182e94",
"steps": [
{
"type": "separator",
"label": "開場:系統活著"
},
{
"type": "note",
"label": "確認服務正常",
"note": "Momentry Core 是一套影片內容分析系統。給它一支影片,它會自動辨識裡面的人臉、追蹤他們的移動、分析誰是誰,還能用文字搜尋影片內容。"
},
{
"type": "curl",
"label": "伺服器狀態檢查",
"note": "先確認服務正常。正式環境伺服器回應狀態「ok」。",
"cmd": "curl -s $BASE/health",
"expect": "ok"
},
{
"type": "browser",
"label": "瀏覽器開啟狀態頁",
"note": "瀏覽器直接開啟狀態頁面也可以。",
"url": "$BASE/health"
},
{
"type": "separator",
"label": "檔案與人臉追蹤"
},
{
"type": "curl",
"label": "檢視已註冊檔案",
"note": "目前系統有三十七支已註冊的影片,以 Charade 這部老電影為主。",
"cmd": "curl -s \"$BASE/api/v1/files?page=1&page_size=3\" -H \"X-API-Key: $KEY\"",
"expect": "file_uuid"
},
{
"type": "curl",
"label": "人臉追蹤總覽",
"note": "核心功能:系統把影片中每個出現的人臉追蹤成一個「追蹤紀錄」。這部 Charade 總共找到六千八百九十二個追蹤、十萬八千二百零四次臉部偵測。最長的一段追蹤有一千一百零九次連續出現,持續四十四點三秒。",
"cmd": "curl -s -X POST $BASE/api/v1/file/$FILE/face_trace/sortby -H \"X-API-Key: $KEY\" -H \"Content-Type: application/json\" -d '{\"sort_by\":\"face_count\",\"limit\":5}'",
"expect": "total_traces"
},
{
"type": "curl",
"label": "追蹤細節與補間動畫",
"note": "人臉處理器每隔三十個影格才取樣一次,原始資料是稀疏的。加上補間參數後,系統會自動計算中間每個影格的方框位置。補間標記為真的代表這是運算產生的,信心度為零。",
"cmd": "curl -s \"$BASE/api/v1/file/$FILE/trace/2/faces?limit=5&interpolate=true\" -H \"X-API-Key: $KEY\"",
"expect": "interpolated"
},
{
"type": "separator",
"label": "影片播放"
},
{
"type": "browser",
"label": "觀看追蹤影片",
"note": "把人臉追蹤渲染成影片,紅色方框標記人臉位置。每個偵測的框會持續到下一次偵測為止。",
"url": "$BASE/api/v1/file/$FILE/trace/5/video?padding=1"
},
{
"type": "browser",
"label": "觀看單張縮圖",
"note": "單一個影格的 JPEG 截圖。",
"url": "$BASE/api/v1/file/$FILE/thumbnail?frame=68280"
},
{
"type": "separator",
"label": "文字搜尋"
},
{
"type": "curl",
"label": "關鍵字搜尋「朋友」",
"note": "文字搜尋:不需要向量,直接用關鍵字比對。這是搜尋「朋友」的結果。",
"cmd": "curl -s -X POST $BASE/api/v1/search/universal -H \"X-API-Key: $KEY\" -H \"Content-Type: application/json\" -d '{\"query\":\"friends\",\"limit\":3,\"mode\":\"bm25\",\"uuid\":\"$FILE\"}'",
"expect": "friends"
},
{
"type": "curl",
"label": "關鍵字搜尋「名字」",
"note": "再搜尋「名字」看看,會找到「你叫什麼名字?」這段台詞。",
"cmd": "curl -s -X POST $BASE/api/v1/search/universal -H \"X-API-Key: $KEY\" -H \"Content-Type: application/json\" -d '{\"query\":\"name\",\"limit\":3,\"mode\":\"bm25\",\"uuid\":\"$FILE\"}'",
"expect": "name"
},
{
"type": "separator",
"label": "身分辨識"
},
{
"type": "curl",
"label": "電影資料庫身分列表",
"note": "系統不只是追蹤臉,它還知道誰是誰。處理管線自動比對電影資料庫後的結果:兩千八百一十個身分,包含 Cary Grant、Audrey Hepburn 等知名演員。",
"cmd": "curl -s \"$BASE/api/v1/identities?page=560&page_size=5\" -H \"X-API-Key: $KEY\"",
"expect": "\"name\""
},
{
"type": "curl",
"label": "未辨識人臉候選",
"note": "還沒被指認的身分叫做候選人,可以在這裡手動綁定到正確人名。",
"cmd": "curl -s \"$BASE/api/v1/faces/candidates?page=1&page_size=3\" -H \"X-API-Key: $KEY\"",
"expect": "candidates"
},
{
"type": "curl",
"label": "系統資源一覽",
"note": "系統資源一覽:包含目前使用的文字嵌入模型等資訊。",
"cmd": "curl -s \"$BASE/api/v1/resources\" -H \"X-API-Key: $KEY\"",
"expect": "success"
},
{
"type": "separator",
"label": "人工智慧語意搜尋"
},
{
"type": "curl",
"label": "向量語意搜尋",
"note": "最後是人工智慧搜尋。查詢先經由嵌入模型轉成七百六十八維的向量,再到向量資料庫做相似度比對。",
"cmd": "curl -s -X POST $BASE/api/v1/search/smart -H \"X-API-Key: $KEY\" -H \"Content-Type: application/json\" -d '{\"query\":\"Audrey Hepburn\",\"uuid\":\"$FILE\"}'",
"expect": "results"
},
{
"type": "separator",
"label": "展示結束"
}
]
}
-173
View File
@@ -1,173 +0,0 @@
# Momentry Demo Script v1.0.0
Curl for POST/API, browser for video/thumbnail. 約 10 分鐘。
---
## 開場:這是什麼?
> 「Momentry Core — 影片內容分析系統。給它一支影片,它會自動辨識裡面的人臉、追蹤他們的移動、分析誰是誰,還能用文字搜尋影片內容。」
---
## Step 0: 設定
```bash
KEY="X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
BASE=https://api.momentry.ddns.net
```
---
## Step 1: 系統活著
> 「先確認服務正常。」
```bash
curl $BASE/health
```
**預期**: `{"status":"ok","version":"1.0.0","uptime_ms":...}`
👉 瀏覽器開 `https://api.momentry.ddns.net/health` 也可。
---
## Step 2: 檔案一覽
> 「目前系統有 37 支已註冊的影片。」
```bash
curl "$BASE/api/v1/files?page=1&page_size=3" -H "$KEY"
```
**預期**: Charade (1963) 為主,還有其他測試檔。
---
## Step 3: 臉部追蹤概覽
> 「這是核心功能。系統把影片中每個出現的人臉追蹤成一個『trace』。這部 Charade 總共找到 **6,892 個 trace、108,204 次臉部偵測**。」
```bash
curl -X POST $BASE/api/v1/file/3abeee81d94597629ed8cb943f182e94/face_trace/sortby -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"sort_by":"face_count","limit":5}'
```
**解說**:
- trace #3128: **1,109 次出現**,持續 44.3 秒 — 這是最長的一段
- trace #3126: 743 次
- 數字越高代表這個人出現在畫面上的時間越長
---
## Step 4: 單一 Trace 細節
> 「點進去看一個 trace 的每一幀。每個框框就是一次臉部偵測,包含位置、大小、信心度。」
```bash
curl "$BASE/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2/faces?limit=3" -H "$KEY"
```
**解說**: 回傳的資料包含 `start_frame`(第幾幀)、`start_time`(第幾秒)、bbox 座標、信心度。
---
## Step 5: 補間動畫
> 「因為 face processor 每隔 30 幀才取樣一次,所以原始資料是稀疏的。加上 `interpolate=true` 後,系統會自動線性補間,填滿中間每一幀的 bbox 位置。」
```bash
curl "$BASE/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2/faces?limit=5&interpolate=true" -H "$KEY"
```
**解說**: `interpolated: false` 是真實偵測,`interpolated: true` 是補間的,confidence = 0。前端的淺色框就是補間框。
---
## Step 6: Trace 影片播放(瀏覽器)
> 「把 trace 渲染成影片,紅框標記人臉位置。」
**瀏覽器開**:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/5/video?padding=1
```
**解說**: 紅框 = 臉部位置,文字標籤 = trace ID。每個 detection 的框會持續到下一次偵測為止。
---
## Step 7: 關鍵字搜尋 (BM25)
> 「文字搜尋 — 不需要向量,直接用關鍵字比對。這是『friends』的搜尋結果。」
```bash
curl -X POST $BASE/api/v1/search/universal -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"query":"friends","limit":3,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
**預期**: `"You won't find it difficult to make some new friends."` score=0.90
> 「再搜尋『name』看看:」
```bash
curl -X POST $BASE/api/v1/search/universal -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"query":"name","limit":3,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
**預期**: `"What's your name?"` score=0.90
---
## Step 8: 身分辨識
> 「系統不只是追蹤臉,它還知道誰是誰。這是 M5 pipeline 自動比對 TMDb 資料庫後的結果 — **2,810 個身分**,包含 Cary Grant、Audrey Hepburn 等。」
```bash
curl "$BASE/api/v1/identities?page=560&page_size=5" -H "$KEY"
```
**預期**: Raoul Delfosse, Albert Daumergue, Claudine Berg...
> 「也可以直接看所有身分的列表,按頁次翻找。」
---
## Step 9: 臉部候選人(未辨識)
> 「還沒被指认的身分叫做『candidate』,可以在這裡手動綁定。」
```bash
curl "$BASE/api/v1/faces/candidates?page=1&page_size=3" -H "$KEY"
```
---
## Step 10: 嵌入向量搜尋
> 「最後是 AI 搜尋。Query 先經由 EmbeddingGemma 轉成 768 維向量,再到 Qdrant 做相似度比對。」
```bash
curl -X POST $BASE/api/v1/search/smart -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"query":"Audrey Hepburn","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
---
## 收尾
> 「以上就是 Momentry Core v1.0.0 的主要功能展示。總結:**
>
> 1. **臉部追蹤** — 6,892 traces, 108,204 detections
> 2. **補間動畫** — 稀疏取樣 → 連續軌跡
> 3. **影片渲染** — bbox overlay MP4
> 4. **關鍵字搜尋** — BM25 全文檢索
> 5. **身分辨識** — 2,810 identities, TMDb 整合
> 6. **AI 語意搜尋** — EmbeddingGemma + Qdrant
>
> 所有 API 皆可透過 `https://api.momentry.ddns.net` 存取,使用 demo/demo 登入取得 API key。"
-173
View File
@@ -1,173 +0,0 @@
# Momentry Demo Script v1.0.0
Curl for POST/API, browser for video/thumbnail. 約 10 分鐘。
---
## 開場:這是什麼?
> 「Momentry Core — 影片內容分析系統。給它一支影片,它會自動辨識裡面的人臉、追蹤他們的移動、分析誰是誰,還能用文字搜尋影片內容。」
---
## Step 0: 設定
```bash
KEY="X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
BASE=https://api.momentry.ddns.net
```
---
## Step 1: 系統活著
> 「先確認服務正常。」
```bash
curl $BASE/health
```
**預期**: `{"status":"ok","version":"1.0.0","uptime_ms":...}`
👉 瀏覽器開 `https://api.momentry.ddns.net/health` 也可。
---
## Step 2: 檔案一覽
> 「目前系統有 37 支已註冊的影片。」
```bash
curl "$BASE/api/v1/files?page=1&page_size=3" -H "$KEY"
```
**預期**: Charade (1963) 為主,還有其他測試檔。
---
## Step 3: 臉部追蹤概覽
> 「這是核心功能。系統把影片中每個出現的人臉追蹤成一個『trace』。這部 Charade 總共找到 **6,892 個 trace、108,204 次臉部偵測**。」
```bash
curl -X POST $BASE/api/v1/file/3abeee81d94597629ed8cb943f182e94/face_trace/sortby -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"sort_by":"face_count","limit":5}'
```
**解說**:
- trace #3128: **1,109 次出現**,持續 44.3 秒 — 這是最長的一段
- trace #3126: 743 次
- 數字越高代表這個人出現在畫面上的時間越長
---
## Step 4: 單一 Trace 細節
> 「點進去看一個 trace 的每一幀。每個框框就是一次臉部偵測,包含位置、大小、信心度。」
```bash
curl "$BASE/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2/faces?limit=3" -H "$KEY"
```
**解說**: 回傳的資料包含 `start_frame`(第幾幀)、`start_time`(第幾秒)、bbox 座標、信心度。
---
## Step 5: 補間動畫
> 「因為 face processor 每隔 30 幀才取樣一次,所以原始資料是稀疏的。加上 `interpolate=true` 後,系統會自動線性補間,填滿中間每一幀的 bbox 位置。」
```bash
curl "$BASE/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2/faces?limit=5&interpolate=true" -H "$KEY"
```
**解說**: `interpolated: false` 是真實偵測,`interpolated: true` 是補間的,confidence = 0。前端的淺色框就是補間框。
---
## Step 6: Trace 影片播放(瀏覽器)
> 「把 trace 渲染成影片,紅框標記人臉位置。」
**瀏覽器開**:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/5/video?padding=1
```
**解說**: 紅框 = 臉部位置,文字標籤 = trace ID。每個 detection 的框會持續到下一次偵測為止。
---
## Step 7: 關鍵字搜尋 (BM25)
> 「文字搜尋 — 不需要向量,直接用關鍵字比對。這是『friends』的搜尋結果。」
```bash
curl -X POST $BASE/api/v1/search/universal -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"query":"friends","limit":3,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
**預期**: `"You won't find it difficult to make some new friends."` score=0.90
> 「再搜尋『name』看看:」
```bash
curl -X POST $BASE/api/v1/search/universal -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"query":"name","limit":3,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
**預期**: `"What's your name?"` score=0.90
---
## Step 8: 身分辨識
> 「系統不只是追蹤臉,它還知道誰是誰。這是 M5 pipeline 自動比對 TMDb 資料庫後的結果 — **2,810 個身分**,包含 Cary Grant、Audrey Hepburn 等。」
```bash
curl "$BASE/api/v1/identities?page=560&page_size=5" -H "$KEY"
```
**預期**: Raoul Delfosse, Albert Daumergue, Claudine Berg...
> 「也可以直接看所有身分的列表,按頁次翻找。」
---
## Step 9: 臉部候選人(未辨識)
> 「還沒被指认的身分叫做『candidate』,可以在這裡手動綁定。」
```bash
curl "$BASE/api/v1/faces/candidates?page=1&page_size=3" -H "$KEY"
```
---
## Step 10: 嵌入向量搜尋
> 「最後是 AI 搜尋。Query 先經由 EmbeddingGemma 轉成 768 維向量,再到 Qdrant 做相似度比對。」
```bash
curl -X POST $BASE/api/v1/search/smart -H "$KEY" \
-H "Content-Type: application/json" \
-d '{"query":"Audrey Hepburn","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
```
---
## 收尾
> 「以上就是 Momentry Core v1.0.0 的主要功能展示。總結:**
>
> 1. **臉部追蹤** — 6,892 traces, 108,204 detections
> 2. **補間動畫** — 稀疏取樣 → 連續軌跡
> 3. **影片渲染** — bbox overlay MP4
> 4. **關鍵字搜尋** — BM25 全文檢索
> 5. **身分辨識** — 2,810 identities, TMDb 整合
> 6. **AI 語意搜尋** — EmbeddingGemma + Qdrant
>
> 所有 API 皆可透過 `https://api.momentry.ddns.net` 存取,使用 demo/demo 登入取得 API key。"
@@ -1,114 +0,0 @@
# Demo Sequence v1.0.0
Curl for POST, browser for GET/Video.
## Setup
```bash
KEY="X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
BASE=https://api.momentry.ddns.net
FILE=3abeee81d94597629ed8cb943f182e94
```
---
## 1. Server Alive
Curl:
```bash
curl $BASE/health
```
Browser: open `https://api.momentry.ddns.net/health`
---
## 2. List Traces (top 3 最多臉孔)
Curl:
```bash
curl -X POST $BASE/api/v1/file/$FILE/face_trace/sortby -H "$KEY" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":3}'
```
**預期**: 6892 traces, 最大 trace 1109 faces
---
## 3. Trace 詳情 + 補間動畫
Curl:
```bash
curl "$BASE/api/v1/file/$FILE/trace/2/faces?limit=3&interpolate=true" -H "$KEY"
```
**預期**: real + interpolated frames,bbox 線性過渡
---
## 4. BM25 關鍵字搜尋
Curl:
```bash
curl -X POST $BASE/api/v1/search/universal -H "$KEY" -H "Content-Type: application/json" -d '{"query":"friends","limit":3,"mode":"bm25","file_uuid":"$FILE"}'
```
**預期**: "You won't find it difficult to make some new friends."
---
## 5. 身分列表
Curl:
```bash
curl "$BASE/api/v1/identities?page=560&page_size=5" -H "$KEY"
```
**預期**: Cary Grant, Audrey Hepburn, Walter Matthau...
---
## 6. Trace 影片播放 (Browser)
Browser 開:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/3128/video?padding=1
```
**預期**: MP4 影片,紅框標記臉部,顯示 "t3128" 標籤
---
## 7. BBOX 影片 (frame 區間)
Browser 開:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/video/bbox?start_frame=68000&end_frame=69000
```
**預期**: 該區間內所有臉部偵測的 bbox overlay 影片
---
## 8. Frame 縮圖
Browser 開:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/thumbnail?frame=68280
```
**預期**: JPEG 圖片(trace #3128 的第一幀)
---
## Summary
| Step | Type | Endpoint | What to See |
|------|------|----------|-------------|
| 1 | Curl/Browser | `/health` | Server ok |
| 2 | Curl | `face_trace/sortby` | 6892 traces |
| 3 | Curl | `trace/:trace_id/faces?interpolate=true` | Interpolated bbox |
| 4 | Curl | `search/universal` | BM25 match |
| 5 | Curl | `/identities` | Named persons |
| 6 | **Browser** | `trace/:trace_id/video` | MP4 with bbox |
| 7 | **Browser** | `video/bbox` | Frame interval overlay |
| 8 | **Browser** | `thumbnail` | Single frame JPEG |
@@ -1,114 +0,0 @@
# Demo Sequence v1.0.0
Curl for POST, browser for GET/Video.
## Setup
```bash
KEY="X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
BASE=https://api.momentry.ddns.net
FILE=3abeee81d94597629ed8cb943f182e94
```
---
## 1. Server Alive
Curl:
```bash
curl $BASE/health
```
Browser: open `https://api.momentry.ddns.net/health`
---
## 2. List Traces (top 3 最多臉孔)
Curl:
```bash
curl -X POST $BASE/api/v1/file/$FILE/face_trace/sortby -H "$KEY" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":3}'
```
**預期**: 6892 traces, 最大 trace 1109 faces
---
## 3. Trace 詳情 + 補間動畫
Curl:
```bash
curl "$BASE/api/v1/file/$FILE/trace/2/faces?limit=3&interpolate=true" -H "$KEY"
```
**預期**: real + interpolated frames,bbox 線性過渡
---
## 4. BM25 關鍵字搜尋
Curl:
```bash
curl -X POST $BASE/api/v1/search/universal -H "$KEY" -H "Content-Type: application/json" -d '{"query":"friends","limit":3,"mode":"bm25","file_uuid":"$FILE"}'
```
**預期**: "You won't find it difficult to make some new friends."
---
## 5. 身分列表
Curl:
```bash
curl "$BASE/api/v1/identities?page=560&page_size=5" -H "$KEY"
```
**預期**: Cary Grant, Audrey Hepburn, Walter Matthau...
---
## 6. Trace 影片播放 (Browser)
Browser 開:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/3128/video?padding=1
```
**預期**: MP4 影片,紅框標記臉部,顯示 "t3128" 標籤
---
## 7. BBOX 影片 (frame 區間)
Browser 開:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/video/bbox?start_frame=68000&end_frame=69000
```
**預期**: 該區間內所有臉部偵測的 bbox overlay 影片
---
## 8. Frame 縮圖
Browser 開:
```
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/thumbnail?frame=68280
```
**預期**: JPEG 圖片(trace #3128 的第一幀)
---
## Summary
| Step | Type | Endpoint | What to See |
|------|------|----------|-------------|
| 1 | Curl/Browser | `/health` | Server ok |
| 2 | Curl | `face_trace/sortby` | 6892 traces |
| 3 | Curl | `trace/:trace_id/faces?interpolate=true` | Interpolated bbox |
| 4 | Curl | `search/universal` | BM25 match |
| 5 | Curl | `/identities` | Named persons |
| 6 | **Browser** | `trace/:trace_id/video` | MP4 with bbox |
| 7 | **Browser** | `video/bbox` | Frame interval overlay |
| 8 | **Browser** | `thumbnail` | Single frame JPEG |
@@ -1,83 +0,0 @@
# Embedding 跨機器部署方案 v1.0.0
## 分工原則
```
M5(Pipeline + 主力 Embedding) M4(Portal + Fallback Embedding)
├── 批量 vectorize(1709 chunks) ├── Portal search query embedding
├── EmbeddingGemma 主 server ├── 備援 embed server
├── 模型已上線(port 11436) └── 預設呼叫 M5 API
└── 出門 demo 可離線運作
```
## 部署架構
```
Portal Search Query
│
▼
┌─────────────┐ 成功 ┌──────────────────┐
│ M4 Portal │ ──────────→ │ M5:11436 │
│ embed │ │ EmbeddingGemma │
│ client │ │ (主力) │
│ │ 失敗 └──────────────────┘
│ retry │ ──────────→ ┌──────────────────┐
│ fallback │ │ M4:11436 │
└─────────────┘ │ EmbeddingGemma │
│ (備援) │
└──────────────────┘
```
## M4 安裝步驟
```bash
# 1. 安裝 Python 依賴
pip install torch transformers flask
# 2. 登入 HuggingFace(需接受授權)
open https://huggingface.co/google/embeddinggemma-300m
huggingface-cli login --token YOUR_TOKEN
# 3. 取得 script
rsync -av accusys@192.168.110.201:/Users/accusys/momentry_core_0.1/scripts/embeddinggemma_server.py \
./scripts/embeddinggemma_server.py
# 4. 啟動備援 server
python3 scripts/embeddinggemma_server.py --port 11436
```
## Portal Embed Client
```javascript
async function embedQuery(text) {
const servers = [
'http://192.168.110.201:11436/v1/embeddings', // M5 主力
'http://localhost:11436/v1/embeddings', // M4 備援
];
for (const url of servers) {
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ input: text }),
});
const data = await res.json();
return data.data[0].embedding;
} catch (e) {
continue; // 下一台
}
}
throw new Error('Embedding servers unreachable');
}
```
## 模型一致性
| 項目 | M5 | M4 |
|------|-----|-----|
| 模型 | EmbeddingGemma 300M | EmbeddingGemma 300M |
| 維度 | 768D | 768D |
| Server | Python MPS (port 11436) | Python CPU/MPS (port 11436) |
| Qdrant | 192.168.110.201:6333 | 192.168.110.201:6333 |
兩台使用同一模型、同一維度,確保 query embedding 與索引 embedding 可比對。
@@ -1,316 +0,0 @@
---
document_type: "deployment_record"
service: "MOMENTRY_CORE"
title: "Gemma 4 31B — M5 Max 部署記錄"
date: "2026-05-06"
version: "V1.1"
status: "active"
owner: "Warren"
created_by: "OpenCode"
---
# Gemma 4 31B — M5 Max 部署記錄
## 1. 環境
| 項目 | M4(開發機) | M5 Max(LLM 伺服器) |
|------|------------|-------------------|
| 機型 | MacBook Pro M4 | MacBook Pro M5 Max |
| 記憶體 | 16 GB | **48 GB** |
| 架構 | arm64 | arm64 |
| OS | macOS 26.x | macOS 26.4.1 |
| IP(初始) | — | 10.10.10.10 |
| IP(最終) | — | **192.168.110.201** |
| 外網 | 有 | 先無 → 後有(接上同網段 192.168.110.x) |
| Homebrew | 有 | 無(用戶非 admin,無法 sudo brew) |
| Xcode CLT | 有 | 無(install_name_tool、codesign 不可用) |
| Rust | 有 | rustup 已安裝 (1.95.0) |
| 專案目錄 | `/Users/accusys/momentry_core_0.1/` | `~/momentry_core_0.1/`(已 clone) |
## 2. 模型規格
| 屬性 | 值 |
|------|-----|
| 模型 | **Gemma 4 31B-it**(Image-Text-to-Text) |
| 參數量 | 33B (30,697,345,596) |
| 量化 | Q5_K_M |
| GGUF 大小 | **20.16 GB** (`21658399744 bytes`) |
| Embedding dim | 5376 |
| Vocabulary | 262144 |
| Context | 4096 (訓練 262144) |
| 來源 | `unsloth/gemma-4-31B-it-GGUF` |
| HF 下載數 | 1,685,377 |
| HF 許可 | Gated(需 `huggingface-cli login`) |
| License | Gemma (Apache 2.0 derived) |
## 3. Binary 與依賴
### 3.1 建置方式
llama.cpp 從 source build,不透過 Homebrew。原因:Homebrew binary 有**絕對路徑** dylib 參照,無法搬移至 M5。
```bash
# M4 上執行
cd /tmp
git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
cmake -B build -DGGML_METAL=ON
cmake --build build -j10 --target llama-server
```
### 3.2 Binary 依賴
llama-server binary 依賴以下 dylib(共 26 個檔案):
| 類別 | 檔案 | 來源 |
|------|------|------|
| 核心 GGML | `libggml.0.dylib`, `libggml.dylib` | `build/bin/` |
| 核心 GGML | `libggml-base.0.dylib`, `libggml-base.dylib` | `build/bin/` |
| Metal GPU | `libggml-metal.0.dylib`, `libggml-metal.dylib` | `build/bin/` |
| CPU | `libggml-cpu.0.dylib`, `libggml-cpu.dylib` | `build/bin/` |
| BLAS | `libggml-blas.0.dylib`, `libggml-blas.dylib` | `build/bin/` |
| LLama | `libllama.0.dylib`, `libllama.dylib` | `build/bin/` |
| LLamaCommon | `libllama-common.0.dylib`, `libllama-common.dylib` | `build/bin/` |
| MTMD | `libmtmd.0.dylib`, `libmtmd.dylib` | `build/bin/` |
| OpenSSL | `libssl.3.dylib`, `libcrypto.3.dylib` | `/opt/homebrew/opt/openssl@3/lib/` |
### 3.3 @rpath 修復
build 時期 embedded 的 @rpath 指向 `/tmp/llama.cpp/build/bin/`,需改為 `@executable_path/../lib`。
在 **M4** 上執行(Xcode CLT 可用):
```bash
cp build/bin/llama-server /tmp/llama_final
chmod +w /tmp/llama_final
# 修復 OpenSSL 絕對路徑
install_name_tool -change /opt/homebrew/opt/openssl@3/lib/libssl.3.dylib @rpath/libssl.3.dylib /tmp/llama_final
install_name_tool -change /opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib @rpath/libcrypto.3.dylib /tmp/llama_final
# 修復 GGML 絕對路徑(Homebrew build 才需要,source build 不需要)
install_name_tool -change /opt/homebrew/opt/ggml/lib/libggml.0.dylib @rpath/libggml.0.dylib /tmp/llama_final
install_name_tool -change /opt/homebrew/opt/ggml/lib/libggml-base.0.dylib @rpath/libggml-base.0.dylib /tmp/llama_final
# 修正 @rpath
install_name_tool -delete_rpath /tmp/llama.cpp/build/bin /tmp/llama_final
install_name_tool -add_rpath @executable_path/../lib /tmp/llama_final
# 重新簽章(install_name_tool 會破壞 code signature)
codesign --force --sign - /tmp/llama_final
```
### 3.4 libssl.3.dylib 自身也需修復
libssl.3.dylib 內部也參照了 `/opt/homebrew/Cellar/openssl@3/3.6.1/lib/libcrypto.3.dylib`:
```bash
cp /opt/homebrew/opt/openssl@3/lib/libssl.3.dylib /tmp/libssl_fixed.dylib
cp /opt/homebrew/opt/openssl@3/lib/libcrypto.3.dylib /tmp/libcrypto_fixed.dylib
chmod +w /tmp/libssl_fixed.dylib /tmp/libcrypto_fixed.dylib
install_name_tool -change /opt/homebrew/Cellar/openssl@3/3.6.1/lib/libcrypto.3.dylib @loader_path/libcrypto.3.dylib /tmp/libssl_fixed.dylib
codesign --force --sign - /tmp/libssl_fixed.dylib /tmp/libcrypto_fixed.dylib
```
### 3.5 全部傳送至 M5
```bash
# 模型(20GB)
scp ~/llama.cpp/models/gemma-4-31B-it-Q5_K_M.gguf \
accusys@192.168.110.201:~/models/
# binary + 全部 dylib
ssh accusys@192.168.110.201 'rm -rf ~/llama && mkdir -p ~/llama/bin ~/llama/lib'
scp /tmp/llama_final accusys@192.168.110.201:~/llama/bin/llama-server
scp /tmp/llama.cpp/build/bin/*.dylib accusys@192.168.110.201:~/llama/lib/
scp /tmp/libssl_fixed.dylib accusys@192.168.110.201:~/llama/lib/libssl.3.dylib
scp /tmp/libcrypto_fixed.dylib accusys@192.168.110.201:~/llama/lib/libcrypto.3.dylib
```
## 4. 啟動與驗證
### 4.1 一次性手動啟動
```bash
ssh accusys@192.168.110.201
export DYLD_LIBRARY_PATH=$HOME/llama/lib
codesign --force --sign - ~/llama/bin/llama-server
codesign --force --sign - ~/llama/lib/*.dylib
nohup ~/llama/bin/llama-server \
-m ~/models/gemma-4-31B-it-Q5_K_M.gguf \
--host 0.0.0.0 --port 8081 \
--n-gpu-layers 999 --ctx-size 4096 \
--threads 10 --mlock \
--reasoning off \
> ~/llama.log 2>&1 &
```
### 4.2 啟動腳本
`~/start_llm.sh`(已建立):
```bash
#!/bin/bash
export DYLD_LIBRARY_PATH=$HOME/llama/lib
pkill -9 -f llama-server 2>/dev/null
sleep 1
nohup $HOME/llama/bin/llama-server \
-m $HOME/models/gemma-4-31B-it-Q5_K_M.gguf \
--host 0.0.0.0 --port 8081 \
--n-gpu-layers 999 --ctx-size 4096 \
--threads 10 --mlock \
--reasoning off \
> $HOME/llama.log 2>&1 &
echo "llama-server PID: $!"
```
### 4.3 參數說明
| 參數 | 值 | 說明 |
|------|-----|------|
| `-m` | `~/models/gemma-4-31B-it-Q5_K_M.gguf` | 模型路徑 |
| `--host` | `0.0.0.0` | 綁定所有網路介面 |
| `--port` | `8081` | HTTP API port |
| `--n-gpu-layers` | `999` | 所有層進 GPU (Metal) |
| `--ctx-size` | `4096` | 上下文長度 |
| `--threads` | `10` | M5 Max P-core 數量 |
| `--mlock` | — | 鎖住記憶體以防 swap |
| `--reasoning` | `off` | 關閉 thinking,否則 content 進 `reasoning_content` |
| `DYLD_LIBRARY_PATH` | `~/llama/lib` | dylib 搜尋路徑 |
### 4.4 啟動過程中遇到的問題
| # | 問題 | 原因 | 解決 |
|---|------|------|------|
| 1 | `Library not loaded: libmtmd.0.dylib` | 未拷貝 Metal 相關 dylib | 從 build 拷貝全部 26 個 dylib |
| 2 | `Library not loaded: /opt/homebrew/.../libssl.3.dylib` | binary 有 OpenSSL 絕對路徑 | `install_name_tool -change → @rpath` |
| 3 | `Killed: 9` (exit 137) | code signature 被破壞 | `codesign --force --sign -` |
| 4 | `Library not loaded: /opt/homebrew/Cellar/.../libcrypto.3.dylib` | libssl.3.dylib 內部也有絕對路徑 | `install_name_tool` 修復 libssl |
| 5 | `no backends are loaded` | 缺少 Metal GPU backend | source build 時需 `-DGGML_METAL=ON` |
| 6 | `couldn't bind HTTP server socket` | 前一個 process 未完全釋放 port | `pkill -9 -f llama-server` 先 |
| 7 | **content 全在 reasoning_content** | Gemma4 預設為 thinking model | `--reasoning off` |
## 5. API 驗證
### 5.1 Health Check
```bash
curl -s http://192.168.110.201:8081/health
# → {"status":"ok"}
```
### 5.2 推理測試(--reasoning off 後)
```bash
curl -s http://192.168.110.201:8081/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4-31B-it-Q5_K_M.gguf",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 100
}'
```
回應(OpenAI-compatible):
```json
{
"choices": [{
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "Hello! How can I help you today?",
"reasoning_content": ""
}
}],
"usage": {
"completion_tokens": 100,
"prompt_tokens": 18,
"total_tokens": 118
},
"model": "gemma-4-31B-it-Q5_K_M.gguf",
"object": "chat.completion"
}
```
### 5.3 效能
| 指標 | 實測 |
|------|------|
| Prompt 速度 | 60.8 tok/s |
| 生成速度 | **25.8 tok/s** |
| Prompt 延遲 | 296 ms(18 tokens) |
| 生成延遲 | 387 ms(10 tokens) |
## 6. 整合至 OpenCode
`~/.config/opencode/config.json` 中新增 provider:
```json
{
"m5-gemma4": {
"npm": "@ai-sdk/openai-compatible",
"name": "M5 Max Gemma 4",
"options": { "baseURL": "http://192.168.110.201:8081/v1" },
"models": {
"gemma-4-31B-it-Q5_K_M.gguf": { "name": "Gemma 4 31B" }
}
}
}
```
預設 model 設為 `"m5-gemma4/gemma-4-31B-it-Q5_K_M.gguf"`。Provider list 確認:
```bash
opencode models m5-gemma4
# → m5-gemma4/gemma-4-31B-it-Q5_K_M.gguf
```
## 7. M5 網路異動記錄
| 時間 | IP | 網路 | 原因 |
|------|-----|------|------|
| 初始 | `10.10.10.10` | bridge (Thunderbolt) | 無外網,需透過 M4 NAT |
| 切換後 | `192.168.110.201` | en0 (WiFi/Ethernet) | 改接同網段,有外網 |
## 8. Rust 安裝(for Momentry dev)
```bash
curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source $HOME/.cargo/env
```
- rustc 1.95.0
- cargo 1.95.0
- 免 sudo
## 9. 記憶體使用
```
48 GB total
├─ 20 GB Gemma 4 31B Q5_K_M (process RSS ~28 GB)
├─ 4 GB macOS + 系統
└─ 24 GB 剩餘
```
實測啟動後 RSS: `28,325,600 KB` (~28 GB)。
## 10. 維護指令
| 操作 | 指令 |
|------|------|
| 啟動 | `ssh accusys@192.168.110.201 '~/start_llm.sh'` |
| 停止 | `ssh accusys@192.168.110.201 'pkill -9 -f llama-server'` |
| 查看日誌 | `ssh accusys@192.168.110.201 'tail -50 ~/llama.log'` |
| 健康檢查 | `curl http://192.168.110.201:8081/health` |
| 模型檔案 | `~/models/gemma-4-31B-it-Q5_K_M.gguf (20G)` |
| Binary 與 lib | `~/llama/bin/llama-server`, `~/llama/lib/*.dylib` |
| config | `~/.config/opencode/config.json` |
| 監控 | `htop -p $(pgrep llama-server)` |
| 記憶體 | `ps -o rss= -p $(pgrep llama-server)` |
## 11. 已知限制
- **Thinking model**: Gemma4 為 thinking 模型(`--reasoning off` 關閉後 content 正常,但某些場景可能需要 reasoning)
- **無 Homebrew**: 非 admin 帳號,無法 `brew install`。Momentry 其他服務(PostgreSQL, Redis, MongoDB)需用 portable binary 手動安裝
- **無 Xcode CLT**: `install_name_tool`, `codesign` 不可用於 M5。binary 修復需在 M4 完成後 scp
@@ -1,296 +0,0 @@
---
document_type: "architecture_design"
service: "MOMENTRY_CORE"
title: "Vision Agent — Rust Integration Design"
date: "2026-05-10"
version: "V1.0"
status: "active"
owner: "M5"
created_by: "OpenCode"
current_state: "draft"
tags:
- "vision-agent"
- "rust-integration"
- "python-executor"
- "grounding-dino"
- "architecture"
ai_query_hints:
- "Vision Agent Rust 整合架構與 PythonExecutor 設計"
- "Grounding DINO 無法 ONNX 匯出的原因與解決方案"
- "Rust 端 detect/search/multimodal handler 實作方式"
- "PythonExecutor persistent mode 與 model cache 設計"
- "Vision Agent 從 Flask 5052 遷移至 Rust 3003 的遷移計畫"
related_documents:
- "../VISION_AGENT_API_V1.0.0.md"
---
# Vision Agent — Rust Integration Design
**Goal:** Replace standalone Python Flask service (port 5052) with a Rust-native agent under `3003/api/v1/agents/vision/*`, following the same pattern as 5W1H, Identity, and Translate agents.
---
## Architecture
```
Client → 3003 (Rust Axum)
│
├── /api/v1/agents/vision/detect → PythonExecutor → vision_inference.py
├── /api/v1/agents/vision/search → PythonExecutor → vision_inference.py
├── /api/v1/agents/vision/multimodal → Rust DB query + PythonExecutor
└── /api/v1/agents/vision/models → pure Rust (no Python needed)
```
### Why PythonExecutor?
Grounding DINO uses `MultiScaleDeformableAttention` — a PyTorch custom CUDA kernel with no Rust/candle/ort equivalent. ONNX export is also impossible due to this custom op. Python is the only viable runtime.
This matches the project's existing processor pattern:
| Component | Rust | Inference |
|-----------|------|-----------|
| ASR | `PythonExecutor` | `asr_processor.py` |
| ASRX | `PythonExecutor` | `asrx_processor_custom.py` |
| YOLO | `PythonExecutor` | `yolo_processor.py` |
| **Vision** | **`PythonExecutor`** | **`vision_inference.py`** |
---
## Config
Add to existing `MOMENTRY_*` env var pattern in `src/core/config.rs`:
```rust
// Existing pattern — env::var("MOMENTRY_*")
pub fn vision_enabled() -> bool {
env::var("MOMENTRY_VISION_ENABLED")
.unwrap_or_else(|_| "true".to_string())
.parse()
.unwrap_or(true)
}
```
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `MOMENTRY_VISION_ENABLED` | `true` | Enable/disable all vision endpoints |
| `MOMENTRY_VISION_MODEL` | `grounding-dino` | Default model: `grounding-dino` or `fusion` |
| `MOMENTRY_VISION_GDINO_MODEL` | `IDEA-Research/grounding-dino-base` | HF model ID or local path |
| `MOMENTRY_VISION_PALIGEMMA_ENABLED` | `false` | Enable PaliGemma (requires ~3GB download) |
| `MOMENTRY_VISION_THRESHOLD` | `0.1` | Default confidence threshold |
| `MOMENTRY_VISION_DEVICE` | `mps` on Apple Silicon, else `cpu` | Inference device |
| `MOMENTRY_VISION_TIMEOUT` | `30000` | PythonExecutor timeout (ms) |
---
## Rust Route — `src/api/vision_agent_api.rs`
### Route Registration
```rust
pub fn vision_agent_routes() -> Router<AppState> {
Router::new()
.route("/api/v1/agents/vision/detect", post(vision_detect))
.route("/api/v1/agents/vision/search", post(vision_search))
.route("/api/v1/agents/vision/multimodal", post(vision_multimodal))
.route("/api/v1/agents/vision/models", get(vision_models))
}
```
Mount in `server.rs`:
```rust
if config::vision_enabled() {
app = app.merge(vision_agent_routes());
}
```
### Detect Handler Flow
```
1. Receive JSON with {frame, query, model, threshold}
2. Parse query → extract prompt (e.g., "find the gun" → "gun")
3. Resolve frame → timestamp (for Python compatibility)
4. Call PythonExecutor::run_script("vision_inference.py", args)
5. Parse Python stdout → JSON response
6. Return formatted result
```
### Frame/Time Resolution
```rust
fn resolve_frame(data: &Value, fps: f64) -> i64 {
// Priority: frame > time
if let Some(f) = data.get("frame").and_then(|v| v.as_i64()) {
return f;
}
if let Some(t) = data.get("time").and_then(|v| v.as_f64()) {
return (t * fps) as i64;
}
0
}
```
### JSON Protocol (Rust ↔ Python)
**Stdin (Rust → Python):**
```json
{
"action": "detect",
"frame": 136525,
"timestamp": 5461.0,
"prompt": "gun",
"model": "grounding-dino",
"threshold": 0.1,
"weights": {"grounding-dino": 0.6, "paligemma": 0.4},
"config": {
"gdino_model": "IDEA-Research/grounding-dino-base",
"paligemma_model": "google/paligemma-3b-mix-224",
"device": "mps"
}
}
```
**Stdout (Python → Rust):**
```json
{
"success": true,
"frame": 136525,
"timestamp": 5461.0,
"detections": [
{"bbox": [726.2, 567.4, 969.0, 694.6], "score": 0.476, "label": "gun"}
],
"time_ms": 345.2
}
```
---
## Python Script — `scripts/vision_inference.py`
### Design
- **No Flask.** Pure stdin/stdout protocol.
- **Model cache.** `_model` global persists across PythonExecutor calls.
- **Single entry point.** Reads JSON from stdin, dispatches by `action` field.
```python
#!/opt/homebrew/bin/python3.11
"""
Vision inference — called by Rust PythonExecutor.
Reads JSON from stdin, runs inference, writes JSON to stdout.
"""
import json, sys, os, torch
from PIL import Image
from transformers import AutoProcessor, AutoModelForZeroShotObjectDetection
_model = None
_processor = None
_device = None
def load_model():
global _model, _processor, _device
if _model is not None:
return _model, _processor
_device = os.environ.get("MOMENTRY_VISION_DEVICE", "mps")
model_name = os.environ.get("MOMENTRY_VISION_GDINO_MODEL",
"IDEA-Research/grounding-dino-base")
_processor = AutoProcessor.from_pretrained(model_name)
_model = AutoModelForZeroShotObjectDetection.from_pretrained(model_name).to(_device)
return _model, _processor
def detect_gdino(img, prompt, threshold):
model, processor = load_model()
inputs = processor(images=img, text=f"{prompt}.", return_tensors="pt").to(_device)
with torch.no_grad():
outputs = model(**inputs)
dets = processor.post_process_grounded_object_detection(
outputs, threshold=threshold,
target_sizes=[img.size[::-1]])[0]
results = []
for i in range(len(dets["boxes"])):
results.append({
"bbox": [round(v, 1) for v in dets["boxes"][i].tolist()],
"score": round(dets["scores"][i].item(), 3),
"label": prompt,
})
return results
def main():
input_data = json.load(sys.stdin)
action = input_data.get("action", "detect")
if action == "detect":
# ... run inference
elif action == "search":
# ... iterate frames
elif action == "models":
# ... return model info
json.dump(result, sys.stdout)
sys.stdout.flush()
if __name__ == "__main__":
main()
```
---
## Model Lifecycle
### Issue
GDINO loads in ~4s (download + CUDA init + weight load). PythonExecutor starts a new process per call — this would add 4s latency to every request.
### Solution: Warm Process
Use `PythonExecutor` in persistent/session mode where the Python process stays alive between calls. The `_model` global cache keeps the model in memory.
From `src/core/processor/executor.rs` — check if persistent mode is supported, or use a simple approach:
```rust
// Keep Python process alive for multiple calls
let executor = PythonExecutor::new("vision_inference.py")
.persistent(true) // reuse same process
.timeout_ms(30000);
```
If `PythonExecutor` doesn't support persistent mode, implement a simple sidecar:
```rust
// Launch Python process on agent init
let child = std::process::Command::new(python_path)
.arg(script_path)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()?;
// Write request, read response per call
child.stdin.write_all(json_request.as_bytes())?;
let response = child.stdout.read_to_string(&mut buffer)?;
```
---
## Files to Create/Modify
| File | Action | Description |
|------|--------|-------------|
| `src/api/vision_agent_api.rs` | **Create** | Rust route handlers |
| `src/core/config.rs` | **Modify** | Add `MOMENTRY_VISION_*` env vars |
| `src/api/server.rs` | **Modify** | Merge `vision_agent_routes()` |
| `scripts/vision_inference.py` | **Create** | Python inference script (stdin/stdout) |
| `API_V1.0.0/VISION_AGENT_API_V1.0.0.md` | Created | API docs |
## Migration Plan
| Phase | Steps | Status |
|-------|-------|--------|
| **1** | Create `vision_inference.py` (stdin/stdout, model cache) | ⏳ |
| **2** | Create `vision_agent_api.rs` (detect + search + multimodal handlers) | ⏳ |
| **3** | Add config + mount routes to 3003 | ⏳ |
| **4** | Test detect/search via 3003 (no 5052) | ⏳ |
| **5** | Deprecate 5052 Flask service | ⏳ |
@@ -1,91 +0,0 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "5W1H+ Agent v1.0.0"
date: "2026-05-07"
version: "V1.0"
status: "active"
owner: "Warren"
tags:
- "momentry"
- "agent"
- "5w1h"
- "llm"
- "summary"
related_documents:
- "../../TRACE/TRACE_API_REFERENCE_V1.0.0.md"
- "../CHUNK_DEFINITION_V1.0.0.md"
- "../VECTOR_SPEC_V1.0.0.md"
---
# 5W1H+ Agent v1.0.0
## 概述
對每個 cut scene 產生 5W1H+ 摘要(parent summary + child enhanced text)。
## 遞迴 Context(Story So Far)
採用方案 B:每段 scene 的 LLM call 帶入前面所有 scene 的摘要。
```
Scene 1 → LLM(context="") → summary_1
Scene 2 → LLM(context=summary_1) → summary_2
Scene 3 → LLM(context=summary_1+summary_2) → summary_3
```
Context truncation:保留最近 ~500 tokens 的前情,避免超過模型 limit。
## Prompt 結構
每個 scene 的 LLM call 包含以下資訊:
| Prompt 區塊 | 來源 | 說明 |
|------------|------|------|
| Scene time | chunk metadata | 目前 scene 的時間區間 |
| Dialogue | sentences in scene | 該 scene 內的對話行 |
| Actors present | face_detections JOIN identity_bindings JOIN identities | 場景中出現的演員 |
| Objects detected | pre_chunks WHERE processor_type='yolo' | YOLO 偵測到的物體 |
| Face traces | face_detections JOIN identity_bindings JOIN identities | trace 與對應的演員名稱 |
| Active speakers | pre_chunks WHERE processor_type='asrx' JOIN identity_bindings | 說話者與對應的演員 |
| Story so far | 前 N 個 scene 的 parent_summary | 前情摘要 |
## LLM 模型
| 項目 | 值 |
|------|-----|
| 模型 | Gemma4 26B MoE (Q5_K_M, 18GB) |
| 部署 | llama-server(Metal GPU, port 8082) |
| 環境變數 | `MOMENTRY_LLM_SUMMARY_URL=http://localhost:8082/v1/chat/completions` |
| 溫度 | 0.1 |
| max_tokens | 4096 |
## 產出
| 輸出 | 儲存位置 | 說明 |
|------|---------|------|
| parent_summary | `cut.summary_text` | 5 句 scene_summary(5W1H 流暢段落) |
| parent_5w1h | `cut.metadata -> 5w1h` | 結構化 who/what/where/when/why/how |
| child_enhanced | `sentence.text_content` | 自包含的 enhanced sentence(供 embedding + search) |
| child_5w1h | `sentence.content -> 5w1h` | 逐句的 5w1h 結構 |
| embedding | `sentence.embedding` | EmbeddingGemma 300M 768D(產出 summary 後自動 vectorize) |
## API
```
POST /api/v1/agents/5w1h/analyze
POST /api/v1/agents/5w1h/batch
GET /api/v1/agents/5w1h/status
```
## Pipeline 觸發
Job Worker 中的 P4 trigger:
```rust
// all_completed + has_cut + has_asr → run_5w1h_agent(db, uuid)
```
## 選型文件
詳細方案比較:`M5_workspace/2026-05-07_5w1h_recursive_summary_design.md`
@@ -1,84 +0,0 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "Identity Agent v1.0.0"
date: "2026-05-07"
version: "V1.0"
status: "active"
owner: "Warren"
tags:
- "momentry"
- "agent"
- "identity"
- "face"
- "speaker"
related_documents:
- "../DATA_SCHEMA_FILE_IDENTITY_V1.0.0.md"
- "../../TRACE/TRACE_API_REFERENCE_V1.0.0.md"
- "../PROCESSORS/FACE_V1.0.0.md"
- "../PROCESSORS/ASRX_V1.0.0.md"
---
# Identity Agent v1.0.0
## 概述
將 face trace 與 speaker 綁定到人物身份(identity),實現跨場景的人員辨識。
## 處理流程
```
face_clustered.json + asrx.json
→ extract_persons (face clusters)
→ extract_speakers (ASRX segments)
→ analyze_person_speaker_overlap
→ 寫入 dev.identities
→ match_faces_iterative (TMDb seed → propagation)
→ bind_speakers (speaker_id → identity_id)
```
## 迭代多角度 Face Matching
```
TMDb seeds (12 identities, with mulitple angles)
→ Round 1: ~33% trace-to-identity
→ Round 2: propagate matched traces as new seeds
→ Round 3: propagate again
→ Final: 99% binding (6,175 / 6,186 face detections)
```
## Speaker Binding
```
face_detections (trace_id, frame_number)
+ ASRX segments (speaker_id, start_time, end_time)
→ frame-level overlap computation
→ winner-takes-all: best_overlap > 30%
→ 寫入 identity_bindings (identity_type='speaker')
```
## Pipeline 觸發
Job Worker 中的 P3 trigger:
```rust
// has_face + has_asrx → run_identity_agent(db, uuid)
```
觸發時機:all_completed,face 與 asrx 皆完成後。
## DB 結構
| Table | 用途 |
|-------|------|
| `identities` | 身份主表(name, type, metadata, embedding) |
| `identity_bindings` | 綁定表(identity_id → trace_id 或 speaker_id) |
| `file_identities` | 檔案級身份對應 |
## API
```
POST /api/v1/agents/identity/analyze
POST /api/v1/agents/identity/suggest
GET /api/v1/agents/identity/status
```
@@ -1,175 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core API 字典 V1.0.0"
date: "2026-05-06"
version: "V1.3"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "api"
- "dictionary"
- "v1.0.0"
ai_query_hints:
- "Momentry Core API 字典查詢"
- "API 端點與參數說明"
- "API 回應格式定義"
- "查詢所有 Public/Internal/Admin API 端點列表"
- "API 端點的 HTTP 方法與路徑結構"
- "搜尋 API 有哪些端點(search/bm25/hybrid/visual)"
- "API 端點的狀態分類(Public/Internal/Admin)"
related_documents:
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
- "API_V1.0.0/API_USAGE_DEMO_V1.0.0.md"
- "API_V1.0.0/CHUNK_DEFINITION_V1.0.0.md"
- "API_V1.0.0/VECTOR_SPEC_V1.0.0.md"
---
# Momentry Core API 字典 V1.0.0
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| Public API | 供前端與外部系統使用的標準介面 |
| Internal API | 系統內部流程或狀態查詢用 |
| Admin API | 管理員專用 |
| file_uuid | 32 碼 birth UUID(MAC + time + path + filename) |
| identity_uuid | 32 碼 UUIDv5(source + external_id) |
| RESTful | 以資源為中心的 API 設計風格,collection 複數、resource 單數 |
## 端點統計
| 分類 | 數量 | 說明 |
|---|---|---|
| Public | 40 | 供前端與外部系統使用的標準介面 |
| Internal | 4 | 系統內部流程或狀態查詢 |
| Admin | 3 | 管理員專用 |
| Health | 2 | 服務健康檢查 |
| **總計** | **48** | 所有已註冊路由 |
## 設計原則
### 1. RESTful 命名規範
- Collection(複數): `/api/v1/files`, `/api/v1/identities`
- Resource(單數): `/api/v1/file/:file_uuid`, `/api/v1/identity/:identity_uuid`
- Action on resource: `/api/v1/identity/:identity_uuid/bind`
### 2. File-Centric
- 每個媒體檔案由 32 碼 UUID (`file_uuid`) 唯一標識
- File 是所有資料的根節點,Chunk、Job 隸屬於特定 File
### 3. Global Identity
- Identity 跨檔案關聯,不受單一檔案限制
- 透過 bind/unbind/mergeinto 管理 Face → Identity 的直接 FK 綁定(V4.0)
---
## 1. 系統與認證
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `GET` | `/health` | Health |
| `GET` | `/health/detailed` | Health |
| `POST` | `/api/v1/auth/login` | Public |
| `POST` | `/api/v1/auth/logout` | Public |
## 2. 檔案管理 (Files)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `GET` | `/api/v1/files` | Public |
| `GET` | `/api/v1/files/scan` | Public |
| `POST` | `/api/v1/files/register` | Public |
| `POST` | `/api/v1/unregister` | Public |
| `GET` | `/api/v1/file/:file_uuid` | Public |
| `GET` | `/api/v1/file/:file_uuid/probe` | Public |
| `POST` | `/api/v1/file/:file_uuid/process` | Public |
| `GET` | `/api/v1/file/:file_uuid/identities` | Public |
| `GET` | `/api/v1/file/:file_uuid/chunks` | Public |
| `GET` | `/api/v1/file/:file_uuid/thumbnail?frame=&x=&y=&w=&h=` | Public |
| `POST` | `/api/v1/file/:file_uuid/face_trace/sortby` | Public |
## 3. 管線與任務 (Pipeline & Jobs)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `GET` | `/api/v1/progress/:file_uuid` | Public |
| `GET` | `/api/v1/jobs` | Public |
| `GET` | `/api/v1/job/:job_id` | Public |
| `GET` | `/api/v1/rule/:rule_id/status` | Public |
| `POST` | `/api/v1/resource/register` | Internal |
| `POST` | `/api/v1/resource/heartbeat` | Internal |
| `GET` | `/api/v1/resources` | Internal |
## 4. 搜尋 (Search)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `POST` | `/api/v1/search` | Public |
| `POST` | `/api/v1/search/bm25` | Public |
| `POST` | `/api/v1/search/hybrid` | Public |
| `POST` | `/api/v1/search/smart` | Public |
| `POST` | `/api/v1/search/universal` | Public |
| `POST` | `/api/v1/search/frames` | Public |
| `POST` | `/api/v1/search/visual` | Public |
| `POST` | `/api/v1/search/visual/class` | Public |
| `POST` | `/api/v1/search/visual/density` | Public |
| `POST` | `/api/v1/search/visual/combination` | Public |
| `POST` | `/api/v1/search/visual/stats` | Public |
## 5. 身份管理 (Identity)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `GET` | `/api/v1/identities` | Public |
| `POST` | `/api/v1/identity` | Public |
| `GET` | `/api/v1/identity/:identity_uuid` | Public |
| `DELETE` | `/api/v1/identity/:identity_uuid` | Public |
| `GET` | `/api/v1/identity/:identity_uuid/files` | Public |
| `GET` | `/api/v1/identity/:identity_uuid/chunks` | Public |
| `POST` | `/api/v1/identity/:identity_uuid/bind` | Public |
| `POST` | `/api/v1/identity/:identity_uuid/unbind` | Public |
| `POST` | `/api/v1/identity/:from_uuid/mergeinto` | Public |
## 6. 臉部 (Faces)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `GET` | `/api/v1/faces/candidates` | Public |
## 7. 代理人 (Agents)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `POST` | `/api/v1/agents/translate` | Public |
| `POST` | `/api/v1/agents/identity/analyze` | Public |
| `POST` | `/api/v1/agents/identity/suggest` | Public |
| `GET` | `/api/v1/agents/identity/status` | Public |
| `POST` | `/api/v1/agents/suggest/merge` | Public |
| `POST` | `/api/v1/agents/5w1h/analyze` | Public |
| `POST` | `/api/v1/agents/5w1h/batch` | Public |
| `GET` | `/api/v1/agents/5w1h/status` | Public |
## 8. 狀態與管理 (Stats & Admin)
| 方法 | 路徑 | 狀態 |
|------|------|------|
| `GET` | `/api/v1/stats/sftpgo` | Internal |
| `GET` | `/api/v1/stats/inference` | Internal |
| `POST` | `/api/v1/config/cache` | Admin |
| `POST` | `/api/v1/config/auto-pipeline` | Admin |
| `POST` | `/api/v1/config/watcher-auto-register` | Admin |
---
## 變更歷史
| 版本 | 日期 | 作者 | 說明 |
|------|------|------|------|
| V1.3 | 2026-05-06 | OpenCode | 新增 `face_thumbnail` ffmpeg 即時裁切端點 + `face_trace/sortby` 端點;portal 修復 hardcoded URL/API key/legacy endpoints |
| V1.1 | 2026-05-01 | OpenCode | Route fixes + arch notes |
| V1.0 | 2026-04 | OpenCode | 初始版本 |
@@ -1,310 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core API 參考文件 V1.0.0 (Demo 完整指南)"
date: "2026-05-01"
version: "V3.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "api"
- "reference"
- "v1.0.0"
- "demo"
- "marcom"
ai_query_hints:
- "查詢 V1.0.0 Demo 所需 API 列表"
- "Momentry Core Demo 流程如何使用 API?"
- "API 的檔案註冊、處理、臉部綁定流程"
- "Demo 流程中 Scan → Unregister → Register → Probe → Process → Faces → Bind 的完整步驟"
- "API 的 curl 範例與回應格式"
- "Process 回傳 400 Bad Request 的常見原因與解決方法"
- "臉部查詢回傳空結果的疑難排解步驟"
related_documents:
- "STANDARDS/DOCS_STANDARD.md"
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
- "TEST_REPORT_CLI.md"
---
# Momentry Core API 參考文件 V1.0.0 (Demo 完整指南)
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| file_uuid | 32 碼 SHA256 檔案識別碼 |
| X-API-Key | API 認證方式,透過 HTTP Header 傳遞 |
| Scan | 掃描檔案系統,列出所有檔案及當前狀態 |
| Register | 將檔案加入資料庫系統 |
| Probe | 讀取檔案 metadata(時長、解析度、幀率) |
| Bind | 將臉部綁定到指定身份 |
| Progress | 獲取處理進度與目前階段 |
## 📊 文件統計 (Document Statistics)
| 項目 | 數值 |
|---|---|
| **收錄端點** | 15+ (Demo 核心流程) |
| **涵蓋率** | Demo 流程 100% |
| **測試狀態** | ✅ CLI Verified |
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-01 |
| 文件版本 | V3.0 |
---
## 1. Demo 流程總覽 (Demo Workflow)
本文件專注於 **Demo 測試計畫** 所需的 API。以下是完整流程與對應 API:
```
1. 掃描狀態 (Scan) → GET /api/v1/files/scan
2. 檔案重置 (Unregister) → POST /api/v1/unregister
3. 檔案註冊 (Register) → POST /api/v1/files/register
4. 檔案探測 (Probe) → GET /api/v1/files/:file_uuid/probe
5. 開始處理 (Process) → POST /api/v1/files/:file_uuid/process
6. 監控進度 (Progress) → GET /api/v1/progress/:file_uuid**
7. 查詢臉部 (Faces) → GET /api/v1/faces/candidates
8. 綁定身份 (Bind) → POST /api/v1/identities/bind
```
---
## 2. 快速資訊
- **Base URL (Dev)**: `http://localhost:3003`
- **Base URL (Prod)**: `http://localhost:3002`
- **認證方式**: Header `X-API-Key: muser_test_001`
- **測試 Key**: `muser_test_001`
---
## 3. API 詳細說明 (依 Demo 順序)
### 3.1 掃描檔案系統 (Scan Files)
**路徑**: `GET /api/v1/files/scan`
**用途**: 列出檔案系統中所有檔案及當前狀態,**是 Demo 流程的第一步**。
**Response**:
```json
{
"files": [
{
"file_name": "A12T3-Share-User Experience of Thunderbolt 3 Shareable Storage.mp4",
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/A12T3-Share-User Experience of Thunderbolt 3 Shareable Storage.mp4",
"file_uuid": "7ab7e25f48b58675e33aca44d15c1ecc",
"is_registered": true,
"status": "processing"
}
],
"total": 20,
"registered_count": 20,
"unregistered_count": 0
}
```
---
### 3.2 取消註冊 (Unregister File)
**路徑**: `POST /api/v1/unregister`
**用途**: 從 Scan 結果中選取 `file_uuid`,對該檔案執行取消註冊。
**Request**:
```json
{
"uuid": "53e3a229bf68878b7a799e811e097f9c"
}
```
**Response**:
```json
{
"success": true,
"uuid": "53e3a229bf68878b7a799e811e097f9c",
"message": "File unregistered successfully"
}
```
---
### 3.3 註冊檔案 (Register File)
**路徑**: `POST /api/v1/files/register`
**用途**: 從 Scan 結果中選取 `file_path`,將檔案加入資料庫系統。
**Request**:
```json
{
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/view15.mp4"
}
```
**Response**:
```json
{
"success": true,
"file_uuid": "53e3a229bf68878b7a799e811e097f9c",
"file_name": "view15.mp4",
"file_path": "/Users/.../demo/view15.mp4",
"already_exists": false
}
```
---
### 3.4 檔案探測 (Probe File)
**路徑**: `GET /api/v1/files/:file_uuid/probe`
**用途**: 讀取檔案的 metadata (時長、解析度、幀率)。**必須在 Process 前執行**。
**Response**:
```json
{
"file_uuid": "7ab7e25f48b58675e33aca44d15c1ecc",
"file_name": "A12T3-Share-User Experience of Thunderbolt 3 Shareable Storage.mp4",
"duration": 621.55,
"width": 1920,
"height": 1080,
"fps": 29.97,
"cached": true
}
```
---
### 3.5 觸發處理 (Process File)
**路徑**: `POST /api/v1/files/:file_uuid/process`
**用途**: 啟動後端 Worker 進行分析 (ASR, Face, YOLO, 等)。
**Request**:
```json
{}
```
**Response**:
```json
{
"success": true,
"message": "Processing started"
}
```
---
### 3.6 查詢進度 (Progress)
**路徑**: `GET /api/v1/progress/:file_uuid`
**用途**: 獲取處理進度與目前階段。
**Response**:
```json
{
"file_uuid": "53e3a229bf68878b7a799e811e097f9c",
"overall_progress": 65,
"current_processor": "face",
"status": "running",
"processors": [
{ "name": "probe", "status": "completed" },
{ "name": "asr", "status": "completed" },
{ "name": "face", "status": "running" }
]
}
```
---
### 3.6 查詢未綁定臉部 (List Face Candidates)
**路徑**: `GET /api/v1/faces/candidates`
**用途**: 列出檔案中尚未綁定身份的臉部。
**Query Parameters**:
- `file_uuid` (必填): 檔案 UUID
- `min_confidence` (選填): 最低信心值 (預設 0.5)
- `page_size` (選填): 每頁數量 (預設 20)
**Response**:
```json
{
"candidates": [
{
"id": 123,
"face_id": "123_RoleA",
"file_uuid": "384b0ff44aaaa1f14cb2cd63b3fea966",
"frame_number": 115,
"confidence": 0.98,
"bbox": { "x": 50, "y": 50, "w": 100, "h": 100 }
}
],
"total": 1,
"page": 1,
"page_size": 20
}
```
---
### 3.7 綁定身份 (Bind Identity)
**路徑**: `POST /api/v1/identities/bind`
**用途**: 將臉部綁定到指定身份 (或建立新身份)。
**Request**:
```json
{
"identity_id": 22,
"binding_type": "face",
"binding_value": "123_RoleA"
}
```
**Response**:
```json
{
"success": true,
"message": "Bound face '123_RoleA' to Identity 'Cary Grant'"
}
```
---
## 4. 補充 API (Demo 選用)
### 4.1 列出身份 (List Identities)
**路徑**: `GET /api/v1/identities`
**用途**: 列出系統中所有已建立的身份。
---
## 5. 常見問題 (FAQ)
### Q1: 為什麼 Process 回傳 400 Bad Request?
**Ans**: 必須先執行 **Probe** (`GET /api/v1/files/:file_uuid/probe`),確保系統已知曉檔案的幀數資訊。
### Q2: 為什麼 Unregister 回傳 404?
**Ans**: 確認伺服器是否已更新至最新版本。舊版可能尚未包含此路由。
### Q3: 臉部查詢回傳空結果?
**Ans**:
1. 確認檔案已**處理完成** (Progress = 100%)。
2. 嘗試降低 `min_confidence` 參數 (例如設為 0.0)。
3. 確認該檔案內容確實包含可辨識的臉部。
---
## 6. 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-04-30 | 初始 API 列表 | OpenCode |
| V2.0 | 2026-05-01 | 基於 Production 測試結果補足文件 | OpenCode |
| V3.0 | 2026-05-01 | 重構為 Demo 流程導向,補齊 Probe/Unregister 說明 | OpenCode |
| V3.1 | 2026-05-01 | 修正 `:uuid`→`:file_uuid`,修正 port 3002→3003,移除重複 Scan 章節 | OpenCode |
@@ -1,376 +0,0 @@
---
document_type: "develop_guide"
service: "MOMENTRY_CORE"
title: "Momentry Core V1.0.0 API 示範與整合指南"
date: "2026-05-01"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "api-usage"
- "demo"
- "n8n"
- "wordpress"
ai_query_hints:
- "查詢 V1.0.0 API 示範與整合指南的內容"
- "如何使用 n8n 呼叫 V1.0.0 API?"
- "如何整合 V1.0.0 API 到 WordPress?"
- "V1.0.0 API 的 curl 範例"
- "PHP 整合 V1.0.0 API 的方式(wp_remote_request)"
- "n8n 工作流如何串接 V1.0.0 API"
- "Face 綁定錯誤修正的 API 操作步驟"
- "前端 Face Interpolation 的實作方式"
related_documents:
- "API_V1.0.0/MOMENTRY_CORE_API_V1.0.0.md"
- "API_V1.0.0/API_DICTIONARY_V1.0.0.md"
- "API_V1.0.0/API_REFERENCE_v1.0.0.20260501md.md"
- "API_V1.0.0/CHUNK_DEFINITION_V1.0.0.md"
- "API_V1.0.0/PROCESSOR_SELECTION_V1.0.0.md"
---
# Momentry Core V1.0.0 API 示範與整合指南
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-01 |
| 文件版本 | V1.0 |
| 適用版本 | Momentry Core V1.0.0+ |
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| file_uuid | 32 碼 SHA256 檔案識別碼 |
| X-API-Key | API 認證方式,透過 HTTP Header 傳遞 |
| face_id | 單一幀中的人臉偵測 ID,格式為 `<檢測ID>_<角色後綴>` |
| Identity | 全域人物身份,跨檔案關聯同一人物 |
| Face Interpolation | 前端線性插值,補足非逐幀臉部標記的顯示 |
| Scan | 掃描檔案系統,列出所有檔案及當前狀態 |
## 1. 快速開始 (Quick Start)
### 1.1 環境 URL
| 環境 | URL | 用途 |
|------|-----|------|
| **對外 URL** | `https://api.momentry.ddns.net` | 外部存取 |
| **Dev Server** | `http://localhost:3003` | **開發環境,所有測試用** |
| **Local Server** | `http://localhost:3002` | Production,僅 release 用 |
### 1.2 測試連線
```bash
curl http://localhost:3003/health
```
```json
{
"status": "ok",
"version": "1.0.0 (build: ...)",
"uptime_ms": 64880
}
```
---
## 2. 核心 API 工作流 (Workflows)
### 2.1 掃描檔案系統 (Scan Files)
**入口 API**: `GET /api/v1/files/scan` — 所有 Demo 流程從這裡開始。
**掃描檔案**:
```bash
curl -s "http://localhost:3003/api/v1/files/scan" \
-H "X-API-Key: <your_api_key>"
```
**列出檔案 (分頁)**:
```bash
curl -s "http://localhost:3003/api/v1/files?page=1&page_size=10" \
-H "X-API-Key: <your_api_key>"
```
**取得單一檔案詳情**:
```bash
curl -s "http://localhost:3003/api/v1/files/<file_uuid>" \
-H "X-API-Key: <your_api_key>"
```
### 2.2 搜尋 (Search)
支援語意搜尋、混合搜尋與視覺搜尋。
```bash
curl -X POST "http://localhost:3003/api/v1/search" \
-H "X-API-Key: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{"query": "尋找紅色信封", "uuid": "<file_uuid>"}'
```
### 2.3 單獨 Face 綁定流程 (Single Face Binding Workflow)
此流程適用於手動將特定臉部關聯到已知人物或建立新人物的場景。系統支援**一人分飾多角**,透過 `face_id` 加上角色後綴來區分。
#### 步驟 1: 選定 Face (Input Format)
使用者需提供一個 **`file_uuid`** 搭配 **`face_id`** 來鎖定目標。
選定的意思是輸入 **`<file_uuid>:<face_id>`** 的組合。
* **命名規則**: `face_id` 格式通常為 `<原始檢測 ID>_<後綴>`,用於區分同一人的不同臉部實體或角色。
* **有角色名稱**: 使用角色名 (如 `123_PeterJoshua`)。
* **無角色名稱**: 使用通用代號 (如 `123_RoleA`, `123_RoleB`)。
#### 步驟 2: 列出 Identities 或新增 Identity
使用者決定將該 Face 綁定到系統中已存在的全域人物 (Identity),或是建立一個新人物。
* **Identity 特性**: 代表現實世界中的真實人物,具備**全域唯一性** (如 "Cary Grant")。
- **選項 A: 列出人物清單**
```bash
curl -s "http://localhost:3003/api/v1/identities?page=1&page_size=20" \
-H "X-API-Key: <your_api_key>"
```
- **選項 B: 決定新增人物名稱**
若列表中沒有對應人物,使用者需準備一個新名稱(如 "Cary Grant")。
#### 步驟 3: 確認綁定
透過 `POST /api/v1/identities/bind` 完成綁定。
* **若提供 `identity_id`**: 將帶有後綴的 `face_id` 綁定至該人物。
* **若提供 `name`**: 系統自動建立新人物 (Identity),並將該臉部綁定上去。
- **綁定至現有身份 (範例)**:
假設我們要綁定的目標是檔案 `file_uuid_abc` 中的臉部 `123_PeterJoshua`。
```bash
curl -X POST "http://localhost:3003/api/v1/identities/bind" \
-H "X-API-Key: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"identity_id": 101,
"binding_type": "face",
"binding_value": "123_PeterJoshua"
}'
```
*註: 雖然 API 接收的是 `binding_value`,但系統內部會根據選定的 `file_uuid` 與 `face_id` 組合來精確鎖定目標。*
#### 步驟 4: 循環
完成綁定後,返回列表處理下一個未綁定的 Face。
---
### 2.4 取得 Face 截圖 (Retrieve Face Snapshots)
在確認綁定前,通常需要檢視臉部截圖。根據使用場景,取得截圖有兩種方式:
#### 1. Local Path / Filename (本地路徑)
* **適用**: Tauri 桌面應用、本機腳本。
* **說明**: 直接從硬碟讀取圖片檔案,速度最快,無需經過網路層。
* **路徑**: `<MOMENTRY_OUTPUT_DIR>/<file_uuid>/snapshots/faces/<face_id>.jpg`
#### 2. URL (網路存取)
* **適用**: Web 前端、外部系統。
* **說明**: 透過 HTTP GET 請求取得影像串流。
* **API Endpoint**: `GET /api/v1/files/<file_uuid>/faces/<face_id>/thumbnail`
* **範例**:
```bash
curl -s -o face.jpg \
"http://localhost:3003/api/v1/files/<file_uuid>/faces/<face_id>/thumbnail" \
-H "X-API-Key: <your_api_key>"
```
---
### 2.4.1 前端動態辨識與插值 (Face Interpolation Logic)
由於系統對臉部標記並非逐幀 (Frame-by-Frame) 進行(為節省運算資源或受限於取樣率),在 Client 端進行**逐幀播放**或**時間軸拖曳**時,若直接顯示會導致臉部框選忽閃忽滅。
#### 運作邏輯
前端需實作**線性插值 (Linear Interpolation)** 機制:
1. **取得資料**:從 API 取得該 `face_id` 在所有 `frame_number` 的座標列表(例如:Frame 10, Frame 15 有資料)。
2. **插值計算**:
* 當使用者停在 **Frame 12** 時,系統無直接資料。
* 前端應找出前後最近的有資料幀(Frame 10 與 Frame 15)。
* 根據時間差比例,動態計算出 Frame 12 的座標 `x, y, w, h`。
#### 實作範例 (JavaScript/TypeScript)
```typescript
// 假設 API 回傳該 Face 的軌跡點
const detections = [
{ frame: 10, bbox: { x: 100, y: 100, w: 50, h: 60 } },
{ frame: 15, bbox: { x: 110, y: 105, w: 50, h: 60 } },
];
// 計算 Frame 12 的預測框選
function getInterpolatedBBox(frameIndex: number, detections) {
// 找到前一幀與後一幀
const prev = detections.find(d => d.frame <= frameIndex); // Frame 10
const next = detections.find(d => d.frame > frameIndex); // Frame 15
if (!prev) return null; // 還沒開始出現
if (!next) return prev.bbox; // 結束了,維持最後位置
// 計算比例 (0.0 - 1.0)
const ratio = (frameIndex - prev.frame) / (next.frame - prev.frame);
return {
x: prev.bbox.x + (next.bbox.x - prev.bbox.x) * ratio,
y: prev.bbox.y + (next.bbox.y - prev.bbox.y) * ratio,
// w, h 亦可依此邏輯進行縮放插值
w: prev.bbox.w,
h: prev.bbox.h,
};
}
```
---
### 2.5 Face 綁定錯誤修正 (Face Binding Error Correction)
此流程適用於移除錯誤綁定的臉部資料,使其恢復為未綁定狀態。
1. **選定 Face**: 確認需要解除綁定的臉部 `face_id` 以及所屬的 `file_uuid`。
2. **解除綁定 (Unbind)**:
```bash
curl -X POST "http://localhost:3003/api/v1/identities/unbind" \
-H "X-API-Key: <your_api_key>" \
-H "Content-Type: application/json" \
-d '{
"binding_type": "face",
"binding_value": "<selected_face_id>"
}'
```
---
## 3. n8n 整合範例
### 3.1 HTTP Request 設定
| 欄位 | 值 |
|---|---|
| Method | `GET` 或 `POST` |
| URL | `http://localhost:3003/api/v1/files` (Dev) 或 `https://<your-domain>` (Prod) |
| Header `X-API-Key` | `<your_api_key>` |
### 3.2 列出檔案 Workflow (JSON)
使用 `GET /api/v1/files/scan` 作為入口。
```json
{
"nodes": [
{
"name": "Get Files",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"method": "GET",
"url": "http://localhost:3003/api/v1/files/scan",
"sendHeaders": true,
"headerParameters": {
"parameters": [{ "name": "X-API-Key", "value": "{{ $env.API_KEY }}" }]
},
"options": { "qs": { "page": 1, "page_size": 10 } }
},
"position": [450, 300]
},
{
"name": "Extract List",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "return $input.first().json.data.map(f => ({\n json: {\n uuid: f.file_uuid,\n name: f.file_name,\n status: f.status\n }\n}));"
},
"position": [650, 300]
}
]
}
```
---
## 4. WordPress / PHP 整合範例
### 4.1 PHP Client Library (V1.0.0 相容)
```php
<?php
class Momentry_API {
private const API_URL = 'http://localhost:3003'; // Dev environment
private const API_KEY = '<your_api_key>';
private function request(string $endpoint, array $data = [], string $method = 'GET'): array {
$url = self::API_URL . $endpoint;
$args = [
'headers' => [
'X-API-Key' => self::API_KEY,
'Content-Type' => 'application/json',
],
'timeout' => 30,
];
if ($method === 'POST') {
$args['method'] = 'POST';
$args['body'] = json_encode($data);
}
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) {
throw new Exception($response->get_error_message());
}
return json_decode(wp_remote_retrieve_body($response), true);
}
// 掃描檔案
public function scan_files(): array {
return $this->request('/api/v1/files/scan');
}
// 列出檔案
public function list_files(): array {
return $this->request('/api/v1/files');
}
// 搜尋
public function search(string $query): array {
return $this->request('/api/v1/search', ['query' => $query], 'POST');
}
}
?>
```
---
## 5. 疑難排解
| 錯誤 | 原因 | 解決方案 |
|------|------|----------|
| `401 Unauthorized` | API Key 無效 | 檢查 Key 格式與權限 |
| `404 Not Found` | 端點不存在 | 確認是否使用了舊版 `/api/v1/videos`,應改為 `/api/v1/files` |
| `400 Bad Request on Process` | 缺少 Probe 資料 | 先執行 `GET /api/v1/files/:file_uuid/probe` |
| `500 Error` | 伺服器錯誤 | 檢查資料庫連線與 Schema 版本 |
---
## 6. 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-01 | 初始版本 | OpenCode | deepseek-chat |
| V1.1 | 2026-05-01 | 修正 port 為 Dev(3003),更新 API 路徑與掃描入口 | OpenCode | deepseek-chat |
---
## 7. 附錄:UUID 格式說明
V1.0.0 使用 **32 碼 SHA256** 作為 `file_uuid`。
```
/Users/.../demo/video.mp4
↓
SHA256 Hash (前 32 字元)
↓
53e3a229bf68878b7a799e811e097f9c
```
@@ -1,148 +0,0 @@
---
document_type: "experiment_report"
service: "MOMENTRY_CORE"
title: "兒童偵測與年齡估算模型選型報告"
date: "2026-05-06"
version: "V1.0"
status: "completed"
owner: "Warren"
created_by: "OpenCode"
---
# 兒童偵測與年齡估算模型選型報告
## 1. 實驗目標
在 Momentry Core 的 Face Trace 資料中,尋找「非主要演員中的兒童角色」並評估三種年齡估算方案的可行性:
1. **DeepFace AgeNet** — 深度學習年齡估算(MIT License)
2. **Apple Vision 頭肩比** — 用頭寬/肩寬比例推測年齡(系統內建)
3. **MiVOLO** — HuggingFace 年齡模型(Apache 2.0)
## 2. 實驗環境
| 項目 | 內容 |
|------|------|
| 測試影片 | Charade (1963), 113 min, 24fps |
| Face detections | 6182 faces, 2347 traces |
| Face 偵測 | Apple Vision `VNDetectFaceRectanglesRequest` (swift_face) |
| Face 嵌入 | CoreML FaceNet512 |
| 取樣間隔 | 60 幀 (2.5 秒) |
| 體態偵測 | Apple Vision `VNDetectHumanBodyPoseRequest` |
## 3. 實驗方法
### 3.1 主要角色年齡估算
從 2347 個 trace 中挑選 face_count ≥ 5 的 12 個主要 trace,提取中間幀進行 DeepFace 年齡估算 + Apple Vision 頭肩比計算。
### 3.2 非主要角色搜尋
搜尋小臉(< 60px)、低 face_count(≤ 2)的 trace,找出群眾演員(可能包含兒童)。
### 3.3 滑雪場水槍場景
Charade 開場 Megève 滑雪場有一名男孩用水槍噴灑女主角的場景。對此場景進行密集幀掃描(30 幀間隔)搜尋兒童臉。
## 4. 模型選型結果
### 4.1 模型可用性
| 方案 | 可用 | 速度/face | License | 結論 |
|------|------|----------|---------|------|
| **DeepFace AgeNet** | ✓ | 0.2s(快取後) | MIT | **推薦** |
| Apple Vision 年齡 | ✗ | — | 系統內建 | Vision 無年齡 API |
| Apple Vision 頭肩比 | ✓ | 即時 | 系統內建 | 僅成人/兒童分類 |
| MiVOLO | ✗ | — | Apache 2.0 | 模型不可用(HuggingFace 不存在) |
### 4.2 DeepFace 年齡估算(12 主要角色取樣)
| Trace | Faces | 出現時間 | 臉寬 | DeepFace 年齡 | 性別 | 情緒 |
|-------|-------|----------|------|-------------|------|------|
| 0 | 45 | 35s | 160px | 35 | Man | sad |
| 24 | 6 | 708s | 100px | 34 | Man | neutral |
| 26 | 5 | 728s | 100px | 31 | Woman | neutral |
| 39 | 14 | 760s | 120px | 30 | Man | sad |
| 43 | 12 | 765s | 120px | 25 | Man | sad |
| 45 | 8 | 775s | — | 36 | Woman | neutral |
| 46 | 9 | 795s | — | 29 | Woman | neutral |
| 48 | 6 | 818s | 140px | 50 | Man | angry |
| 76 | 13 | 908s | — | 29 | Man | sad |
| 87 | 5 | 972s | — | 35 | Man | sad |
| 103 | 7 | 1022s | — | 35 | Woman | neutral |
| 132 | 5 | 1158s | — | 27 | Man | surprise |
**年齡範圍:25–50 歲,全成人。**
### 4.3 Apple Vision 頭肩比
| Frame | 臉寬 | 肩寬 | 頭肩比 | DeepFace 年齡 | 場景 |
|-------|------|------|--------|-------------|------|
| 840 | 160px | 407px | **0.39** | 35 | 滑雪場(主角) |
| 17460 | 100px | 354px | **0.28** | 31 | 中段場景 |
| 18360 | 120px | 306px | **0.39** | 25 | 中段場景 |
| 19620 | 140px | 425px | **0.33** | 50 | 最年長角色 |
| 27780 | 110px | 381px | **0.29** | 27 | 後段場景 |
**頭肩比範圍:0.28–0.39(全成人範圍)。兒童預期 > 0.6。**
### 4.4 非主要演員(群眾)
| Trace | Faces | 臉寬 | DeepFace 年齡 | 性別 | 頭肩比 | 場景 |
|-------|-------|------|-------------|------|--------|------|
| 129 | 1 | 42px | 37 | Man | 0.13 | 遠景群眾 |
| 172 | 2 | 51px | 31 | Man | 0.22 | 遠景群眾 |
| 304 | 2 | 47px | 41 | Man | 0.14 | 遠景群眾 |
| 57 | 1 | 52px | 35 | Woman | — | 遠景群眾 |
| 322 | 1 | 52px | 34 | Man | 0.18 | 遠景群眾 |
**全成人。遠景群眾頭肩比更低 (0.13–0.22),因相機距離影響 > 體型差異。**
## 5. 水槍場景搜尋結果
**成功找到小孩,但無法可靠估算年齡。**
| 參數 | 數值 |
|------|------|
| 影片 | Charade (1963) |
| 場景 | Megève 滑雪場戶外餐廳 |
| 時間 | Frame 2450 (102 秒 / 1:42) |
| 臉部尺寸 | **29 × 29 px** |
| Swift Face 偵測 | ✓ 已偵測(trace_id 未分配,單幀) |
| DeepFace 年齡 | 33 Man ❌ **誤判**(解析度不足) |
| Apple Vision 頭肩比 | 無法計算(身體被遮擋) |
### 誤判原因
29×29px 遠低於年齡估算模型的最低解析度需求(一般需 ≥ 50×50px)。在遠景中,兒童的臉太小,神經網路無法提取足夠的年齡特徵,導致:
- DeepFace 將兒童誤判為成人
- 頭肩比受距離影響大於實際年齡
## 6. 結論與建議
| 發現 | 說明 |
|------|------|
| Charade 無兒童主要角色 | 全卡司成人,DeepFace 年齡範圍 25–50 |
| 水槍小孩已找到 | Frame 2450,102 秒,但 29px 太小無法估齡 |
| DeepFace 可行 | MIT license,0.2s/face,適合 ≥ 50px 臉部 |
| Apple Vision 頭肩比 | 僅適合作近景成人/兒童分類(非精確年齡) |
| MiVOLO | 不可用(HuggingFace 模型不存在) |
### 建議
1. **整合 DeepFace** 年齡估算入 `face_processor.py` pipeline,對 ≥ 50px 的臉進行年齡標記
2. **保留頭肩比** 做為輔助驗證(成人/兒童二元分類)
3. **降低取樣間隔** 從 60 幀降至 10–15 幀以捕捉更多短暫出現的角色
4. **若需測試兒童年齡**:使用片庫中的 `Alice Comedies (1926)`,該片有近景小女孩(Virginia Davis,6–8 歲),臉部可達 150px+
---
## 附錄:測試資料
| 檔案 | 路徑 |
|------|------|
| DeepFace 年齡 JSON | `output_dev/experiments/age_benchmark/age_benchmark_report.json` |
| 頭肩比 JSON | `output_dev/experiments/head_shoulder/head_shoulder_report.json` |
| 水槍場景幀 | `output_dev/experiments/head_shoulder/child_f2450.jpg` |
| 年齡基準腳本 | `scripts/age_benchmark.py` |
| 頭肩比腳本 | `scripts/head_shoulder_quick.py` |
| Face trace 排序 API | `POST /api/v1/file/:file_uuid/face_trace/sortby` |
@@ -1,298 +0,0 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "Story Parent-Child Chunk Rules V1.0"
date: "2026-05-05"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "chunk"
- "story"
- "parent-child"
- "v1.0"
ai_query_hints:
- "Story parent-child chunk generation rules"
- "CUT scene → parent chunk, ASR sentence → child chunk"
- "boundary overlap: partial match enriches child context"
- "parent_summary template + child_summary template"
- "children per parent distribution"
related_documents:
- "../CHUNK_DEFINITION_V1.0.0.md"
- "../DUAL_EMBEDDING_PIPELINE_V1.0.0.md"
- "../PROCESSORS/ASR_V1.0.0.md"
- "../PROCESSORS/CUT_V1.0.0.md"
---
# Story Parent-Child Chunk Rules V1.0
## 核心概念
- **Parent chunk** = CUT 場景邊界內的所有對話 → 一個場景敘述
- **Child chunk** = 單一 ASR sentence → 一句對白
- **Boundary overlap** = 場景邊界重疊的句子 → 同時歸屬前後 parent
## 匹配規則
### Rule 1: Fully-Contained Matching
```
ASR sentence 完全在 CUT 場景時間範圍內
→ seg.start >= scene.start_time AND seg.end <= scene.end_time
→ 加入該 scene 的 children 列表
```
### Rule 2: Boundary Overlap (所有 parent)
```
對於每個 parent chunk(即使只有 1 child):
→ 找出與 scene 時間範圍有 partial overlap 的 ASR sentence
→ seg.start < scene.end_time AND seg.end > scene.start_time
→ AND 未被 Rule 1 匹配(不是 fully-contained)
→ 加入該 scene 的 children 列表
```
邊界 overlap 讓 child chunk 可以同時歸屬前後兩個 parent,提供更多上下文。
### Rule 3: Scene Filter
```
CUT scene duration < 1s → 跳過(場景太短無意義)
```
## Parent Summary 模板
```
[{start}s-{end}s, {duration}s]
Cast: {character_list}
Total dialogue: N lines, W words
Speakers: {name} (N lines): "sample text..."
```
## Child Summary 模板
```
[{start}s-{end}s] {speaker_name}: "{asr_text}"
```
### Embedding Target
Child summary text → Ollama nomic-embed-text-v2-moe → 768D vector → pgvector
## 數據實例:Charade (1963) — 長片 113min
### 輸入
| 來源 | 數量 | 說明 |
|------|------|------|
| ASR segments | **1,629** | Whisper small 英文字幕 |
| ASR with text | 1,629 | 全部有文字 |
| ASR total duration | 6,760s (113 min) | |
| CUT scenes | **1,331** | PySceneDetect 場景切割 |
| CUT scenes ≥ 1s | 1,200 | 過濾後有效場景 |
| CUT mean duration | 5.2s | 平均場景長度 |
| CUT scene gap (unmatched) | 131 | < 1s 場景被過濾 |
### 輸出 (V2.1 — boundary overlap for ALL scenes, duration filter removed)
| 指標 | 數值 |
|------|------|
| **Parent chunks** | **1,313** (all CUT scenes ≥ 0s) |
| **Child chunks** (total in DB) | **2,927** (1,629 unique + 1,298 overlaps) |
| **Unique children** | **1,629** (100% ASR coverage) |
| DB duplicates (shared) | 1,298 (ON CONFLICT merge) |
| Children per parent | 1 ~ 43, avg **2.2** |
| Unmatched | **0** |
### 分佈
```
Children per parent:
1: 128 parents (獨白/短場景)
2: 58 parents
3: 0 parents ← 邊界 overlap 後 3 被 2/4 吸收
4-9: 64 parents (中等對話場景)
10-27: 50 parents (多人對話場景)
```
### 已匹配率
| 指標 | 數值 |
|------|------|
| ASR unmatched | **0** (V2.1: boundary overlap for ALL scenes) |
| 已匹配率 | **100%** |
## 輸入/輸出範例
### Big Parent(多子女)
**輸入原始數據**:
```
CUT scene [2783s-2847s, 65s]
27 ASR sentences, all spoken by Audrey Hepburn + Cary Grant + SPEAKER_2
```
**輸出 Parent Summary**:
```
[2783s-2847s, 65s] Cast: Audrey Hepburn, Cary Grant, SPEAKER_2.
Total dialogue: 27 lines, 143 words.
```
**輸出 Child Summaries**(embedding target):
```
[2784s-2786s] Audrey Hepburn: "they stole it"
[2786s-2788s] Audrey Hepburn: "by burying it"
[2788s-2790s] Audrey Hepburn: "then reporting the Germans had captured it"
... (27 total)
```
**Metadata 信度**(隨 parent/child 傳遞):
```json
// Parent metadata
{
"speaker_confidence": { "Audrey Hepburn": 0.85, "Cary Grant": 0.64 },
"face_confidence": { "Audrey Hepburn": 0.60, "Cary Grant": 0.64 },
"yolo_objects": { "car": 0.72, "bottle": 0.55, "chair": 0.68 }
}
// Child metadata
{
"speaker_name": "Audrey Hepburn",
"speaker_confidence": 0.85, // MAR lip: 57% events during SPEAKER_1
"face_confidence": 0.60, // clustering composite
"asr_confidence": 0.92 // Whisper confidence
}
```
### 1:1 Parent(單子女)
**輸入原始數據**:
```
CUT scene [304s-318s, 14s]
1 ASR sentence, spoken by Cary Grant alone
```
**輸出 Parent Summary**:
```
[304s-318s, 14s] Cast: Cary Grant.
Total dialogue: 1 lines, 13 words.
```
**輸出 Child Summary**(embedding target):
```
[309s-317s] Cary Grant: "Sylvia I'm getting a divorce what from Charles he's the only husband I"
```
## 與 LLM Pipeline 的關係
```
Pipeline 1 (Story): template summary → DB + embedding
Pipeline 2 (LLM): LLM summary → DB + embedding (future)
chunk_type:
story_parent / story_child ← Pipeline 1
llm_parent / llm_child ← Pipeline 2 (future)
```
## 版本歷史
| 版本 | 日期 | 變更 |
|------|------|------|
| V1.0 | 2026-05-05 | 初始規則:fully-contained + boundary overlap |
| V2.1 | 2026-05-05 | 移除 duration filter,boundary overlap 對所有場景(含空場景)。100% ASR coverage。Speaker mapping 從 DB 動態讀取。 |
## Charade 1963 統計分析記錄
### 影片資料
| 指標 | 值 |
|------|-----|
| 片長 | 113 分鐘 |
| 總幀數 | 412,343 |
| FPS | 59.94 |
| 解析度 | 1920×1080 |
### 處理器產出
| Processor | 輸出行數 | 說明 |
|-----------|---------|------|
| CUT | 1,331 scenes | 平均 5.2s/scene,min 0.2s,max 64.5s |
| ASR | 1,629 segments | Whisper small,113 min total |
| ASRX | 10 speakers | SPEAKER_0/1 為主要角色 |
| Face | 4,008 frames, 6,182 faces | sample=60, Vision+CoreML ANE |
| Face Trace | 6,182 detections, 2,347 traces | IoU+embedding tracking |
| Identity | 677 traces → 7 identities | 99.4% coverage, MAR lip speaker binding |
| YOLO | 328,800 frames, 57 object classes | CoreML ANE |
### Matching 迭代記錄
#### Iteration 1: Fully-contained only, >= 1s scene filter
```
Rule: seg.start >= scene.start AND seg.end <= scene.end
Scene filter: duration >= 1s (131 scenes filtered out)
Result: 990/1629 (61%) matched
454 unmatched, 74 in filtered scenes
Only scenes with children got boundary overlaps
```
#### Iteration 2: Add boundary overlap for scenes with >= 3 children
```
Rule: For scenes with >= 3 children, add partial overlaps
Result: 1,210 children (+220 partial)
Still 454 unmatched (boundary overlap only for rich scenes)
```
#### Iteration 3: Remove duration filter
```
Rule: Remove >=1s scene filter
Result: 1,496 unique children (92% coverage)
133 unmatched
Root cause: boundary overlap still gated by "if children:"
```
#### Iteration 4: Boundary overlap for ALL scenes (regardless of children)
```
Rule: Move boundary overlap code outside "if children:" guard
All 1,331 scenes participate
Result: 1,629 unique children (100% coverage)
1,313 parents (all scenes)
2,927 total children (1,629 unique + 1,298 overlaps)
```
### 關鍵決策
| 決策 | 理由 | 影響 |
|------|------|------|
| 移除 duration filter | 131 scenes <1s 會漏掉句子 | +24 parents, +321 children |
| 移除 children guard | 空場景也要加 boundary children | +133 children (100%) |
| 用 overlap 而非 fully-contained | ASR/CUT 時間邊界不對齊 | 避免 565 sentences orphan |
| Partial overlaps 存兩次 | 邊界句可歸屬兩個 parent | 1,298 duplicates via ON CONFLICT |
| Speaker map 從 DB 讀 | 不再 hardcode 演員名 | 通用化任何影片 |
### 效能指標
| 指標 | 值 |
|------|-----|
| Story 生成時間 | < 1s (template, instant) |
| Embedding 時間 (Ollama) | ~2 min for 1,629 chunks |
| Qdrant sync 時間 | ~3 min for rule1, ~1 min for story |
| BM25 search 時間 | < 10ms per query |
### 教學要點
1. **時間邊界不對齊是常態**:ASR(語音邊界)與 CUT(視覺邊界)用不同演算法,永遠不會完美對齊。overlap matching 是必要設計。
2. **Boundary overlap 需對所有場景生效**:不能只限有 children 的場景,否則會產生 orphan sentences。
3. **ON CONFLICT merge**:同一 sentence 出現在兩個 parent 時,DB 層面用最後一個 parent。如需多對多關係,需 junction table。
4. **Hardcoded 到 Dynamic**:speaker map 從 hardcode → DB-driven 是通用化的關鍵一步。
@@ -1,192 +0,0 @@
---
document_type: "design"
service: "MOMENTRY_CORE"
title: "Class 分類系統設計 V1.0"
date: "2026-05-05"
version: "V1.0"
status: "design"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "class"
- "taxonomy"
- "design"
- "v1.0"
ai_query_hints:
- "Class 分層分類系統設計"
- "參照 IPC (國際專利分類) 及 HS (海關稅則)"
- "編碼格式: {section}-{NNNN}"
- "用於 identity 多層分類、快速定位"
related_documents:
- "../DATA_SCHEMA_FILE_IDENTITY_V1.0.0.md"
- "../UUID_ENCODING_RULES_V1.0.0.md"
---
# Class 分類系統設計 V1.0
> 狀態:設計階段,尚未實施
## 設計參考
IPC(國際專利分類)與 HS(海關稅則)。
共通原則:**層級碼**、**數字越長越精細**、**全球通用**、**可無限擴展**。
## 設計目標
- IPC/HS 式的 hierarchical code → **快速定位**
- Tag 式的 multi-label 使用 → **靈活分類**
- 同一 entity 可擁有多條 class path
- 新增分類只需 INSERT,無 migration
```
Cary Grant
→ P-0201 (演員/主角)
→ T-0102 (1960s)
→ S-0200 (場景/戶外 — 他在片中出現的場景)
Ferrari 250 GT
→ O-0101 (汽車)
→ B-0300 (汽車品牌/Ferrari)
→ T-0102 (1960s)
## 編碼格式
```
{section}-{NNNN}
│ └── 4 digits,每 2 digits 一層
└───────── 1 char section prefix
```
| 層級 | 範例 | 意義 |
|------|------|------|
| `P-0000` | top section | 人物 |
| `P-0200` | subclass | 人物 → 演員 |
| `P-0201` | group | 人物 → 演員 → 主角 |
| `P-0202` | group | 人物 → 演員 → 配角 |
層級判斷:`code.length`。`P-` = section,`P-02` = subclass,`P-0201` = group。
### Section 定義
| Section | 名稱 | 範疇 | 預留 |
|---------|------|------|------|
| `P` | 人物 | 演員、導演、公眾人物、虛構角色、運動員... | 01-99 |
| `O` | 物件 | 交通工具、家具、武器、工具、電子產品... | 01-99 |
| `B` | 品牌/組織 | 時尚、科技、汽車品牌、政府機構、NGO... | 01-99 |
| `C` | 概念/抽象 | 情感、思想、事件、主題、風格... | 01-99 |
| `A` | 生物 | 動物、植物、真菌... | 01-99 |
| `S` | 場景/地點 | 室內、戶外、城市、自然地標、建築內部... | 01-99 |
| `E` | 環境/自然 | 天氣、地形、天象、自然災害... | 01-99 |
| `M` | 音樂/聲音 | 樂器、音樂類型、自然聲音、人工聲音... | 01-99 |
| `L` | 語言/文字 | 語言、方言、書寫系統、符號... | 01-99 |
| `T` | 時間/時期 | 年代、季節、節日、歷史時期... | 01-99 |
| `F` | 檔案類型 | 影片格式、文件類型、圖片格式... | 01-99 |
| `D` | 領域/學科 | 科學、藝術、體育、政治、經濟... | 01-99 |
12 個 Section,各 99 subclass × 99 group = ~117K 分類槽位。可隨時新增 Section。
## 初始 Class Tree
```
P-0000 人物
├── P-0100 公眾人物
├── P-0200 演員
│ ├── P-0201 主角
│ └── P-0202 配角
├── P-0300 導演
├── P-0400 虛構角色
└── P-9900 其他人物
O-0000 物件
├── O-0100 交通工具
│ ├── O-0101 汽車
│ ├── O-0102 船
│ └── O-0103 飛機
├── O-0200 建築
├── O-0300 家具
└── O-9900 其他物件
B-0000 品牌
├── B-0100 時尚
├── B-0200 科技
└── B-9900 其他品牌
C-0000 概念
├── C-0100 情感
├── C-0200 思想
└── C-9900 其他概念
```
## Table
```sql
CREATE TABLE classes (
code VARCHAR(8) PRIMARY KEY, -- P-0201
name TEXT NOT NULL, -- 主角
description TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 多對多:同一 identity 可有多個 class code(如 tag 使用)
CREATE TABLE identity_classes (
identity_id INTEGER REFERENCES identities(id),
class_code VARCHAR(8) REFERENCES classes(code),
confidence REAL DEFAULT 1.0,
source VARCHAR(20), -- which agent classified
PRIMARY KEY (identity_id, class_code)
);
```
## Query 範例
```sql
-- 查某 identity 的所有 class
SELECT c.code, c.name
FROM identity_classes ic
JOIN classes c ON ic.class_code = c.code
WHERE ic.identity_id = 8;
-- 查所有屬於 "演員" (P-0200) 的 identity
SELECT i.name
FROM identity_classes ic
JOIN identities i ON ic.identity_id = i.id
WHERE ic.class_code LIKE 'P-02%';
-- 查某 section 下的所有 identity
SELECT DISTINCT i.name
FROM identity_classes ic
JOIN identities i ON ic.identity_id = i.id
WHERE ic.class_code LIKE 'P-%';
```
## 擴展方式
1. 新增 leaf class:`INSERT INTO classes VALUES ('P-0203', '配音員')` — P-02 底下的新 group
2. 新增 subclass:`INSERT INTO classes VALUES ('P-0500', '製作團隊')` — P 底下的新 subclass
3. 新增 section:`INSERT INTO classes VALUES ('X-0000', '新分類')` — 全新 top-level
無需 migration,insert 即可。
## 版本歷史
| 版本 | 日期 | 狀態 |
|------|------|------|
| V1.0 | 2026-05-05 | 設計階段 |
## Future: Class-Based Search
實施 class 系統後,search API 可加入 class filter 提升命中率:
```
GET /api/v1/search?q=car&class=O-0101
→ 只搜被分類為「汽車」的內容,過濾 "care", "car accident", "car wash"
GET /api/v1/search/hybrid?q=divorce&class=P-0200
→ 只搜演員說出的 "divorce",排除旁白、字幕
GET /api/v1/search/universal?class=T-0102
→ 搜所有 1960s 相關內容
```
@@ -1,328 +0,0 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "Data Schema: File & Identity V1.0"
date: "2026-05-05"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "schema"
- "file"
- "identity"
- "v1.0"
ai_query_hints:
- "File & Identity DB schema"
- "face_detections.identity_id direct FK"
- "identity multi-modal: face + voice + TMDb + manual"
related_documents:
- "../DUAL_EMBEDDING_PIPELINE_V1.0.0.md"
- "../UUID_ENCODING_RULES_V1.0.0.md"
---
# Data Schema: File & Identity V1.0
## 1. File Schema
### videos / files
| Column | Type | 說明 |
|--------|------|------|
| `id` | SERIAL PK | |
| `file_uuid` | VARCHAR(32) | Birth UUID |
| `file_path` | VARCHAR(512) | 檔案完整路徑 |
| `file_name` | VARCHAR(256) | |
| `probe_json` | JSONB | ffprobe raw output |
| `status` | VARCHAR(20) | ready / processing / completed |
| `processing_status` | JSONB | per-processor progress |
| `total_frames` | INTEGER | |
| `fps` | DOUBLE | |
| `duration` | DOUBLE | 影片長度(秒) |
| `width` / `height` | INTEGER | 解析度 |
| `registration_time` | TIMESTAMP | 註冊時間 |
### face_detections (per-file face data)
| Column | Type | 說明 |
|--------|------|------|
| `id` | SERIAL PK | |
| `file_uuid` | VARCHAR(32) | → videos.file_uuid |
| `frame_number` | BIGINT | 幀號 |
| `face_id` | VARCHAR(64) | per-file face identifier |
| `trace_id` | INTEGER | 跨幀追蹤 ID |
| `x, y, width, height` | INTEGER | bbox |
| `confidence` | REAL | 偵測信度 |
| `embedding` | REAL[] | 512D CoreML FaceNet |
| `identity_id` | INTEGER | → identities.id (V4.0 direct FK) |
### chunks (per-file parent/child chunks)
| Column | Type | 說明 |
|--------|------|------|
| `id` | SERIAL PK | |
| `chunk_id` / `old_chunk_id` | VARCHAR | chunk identifier |
| `file_uuid` | VARCHAR(32) | → videos.file_uuid |
| `chunk_type` | VARCHAR(32) | story_parent / story_child / rule1_sentence |
| `chunk_index` | INTEGER | per-file ordering |
| `start_time` / `end_time` | DOUBLE | time range |
| `content` | JSONB | metadata |
| `text_content` | TEXT | summary text → embedding target |
| `embedding` | VECTOR | pgvector 768D |
| `search_vector` | TSVECTOR | BM25 full-text |
| `parent_chunk_id` | VARCHAR | → chunks.chunk_id |
## 2. Identity Schema
### 概念
Identity 是可命名的任何識別標的,不限於人。
| identity_type | 範例 | 識別模型 |
|--------------|------|---------|
| `people` | Cary Grant, Audrey Hepburn | face, voice, name |
| `animal` | 電影中的狗、馬 | face, body, sound |
| `object` | 特定道具、車輛 | yolo, image embedding |
| `plant` | 場景中的特定植物 | image embedding |
| `building` | 艾菲爾鐵塔、特定建築 | image embedding, OCR |
| `place` | Paris, 咖啡廳 | scene classification |
| `concept` | "離婚", "復仇" | text embedding |
| `brand` | Coca-Cola | OCR, logo detection |
每種 identity_type 可以使用不同的識別模型組合。
### 識別模型
| model | dimension | source | 適用 identity_type |
|-------|-----------|--------|-------------------|
| `face` | 512D | CoreML FaceNet | people, animal |
| `voice` | 192D | SpeechBrain ECAPA-TDNN | people |
| `text` | 768D | Ollama nomic-embed | concept, place |
| `image` | 768D | — (future) | object, building, plant |
| `yolo_class` | — | YOLO label | object |
### Table
```sql
CREATE TABLE identities (
id SERIAL PRIMARY KEY,
uuid UUID, -- 32-char UUIDv5 (source:external_id)
name TEXT NOT NULL UNIQUE,
identity_type VARCHAR(30) DEFAULT 'people', -- people/animal/object/building/place/concept
source VARCHAR(20) DEFAULT 'manual', -- tmdb/manual/face_cluster/yolo
status VARCHAR(20) DEFAULT 'pending',
-- Reference vectors per model (in JSONB for extensibility)
reference_vectors JSONB DEFAULT '{}',
-- {
-- "face": [{"vec":[...], "pose":"frontal", "source":"video_trace"}],
-- "voice": [{"vec":[...], "speaker_id":"SPEAKER_0"}],
-- "image": [{"vec":[...], "source":"manual"}]
-- }
-- Legacy columns (migrating to reference_vectors)
face_embedding VECTOR(512),
voice_embedding VECTOR(192),
identity_embedding VECTOR(768),
reference_data JSONB DEFAULT '{}',
metadata JSONB DEFAULT '{}',
tmdb_id INTEGER,
tmdb_profile TEXT,
created_at TIMESTAMP DEFAULT now()
);
```
### 彈性設計
現有 `face_embedding` / `voice_embedding` column 維持向下相容。
未來全部移入 `reference_vectors` JSONB,支援任意 model × 多個 reference vectors:
```json
{
"reference_vectors": {
"face": [
{"vec": [0.1, 0.2, ...], "pose": "frontal", "source": "video_trace_0", "confidence": 0.95},
{"vec": [0.3, 0.4, ...], "pose": "profile", "source": "video_trace_0", "confidence": 0.88}
],
"voice": [
{"vec": [0.5, 0.6, ...], "speaker_id": "SPEAKER_0", "source": "asrx"}
],
"image": []
}
}
```
### 識別 Agent 架構
每個識別模型由對應的 Agent 負責。Identity 本身只存 reference vectors,不綁定特定 model。
```
┌─────────────────────────┐
│ identities │
│ name, type, source │
│ reference_vectors (JSONB)│
└──────────┬──────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│FaceAgent│ │VoiceAgent│ │ImageAgent│
│ │ │ │ │ (future) │
│ input: │ │ input: │ │ input: │
│ face_ │ │ asrx │ │ image │
│ detect │ │ segments│ │ features│
│ ions │ │ │ │ │
│ │ │ │ │ │
│ output: │ │ output: │ │ output: │
│ face → │ │ voice → │ │ img → │
│ identity│ │ identity│ │ identity│
└─────────┘ └─────────┘ └─────────┘
```
### Agent 定義
| Agent | 輸入 | 模型 | 輸出 | 狀態 |
|-------|------|------|------|------|
| **FaceAgent** | `face_detections` | CoreML FaceNet 512D | `identity_id` on face_detections | ✅ |
| **VoiceAgent** | ASRX segments | ECAPA-TDNN 192D + MAR lip | `metadata.speaker_id` | ✅ |
| **ImageAgent** | — | — | — | ⬜ future |
| **YoloAgent** | YOLO detections | — | object → identity | ⬜ future |
| **TextAgent** | chunk text | nomic-embed 768D | concept → identity | ⬜ future |
### Agent 運作模式
```
1. Agent 讀取 raw detections(face / voice / yolo)
2. 對比 identities.reference_vectors[model]
3. 相似度達標 → bind to existing identity
4. 不達標 → create new identity
5. 更新 identities.reference_vectors(enrich reference set)
```
同一個 identity 可以被多個 Agent 同時更新。例如:
- FaceAgent 寫入 `reference_vectors.face`
- VoiceAgent 寫入 `reference_vectors.voice`
- 兩者指向同一個 identity (Cary Grant)
### Face → Identity 綁定(V4.0)
```
face_detections.identity_id ──── FK ────→ identities.id
```
Direct FK。不需要 intermediate table。操作 API:
```
POST /api/v1/identities/bind
{ "file_uuid": "...", "face_id": "face_1", "identity_uuid": "..." }
→ UPDATE face_detections SET identity_id = X
POST /api/v1/identities/unbind
{ "file_uuid": "...", "face_id": "face_1" }
→ UPDATE face_detections SET identity_id = NULL
```
### Voice/Speaker → Identity 綁定
透過 `identities.metadata.speaker_id`:
```
identities.metadata = {"speaker_id": "SPEAKER_0", "speaker_confidence": 0.85}
```
Voice embedding 直接寫入 `identities.voice_embedding`。
## 3. File-Identity 關聯
```
file (1a04db97...) identity (Cary Grant)
│ │
├── face_detections │
│ ├── face_id="face_1" │
│ │ identity_id ──────────────────┤
│ ├── face_id="face_2" │
│ │ identity_id ──────────────────┤
│ └── face_id="face_3" │
│ identity_id = NULL │ ← unbounded
│ │
├── chunks │
│ ├── story_parent │
│ │ content.metadata.characters │
│ │ = ["Cary Grant", ...] │
│ └── story_child │
│ content.metadata.speaker │
│ = "Cary Grant" │
│ │
└── asrx.json │
└── segments[].speaker_id │
= "SPEAKER_0" ────────────────┘
file_identities (N:N junction, if needed)
file_uuid → identity_uuid
```
## 4. Class 分層分類(參照 IPC + HS)
### 設計參考
IPC(國際專利分類)與 HS(海關稅則)的分層編碼體系。
| 標準 | 結構 |
|------|------|
| **IPC** | Section(A-H) → Class(2digits) → Subclass → Group/NNN |
| **HS** | Section → Chapter(2digits) → Heading(4digits) → Subheading(6digits) |
共通原則:**層級碼**、**數字越長越精細**、**全球通用**。
### 編碼格式
```
{SECTION}-{NNN}-{NNN}-{NNN}
│ │ │ └─ subgroup
│ │ └──────── main_group
│ └─────────────── subclass
└─────────────────────── section
```
| Section | 涵蓋 |
|---------|------|
| `P` | People |
| `O` | Object |
| `B` | Brand |
| `C` | Concept |
| `A` | Animal |
| `S` | Scene |
| `E` | Environment |
| `M` | Music/Sound |
### Table
```sql
CREATE TABLE classes (
code VARCHAR(20) PRIMARY KEY, -- P-001-010/010
name TEXT NOT NULL,
parent_code VARCHAR(20) REFERENCES classes(code),
section CHAR(1),
level INTEGER DEFAULT 0,
description TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE identity_classes (
identity_id INTEGER REFERENCES identities(id),
class_code VARCHAR(20) REFERENCES classes(code),
confidence REAL DEFAULT 1.0,
source VARCHAR(20),
PRIMARY KEY (identity_id, class_code)
);
```
## 版本歷史
| 版本 | 日期 | 變更 |
|------|------|------|
| V1.0 | 2026-05-05 | File & Identity schema,V4.0 direct FK binding |
| V1.1 | 2026-05-05 | Class 分層分類(IPC/HS),Agent 識別架構 |
@@ -1,216 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core Dev API 參考文件"
date: "2026-05-06"
version: "V1.1"
status: "deprecated"
owner: "Warren"
---
> ⚠️ **此文件為 V3.x 歷史參考,含已移除的路由。**
> 請改用 `API_DICTIONARY_V1.0.0.md`(root)取得當前準確的 53 條 API 路由。
created_by: "OpenCode"
tags:
- "api"
- "reference"
- "dev"
- "v1.1"
- "restful"
related_documents:
- "MOMENTRY_CORE_API_V1.0.0.md"
- "RELEASE/RELEASE_API_REFERENCE_v1.0.0.md"
---
# Momentry Core Dev API 參考文件
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-06 |
| 文件版本 | V1.1 |
| Base URL | `http://localhost:3003` |
| 認證方式 | Header `X-API-Key`(部分端點需要) |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.1 | 2026-05-06 | 從程式碼實際路由重新產生 53 端點清單 | OpenCode |
| V1.0 | 2026-04-30 | 原始文件,含多個不存在之端點 | OpenCode |
---
## 認證
- **Header**: `X-API-Key: <your_api_key>`
- 目前 `/api/v1/auth/login` 回傳固定 demo Key: `muser_test_001`
- Protected routes 透過 `api_key_validation` middleware 驗證
- Public routes(免 Key): `/health`, `/health/detailed`, `/api/v1/auth/login`
---
## 端點列表
總計 **53 個註冊路由**(另有 1 個定義但未掛載)。
### 1. 系統與認證(System & Auth)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 1 | GET | `/health` | 基本健康檢查(回傳 status/version/uptime) | ❌ |
| 2 | GET | `/health/detailed` | 詳細健康狀態(含 PG/Redis/Qdrant/MongoDB 各別延遲) | ❌ |
| 3 | POST | `/api/v1/auth/login` | 登入(固定 demo/demo,回傳 API Key) | ❌ |
| 4 | POST | `/api/v1/auth/logout` | 登出 | ✅ |
### 2. 檔案管理(File Management)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 5 | GET | `/api/v1/files` | 檔案列表(支援分頁、status、q、uuid 過濾) | ✅ |
| 6 | GET | `/api/v1/file/:file_uuid` | 檔案詳細資訊(含 probe_json、metadata) | ✅ |
| 7 | POST | `/api/v1/files/register` | 從磁碟註冊新檔案(支援 pattern 批次註冊) | ✅ |
| 8 | POST | `/api/v1/unregister` | 取消註冊檔案 | ✅ |
| 9 | GET | `/api/v1/files/scan` | 掃描 SFTPGo demo 目錄中的新檔案 | ✅ |
| 10 | GET | `/api/v1/file/:file_uuid/probe` | 取得/快取 ffprobe 資訊 | ✅ |
| 11 | POST | `/api/v1/file/:file_uuid/process` | 啟動處理 pipeline(建立 monitor job) | ✅ |
| 12 | GET | `/api/v1/file/:file_uuid/chunks` | 列出 pre_chunks | ✅ |
| 13 | GET | `/api/v1/progress/:uuid` | 即時處理進度(來自 Redis PubSub) | ✅ |
| 14 | GET | `/api/v1/jobs` | 任務列表(支援分頁、status 過濾) | ✅ |
### 3. 搜尋(Search)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 15 | POST | `/api/v1/search/visual` | 視覺搜尋 | ✅ |
| 16 | POST | `/api/v1/search/visual/class` | 依物件類別過濾搜尋 | ✅ |
| 17 | POST | `/api/v1/search/visual/density` | 依視覺密度搜尋 | ✅ |
| 18 | POST | `/api/v1/search/visual/stats` | 視覺統計資料 | ✅ |
| 19 | POST | `/api/v1/search/visual/combination` | 視覺組合搜尋(多條件) | ✅ |
| 20 | POST | `/api/v1/search/smart` | 智慧搜尋(語意向量) | ✅ |
| 21 | POST | `/api/v1/search/universal` | 通用搜尋 | ✅ |
| 22 | POST | `/api/v1/search/frames` | 影格搜尋 | ✅ |
### 4. 身份管理(Identity)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 23 | GET | `/api/v1/identities` | 身份列表 | ✅ |
| 24 | POST | `/api/v1/identity` | 建立身份(從 face.json 建立參考向量) | ✅ |
| 25 | GET | `/api/v1/identity/:identity_uuid` | 身份詳細資訊 | ✅ |
| 26 | DELETE | `/api/v1/identity/:identity_uuid` | 刪除身份 | ✅ |
| 27 | GET | `/api/v1/identity/:identity_uuid/files` | 該身份出現的所有檔案 | ✅ |
| 28 | GET | `/api/v1/identity/:identity_uuid/chunks` | 該身份的時間軸片段 | ✅ |
| 29 | POST | `/api/v1/identity/:identity_uuid/bind` | 綁定信號至身份 | ✅ |
| 30 | POST | `/api/v1/identity/:identity_uuid/unbind` | 解除綁定 | ✅ |
| 31 | POST | `/api/v1/identity/:from_uuid/mergeinto` | 合併身份(將 from 合併至目標) | ✅ |
### 5. 臉部(Face)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 32 | GET | `/api/v1/faces/candidates` | 臉部候選列表(未綁定者) | ✅ |
### 6. 媒體串流(Media)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 33 | GET | `/api/v1/file/:file_uuid/video` | 影片串流 | ✅ |
| 34 | GET | `/api/v1/file/:file_uuid/video/bbox` | 含 Bounding Box 的影片串流 | ✅ |
| 35 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | 特定 trace 的影片片段 | ✅ |
| 36 | GET | `/api/v1/file/:file_uuid/thumbnail` | 影片縮圖 | ✅ |
### 7. 檔案身份關聯(File-Identity)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 37 | GET | `/api/v1/file/:file_uuid/identities` | 該檔案的所有關聯身份 | ✅ |
### 8. Agent
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 38 | POST | `/api/v1/agents/translate` | 翻譯 Agent | ✅ |
| 39 | POST | `/api/v1/agents/identity/analyze` | 身份分析 Agent | ✅ |
| 40 | POST | `/api/v1/agents/identity/suggest` | 身份合併建議 | ✅ |
| 41 | GET | `/api/v1/agents/identity/status` | 身份 Agent 狀態 | ✅ |
| 42 | POST | `/api/v1/agents/suggest/clustering` | 聚類建議 | ✅ |
| 43 | POST | `/api/v1/agents/suggest/merge` | 合併建議 | ✅ |
| 44 | POST | `/api/v1/agents/5w1h/analyze` | 5W1H 分析 | ✅ |
| 45 | POST | `/api/v1/agents/5w1h/batch` | 5W1H 批量分析 | ✅ |
| 46 | GET | `/api/v1/agents/5w1h/status` | 5W1H 狀態 | ✅ |
### 9. 資源管理(Resource)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 47 | POST | `/api/v1/resource/register` | 註冊運算資源 | ✅ |
| 48 | POST | `/api/v1/resource/heartbeat` | 資源心跳回報 | ✅ |
| 49 | GET | `/api/v1/resources` | 資源列表 | ✅ |
### 10. 統計與設定(Stats & Config)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 50 | GET | `/api/v1/stats/ingest` | 攝取統計(video/chunk 計數) | ✅ |
| 51 | GET | `/api/v1/stats/sftpgo` | SFTPGo 使用者狀態 | ✅ |
| 52 | GET | `/api/v1/stats/inference` | 推理叢集健康狀態 | ✅ |
| 53 | POST | `/api/v1/config/cache` | 切換快取開關 | ✅ |
| 54 | POST | `/api/v1/config/auto-pipeline` | 註冊後自動處理 | ✅ |
| 55 | POST | `/api/v1/config/watcher-auto-register` | Watcher 自動註冊 | ✅ |
---
## 未掛載的端點(定義了 handler 但未註冊路由)
| Handler | 位置 | 說明 |
|---------|------|------|
| `POST /api/v1/file/:file_uuid/face_trace/sortby` | `trace_agent_api.rs` | 定義了 `trace_agent_routes()` 但從未被 `server.rs` merge |
---
## 程式碼中存在 handler 但未註冊路由的端點
下列 handler 有實作但**沒有對應的 `.route()` 呼叫**,無法透過 HTTP 存取:
- `GET /api/v1/assets/:uuid/status` — `get_asset_status`
- `GET /api/v1/jobs/:job_id` — `get_job`
- `GET /api/v1/rules/:rule/status` — `get_rule_status`
- `GET /api/v1/videos/:uuid/details` — `video_details`
- `DELETE /api/v1/videos/:uuid` — `delete_video`
- `POST /api/v1/search` — `search`(語意搜尋)
- `POST /api/v1/search/hybrid` — `hybrid_search`
- `POST /api/v1/search/bm25` — `search_bm25`
- `GET /api/v1/lookup` — `lookup`
- `POST /api/v1/search/smart` — `search_smart`(server.rs 版,實際註冊的是 search.rs 版)
---
## 與 V1.0 文件的差異
V1.0 文件(`MOMENTRY_CORE_API_V1.0.0.md`)宣稱的端點中有以下**不存在於實際程式碼**:
| 文件宣稱 | 實際狀況 |
|----------|---------|
| `DELETE /api/v1/videos/:uuid` | handler 存在但未註冊路由 |
| `POST /api/v1/search` | handler 存在但未註冊路由 |
| `POST /api/v1/search/hybrid` | handler 存在但未註冊路由 |
| `POST /api/v1/assets/:uuid/process` | 實際是 `POST /api/v1/file/:file_uuid/process` |
| `GET /api/v1/files/:uuid/snapshots` | 不存在 |
| `POST /api/v1/files/:uuid/snapshots/migrate` | 不存在 |
| `GET /api/v1/face/list` | 不存在 |
| `POST /api/v1/face/recognize` | 不存在 |
---
## 路徑命名慣例
| 資源 | 路由格式 | 參數 |
|------|---------|------|
| 檔案 | `/api/v1/file/:file_uuid` | 32 碼 hex string |
| 身份 | `/api/v1/identity/:identity_uuid` | UUID v4 |
| 資源 | `/api/v1/resource/...` | - |
注意路徑使用**單數**(`file`, `identity`),與 RELEASE 文件的 `files`, `identities` 不同。
@@ -1,216 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core Dev API 參考文件"
date: "2026-05-06"
version: "V1.1"
status: "deprecated"
owner: "Warren"
---
> ⚠️ **此文件為 V3.x 歷史參考,含已移除的路由。**
> 請改用 `API_DICTIONARY_V1.0.0.md`(root)取得當前準確的 53 條 API 路由。
created_by: "OpenCode"
tags:
- "api"
- "reference"
- "dev"
- "v1.1"
- "restful"
related_documents:
- "MOMENTRY_CORE_API_V1.0.0.md"
- "RELEASE/RELEASE_API_REFERENCE_v1.0.0.md"
---
# Momentry Core Dev API 參考文件
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-05-06 |
| 文件版本 | V1.1 |
| Base URL | `http://localhost:3003` |
| 認證方式 | Header `X-API-Key`(部分端點需要) |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.1 | 2026-05-06 | 從程式碼實際路由重新產生 53 端點清單 | OpenCode |
| V1.0 | 2026-04-30 | 原始文件,含多個不存在之端點 | OpenCode |
---
## 認證
- **Header**: `X-API-Key: <your_api_key>`
- 目前 `/api/v1/auth/login` 回傳固定 demo Key: `muser_test_001`
- Protected routes 透過 `api_key_validation` middleware 驗證
- Public routes(免 Key): `/health`, `/health/detailed`, `/api/v1/auth/login`
---
## 端點列表
總計 **53 個註冊路由**(另有 1 個定義但未掛載)。
### 1. 系統與認證(System & Auth)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 1 | GET | `/health` | 基本健康檢查(回傳 status/version/uptime) | ❌ |
| 2 | GET | `/health/detailed` | 詳細健康狀態(含 PG/Redis/Qdrant/MongoDB 各別延遲) | ❌ |
| 3 | POST | `/api/v1/auth/login` | 登入(固定 demo/demo,回傳 API Key) | ❌ |
| 4 | POST | `/api/v1/auth/logout` | 登出 | ✅ |
### 2. 檔案管理(File Management)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 5 | GET | `/api/v1/files` | 檔案列表(支援分頁、status、q、uuid 過濾) | ✅ |
| 6 | GET | `/api/v1/file/:file_uuid` | 檔案詳細資訊(含 probe_json、metadata) | ✅ |
| 7 | POST | `/api/v1/files/register` | 從磁碟註冊新檔案(支援 pattern 批次註冊) | ✅ |
| 8 | POST | `/api/v1/unregister` | 取消註冊檔案 | ✅ |
| 9 | GET | `/api/v1/files/scan` | 掃描 SFTPGo demo 目錄中的新檔案 | ✅ |
| 10 | GET | `/api/v1/file/:file_uuid/probe` | 取得/快取 ffprobe 資訊 | ✅ |
| 11 | POST | `/api/v1/file/:file_uuid/process` | 啟動處理 pipeline(建立 monitor job) | ✅ |
| 12 | GET | `/api/v1/file/:file_uuid/chunks` | 列出 pre_chunks | ✅ |
| 13 | GET | `/api/v1/progress/:uuid` | 即時處理進度(來自 Redis PubSub) | ✅ |
| 14 | GET | `/api/v1/jobs` | 任務列表(支援分頁、status 過濾) | ✅ |
### 3. 搜尋(Search)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 15 | POST | `/api/v1/search/visual` | 視覺搜尋 | ✅ |
| 16 | POST | `/api/v1/search/visual/class` | 依物件類別過濾搜尋 | ✅ |
| 17 | POST | `/api/v1/search/visual/density` | 依視覺密度搜尋 | ✅ |
| 18 | POST | `/api/v1/search/visual/stats` | 視覺統計資料 | ✅ |
| 19 | POST | `/api/v1/search/visual/combination` | 視覺組合搜尋(多條件) | ✅ |
| 20 | POST | `/api/v1/search/smart` | 智慧搜尋(語意向量) | ✅ |
| 21 | POST | `/api/v1/search/universal` | 通用搜尋 | ✅ |
| 22 | POST | `/api/v1/search/frames` | 影格搜尋 | ✅ |
### 4. 身份管理(Identity)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 23 | GET | `/api/v1/identities` | 身份列表 | ✅ |
| 24 | POST | `/api/v1/identity` | 建立身份(從 face.json 建立參考向量) | ✅ |
| 25 | GET | `/api/v1/identity/:identity_uuid` | 身份詳細資訊 | ✅ |
| 26 | DELETE | `/api/v1/identity/:identity_uuid` | 刪除身份 | ✅ |
| 27 | GET | `/api/v1/identity/:identity_uuid/files` | 該身份出現的所有檔案 | ✅ |
| 28 | GET | `/api/v1/identity/:identity_uuid/chunks` | 該身份的時間軸片段 | ✅ |
| 29 | POST | `/api/v1/identity/:identity_uuid/bind` | 綁定信號至身份 | ✅ |
| 30 | POST | `/api/v1/identity/:identity_uuid/unbind` | 解除綁定 | ✅ |
| 31 | POST | `/api/v1/identity/:from_uuid/mergeinto` | 合併身份(將 from 合併至目標) | ✅ |
### 5. 臉部(Face)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 32 | GET | `/api/v1/faces/candidates` | 臉部候選列表(未綁定者) | ✅ |
### 6. 媒體串流(Media)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 33 | GET | `/api/v1/file/:file_uuid/video` | 影片串流 | ✅ |
| 34 | GET | `/api/v1/file/:file_uuid/video/bbox` | 含 Bounding Box 的影片串流 | ✅ |
| 35 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | 特定 trace 的影片片段 | ✅ |
| 36 | GET | `/api/v1/file/:file_uuid/thumbnail` | 影片縮圖 | ✅ |
### 7. 檔案身份關聯(File-Identity)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 37 | GET | `/api/v1/file/:file_uuid/identities` | 該檔案的所有關聯身份 | ✅ |
### 8. Agent
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 38 | POST | `/api/v1/agents/translate` | 翻譯 Agent | ✅ |
| 39 | POST | `/api/v1/agents/identity/analyze` | 身份分析 Agent | ✅ |
| 40 | POST | `/api/v1/agents/identity/suggest` | 身份合併建議 | ✅ |
| 41 | GET | `/api/v1/agents/identity/status` | 身份 Agent 狀態 | ✅ |
| 42 | POST | `/api/v1/agents/suggest/clustering` | 聚類建議 | ✅ |
| 43 | POST | `/api/v1/agents/suggest/merge` | 合併建議 | ✅ |
| 44 | POST | `/api/v1/agents/5w1h/analyze` | 5W1H 分析 | ✅ |
| 45 | POST | `/api/v1/agents/5w1h/batch` | 5W1H 批量分析 | ✅ |
| 46 | GET | `/api/v1/agents/5w1h/status` | 5W1H 狀態 | ✅ |
### 9. 資源管理(Resource)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 47 | POST | `/api/v1/resource/register` | 註冊運算資源 | ✅ |
| 48 | POST | `/api/v1/resource/heartbeat` | 資源心跳回報 | ✅ |
| 49 | GET | `/api/v1/resources` | 資源列表 | ✅ |
### 10. 統計與設定(Stats & Config)
| # | Method | Path | 說明 | 需 Key |
|---|--------|------|------|--------|
| 50 | GET | `/api/v1/stats/ingest` | 攝取統計(video/chunk 計數) | ✅ |
| 51 | GET | `/api/v1/stats/sftpgo` | SFTPGo 使用者狀態 | ✅ |
| 52 | GET | `/api/v1/stats/inference` | 推理叢集健康狀態 | ✅ |
| 53 | POST | `/api/v1/config/cache` | 切換快取開關 | ✅ |
| 54 | POST | `/api/v1/config/auto-pipeline` | 註冊後自動處理 | ✅ |
| 55 | POST | `/api/v1/config/watcher-auto-register` | Watcher 自動註冊 | ✅ |
---
## 未掛載的端點(定義了 handler 但未註冊路由)
| Handler | 位置 | 說明 |
|---------|------|------|
| `POST /api/v1/file/:file_uuid/face_trace/sortby` | `trace_agent_api.rs` | 定義了 `trace_agent_routes()` 但從未被 `server.rs` merge |
---
## 程式碼中存在 handler 但未註冊路由的端點
下列 handler 有實作但**沒有對應的 `.route()` 呼叫**,無法透過 HTTP 存取:
- `GET /api/v1/assets/:uuid/status` — `get_asset_status`
- `GET /api/v1/jobs/:job_id` — `get_job`
- `GET /api/v1/rules/:rule/status` — `get_rule_status`
- `GET /api/v1/videos/:uuid/details` — `video_details`
- `DELETE /api/v1/videos/:uuid` — `delete_video`
- `POST /api/v1/search` — `search`(語意搜尋)
- `POST /api/v1/search/hybrid` — `hybrid_search`
- `POST /api/v1/search/bm25` — `search_bm25`
- `GET /api/v1/lookup` — `lookup`
- `POST /api/v1/search/smart` — `search_smart`(server.rs 版,實際註冊的是 search.rs 版)
---
## 與 V1.0 文件的差異
V1.0 文件(`MOMENTRY_CORE_API_V1.0.0.md`)宣稱的端點中有以下**不存在於實際程式碼**:
| 文件宣稱 | 實際狀況 |
|----------|---------|
| `DELETE /api/v1/videos/:uuid` | handler 存在但未註冊路由 |
| `POST /api/v1/search` | handler 存在但未註冊路由 |
| `POST /api/v1/search/hybrid` | handler 存在但未註冊路由 |
| `POST /api/v1/assets/:uuid/process` | 實際是 `POST /api/v1/file/:file_uuid/process` |
| `GET /api/v1/files/:uuid/snapshots` | 不存在 |
| `POST /api/v1/files/:uuid/snapshots/migrate` | 不存在 |
| `GET /api/v1/face/list` | 不存在 |
| `POST /api/v1/face/recognize` | 不存在 |
---
## 路徑命名慣例
| 資源 | 路由格式 | 參數 |
|------|---------|------|
| 檔案 | `/api/v1/file/:file_uuid` | 32 碼 hex string |
| 身份 | `/api/v1/identity/:identity_uuid` | UUID v4 |
| 資源 | `/api/v1/resource/...` | - |
注意路徑使用**單數**(`file`, `identity`),與 RELEASE 文件的 `files`, `identities` 不同。
File diff suppressed because it is too large Load Diff
@@ -1,241 +0,0 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core V1.0.0 API 參考文件"
date: "2026-04-30"
version: "V1.0"
status: "superseded"
owner: "Warren"
created_by: "OpenCode"
tags:
- "api"
- "reference"
- "v1.0.0"
- "marcom"
- "restful"
- "endpoint"
- "file-centric"
ai_query_hints:
- "Momentry Core V1.0.0 API 參考文件的主要內容是什麼?"
- "查詢 V1.0.0 API 列表包含哪些端點?"
- "Marcom 團隊如何使用 API Reference?"
- "API 的 Progressive Workflow 範例"
- "Momentry API 的檔案管理與搜尋功能"
- "API 的 Progressive Workflow 操作步驟"
- "API 的檔案管理與搜尋功能"
related_documents:
- "STANDARDS/DOCS_STANDARD.md"
- "DEV_API_V1.0/API_REFERENCE_v1.0.0.md"
- "API_DICTIONARY_V1.0.0.md"
- "API_USAGE_DEMO_V1.0.0.md"
- "PRODUCTION_VERIFICATION_V1.0.0.md"
---
# Momentry Core V1.0.0 API 參考文件
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-04-30 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-04-30 | 創建 V1.0.0 API 列表,移除過時端點 | OpenCode | OpenCode |
| V1.1 | 2026-05-06 | 被 DEV_API_REFERENCE_v1.0.0.md 取代(實際路由與此文件有大量差異) | OpenCode | OpenCode |
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| file_uuid | 媒體檔案(影片/圖片/音訊)的唯一 32 碼 SHA256 識別碼 |
| identity_uuid | 全域人物身份識別碼,跨檔案關聯同一人物 |
| Chunk | 可搜尋單位,由 Rule 組合 pre_chunks 產出 |
| Snapshot | 臉部或場景的快取快照,需 migrate 後供 UI 使用 |
| API Key | 認證方式,透過 Header `X-API-Key` 傳遞 |
## 概述
本文檔定義 Momentry Core **V1.0.0** 版本供 **Marcom 團隊** 使用的 API 列表與開發範例。此列表已移除舊版、冗餘及內部使用的端點,確保前端開發使用的是標準且穩定的介面。
---
## 🚀 設計原則 (Design Principles)
### 1. Clear API (介面清晰化)
* **去蕪存菁**: 嚴格區分 **Public** (公開) 與 **Internal** (內部) 端點。舊版冗餘路徑(如 `/api/v1/videos`, `/api/v1/probe`)已全面移除或合併。
* **標準化回應**: 所有列表型 API 均回傳統一結構 `{ "success": true, "data": [...], "total": N }`。
* **命名規範**: 採用 RESTful 風格,資源以複數名詞或明確動作命名(如 `files`, `identities`)。
### 2. File-Centric (以檔案為核心)
* **唯一識別**: 每個媒體檔案(影片/圖片/音訊)均由 **32 碼 UUID** (`file_uuid`) 唯一標識。
* **生命週期**: `File` 是所有資料的根節點。所有的 `Chunk` (片段), `Snapshot` (快照), `Jobs` (任務) 皆隸屬於特定的 `File`。
* **操作模式**: 前端應優先呼叫 `GET /api/v1/files` 取得清單,再透過 `POST /api/v1/files/:uuid/snapshots/migrate` 載入詳細資源。
### 3. Global Identity (全域身份識別)
* **跨檔案關聯**: `Identity` 代表一個獨立的人物或角色,不受單一檔案限制。
* **綁定機制 (Binding)**: 透過 `POST /api/v1/identities/bind`,我們可以將多個檔案中偵測到的臉部 (`face`) 或聲音 (`speaker`) 聚合到同一個 `Identity` 下。
* **資料聚合**: 查詢某個 `Identity` 即可看到該人物在所有歷史檔案中的軌跡 (`/api/v1/identities/:uuid/files`)。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| API 版本 | V1.0.0 |
| 開發環境 Port | 3003 |
| 正式環境 Port | 3002 |
| 認證方式 | Header `X-API-Key` |
---
## 1. API Dictionary (端點清單)
### 1.1 系統與認證 (System & Auth)
| Method | Endpoint | 說明 |
| :--- | :--- | :--- |
| `GET` | `/health` | 基本健康檢查 |
| `POST` | `/api/v1/auth/login` | 登入以取得 API Key |
### 1.2 檔案管理 (File Management)
*主要入口:瀏覽與管理資產*
| Method | Endpoint | 說明 |
| :--- | :--- | :--- |
| `GET` | `/api/v1/files` | **列出所有檔案** (支援分頁) |
| `GET` | `/api/v1/files/:uuid` | 取得檔案詳情 (包含 probe_json, metadata) |
| `POST` | `/api/v1/files/register` | 從磁碟註冊新檔案 |
| `DELETE`| `/api/v1/videos/:uuid` | **刪除影片** 及其關聯資料 |
### 1.3 搜尋與檢索 (Search & Retrieval)
| Method | Endpoint | 說明 |
| :--- | :--- | :--- |
| `POST` | `/api/v1/search` | **語意搜尋** (Text-based, 使用 Embedding) |
| `POST` | `/api/v1/search/hybrid` | 混合搜尋 (Vector + BM25 關鍵字) |
| `POST` | `/api/v1/search/visual` | 視覺搜尋 (尋找物件/形狀) |
| `POST` | `/api/v1/search/visual/class`| 依物件類別過濾 (如 "person", "car") |
### 1.4 身份與人物管理 (Identity Management)
*跨影片的人物/角色關聯*
| Method | Endpoint | 說明 |
| :--- | :--- | :--- |
| `GET` | `/api/v1/identities` | **列出所有身份** (人物/角色) |
| `GET` | `/api/v1/identities/:uuid` | 取得身份詳情 (名稱, 品質, 來源) |
| `GET` | `/api/v1/identities/:uuid/files`| 列出該身份出現的所有檔案 |
| `GET` | `/api/v1/identities/:uuid/chunks`| 列出特定的時間軸片段 (Chunks) |
| `POST` | `/api/v1/identities/bind` | 將臉部/聲音訊號綁定至身份 |
### 1.5 臉部與快照 (Face & Snapshots)
| Method | Endpoint | 說明 |
| :--- | :--- | :--- |
| `GET` | `/api/v1/face/list` | 列出特定影片中偵測到的所有臉部 |
| `POST` | `/api/v1/face/recognize` | 對指定影片觸發臉部辨識流程 |
| `GET` | `/api/v1/files/:uuid/snapshots` | 檢查快照快取狀態 (Hot/Cold) |
| `POST` | `/api/v1/files/:uuid/snapshots/migrate`| **載入快照至記憶體** (UI 顯示快圖前需呼叫) |
### 1.6 任務與代理人 (Jobs & Agents)
| Method | Endpoint | 說明 |
| :--- | :--- | :--- |
| `GET` | `/api/v1/progress/:uuid` | 檢查即時處理進度 |
| `POST` | `/api/v1/assets/:uuid/process` | 觸發處理流程 (ASR, YOLO, 等) |
| `POST` | `/api/v1/agents/identity/analyze` | AI Agent: 分析身份重複情況 |
---
## 2. Progressive Workflow Examples (操作範例)
此章節展示典型的使用者操作情境:**尋找影片 → 處理 → 搜尋 → 人物綁定**。
### Phase 1: 瀏覽與檢視
*使用者瀏覽檔案庫以尋找目標影片。*
**Step 1: 登入**
```bash
curl -s -X POST http://localhost:3003/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "demo", "password": "demo"}'
# 回應範例: { "api_key": "muser_test_001..." }
```
**Step 2: 列出檔案**
```bash
curl -s "http://localhost:3003/api/v1/files?page=1&page_size=5" \
-H "X-API-Key: muser_test_001"
# 回應範例: { "success": true, "data": [ { "file_uuid": "...", "file_name": "Demo.mp4" ... } ] }
```
### Phase 2: 處理與監控
*使用者決定分析該影片的臉部與語音內容。*
**Step 3: 觸發處理**
```bash
curl -s -X POST "http://localhost:3003/api/v1/assets/{file_uuid}/process" \
-H "X-API-Key: muser_test_001" \
-H "Content-Type: application/json" \
-d '{}'
# 啟動 ASR, 臉部偵測等處理器
```
**Step 4: 檢查進度**
```bash
curl -s "http://localhost:3003/api/v1/progress/{file_uuid}" \
-H "X-API-Key: muser_test_001"
# 回應範例: { "overall_progress": 50, "processors": [...] }
```
### Phase 3: 搜尋內容
*使用者搜尋影片中的特定內容。*
**Step 5: 語意搜尋 (文字描述)**
```bash
curl -s -X POST "http://localhost:3003/api/v1/search" \
-H "X-API-Key: muser_test_001" \
-H "Content-Type: application/json" \
-d '{"query": "一個人拿著紅色的信封", "uuid": "{file_uuid}"}'
# 回應範例: 符合文字描述的片段列表
```
### Phase 4: 身份管理 (GUI 開發重點)
*使用者發現了一張臉,確認該人物,並將其綁定到已知身份。*
**Step 6: 載入快照 (Migrate Snapshots)**
*在 GUI 渲染大量臉部縮圖前,必須先將快取載入記憶體以加速讀取。*
```bash
curl -s -X POST "http://localhost:3003/api/v1/files/{file_uuid}/snapshots/migrate" \
-H "X-API-Key: muser_test_001" \
-H "Content-Type: application/json" \
-d '{"parent_uuid": "{file_uuid}"}'
# 回應範例: { "success": true, "migrated_types": ["faces", ...] }
```
**Step 7: 綁定臉部到身份 (Bind Face)**
*假設偵測到臉部 `face_123`,欲綁定至身份 `uuid_identity`。*
```bash
curl -s -X POST "http://localhost:3003/api/v1/identities/bind" \
-H "X-API-Key: muser_test_001" \
-H "Content-Type: application/json" \
-d '{
"identity_id": null,
"name": "Cary Grant",
"binding_type": "face",
"binding_value": "face_123"
}'
```
---
## 3. 棄用聲明 (Deprecation Notices)
以下端點已在 V1.0.0 移除或棄用,**請勿**在新的開發中使用。
* `GET /api/v1/videos` (列表) → 已取代為 `GET /api/v1/files`
* `POST /api/v1/register` → 已取代為 `POST /api/v1/files/register`
* `POST /api/v1/probe` → 已取代為 `GET /api/v1/files/:uuid`
* `GET /api/v1/people/...` → 已合併為 `GET /api/v1/identities/...`
* `/api/v1/n8n/search/...` → 僅供內部 n8n 工作流使用 (請使用標準 `/api/v1/search`)

Some files were not shown because too many files have changed in this diff Show More