feat: ASRX hybrid pipeline, identity history, worker fixes, checkpoint system
This commit is contained in:
761
deliverable_v1.1.0/AGENTS.md
Normal file
761
deliverable_v1.1.0/AGENTS.md
Normal file
@@ -0,0 +1,761 @@
|
||||
# AGENTS.md - Momentry Core
|
||||
|
||||
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` |
|
||||
|
||||
## ⚠️ 交叉污染防制 (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
|
||||
|
||||
```bash
|
||||
# Build project (use debug builds for development/testing)
|
||||
cargo build
|
||||
cargo build --bin momentry
|
||||
cargo build --bin momentry_playground
|
||||
|
||||
# Build all binaries
|
||||
cargo build --bins
|
||||
|
||||
# Run CLI
|
||||
cargo run -- --help
|
||||
cargo run -- register /path/to/video.mp4
|
||||
cargo run -- server --host 0.0.0.0 --port 3002
|
||||
|
||||
# Run playground (development binary)
|
||||
cargo run --bin momentry_playground -- server
|
||||
cargo run --bin momentry_playground -- --help
|
||||
```
|
||||
|
||||
### ⚠️ 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
|
||||
|
||||
| Binary | Purpose | Port | Redis Prefix | Environment |
|
||||
|--------|---------|------|--------------|-------------|
|
||||
| `momentry` | Production | 3002 | `momentry:` | `.env` |
|
||||
| `momentry_playground` | Development | 3003 | `momentry_dev:` | `.env.development` |
|
||||
| `momentry_player` | Video player | - | - | - |
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
cargo test
|
||||
|
||||
# Run single test by name
|
||||
cargo test test_name
|
||||
|
||||
# Run with output
|
||||
cargo test -- --nocapture
|
||||
|
||||
# Doc tests
|
||||
cargo test --doc
|
||||
```
|
||||
|
||||
## Linting & Formatting
|
||||
|
||||
```bash
|
||||
# Format code (edition=2021, max_width=100, tab_spaces=4)
|
||||
cargo fmt
|
||||
cargo fmt -- --check
|
||||
|
||||
# Lint
|
||||
cargo clippy
|
||||
cargo clippy --all-features
|
||||
|
||||
# Check for errors
|
||||
cargo check
|
||||
cargo check --all-features
|
||||
```
|
||||
|
||||
## Code Style
|
||||
|
||||
### General
|
||||
- Use Rust 2021 edition
|
||||
- Use tracing for logging (not println!)
|
||||
- Keep lines under 100 characters
|
||||
|
||||
### Imports (order: std → external → local)
|
||||
```rust
|
||||
use std::path::Path;
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::core::chunk::Chunk;
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
- Use `anyhow::Result<T>` for application code
|
||||
- Use `thiserror` for library code
|
||||
- Use `.context()` for error context
|
||||
- Use `anyhow::bail!()` for early returns
|
||||
|
||||
```rust
|
||||
fn example() -> Result<SomeType> {
|
||||
let output = Command::new("ffprobe")
|
||||
.args([...])
|
||||
.output()
|
||||
.context("Failed to run ffprobe")?;
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!("Command failed");
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
```
|
||||
|
||||
### Naming
|
||||
- Types/Enums: PascalCase (`VideoRecord`, `ChunkType`)
|
||||
- Functions/Variables: snake_case (`get_video_by_uuid`)
|
||||
- Traits: PascalCase with -er suffix (`Database`, `ChunkStore`)
|
||||
- Files: snake_case (`postgres_db.rs`)
|
||||
|
||||
### Types
|
||||
- Use `serde::{Deserialize, Serialize}` for serializable types
|
||||
- Use `#[serde(rename_all = "snake_case")]` for enum variants
|
||||
- Use explicit numeric types (i64, u32, f64)
|
||||
|
||||
```rust
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VideoRecord {
|
||||
pub id: i64,
|
||||
pub uuid: String,
|
||||
pub duration: f64,
|
||||
pub width: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChunkType {
|
||||
TimeBased,
|
||||
Sentence,
|
||||
Cut,
|
||||
}
|
||||
```
|
||||
|
||||
### Async Programming
|
||||
- Use `tokio` runtime with full features
|
||||
- Use `#[async_trait]` for async trait methods
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait Database: Send + Sync {
|
||||
async fn init() -> Result<Self>
|
||||
where Self: Sized;
|
||||
}
|
||||
```
|
||||
|
||||
## Code Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── main.rs # CLI entry point
|
||||
├── lib.rs # Library exports
|
||||
├── core/
|
||||
│ ├── api_key/ # API key management (anomaly, blacklist, encryption, etc.)
|
||||
│ ├── chunk/ # Chunking logic
|
||||
│ ├── config.rs # Centralized configuration (env vars)
|
||||
│ ├── db/ # Database (PostgreSQL, MongoDB, Redis, Qdrant)
|
||||
│ ├── embedding/ # Vector embeddings
|
||||
│ ├── overlay/ # Video overlay
|
||||
│ ├── probe/ # ffprobe integration
|
||||
│ ├── processor/ # ASR, OCR, YOLO, Face, Pose, CUT, ASRX
|
||||
│ │ └── executor.rs # Unified Python script executor
|
||||
│ ├── storage/ # File management
|
||||
│ └── thumbnail/ # Thumbnail extraction
|
||||
├── api/ # HTTP API (axum)
|
||||
├── player/ # Video player
|
||||
├── ui/ # TUI components
|
||||
└── watcher/ # File system watcher
|
||||
```
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **Error handling**: `anyhow`, `thiserror`
|
||||
- **Async**: `tokio` (full features), `async-trait`
|
||||
- **CLI**: `clap` (derive)
|
||||
- **Serialization**: `serde`, `serde_json`, `chrono`
|
||||
- **Database**: `sqlx`, `mongodb`, `redis` (1.0), `qdrant-client`
|
||||
- **HTTP**: `axum`, `tower`
|
||||
- **Logging**: `tracing`, `tracing-subscriber`
|
||||
- **Config**: `once_cell` (lazy static config)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Server
|
||||
- `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_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_URL` - PostgreSQL (default: `postgres://accusys@localhost:5432/momentry`)
|
||||
|
||||
### Redis
|
||||
- `REDIS_URL` - Redis URL (default: `redis://:accusys@localhost:6379`)
|
||||
- `REDIS_PASSWORD` - Redis password (default: `accusys`)
|
||||
|
||||
### Paths
|
||||
- `MOMENTRY_OUTPUT_DIR` - Output directory (default: `/Users/accusys/momentry/output`)
|
||||
- `MOMENTRY_BACKUP_DIR` - Backup directory
|
||||
- `MOMENTRY_PYTHON_PATH` - Python path (default: `/opt/homebrew/bin/python3.11`)
|
||||
- `MOMENTRY_SCRIPTS_DIR` - Scripts directory
|
||||
|
||||
### Processor Timeouts
|
||||
- `MOMENTRY_ASR_TIMEOUT` - ASR timeout in seconds (default: 3600)
|
||||
- `MOMENTRY_CUT_TIMEOUT` - CUT timeout in seconds (default: 3600)
|
||||
- `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
|
||||
- `RUST_LOG` or `MOMENTRY_LOG_LEVEL` - Log level (default: `info`)
|
||||
|
||||
## Notes
|
||||
|
||||
- Unit tests exist (86 library tests)
|
||||
- Video processing uses external tools (ffprobe, Python scripts)
|
||||
- Multi-database architecture (PostgreSQL, MongoDB, Redis, Qdrant)
|
||||
- Monitor directory is a separate system (not Rust)
|
||||
- PythonExecutor provides unified script execution with timeout support
|
||||
- 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
|
||||
|
||||
### 使用 todowrite 追蹤任務
|
||||
```bash
|
||||
# 創建任務清單
|
||||
/todo 建立配置模組 [in_progress]
|
||||
/todo 添加單元測試 [pending]
|
||||
|
||||
# 更新狀態
|
||||
/todo 完成標記 [completed]
|
||||
```
|
||||
|
||||
### 任務批次建議
|
||||
- 一次處理 1-2 個功能
|
||||
- 每個功能完成後驗證 (clippy + test)
|
||||
- 驗證通過後再繼續下一個
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
完成任務後檢查:
|
||||
- [ ] `cargo clippy --lib` 通過
|
||||
- [ ] `cargo test --lib` 通過
|
||||
- [ ] `cargo fmt -- --check` 通過
|
||||
- [ ] 文檔已更新 (如需要)
|
||||
- [ ] 新功能有單元測試
|
||||
|
||||
## Commit Guidelines
|
||||
|
||||
```bash
|
||||
# feat: 新功能
|
||||
git commit -m "feat: add monitor_jobs table"
|
||||
|
||||
# fix: 錯誤修復
|
||||
git commit -m "fix: resolve SQL injection in store_vector"
|
||||
|
||||
# refactor: 重構
|
||||
git commit -m "refactor: use parameterized queries"
|
||||
|
||||
# docs: 文檔更新
|
||||
git commit -m "docs: update AGENTS.md with new modules"
|
||||
```
|
||||
|
||||
## Pre-commit Hook
|
||||
|
||||
專案已配置 `.git/hooks/pre-commit`,提交前自動檢查:
|
||||
|
||||
```bash
|
||||
# 檢查內容
|
||||
1. cargo fmt --check # Rust 格式化檢查
|
||||
2. cargo clippy --lib # Rust Lint 檢查
|
||||
3. cargo test --lib # Rust 單元測試
|
||||
4. ruff check # Python Lint 檢查
|
||||
5. ruff format --check # Python 格式化檢查
|
||||
6. markdownlint # Markdown 格式檢查
|
||||
7. shellcheck # Shell 腳本檢查
|
||||
|
||||
# 跳過檢查(不建議)
|
||||
git commit --no-verify
|
||||
|
||||
# 跳過特定檢查
|
||||
git commit --skip-checks
|
||||
```
|
||||
|
||||
**注意**: Hook 僅檢查已暫存的 Rust/Python/Markdown 文件。
|
||||
|
||||
### Python 環境設置
|
||||
```bash
|
||||
# 安裝 ruff
|
||||
pip install ruff==0.11.2
|
||||
|
||||
# 格式化 Python 文件
|
||||
ruff format scripts/
|
||||
|
||||
# Lint Python 文件
|
||||
ruff check scripts/
|
||||
```
|
||||
|
||||
### Markdown 環境設置
|
||||
```bash
|
||||
# 安裝 markdownlint-cli (使用系統 Node.js)
|
||||
npm install -g markdownlint-cli
|
||||
|
||||
# 檢查 Markdown 文件
|
||||
markdownlint docs/
|
||||
|
||||
# 配置檔案
|
||||
.markdownlint.json
|
||||
```
|
||||
|
||||
### Shell 環境設置
|
||||
```bash
|
||||
# 安裝 shellcheck
|
||||
brew install shellcheck
|
||||
|
||||
# 檢查 Shell 腳本
|
||||
shellcheck scripts/*.sh monitor/**/*.sh
|
||||
```
|
||||
|
||||
**注意**: Hook 只檢查 error 等級的 shellcheck 問題,style 警告會顯示但不阻擋提交。
|
||||
|
||||
## 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
|
||||
|
||||
| 文件 | 用途 |
|
||||
|------|------|
|
||||
| `docs/OPENCODE_GUIDE.md` | OpenCode 使用規範 |
|
||||
| `docs/ARCHITECTURE_EVALUATION.md` | 架構優化待評估項目 (含 GraphRAG) |
|
||||
| `docs/PENDING_ISSUES.md` | 待解決問題追蹤 |
|
||||
| `docs/MOMENTRY_CORE_MONITORING.md` | 監控系統規範 |
|
||||
| `docs/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 |
|
||||
| `docs/PYTHON.md` | Python 腳本規範 |
|
||||
| `docs/FILE_CHANGE_MANAGEMENT.md` | 文件修改管理規範 |
|
||||
| `docs/YOLO_RESUME_INTEGRATION.md` | YOLO Resume 功能整合記錄 |
|
||||
| `docs/DOCUMENT_EMBEDDING_STRATEGY.md` | Parent-Child 嵌入策略 |
|
||||
| `docs/PROCESSING_PIPELINE.md` | 處理流程文檔 |
|
||||
| `docs/N8N_DEMO_WORKFLOW.md` | n8n 工作流文檔 |
|
||||
| `docs/FRESH_MAC_INSTALLATION.md` | 全新 Mac 安裝指南 |
|
||||
| `docs/SERVICES.md` | 服務總覽與管理 |
|
||||
| `docs/SFTPGO_DEMO_USER.md` | SFTPGo 用戶指南 |
|
||||
|
||||
## Document Change Workflow
|
||||
|
||||
修改文件前請參考 `docs/FILE_CHANGE_MANAGEMENT.md`,確保:
|
||||
|
||||
1. **修改前**:完整閱讀文件、執行預檢清單
|
||||
2. **修改中**:提供變更計畫、取得確認
|
||||
3. **修改後**:展示 diff、更新版本歷史
|
||||
4. **驗證**:執行 lint/test、提交前審查
|
||||
|
||||
### AI 工具修改規範
|
||||
|
||||
AI 工具修改文件時:
|
||||
- 必須先完整閱讀文件(不可只讀取部分章節)
|
||||
- 修改前先提出變更計畫供確認
|
||||
- 修改後展示 diff 內容
|
||||
- 更新版本歷史表
|
||||
|
||||
## PHP Development
|
||||
|
||||
WordPress 作為 Momentry Portal,負責 n8n 自動化與 sftpgo 檔案服務的頁面整合。
|
||||
|
||||
### 編輯器設定
|
||||
|
||||
| 編輯器 | LSP 方案 | 安裝方式 |
|
||||
|--------|----------|----------|
|
||||
| VS Code | Intelephense | Extension Marketplace (推薦) |
|
||||
| Cursor | Intelephense | Extension Marketplace (推薦) |
|
||||
| CLI | phpactor | `~/bin/phpactor` |
|
||||
|
||||
### Intelephense (VS Code/Cursor)
|
||||
|
||||
1. 安裝 Extension: 搜尋 "Intelephense"
|
||||
2. 設定:
|
||||
```json
|
||||
{
|
||||
"intelephense.stubs": ["wordpress"]
|
||||
}
|
||||
```
|
||||
|
||||
### phpactor (CLI)
|
||||
|
||||
```bash
|
||||
# 安裝方式
|
||||
brew install composer
|
||||
curl -sSL https://github.com/phpactor/phpactor/releases/latest/download/phpactor.phar -o ~/bin/phpactor
|
||||
chmod +x ~/bin/phpactor
|
||||
|
||||
# 安裝 WordPress Stubs
|
||||
cd /Users/accusys/wordpress/web
|
||||
composer require --dev php-stubs/wordpress-stubs
|
||||
|
||||
# 建立 WordPress 索引
|
||||
cd /Users/accusys/wordpress/web
|
||||
~/bin/phpactor index:build --reset
|
||||
|
||||
# 常用指令
|
||||
~/bin/phpactor class:search "WP_User" # 搜尋類別
|
||||
~/bin/phpactor index:query WP_User # 查看類別資訊
|
||||
~/bin/phpactor navigate /path/to/file.php # 導航到定義
|
||||
```
|
||||
|
||||
### WordPress 程式碼位置
|
||||
| 類型 | 路徑 |
|
||||
|------|------|
|
||||
| 主題 | `/Users/accusys/wordpress/web/wp-content/themes/` |
|
||||
| 插件 | `/Users/accusys/wordpress/web/wp-content/plugins/` |
|
||||
|
||||
### 與 marcom 團隊協作
|
||||
| 角色 | 負責 |
|
||||
|------|------|
|
||||
| marcom 團隊 | Figma 設計 / Elementor 建構 |
|
||||
| OpenCode | 程式碼實作 / 重構 |
|
||||
|
||||
### 開發時程
|
||||
```
|
||||
Phase 1: marcom 建構 (現在) → Elementor 頁面建構
|
||||
Phase 2: 交付審視 (TBD) → 功能確認 / 重構評估
|
||||
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`
|
||||
71
deliverable_v1.1.0/SYSTEM_AUDIT_2026-05-17.md
Normal file
71
deliverable_v1.1.0/SYSTEM_AUDIT_2026-05-17.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# System Audit — 2026-05-17
|
||||
|
||||
## Current State
|
||||
|
||||
### Embedding Storage (三重冗余,無主)
|
||||
|
||||
| 資料類型 | PG pgvector | Qdrant | JSON 檔案 |
|
||||
|---------|------------|--------|-----------|
|
||||
| Sentence 向量 | `chunk.embedding` ✅ | `dev_v1` / `rule1_v2` / `sentence_*` ✅ | ❌ 無 |
|
||||
| Story 向量 | `chunk.embedding` ✅ | `dev_v1` / `dev_stories` ✅ | `.story_llm.json` ✅ |
|
||||
| Face 向量 | ❌ 已清除(依使用者指示) | `dev_faces` ✅ (97K) | `.face.json` ✅ |
|
||||
| Voice 向量 | ❌ 無 | `dev_voice` ✅ (4K) | ❌ 無 |
|
||||
|
||||
### Pipeline 問題
|
||||
|
||||
| 問題 | 影響 |
|
||||
|------|------|
|
||||
| `processor_results.duration_secs` 全為 0 | 無法查各步驟耗時 |
|
||||
| `processor_results.started_at/completed_at` 全 NULL | 時間線遺失 |
|
||||
| Redis timing 在 job 完成後被清掉 | 唯一 timing 來源消失 |
|
||||
| `get_chunk_by_chunk_id_and_uuid` 原本是 stub(已修) | Smart search 找不到 PG chunk |
|
||||
| `server.rs::search()` 未 mount 但仍編譯 | Dead code,混淆 Qdrant 用途 |
|
||||
| Face embedding 只寫 Qdrant 不寫 PG | 已刪除則全失 |
|
||||
|
||||
### Qdrant Collections 現況
|
||||
|
||||
| Collection | Points | 來源 | UUID |
|
||||
|-----------|--------|------|------|
|
||||
| `dev_v1` | 9,936 | PG rebuild | ✅ bd80fec... |
|
||||
| `dev_faces` | 97,000 | face.json rebuild | ✅ bd80fec... |
|
||||
| `dev_stories` | 560 | Snapshot | ✅ bd80fec... |
|
||||
| `dev_voice` | 4,188 | Snapshot | ✅ bd80fec... |
|
||||
| `dev_rule1_v2` | 3,417 | Snapshot | ✅ bd80fec... |
|
||||
| `sentence_story` | 4,188 | Snapshot | ✅ bd80fec... |
|
||||
| `sentence_summary` | 4,188 | Snapshot | ✅ bd80fec... |
|
||||
|
||||
## Safeguards & Fixes
|
||||
|
||||
### P0 — 必須修
|
||||
|
||||
| # | Fix | 做法 |
|
||||
|---|-----|------|
|
||||
| 1 | **Pipeline timing 寫入 DB** | `update_processor_result()` 加入 `started_at`、`completed_at`、`duration_secs` |
|
||||
| 2 | **Qdrant 不當主要儲存** | Embedding 以 PG `chunk.embedding` 為 source of truth,Qdrant 唯讀 cache |
|
||||
| 3 | **Smart search 只走 PG pgvector** | `search_parent_chunks_semantic` 已正確,無需 Qdrant |
|
||||
| 4 | **移除 `server.rs::search()` dead code** | 或 mount 到正式 route 並確認可用 |
|
||||
|
||||
### P1 — 建議修
|
||||
|
||||
| # | Fix | 做法 |
|
||||
|---|-----|------|
|
||||
| 5 | **刪除 Qdrant 前先 snapshot** | 自動 snapshot script |
|
||||
| 6 | **清理多餘 Qdrant collections** | `dev_voice` / `dev_stories` / `dev_rule1_v2` / `sentence_*` 無 server reader,可移除 |
|
||||
| 7 | **Face embedding 寫入 PG 或移除 dead code** | 目前 face Qdrant write 無人讀取,可移除 `sync_face_embeddings` |
|
||||
| 8 | **UUID 一致性檢查** | 同一 content 不應產生不同 UUID |
|
||||
|
||||
### P2 — 可選
|
||||
|
||||
| # | Fix | 做法 |
|
||||
|---|-----|------|
|
||||
| 9 | `chunk_selector.rs` (player binary)hardcode `momentry_rule1` | 改讀 env var 或 PG |
|
||||
| 10 | AGENTS.md 已加入 delete 安全規則 | ✅ Done |
|
||||
|
||||
## Data Recovery Path
|
||||
|
||||
| 資料來源 | 可恢復到 | 方法 |
|
||||
|---------|---------|------|
|
||||
| `chunk.embedding` (PG) | Qdrant `dev_v1` | SQL → Qdrant upsert |
|
||||
| `face.json` (磁碟) | Qdrant `dev_faces` | Python script |
|
||||
| `story_llm.json` (磁碟) | Qdrant `dev_stories` | Python script |
|
||||
| Qdrant snapshots (phase1) | Qdrant collections | Snapshot upload API |
|
||||
388
deliverable_v1.1.0/html_docs/doc/01_auth.html
Normal file
388
deliverable_v1.1.0/html_docs/doc/01_auth.html
Normal file
@@ -0,0 +1,388 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>01 Auth - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: auth -->
|
||||
<!-- description: Authentication — login, logout, JWT, session cookie, API key -->
|
||||
<!-- depends: -->
|
||||
|
||||
<h2>Base URL</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Environment</th>
|
||||
<th>URL</th>
|
||||
<th>Purpose</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Production</td>
|
||||
<td><code>http://localhost:3002</code></td>
|
||||
<td>Production deployment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>External (M5)</td>
|
||||
<td><code>https://m5api.momentry.ddns.net</code></td>
|
||||
<td>Remote access</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h2>Variables</h2>
|
||||
<p>All examples in this documentation use these environment variables:</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="nv">API</span><span class="o">=</span><span class="s2">"http://localhost:3002"</span>
|
||||
<span class="nv">KEY</span><span class="o">=</span><span class="s2">"your-api-key-here"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h2>Authentication</h2>
|
||||
<p>All endpoints under <code>/api/v1/*</code> require authentication.
|
||||
The following endpoints are public (no auth needed):</p>
|
||||
<ul>
|
||||
<li><code>GET /health</code></li>
|
||||
<li><code>POST /api/v1/auth/login</code></li>
|
||||
<li><code>POST /api/v1/auth/logout</code></li>
|
||||
</ul>
|
||||
<h3>Three Authentication Modes</h3>
|
||||
<p>The system supports three authentication methods, checked in <strong>priority order</strong> by the middleware:</p>
|
||||
<div class="codehilite"><pre><span></span><code>Middleware priority:
|
||||
1. Session Cookie (Portal/browser)
|
||||
2. JWT Bearer (API clients, CLI)
|
||||
3. API Key Header (legacy compatibility)
|
||||
4. API Key Query Param (?api_key=)
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Mode</th>
|
||||
<th>Transport</th>
|
||||
<th>Expiry</th>
|
||||
<th>Scope</th>
|
||||
<th>Best for</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Session Cookie</strong></td>
|
||||
<td><code>Cookie: session_id=<session_id></code></td>
|
||||
<td>24h</td>
|
||||
<td>per-browser session</td>
|
||||
<td>Portal (browser)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>JWT</strong></td>
|
||||
<td><code>Authorization: Bearer <token></code></td>
|
||||
<td>1h</td>
|
||||
<td>per-login token</td>
|
||||
<td>API clients, CLI, scripts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>API Key</strong></td>
|
||||
<td><code>X-API-Key: <key></code></td>
|
||||
<td>90d</td>
|
||||
<td>fixed key for automation</td>
|
||||
<td>Legacy scripts, WordPress</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Login</h3>
|
||||
<p><strong>Default accounts & API keys:</strong></p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Password</th>
|
||||
<th>API Key</th>
|
||||
<th>Role</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>admin</code></td>
|
||||
<td><code>admin</code></td>
|
||||
<td>—</td>
|
||||
<td>admin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>demo</code></td>
|
||||
<td><code>demo</code></td>
|
||||
<td><code>muser_demo_key_32chars_abcdef1234567890</code></td>
|
||||
<td>user</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>The demo API key is set via <code>MOMENTRY_DEMO_API_KEY</code> env var and can be used in place of JWT for marcom integrations:</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Using API key instead of JWT</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: muser_demo_key_32chars_abcdef1234567890"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Login as admin</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"username": "admin", "password": "admin"}'</span>
|
||||
|
||||
<span class="c1"># Login as demo user</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"username": "demo", "password": "demo"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Success Response</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"jwt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"eyJhbGciOiJIUzI1NiIs..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"api_key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"muser_..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"user"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"admin"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"expires_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-18T13:00:00Z"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>jwt</code></td>
|
||||
<td>string</td>
|
||||
<td>JWT access token. Use as <code>Authorization: Bearer <jwt></code>. Expires in 1 hour.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>api_key</code></td>
|
||||
<td>string</td>
|
||||
<td>Legacy API key. Use as <code>X-API-Key: <key></code>. Good for 90 days.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>user.username</code></td>
|
||||
<td>string</td>
|
||||
<td>Username</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>user.role</code></td>
|
||||
<td>string</td>
|
||||
<td>Role: <code>admin</code>, <code>user</code>, or <code>readonly</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>expires_at</code></td>
|
||||
<td>string</td>
|
||||
<td>ISO8601 timestamp of JWT expiration</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>The login endpoint also sets a <code>Set-Cookie</code> header for browser-based clients:</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="nt">Set-Cookie</span><span class="o">:</span><span class="w"> </span><span class="nt">session_id</span><span class="o">=<</span><span class="nt">session_id</span><span class="o">>;</span><span class="w"> </span><span class="nt">Path</span><span class="o">=/;</span><span class="w"> </span><span class="nt">HttpOnly</span><span class="o">;</span><span class="w"> </span><span class="nt">SameSite</span><span class="o">=</span><span class="nt">Strict</span><span class="o">;</span><span class="w"> </span><span class="nt">Max-Age</span><span class="o">=</span><span class="nt">86400</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Error Response (401)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Invalid username or password"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3>Using JWT</h3>
|
||||
<p>JWT is preferred for API clients (CLI scripts, WordPress). It is validated by the middleware without a database lookup (stateless).</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Login and capture JWT</span>
|
||||
<span class="nv">JWT</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"username":"admin","password":"admin"}'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>python3<span class="w"> </span>-c<span class="w"> </span><span class="s2">"import json,sys;print(json.load(sys.stdin)['jwt'])"</span><span class="k">)</span>
|
||||
|
||||
<span class="c1"># Use JWT for all subsequent requests</span>
|
||||
curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan"</span>
|
||||
curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>JWT is short-lived (1 hour). When it expires, request a new one via login.</p>
|
||||
<hr />
|
||||
<h3>Using Session Cookie (Browser)</h3>
|
||||
<p>Browser-based clients (Portal) get a session cookie automatically after login. The browser sends the cookie with every request—no manual header needed.</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Login captures the session cookie from Set-Cookie header</span>
|
||||
curl<span class="w"> </span>-v<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"username":"admin","password":"admin"}'</span><span class="w"> </span><span class="m">2</span>><span class="p">&</span><span class="m">1</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s2">"Set-Cookie"</span>
|
||||
|
||||
<span class="c1"># Browser automatically sends: Cookie: session_id=<session_id></span>
|
||||
<span class="c1"># No manual header needed for subsequent requests</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>The session cookie is HttpOnly (not accessible from JavaScript) and SameSite=Strict (protected against CSRF).</p>
|
||||
<hr />
|
||||
<h3>Using Legacy API Key</h3>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan"</span>
|
||||
|
||||
<span class="c1"># Also accepted via Bearer header (non-JWT format) or query parameter:</span>
|
||||
curl<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan"</span>
|
||||
curl<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?api_key=</span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>API keys are validated via SHA256 hash lookup in the database. They are long-lived (90 days) and intended for automation.</p>
|
||||
<h3>Obtaining an API Key (CLI)</h3>
|
||||
<div class="codehilite"><pre><span></span><code>momentry<span class="w"> </span>api-key<span class="w"> </span>create<span class="w"> </span><span class="s2">"My API Key"</span><span class="w"> </span>--key-type<span class="w"> </span>user
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3>Logout</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Logout using the session cookie (browser)</span>
|
||||
curl<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/logout"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Cookie: session_id=<uuid>"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>What logout does</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Auth mode</th>
|
||||
<th>Effect</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Session Cookie</strong></td>
|
||||
<td>Session deleted from database. Same cookie returns 401 on subsequent requests.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>JWT</strong></td>
|
||||
<td>JWT remains valid until expiry. (JWT is stateless — logout adds JWT to a blacklist only if API key mode is used.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>API Key</strong></td>
|
||||
<td>API key remains valid. (Legacy keys are shared across sessions — revoking would break other clients.)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example: full session lifecycle</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># 1. Login</span>
|
||||
<span class="nv">SESSION_ID</span><span class="o">=</span><span class="k">$(</span>curl<span class="w"> </span>-s<span class="w"> </span>-D<span class="w"> </span>-<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/login"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"username":"admin","password":"admin"}'</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>grep<span class="w"> </span><span class="s2">"Set-Cookie"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>sed<span class="w"> </span><span class="s1">'s/.*session_id=\([^;]*\).*/\1/'</span><span class="k">)</span>
|
||||
|
||||
<span class="c1"># 2. Use session (works)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-o<span class="w"> </span>/dev/null<span class="w"> </span>-w<span class="w"> </span><span class="s2">"HTTP %{http_code}\n"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Cookie: session_id=</span><span class="nv">$SESSION_ID</span><span class="s2">"</span>
|
||||
<span class="c1"># → HTTP 200</span>
|
||||
|
||||
<span class="c1"># 3. Logout</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/auth/logout"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Cookie: session_id=</span><span class="nv">$SESSION_ID</span><span class="s2">"</span>
|
||||
<span class="c1"># → {"success": true}</span>
|
||||
|
||||
<span class="c1"># 4. Use session again (rejected)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-o<span class="w"> </span>/dev/null<span class="w"> </span>-w<span class="w"> </span><span class="s2">"HTTP %{http_code}\n"</span><span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Cookie: session_id=</span><span class="nv">$SESSION_ID</span><span class="s2">"</span>
|
||||
<span class="c1"># → HTTP 401</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3>Authentication Flow Summary</h3>
|
||||
<div class="codehilite"><pre><span></span><code>Login Request
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ 1. Check users │ ← users table (argon2 password verify)
|
||||
│ table │
|
||||
└──────┬───────────┘
|
||||
│
|
||||
┌───┴───┐
|
||||
│ match │
|
||||
└───┬───┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ 2. Create JWT │ ← 1h expiry, signed with JWT_SECRET
|
||||
├──────────────────┤
|
||||
│ 3. Create │ ← 24h expiry, stored in sessions table
|
||||
│ session │
|
||||
├──────────────────┤
|
||||
│ 4. Set-Cookie │ ← HttpOnly, SameSite=Strict, Path=/
|
||||
├──────────────────┤
|
||||
│ 5. Return │ ← JWT + api_key + user info to client
|
||||
└──────────────────┘
|
||||
</code></pre></div>
|
||||
|
||||
<div class="codehilite"><pre><span></span><code>Protected Request
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Middleware checks: │
|
||||
│ │
|
||||
│ 1. Cookie session? │ → DB lookup session → get api_key → verify
|
||||
│ │
|
||||
│ 2. JWT Bearer? │ → verify JWT signature → decode claims
|
||||
│ │
|
||||
│ 3. X-API-Key? │ → SHA256 hash → DB lookup → verify
|
||||
│ │
|
||||
│ 4. ?api_key=? │ → same as #3
|
||||
│ │
|
||||
│ 5. None → 401 │
|
||||
└──────────────────────┘
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3>Error Responses</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>Missing or invalid authentication</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>Session expired or logged out</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>JWT expired</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>API key revoked or inactive</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Related</h3>
|
||||
<ul>
|
||||
<li><code>POST /api/v1/resource/tmdb/check</code> — test authentication + TMDb API connectivity</li>
|
||||
<li><code>GET /health/detailed</code> — view auth status (integrations section)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
277
deliverable_v1.1.0/html_docs/doc/02_health.html
Normal file
277
deliverable_v1.1.0/html_docs/doc/02_health.html
Normal file
@@ -0,0 +1,277 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>02 Health - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: health -->
|
||||
<!-- description: Health check endpoints -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Health Check</h2>
|
||||
<h3><code>GET /health</code></h3>
|
||||
<p><strong>Auth</strong>: Public
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Returns basic server health status — used by load balancers and monitoring.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/health"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{status, version}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ok"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"build_git_hash"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"build_timestamp"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-16T13:38:15Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"uptime_ms"</span><span class="p">:</span><span class="w"> </span><span class="mi">3015</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>ok</code> or <code>degraded</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>version</code></td>
|
||||
<td>string</td>
|
||||
<td>Semver version</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>build_git_hash</code></td>
|
||||
<td>string</td>
|
||||
<td>Git commit hash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>build_timestamp</code></td>
|
||||
<td>string</td>
|
||||
<td>Binary build time</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>uptime_ms</code></td>
|
||||
<td>integer</td>
|
||||
<td>Milliseconds since server start</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /health/detailed</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Returns full system health including each service status, resource utilization, pipeline readiness, schema migration status, identity file sync status, and external integrations.</p>
|
||||
<blockquote>
|
||||
<p>Requires authentication (JWT, session cookie, or API key). The basic <code>/health</code> endpoint remains public for load balancer checks.</p>
|
||||
</blockquote>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/health/detailed"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{status, services, resources: {cpu: .resources.cpu_used_percent, memory: .resources.memory_used_percent}}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ok"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"services"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"postgres"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ok"</span><span class="p">,</span><span class="w"> </span><span class="nt">"latency_ms"</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"redis"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ok"</span><span class="p">,</span><span class="w"> </span><span class="nt">"latency_ms"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"qdrant"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ok"</span><span class="p">,</span><span class="w"> </span><span class="nt">"latency_ms"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"cpu_used_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">12.5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"memory_available_mb"</span><span class="p">:</span><span class="w"> </span><span class="mi">32768</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"memory_used_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">31.7</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"pipeline"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"scripts_ready"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"scripts_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">345</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"processors"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"asr"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"yolo"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"face"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"pose"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"ocr"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"cut"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"scene"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"asrx"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"visual_chunk"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"models_ready"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"models_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"scripts_integrity"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"matched"</span><span class="p">:</span><span class="w"> </span><span class="mi">332</span><span class="p">,</span><span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">345</span><span class="p">,</span><span class="w"> </span><span class="nt">"ok"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"ffmpeg"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"schema"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"table_exists"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"applied"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="nt">"filename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"migrate_add_users_table.sql"</span><span class="p">}],</span>
|
||||
<span class="w"> </span><span class="nt">"required"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span>
|
||||
<span class="w"> </span><span class="nt">"ok"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"identities"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"directory_exists"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"files_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">3481</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"index_ok"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"db_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">3481</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"synced"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"integrations"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"api_key_configured"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"api_reachable"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response Fields</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>ok</code> if all essential services healthy</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>services</code></td>
|
||||
<td>object</td>
|
||||
<td>Per-service status (postgres, redis, qdrant)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>services.*.status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>ok</code>, <code>error</code>, or <code>degraded</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>services.*.latency_ms</code></td>
|
||||
<td>int</td>
|
||||
<td>Response time in milliseconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>resources</code></td>
|
||||
<td>object</td>
|
||||
<td>CPU, memory usage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pipeline.scripts_ready</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Scripts directory accessible</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pipeline.scripts_count</code></td>
|
||||
<td>int</td>
|
||||
<td>Number of Python processor scripts</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pipeline.processors</code></td>
|
||||
<td>object</td>
|
||||
<td>Per-processor availability</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pipeline.models_ready</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Models directory accessible</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pipeline.scripts_integrity</code></td>
|
||||
<td>object</td>
|
||||
<td>SHA256 checksum verification results</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>schema.ok</code></td>
|
||||
<td>boolean</td>
|
||||
<td>All required migrations applied</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>identities.synced</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Identity file count matches DB count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>integrations.tmdb</code></td>
|
||||
<td>object</td>
|
||||
<td>TMDB API key config and reachability</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Health status rules</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Condition</th>
|
||||
<th>status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>All services ok</td>
|
||||
<td><code>ok</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Any service error</td>
|
||||
<td><code>degraded</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Postgres or Redis error</td>
|
||||
<td><code>degraded</code> (server still responds)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3>Stats Endpoints</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Auth</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>GET</td>
|
||||
<td><code>/api/v1/stats/sftpgo</code></td>
|
||||
<td>No</td>
|
||||
<td>SFTPGo service status</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
444
deliverable_v1.1.0/html_docs/doc/03_register.html
Normal file
444
deliverable_v1.1.0/html_docs/doc/03_register.html
Normal file
@@ -0,0 +1,444 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>03 Register - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: register -->
|
||||
<!-- description: File registration — register, scan -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>File Registration</h2>
|
||||
<h3><code>POST /api/v1/files/register</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Register a video file for processing. Returns the file's metadata and UUID.</p>
|
||||
<p><strong>New in v0.1.2</strong>: Registration now <strong>automatically triggers the processing pipeline</strong> — no need to call <code>POST /api/v1/file/:file_uuid/process</code> separately. The system will:
|
||||
1. Register the file and run ffprobe
|
||||
2. Auto-run offline TMDb probe (reads local identity files, no API calls)
|
||||
3. Create a monitor job for the worker
|
||||
4. Worker starts all 10 processors (Cut → ASR → ASRX → YOLO → OCR → Face → Pose → VisualChunk → Story → 5W1H)</p>
|
||||
<p>If the file already exists (same content hash), returns the existing record with <code>already_exists: true</code>.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_path</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>—</td>
|
||||
<td>Path to video file on disk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pattern</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Regex pattern for batch register (requires <code>file_path</code> to be a directory)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>user_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>User ID to associate with registration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>content_hash</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Pre-computed SHA-256 hash (skips computation)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Register a single file</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/register"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_path": "/path/to/video.mp4"}'</span>
|
||||
|
||||
<span class="c1"># Batch register files matching a pattern in a directory</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/register"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_path": "/path/to/dir", "pattern": ".*\\.mp4$"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/path/to/video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"duration"</span><span class="p">:</span><span class="w"> </span><span class="mf">120.5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total_frames"</span><span class="p">:</span><span class="w"> </span><span class="mi">2892</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"already_exists"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"File registered successfully"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID of the registered file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>File name (auto-renamed if name conflict)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_path</code></td>
|
||||
<td>string</td>
|
||||
<td>Canonical path on disk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_type</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"video"</code>, <code>"audio"</code>, or <code>"unknown"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>duration</code></td>
|
||||
<td>float</td>
|
||||
<td>Duration in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>width</code></td>
|
||||
<td>integer</td>
|
||||
<td>Video width in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>height</code></td>
|
||||
<td>integer</td>
|
||||
<td>Video height in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fps</code></td>
|
||||
<td>float</td>
|
||||
<td>Frames per second</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total_frames</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total frame count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>already_exists</code></td>
|
||||
<td>boolean</td>
|
||||
<td>True if same content was already registered</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>string</td>
|
||||
<td>Human-readable status</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>Missing or invalid API key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>Invalid request body</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>File path does not exist</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/files/scan</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Scan the filesystem directory and list all media files, showing which are registered, processing, or unregistered.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number (1-based)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>all</td>
|
||||
<td>Items per page (alias: <code>limit</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>all</td>
|
||||
<td>Max items (alias for <code>page_size</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pattern</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Regex filter on file name (e.g., <code>.*\\.mp4$</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sort_by</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>name</code></td>
|
||||
<td>Sort field: <code>name</code>, <code>size</code>, <code>modified</code>, <code>status</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>sort_order</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>asc</code></td>
|
||||
<td>Sort direction: <code>asc</code> or <code>desc</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Full scan</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{total, registered_count, unregistered_count}'</span>
|
||||
|
||||
<span class="c1"># Paginated (page 1, 5 per page)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?page=1&page_size=5"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{page, total_pages, files: [.files[].file_name]}'</span>
|
||||
|
||||
<span class="c1"># Regex filter: only mp4 files</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?pattern=.*\\.mp4</span>$<span class="s2">"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{filtered_total, files: [.files[].file_name]}'</span>
|
||||
|
||||
<span class="c1"># Sort by file size (largest first)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?sort_by=size&sort_order=desc&page_size=5"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'[.files[] | {file_name, file_size}]'</span>
|
||||
|
||||
<span class="c1"># Sort by modified time (most recent first)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?sort_by=modified&sort_order=desc&page_size=5"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'[.files[] | {file_name, modified_time}]'</span>
|
||||
|
||||
<span class="c1"># Sort by status</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/scan?sort_by=status&page_size=5"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'[.files[] | {file_name, status}]'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"files"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">12345678</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"is_registered"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"completed"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"registration_time"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-16T12:00:00Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"job_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">107</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"filtered_total"</span><span class="p">:</span><span class="w"> </span><span class="mi">80</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total_pages"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"registered_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">26</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"unregistered_count"</span><span class="p">:</span><span class="w"> </span><span class="mi">81</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>files</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of file info objects (paginated)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>File name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].relative_path</code></td>
|
||||
<td>string</td>
|
||||
<td>Path relative to scan root</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].file_path</code></td>
|
||||
<td>string</td>
|
||||
<td>Absolute path on disk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].file_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>File size in bytes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].modified_time</code></td>
|
||||
<td>string</td>
|
||||
<td>Last modified timestamp (ISO8601)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].is_registered</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Whether file is registered in DB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID (only if registered)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"completed"</code>, <code>"processing"</code>, <code>"registered"</code>, <code>"unregistered"</code>, or <code>null</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].registration_time</code></td>
|
||||
<td>string</td>
|
||||
<td>DB registration timestamp (only if registered)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>files[].job_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Processing job ID (only if a job exists)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total files found on disk (unfiltered)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>filtered_total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Files matching regex filter</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total_pages</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total pages</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>registered_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Files registered in DB</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>unregistered_count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Files not yet registered</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Notes</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Regex</strong></td>
|
||||
<td>Case-insensitive (<code>(?i)</code> prefix auto-applied). Applied to <code>file_name</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Sort order</strong></td>
|
||||
<td>Default (<code>sort_by=name</code>): registered files first, then alphabetically. <code>sort_by=status</code>: alphabetical by status string.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Pagination</strong></td>
|
||||
<td><code>page_size</code> and <code>limit</code> are aliases. Default: show all results.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Processing order</strong></td>
|
||||
<td><code>pattern</code> regex filter → <code>sort_by</code>/<code>sort_order</code> → <code>page</code>/<code>page_size</code> slice.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
291
deliverable_v1.1.0/html_docs/doc/04_lookup.html
Normal file
291
deliverable_v1.1.0/html_docs/doc/04_lookup.html
Normal file
@@ -0,0 +1,291 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>04 Lookup - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: lookup -->
|
||||
<!-- description: File lookup by name and unregistration -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
<h2>File Lookup</h2>
|
||||
<h3><code>GET /api/v1/files/lookup</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Search registered files by file name. Performs a case-insensitive LIKE search on the file name column. Returns basic info about matching files.</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>File name to search for (partial matches supported)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Look up a specific file</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/lookup?file_name=video.mp4"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
|
||||
<span class="c1"># Partial name search</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/files/lookup?file_name=charade"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'.matches[].file_name'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"exists"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"matches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a03485a40b2df2d3"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"completed"</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"next_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video (2).mp4"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Searched name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>exists</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Exact name match exists</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of matching registered files</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Registered file name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].file_type</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"video"</code>, <code>"audio"</code>, or <code>null</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>matches[].status</code></td>
|
||||
<td>string</td>
|
||||
<td>Registration/processing status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>next_name</code></td>
|
||||
<td>string</td>
|
||||
<td>Suggested name for avoiding conflicts</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h2>Unregister</h2>
|
||||
<h3><code>POST /api/v1/unregister</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Delete a registered file from the system. Supports single file by UUID, or batch by directory + regex pattern.</p>
|
||||
<h4>What gets deleted</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Removed (default)</th>
|
||||
<th>Not removed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Database records (videos, chunks, embeddings, processor_results, pre_chunks)</td>
|
||||
<td>The original source video file on disk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Processor output JSON files (<code>{uuid}.*.json</code>) — unless <code>delete_output_files: false</code></td>
|
||||
<td>Temp/working directories</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>In-memory cache entries</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MongoDB cached lists</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<blockquote>
|
||||
<p>⚠️ Database deletion is <strong>irreversible</strong>. To keep output files, set <code>"delete_output_files": false</code>.</p>
|
||||
</blockquote>
|
||||
<h4>Request Parameters</h4>
|
||||
<p>At least one mode must be specified: either <code>file_uuid</code> alone, or <code>file_path</code> + <code>pattern</code> together.</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>*</td>
|
||||
<td>—</td>
|
||||
<td>Single file UUID to delete</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_path</code></td>
|
||||
<td>string</td>
|
||||
<td>*</td>
|
||||
<td>—</td>
|
||||
<td>Directory path (for batch delete)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pattern</code></td>
|
||||
<td>string</td>
|
||||
<td>*</td>
|
||||
<td>—</td>
|
||||
<td>Regex pattern (requires <code>file_path</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>delete_output_files</code></td>
|
||||
<td>boolean</td>
|
||||
<td>No</td>
|
||||
<td><code>true</code></td>
|
||||
<td>If <code>true</code>, also delete processor output JSON files (<code>{uuid}.*.json</code>). Set to <code>false</code> to keep them.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Delete a single file by UUID (default: also deletes output JSON files)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/unregister"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'"}'</span>
|
||||
|
||||
<span class="c1"># Keep output JSON files, only delete DB records</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/unregister"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "delete_output_files": false}'</span>
|
||||
|
||||
<span class="c1"># Batch delete all mp4 files in a directory</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/unregister"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_path": "/path/to/dir", "pattern": ".*\\.mp4$"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a03485a40b2df2d3"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Video unregistered successfully"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>True if deletion succeeded</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>UUID of the deleted file (single mode)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>string</td>
|
||||
<td>Human-readable status</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>Neither <code>file_uuid</code> nor <code>file_path</code>+<code>pattern</code> provided</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>File UUID not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>Missing or invalid API key</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
505
deliverable_v1.1.0/html_docs/doc/05_process.html
Normal file
505
deliverable_v1.1.0/html_docs/doc/05_process.html
Normal file
@@ -0,0 +1,505 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>05 Process - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: process -->
|
||||
<!-- description: Processing pipeline — trigger, probe, progress, jobs -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
<h2>Processing Pipeline</h2>
|
||||
<h3><code>POST /api/v1/file/:file_uuid/process</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Trigger the processing pipeline for a registered file. Creates a monitor job that the worker picks up and processes sequentially. Returns immediately with the job info—processing runs asynchronously in the background.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>processors</code></td>
|
||||
<td>string[]</td>
|
||||
<td>No</td>
|
||||
<td>all</td>
|
||||
<td>Specific processors to run: <code>["cut","asr","asrx","yolo","ocr","face","pose","visual_chunk","story","5w1h"]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rules</code></td>
|
||||
<td>string[]</td>
|
||||
<td>No</td>
|
||||
<td>all</td>
|
||||
<td>Rule names to apply (currently unused)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Run all processors</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/process"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span>-d<span class="w"> </span><span class="s1">'{}'</span>
|
||||
|
||||
<span class="c1"># Run specific processors only</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/process"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"processors": ["asr", "face", "yolo"]}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"job_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"processing"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"pids"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="mi">12345</span><span class="p">,</span><span class="w"> </span><span class="mi">12346</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Processing triggered for video.mp4"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>success</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Always true on 200</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>job_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Monitor job ID (for job tracking)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID of the file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"processing"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pids</code></td>
|
||||
<td>integer[]</td>
|
||||
<td>Process IDs of started processors</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>message</code></td>
|
||||
<td>string</td>
|
||||
<td>Human-readable status</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Error Responses</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>File UUID not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>401</code></td>
|
||||
<td>Missing or invalid API key</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/probe</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get ffprobe metadata for a registered file. Returns video/audio stream info, codec details, duration, resolution, and frame rate.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/probe"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">794863677</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"duration"</span><span class="p">:</span><span class="w"> </span><span class="mf">120.5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"total_frames"</span><span class="p">:</span><span class="w"> </span><span class="mi">2892</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"cached"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"format"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"filename"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/path/to/video.mp4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"format_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mov,mp4,m4a,3gp"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"duration"</span><span class="p">:</span><span class="w"> </span><span class="s2">"120.5"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"size"</span><span class="p">:</span><span class="w"> </span><span class="s2">"12345678"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"bit_rate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"819200"</span>
|
||||
<span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="nt">"streams"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"index"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"codec_name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"h264"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"codec_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"video"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1080</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"r_frame_rate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"24/1"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"duration"</span><span class="p">:</span><span class="w"> </span><span class="s2">"120.5"</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_name</code></td>
|
||||
<td>string</td>
|
||||
<td>File name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>File size in bytes (from filesystem)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>duration</code></td>
|
||||
<td>float</td>
|
||||
<td>Duration in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>width</code></td>
|
||||
<td>integer</td>
|
||||
<td>Video width in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>height</code></td>
|
||||
<td>integer</td>
|
||||
<td>Video height in pixels</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fps</code></td>
|
||||
<td>float</td>
|
||||
<td>Frames per second</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>total_frames</code></td>
|
||||
<td>integer</td>
|
||||
<td>Estimated total frames</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cached</code></td>
|
||||
<td>boolean</td>
|
||||
<td>True if result was from cached probe JSON</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>format</code></td>
|
||||
<td>object</td>
|
||||
<td>Container format info (ffprobe format section)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>streams</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of stream info objects</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/progress/:file_uuid</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Get real-time processing progress for a file via Redis pub/sub. Includes per-processor status, current/total frames, ETA, and system resource stats.</p>
|
||||
<h4>Pipeline Order</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Order</th>
|
||||
<th>Processor</th>
|
||||
<th>Dependencies</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><code>cut</code></td>
|
||||
<td>—</td>
|
||||
<td>Scene detection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><code>asr</code></td>
|
||||
<td>cut</td>
|
||||
<td>Speech-to-text (per scene)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td><code>asrx</code></td>
|
||||
<td>asr</td>
|
||||
<td>Speaker diarization</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><code>yolo</code></td>
|
||||
<td>—</td>
|
||||
<td>Object detection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td><code>ocr</code></td>
|
||||
<td>—</td>
|
||||
<td>Text recognition</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><code>face</code></td>
|
||||
<td>—</td>
|
||||
<td>Face detection & embedding</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td><code>pose</code></td>
|
||||
<td>—</td>
|
||||
<td>Pose estimation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td><code>visual_chunk</code></td>
|
||||
<td>yolo</td>
|
||||
<td>Visual scene chunks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td><code>story</code></td>
|
||||
<td>asr, asrx, cut, yolo, face</td>
|
||||
<td>Scene summaries (template)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td><code>5w1h</code></td>
|
||||
<td>story</td>
|
||||
<td>5W1H analysis (Gemma4 LLM)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>All processors except <code>story</code> and <code>5w1h</code> run concurrently when their dependencies are met. Story and 5W1H run sequentially after their prerequisites.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/progress/</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{overall_progress, processors: [.processors[] | {processor_type, status}]}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"overall_progress"</span><span class="p">:</span><span class="w"> </span><span class="mi">71</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"cpu_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">45.2</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"gpu_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">30.1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"memory_percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">62.4</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"processors"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="nt">"processor_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"asr"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"complete"</span><span class="p">,</span><span class="w"> </span><span class="nt">"progress"</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="nt">"processor_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yolo"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"running"</span><span class="p">,</span><span class="w"> </span><span class="nt">"progress"</span><span class="p">:</span><span class="w"> </span><span class="mi">65</span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="nt">"processor_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"progress"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>32-char hex UUID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>overall_progress</code></td>
|
||||
<td>integer</td>
|
||||
<td>Overall progress percentage (0–100)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors</code></td>
|
||||
<td>array</td>
|
||||
<td>Per-processor status list</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].processor_type</code></td>
|
||||
<td>string</td>
|
||||
<td>Processor name (<code>asr</code>, <code>cut</code>, <code>yolo</code>, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"pending"</code>, <code>"running"</code>, <code>"complete"</code>, or <code>"failed"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].progress</code></td>
|
||||
<td>integer</td>
|
||||
<td>Per-processor progress (0–100)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].eta_seconds</code></td>
|
||||
<td>integer</td>
|
||||
<td>Estimated seconds remaining (running processors)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].current</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current frame count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>processors[].total</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total frame count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>cpu_percent</code></td>
|
||||
<td>float</td>
|
||||
<td>Current CPU usage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>gpu_percent</code></td>
|
||||
<td>float</td>
|
||||
<td>Current GPU utilization</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>memory_percent</code></td>
|
||||
<td>float</td>
|
||||
<td>Current memory usage</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/jobs</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>List all processing jobs (monitor jobs) in the system. Shows job status, which file each job is processing, and current processor info.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/jobs"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{count, jobs: [.jobs[] | {uuid, status}]}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"jobs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3a6c1865..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"running"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"current_processor"</span><span class="p">:</span><span class="w"> </span><span class="s2">"yolo"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-16T12:00:00Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"started_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-16T12:01:00Z"</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"count"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>jobs</code></td>
|
||||
<td>array</td>
|
||||
<td>Array of job info objects</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>jobs[].id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Job ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>jobs[].uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>File UUID being processed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>jobs[].status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"pending"</code>, <code>"running"</code>, <code>"completed"</code>, <code>"failed"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>jobs[].current_processor</code></td>
|
||||
<td>string</td>
|
||||
<td>Currently active processor, or null</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>count</code></td>
|
||||
<td>integer</td>
|
||||
<td>Total job count</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>Current page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>Jobs per page</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
280
deliverable_v1.1.0/html_docs/doc/06_search.html
Normal file
280
deliverable_v1.1.0/html_docs/doc/06_search.html
Normal file
@@ -0,0 +1,280 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>06 Search - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: search -->
|
||||
<!-- description: Vector search, BM25, smart search, universal search, visual search -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Search APIs</h2>
|
||||
<h3><code>POST /api/v1/search/smart</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Semantic vector search using EmbeddingGemma-300m. Generates a query embedding via EmbeddingGemma (port 11436), then searches pgvector <code>story_parent</code> and <code>llm_parent</code> chunks by cosine similarity.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>—</td>
|
||||
<td>File UUID to search within</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>query</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>—</td>
|
||||
<td>Search text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>5</td>
|
||||
<td>Max results to return</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>5</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/search/smart"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "query": "Audrey Hepburn"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"query"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Audrey Hepburn"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"results"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"parent_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">1087822</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"scene_order"</span><span class="p">:</span><span class="w"> </span><span class="mi">1087822</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">104438</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">104538</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">4351.6</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">4355.76</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"summary"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[4352s-4356s, 4s] Cast: Audrey Hepburn. Total: 2 lines, 10 words. Speakers: Audrey Hepburn (2 lines)"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"similarity"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.67</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"page"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"page_size"</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"strategy"</span><span class="p">:</span><span class="w"> </span><span class="s2">"semantic_vector_search"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/search/universal</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Multi-type BM25 full-text search across chunks, frames, and persons. Uses PostgreSQL <code>tsvector</code>.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>query</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>—</td>
|
||||
<td>Search text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Restrict to specific file</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>types</code></td>
|
||||
<td>string[]</td>
|
||||
<td>No</td>
|
||||
<td><code>["chunk","frame","person"]</code></td>
|
||||
<td>Search types</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>limit</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>10</td>
|
||||
<td>Max results per type</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>1</td>
|
||||
<td>Page number</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>page_size</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>20</td>
|
||||
<td>Items per page</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/search/universal"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "query": "Cary Grant"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"results"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"chunk"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bd80fec92b0b6963d177a2c55bf713e2_2"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"story_child"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5103</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5127</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">212.64</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">213.64</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[213s-214s] Cary Grant: \"Olá!\""</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"score"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.9</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"total"</span><span class="p">:</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"took_ms"</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/search/frames</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Search face detection frames by identity name or trace ID.</p>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/search/identity_text</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Search text chunks spoken by a specific identity.</p>
|
||||
<hr />
|
||||
<h3>Visual Search</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual</code></td>
|
||||
<td>Search visual chunks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/class</code></td>
|
||||
<td>Search by object class</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/density</code></td>
|
||||
<td>Search by object density</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/combination</code></td>
|
||||
<td>Search by object combination</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>POST</td>
|
||||
<td><code>/api/v1/search/visual/stats</code></td>
|
||||
<td>Visual chunk statistics</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Embedding Model</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Detail</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Model</strong></td>
|
||||
<td>EmbeddingGemma-300m</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Endpoint</strong></td>
|
||||
<td><code>POST /api/v1/embeddings</code> on port 11436</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Dimension</strong></td>
|
||||
<td>768</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Storage</strong></td>
|
||||
<td>pgvector (<code>chunk.embedding</code> column)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
510
deliverable_v1.1.0/html_docs/doc/07_identity.html
Normal file
510
deliverable_v1.1.0/html_docs/doc/07_identity.html
Normal file
@@ -0,0 +1,510 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>07 Identity - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: identity -->
|
||||
<!-- description: Global identities — CRUD, detail, files, faces, bind, unbind, search -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Global Identities</h2>
|
||||
<h3><code>GET /api/v1/identities</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>List all registered identities with pagination.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identities?page=1&page_size=20"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{count, identities: [.identities[] | {name}]}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identity/:identity_uuid</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Get detailed information for a specific identity, including metadata and TMDb references.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Cary Grant"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"people"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"source"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tmdb"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"confirmed"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">112</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"tmdb_profile"</span><span class="p">:</span><span class="w"> </span><span class="s2">"{output}/identities/{identity_uuid}/profile.jpg"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"metadata"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
|
||||
<span class="w"> </span><span class="nt">"reference_data"</span><span class="p">:</span><span class="w"> </span><span class="p">{},</span>
|
||||
<span class="w"> </span><span class="nt">"created_at"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2026-05-16T12:00:00Z"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"updated_at"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>identity_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Identity identifier</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>name</code></td>
|
||||
<td>string</td>
|
||||
<td>Identity name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>identity_type</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"people"</code> or null</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>source</code></td>
|
||||
<td>string</td>
|
||||
<td><code>.json</code>, <code>auto</code>, <code>tmdb</code>, <code>user_defined</code>, or <code>merged</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td><code>"confirmed"</code>, <code>"pending"</code>, or <code>"inactive"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tmdb_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>TMDb person ID (only if source = tmdb)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tmdb_profile</code></td>
|
||||
<td>string</td>
|
||||
<td>Local profile image path (<code>{output}/identities/{uuid}/profile.jpg</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>metadata</code></td>
|
||||
<td>object</td>
|
||||
<td>Metadata JSON (tmdb_character, cast_order, etc.)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>created_at</code></td>
|
||||
<td>string</td>
|
||||
<td>Creation timestamp</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>DELETE /api/v1/identity/:identity_uuid</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Delete an identity permanently.</p>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identity/:identity_uuid/files</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Get all files where this identity appears. Returns per-file summary including face count, confidence, and appearance time range.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/files"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identity/:identity_uuid/faces</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Get all face detection records associated with this identity.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/faces"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>File where face was detected</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>frame_number</code></td>
|
||||
<td>integer</td>
|
||||
<td>Frame number of detection</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>face_id</code></td>
|
||||
<td>string</td>
|
||||
<td>Face ID (format: <code>face_{frame_number}</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>confidence</code></td>
|
||||
<td>float</td>
|
||||
<td>Detection confidence</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identity/:identity_uuid/chunks</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Get all text chunks (sentences) spoken while this identity's face was on screen. Useful for finding what a person said.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/chunks"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bd80fec92b0b6963d177a2c55bf713e2"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bd80fec92b0b6963d177a2c55bf713e2_2"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"chunk_type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sentence"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5103</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_frame"</span><span class="p">:</span><span class="w"> </span><span class="mi">5127</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"fps"</span><span class="p">:</span><span class="w"> </span><span class="mf">24.0</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"start_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">212.64</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"end_time"</span><span class="p">:</span><span class="w"> </span><span class="mf">213.64</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"text_content"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[213s-214s] Cary Grant: \"Olá!\""</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>File identifier</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>chunk_id</code></td>
|
||||
<td>string</td>
|
||||
<td>Sentence chunk identifier</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>start_frame</code></td>
|
||||
<td>integer</td>
|
||||
<td>Frame-accurate start position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>end_frame</code></td>
|
||||
<td>integer</td>
|
||||
<td>Frame-accurate end position</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fps</code></td>
|
||||
<td>float</td>
|
||||
<td>Frames per second</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>start_time</code></td>
|
||||
<td>float</td>
|
||||
<td>Start time in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>end_time</code></td>
|
||||
<td>float</td>
|
||||
<td>End time in seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>text_content</code></td>
|
||||
<td>string</td>
|
||||
<td>Spoken text content</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/identity/:identity_uuid/bind</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Bind a face detection to an identity. Associates the face trace with the identity for future search and recognition.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>File where face is detected</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>face_id</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>Face ID (format: <code>{frame}_{idx}</code>)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/bind"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "face_id": "1_5"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/identity/:identity_uuid/unbind</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Unbind a face detection from an identity. Removes the identity association from the face record.</p>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identities/search</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Search identities by name (ILIKE search). Returns matching identity records.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identities/search?q=Cary"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>name</code></td>
|
||||
<td>string</td>
|
||||
<td>Identity name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>source</code></td>
|
||||
<td>string</td>
|
||||
<td>Identity source</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tmdb_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>TMDb ID (if source = tmdb)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Associated file</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/identity/upload</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Upload an identity.json file to create or update an identity. Accepts the same format as the identity.json files stored on disk.</p>
|
||||
<p>If an identity with the same <code>name</code> already exists, it will be updated with the new values.</p>
|
||||
<h4>Request</h4>
|
||||
<p>The request body is an <code>IdentityFile</code> object:</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>identity_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>Identity identifier</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>name</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>Identity display name</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>identity_type</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>"people"</code> or null</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>source</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>.json</code>, <code>auto</code>, <code>tmdb</code>, <code>user_defined</code>, or <code>merged</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>status</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>"confirmed"</code>, <code>"pending"</code>, or <code>"inactive"</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tmdb_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>TMDb person ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tmdb_profile</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td>TMDb profile image URL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>metadata</code></td>
|
||||
<td>object</td>
|
||||
<td>No</td>
|
||||
<td>Arbitrary metadata JSON</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>file_bindings</code></td>
|
||||
<td>array</td>
|
||||
<td>No</td>
|
||||
<td>Array of <code>{ file_uuid, trace_ids, face_count }</code> (informational)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/upload"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{</span>
|
||||
<span class="s1"> "version": 1,</span>
|
||||
<span class="s1"> "identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",</span>
|
||||
<span class="s1"> "name": "Cary Grant",</span>
|
||||
<span class="s1"> "identity_type": "people",</span>
|
||||
<span class="s1"> "source": ".json",</span>
|
||||
<span class="s1"> "status": "confirmed",</span>
|
||||
<span class="s1"> "metadata": {},</span>
|
||||
<span class="s1"> "file_bindings": []</span>
|
||||
<span class="s1"> }'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Cary Grant"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Identity uploaded successfully"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/identity/:identity_uuid/profile-image</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Upload a profile image (JPEG or PNG) for an identity. The image is saved to <code>{output}/identities/{uuid}/profile.{ext}</code>.</p>
|
||||
<p>Uses <code>multipart/form-data</code> with field name <code>image</code>.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/profile-image"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-F<span class="w"> </span><span class="s2">"image=@/path/to/photo.jpg"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a901056d6b46ff92da0c3c1a57dff4"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/path/to/output/identities/.../profile.jpg"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Profile image saved: profile.jpg"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Error Responses</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>HTTP</th>
|
||||
<th>When</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>400</code></td>
|
||||
<td>Missing image field or unsupported format</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>404</code></td>
|
||||
<td>Identity not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>415</code></td>
|
||||
<td>Unsupported image type (use JPEG or PNG)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/identity/:identity_uuid/profile-image</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: identity-level</p>
|
||||
<p>Retrieve the profile image for an identity. Returns the raw image data with appropriate Content-Type header.</p>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/identity/</span><span class="nv">$IDENTITY_UUID</span><span class="s2">/profile-image"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>profile.jpg
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Response Header</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>content-type</code></td>
|
||||
<td><code>image/jpeg</code> or <code>image/png</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
97
deliverable_v1.1.0/html_docs/doc/08_identity_agent.html
Normal file
97
deliverable_v1.1.0/html_docs/doc/08_identity_agent.html
Normal file
@@ -0,0 +1,97 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>08 Identity Agent - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: identity_agent -->
|
||||
<!-- description: Identity agent — match from photo, match from trace -->
|
||||
<!-- depends: 01_auth, 07_identity -->
|
||||
|
||||
<h2>Identity Agent</h2>
|
||||
<h3><code>POST /api/v1/agents/identity/match-from-photo</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Upload a face photo to match against known identities. Detects face via InsightFace, extracts 512D embedding via CoreML FaceNet, then searches pgvector for the closest identity.</p>
|
||||
<h4>Request</h4>
|
||||
<p><code>multipart/form-data</code> with field <code>image</code> (JPEG/PNG) and optional <code>file_uuid</code>.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/agents/identity/match-from-photo"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-F<span class="w"> </span><span class="s2">"image=@/path/to/face.jpg"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-F<span class="w"> </span><span class="s2">"file_uuid=</span><span class="nv">$FILE_UUID</span><span class="s2">"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"matches"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"identity_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"a9a90105..."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Cary Grant"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"similarity"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.87</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<hr />
|
||||
<h3><code>POST /api/v1/agents/identity/match-from-trace</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Match a face trace (tracked face across frames) against known identities. Samples 3 angles from the trace, generates embeddings, and searches pgvector.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>File containing the trace</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>trace_id</code></td>
|
||||
<td>integer</td>
|
||||
<td>Yes</td>
|
||||
<td>Face trace ID to match</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/agents/identity/match-from-trace"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'", "trace_id": 10}'</span>
|
||||
</code></pre></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
303
deliverable_v1.1.0/html_docs/doc/08_media.html
Normal file
303
deliverable_v1.1.0/html_docs/doc/08_media.html
Normal file
@@ -0,0 +1,303 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>08 Media - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: media -->
|
||||
<!-- description: Video streaming & frame extraction -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Video Streaming & Frame Extraction</h2>
|
||||
<p>All video streaming endpoints support the following common query parameters:</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>mode</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>normal</code></td>
|
||||
<td><code>normal</code> or <code>debug</code> (draws detection overlays)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>on</code></td>
|
||||
<td><code>on</code> or <code>off</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/video</code></h3>
|
||||
<p>Stream the full video file with range support for seeking.</p>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<h4>Response</h4>
|
||||
<ul>
|
||||
<li><strong>200</strong>: Video stream (<code>Content-Type</code> based on file extension)</li>
|
||||
<li><strong>206</strong>: Partial content (range request)</li>
|
||||
<li>Supports <code>Range</code> header for seeking</li>
|
||||
</ul>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/trace/:trace_id/video</code></h3>
|
||||
<p>Stream video with highlights for a specific face trace (follows a single person across frames with bounding box overlay).</p>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/video/bbox</code></h3>
|
||||
<p>Stream video with bounding box overlay for all detected objects/faces.</p>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Uses a built-in 5×7 bitmap font renderer to draw labels directly on video frames via FFmpeg <code>drawtext</code> filter.</p>
|
||||
<hr />
|
||||
<h3><code>GET /api/v1/file/:file_uuid/thumbnail</code></h3>
|
||||
<p>Extract a single frame from a video as JPEG image. Uses FFmpeg <code>select</code> filter.</p>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>frame</code></td>
|
||||
<td>integer</td>
|
||||
<td>Yes</td>
|
||||
<td>—</td>
|
||||
<td>Zero-based frame number to extract</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>x</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Crop start X (left edge). Requires <code>y</code>, <code>w</code>, <code>h</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>y</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Crop start Y (top edge). Requires <code>x</code>, <code>w</code>, <code>h</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>w</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Crop width in pixels. Requires <code>x</code>, <code>y</code>, <code>h</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>h</code></td>
|
||||
<td>integer</td>
|
||||
<td>No</td>
|
||||
<td>—</td>
|
||||
<td>Crop height in pixels. Requires <code>x</code>, <code>y</code>, <code>w</code>.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>All four crop params (<code>x</code>, <code>y</code>, <code>w</code>, <code>h</code>) must be provided together or omitted.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Extract frame 1000 (full frame)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>frame_1000.jpg
|
||||
|
||||
<span class="c1"># Extract and crop face region (x=320, y=240, w=160, h=160)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000&x=320&y=240&w=160&h=160"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>face_crop.jpg
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<ul>
|
||||
<li><strong>200</strong>: <code>image/jpeg</code> binary data</li>
|
||||
<li><strong>404</strong>: File not found</li>
|
||||
<li><strong>500</strong>: FFmpeg error (e.g., frame number exceeds video duration)</li>
|
||||
</ul>
|
||||
<h3><code>GET /api/v1/file/:file_uuid/clip</code></h3>
|
||||
<p>Extract a video clip (time range) as MPEG-TS stream. Uses FFmpeg <code>-ss</code> fast seek.</p>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<h4>Query Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>start_frame</code></td>
|
||||
<td>integer</td>
|
||||
<td>No*</td>
|
||||
<td>—</td>
|
||||
<td>Start frame (zero-based). <strong>Frame-accurate</strong> — use this for precision.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>end_frame</code></td>
|
||||
<td>integer</td>
|
||||
<td>No*</td>
|
||||
<td>—</td>
|
||||
<td>End frame (zero-based, inclusive). Requires <code>start_frame</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>start_time</code></td>
|
||||
<td>float</td>
|
||||
<td>No*</td>
|
||||
<td>—</td>
|
||||
<td>Start time in seconds. Approximate (FPS-dependent). Fallback if frames not given.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>end_time</code></td>
|
||||
<td>float</td>
|
||||
<td>No*</td>
|
||||
<td>—</td>
|
||||
<td>End time in seconds. Approximate (FPS-dependent). Fallback if frames not given.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fps</code></td>
|
||||
<td>float</td>
|
||||
<td>No</td>
|
||||
<td>video FPS</td>
|
||||
<td>Override frames-per-second for frame↔time calculation. Defaults to video's detected FPS.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>mode</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>normal</code></td>
|
||||
<td><code>normal</code> or <code>debug</code> (draws "CLIP" overlay)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>audio</code></td>
|
||||
<td>string</td>
|
||||
<td>No</td>
|
||||
<td><code>on</code></td>
|
||||
<td><code>on</code> or <code>off</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Either (<code>start_frame</code>+<code>end_frame</code>) OR (<code>start_time</code>+<code>end_time</code>) must be provided.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="c1"># Clip by frame range (primary)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_frame=0&end_frame=47"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>clip.ts
|
||||
|
||||
<span class="c1"># Clip by time range (fallback)</span>
|
||||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_time=30&end_time=45"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>clip.ts
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<ul>
|
||||
<li><strong>200</strong>: <code>video/mp2t</code> MPEG-TS stream</li>
|
||||
<li><strong>400</strong>: Missing/invalid range parameters</li>
|
||||
<li><strong>404</strong>: File not found</li>
|
||||
<li><strong>500</strong>: FFmpeg error</li>
|
||||
</ul>
|
||||
<h4>Technical Notes</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Detail</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Backend</strong></td>
|
||||
<td>FFmpeg (<code>ffmpeg-full</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Seek</strong></td>
|
||||
<td><code>-ss</code> before <code>-i</code> (fast keyframe seek)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Format</strong></td>
|
||||
<td>MPEG-TS (<code>mpegts</code> muxer, pipe-safe)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Codec</strong></td>
|
||||
<td>H.264 + AAC</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Cache</strong></td>
|
||||
<td><code>Cache-Control: public, max-age=86400</code> (24h)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Detail</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Backend</strong></td>
|
||||
<td>FFmpeg (<code>ffmpeg-full</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Filter</strong></td>
|
||||
<td><code>select=eq(n\,FRAME)</code> to select frame, optional <code>crop=W:H:X:Y</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Output</strong></td>
|
||||
<td>Single JPEG via pipe (<code>image2pipe</code>, <code>mjpeg</code> codec)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Cache</strong></td>
|
||||
<td><code>Cache-Control: public, max-age=86400</code> (24h)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Frame number</strong></td>
|
||||
<td>Zero-based (<code>frame=0</code> = first frame of video)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
123
deliverable_v1.1.0/html_docs/doc/09_tmdb.html
Normal file
123
deliverable_v1.1.0/html_docs/doc/09_tmdb.html
Normal file
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>09 Tmdb - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: tmdb -->
|
||||
<!-- description: TMDb enrichment endpoints — prefetch, probe, resource, check -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
<h2>TMDb Enrichment</h2>
|
||||
<blockquote>
|
||||
<p><strong>Offline operation</strong>: TMDb prefetch now checks local identity files first (<code>identities/_index.json</code> + <code>*.tmdb.json</code>).
|
||||
If local files exist, no external API call is made. Internet is only needed for initial data seeding.</p>
|
||||
</blockquote>
|
||||
<h3>Overview</h3>
|
||||
<p>TMDb enrichment is an optional identity enrichment step that can be run after Pipeline face detection completes. The workflow is:</p>
|
||||
<ol>
|
||||
<li><strong>Prefetch</strong> (requires internet): Download movie cast data from TMDb API → cache to <code>{file_uuid}.tmdb.json</code></li>
|
||||
<li><strong>Probe</strong>: Read local cache → create identities for <strong>all</strong> cast members (<code>source='tmdb'</code>) + save <code>identity.json</code> + download profile image to <code>{OUTPUT}/identities/{uuid}/profile.jpg</code></li>
|
||||
<li><strong>Match</strong>: The worker automatically matches video faces against TMDb identities when <code>MOMENTRY_TMDB_PROBE_ENABLED=true</code></li>
|
||||
</ol>
|
||||
<h3><code>POST /api/v1/agents/tmdb/prefetch</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Fetch TMDb cast data for a registered file and cache it locally. This is the only step requiring internet access.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>file_uuid</code></td>
|
||||
<td>string</td>
|
||||
<td>Yes</td>
|
||||
<td>File UUID to enrich</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/agents/tmdb/prefetch"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"file_uuid": "'</span><span class="s2">"</span><span class="nv">$FILE_UUID</span><span class="s2">"</span><span class="s1">'"}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"..."</span><span class="p">,</span><span class="w"> </span><span class="nt">"cache_path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/output/...tmdb.json"</span><span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3><code>POST /api/v1/file/:file_uuid/tmdb-probe</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: file-level</p>
|
||||
<p>Read local TMDb cache and create/update identities. Requires prefetch to have been run first.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/</span><span class="nv">$FILE_UUID</span><span class="s2">/tmdb-probe"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{identities_created, movie_title}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200 — identities created)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nt">"identities_created"</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="p">,</span><span class="w"> </span><span class="nt">"movie_title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Charade"</span><span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response (200 — no cache)</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"No TMDb cache found. Run tmdb-prefetch first."</span><span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3><code>GET /api/v1/resource/tmdb</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>View TMDb resource status including configuration, identity counts, and cache file count.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb"</span><span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'{identities_seeded, cache_files}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3><code>POST /api/v1/resource/tmdb/check</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Ping the TMDb API to verify connectivity and measure latency.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/resource/tmdb/check"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'.status'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"api_key_configured"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"enabled"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"api_reachable"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"api_latency_ms"</span><span class="p">:</span><span class="w"> </span><span class="mi">120</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
364
deliverable_v1.1.0/html_docs/doc/10_pipeline.html
Normal file
364
deliverable_v1.1.0/html_docs/doc/10_pipeline.html
Normal file
@@ -0,0 +1,364 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>10 Pipeline - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: pipeline -->
|
||||
<!-- description: Pipeline processors, ingestion status, stats endpoints -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
<h2>Pipeline</h2>
|
||||
<h3>Dependency Graph</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="n">flowchart</span><span class="w"> </span><span class="n">TB</span>
|
||||
<span class="w"> </span><span class="n">subgraph</span><span class="w"> </span><span class="n">Processors</span><span class="p">[</span><span class="s">"10 Processors"</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Cut</span><span class="p">[</span><span class="n">Cut</span><span class="p">]</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">ASR</span><span class="p">[</span><span class="n">ASR</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASR</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">ASRX</span><span class="p">[</span><span class="n">ASRX</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASRX</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Story</span><span class="p">[</span><span class="n">Story</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Cut</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Story</span>
|
||||
<span class="w"> </span><span class="n">YOLO</span><span class="p">[</span><span class="n">YOLO</span><span class="p">]</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">VisualChunk</span><span class="p">[</span><span class="n">VisualChunk</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">VisualChunk</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Story</span>
|
||||
<span class="w"> </span><span class="n">Face</span><span class="p">[</span><span class="n">Face</span><span class="p">]</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Story</span>
|
||||
<span class="w"> </span><span class="n">Story</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">FiveW1H</span><span class="p">[</span><span class="mi">5</span><span class="n">W1H</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">OCR</span><span class="p">[</span><span class="n">OCR</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Pose</span><span class="p">[</span><span class="n">Pose</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">end</span>
|
||||
|
||||
<span class="w"> </span><span class="n">subgraph</span><span class="w"> </span><span class="n">Ingestion</span><span class="p">[</span><span class="s">"入庫 (Post-Processing)"</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASR</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Rule1</span><span class="p">[</span><span class="n">Rule</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">Sentence</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASRX</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Rule1</span>
|
||||
<span class="w"> </span><span class="n">Rule1</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Vectorize</span><span class="p">[</span><span class="n">Auto</span><span class="o">-</span><span class="n">Vectorize</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Rule1</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Phase1</span><span class="p">[</span><span class="n">Phase</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">Pack</span><span class="p">]</span>
|
||||
|
||||
<span class="w"> </span><span class="n">Cut</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Rule3</span><span class="p">[</span><span class="n">Rule</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Scene</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASR</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Rule3</span>
|
||||
|
||||
<span class="w"> </span><span class="n">Face</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Trace</span><span class="p">[</span><span class="n">Face</span><span class="w"> </span><span class="n">Trace</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Trace</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Qdrant</span><span class="p">[</span><span class="n">Qdrant</span><span class="w"> </span><span class="n">Sync</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Trace</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">TraceChunks</span><span class="p">[</span><span class="n">Trace</span><span class="w"> </span><span class="n">Chunks</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Trace</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">TKG</span><span class="p">[</span><span class="n">TKG</span><span class="w"> </span><span class="n">Builder</span><span class="p">]</span>
|
||||
|
||||
<span class="w"> </span><span class="n">Face</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">TMDbMatch</span><span class="p">[</span><span class="n">TMDb</span><span class="w"> </span><span class="n">Match</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">Face</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">SceneMeta</span><span class="p">[</span><span class="n">Scene</span><span class="w"> </span><span class="n">Metadata</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">YOLO</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">SceneMeta</span>
|
||||
<span class="w"> </span><span class="n">Face</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">IdentityAgent</span><span class="p">[</span><span class="n">Identity</span><span class="w"> </span><span class="n">Agent</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASRX</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">IdentityAgent</span>
|
||||
|
||||
<span class="w"> </span><span class="n">Cut</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Agent5W1H</span><span class="p">[</span><span class="mi">5</span><span class="n">W1H</span><span class="w"> </span><span class="n">Agent</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">ASR</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Agent5W1H</span>
|
||||
<span class="w"> </span><span class="n">Agent5W1H</span><span class="w"> </span><span class="o">--></span><span class="w"> </span><span class="n">Phase2</span><span class="p">[</span><span class="n">Phase</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">Pack</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="n">end</span>
|
||||
|
||||
<span class="w"> </span><span class="n">style</span><span class="w"> </span><span class="n">Processors</span><span class="w"> </span><span class="n">fill</span><span class="o">:</span><span class="err">#</span><span class="mi">1</span><span class="n">a1a2e</span><span class="p">,</span><span class="n">stroke</span><span class="o">:</span><span class="err">#</span><span class="n">e94560</span>
|
||||
<span class="w"> </span><span class="n">style</span><span class="w"> </span><span class="n">Ingestion</span><span class="w"> </span><span class="n">fill</span><span class="o">:</span><span class="err">#</span><span class="mi">16213</span><span class="n">e</span><span class="p">,</span><span class="n">stroke</span><span class="o">:</span><span class="err">#</span><span class="mf">0f</span><span class="mi">3460</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3>Pipeline Completion Flow</h3>
|
||||
<p>The pipeline is <strong>not complete</strong> until both the 10 processors AND the 入庫 (ingestion) steps have finished. The worker polls every 3 seconds and only marks the job as <code>completed</code> when all ingestion steps verify OK.</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="mf">10</span><span class="w"> </span><span class="n">processors</span><span class="w"> </span><span class="n">done</span>
|
||||
<span class="w"> </span><span class="err">↓</span><span class="w"> </span><span class="p">(</span><span class="n">job</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="n">stays</span><span class="w"> </span><span class="s">"running"</span><span class="p">)</span>
|
||||
<span class="n">Algorithm</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="n">Trigger</span><span class="p">:</span><span class="w"> </span><span class="n">Rule</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Vectorize</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">Phase</span><span class="w"> </span><span class="mf">1</span><span class="w"> </span><span class="n">Pack</span>
|
||||
<span class="w"> </span><span class="err">↓</span><span class="w"> </span><span class="p">(</span><span class="n">job</span><span class="w"> </span><span class="kr">run</span><span class="n">s</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="n">parallel</span><span class="p">)</span>
|
||||
<span class="n">Algorithm</span><span class="w"> </span><span class="mf">2</span><span class="w"> </span><span class="n">Trigger</span><span class="p">:</span><span class="w"> </span><span class="n">Face</span><span class="w"> </span><span class="n">Trace</span><span class="w"> </span><span class="err">→</span><span class="w"> </span><span class="n">TKG</span><span class="p">,</span><span class="w"> </span><span class="n">Scene</span><span class="w"> </span><span class="n">Metadata</span><span class="p">,</span><span class="w"> </span><span class="n">Identity</span><span class="w"> </span><span class="n">Agent</span><span class="p">,</span><span class="w"> </span><span class="mf">5</span><span class="n">W1H</span><span class="w"> </span><span class="n">Agent</span>
|
||||
<span class="w"> </span><span class="err">↓</span><span class="w"> </span><span class="p">(</span><span class="n">poll</span><span class="w"> </span><span class="n">checks</span><span class="w"> </span><span class="n">every</span><span class="w"> </span><span class="mf">3</span><span class="n">s</span><span class="p">)</span>
|
||||
<span class="n">Ingestion</span><span class="w"> </span><span class="n">verification</span><span class="p">:</span><span class="w"> </span><span class="n">rule1</span><span class="w"> </span><span class="err">✓</span><span class="w"> </span><span class="n">vectorize</span><span class="w"> </span><span class="err">✓</span><span class="w"> </span><span class="n">rule3</span><span class="w"> </span><span class="err">✓</span><span class="w"> </span><span class="n">face_trace</span><span class="w"> </span><span class="err">✓</span><span class="w"> </span><span class="n">tkg</span><span class="w"> </span><span class="err">✓</span><span class="w"> </span><span class="n">scene_meta</span><span class="w"> </span><span class="err">✓</span><span class="w"> </span><span class="mf">5</span><span class="n">w1h</span><span class="w"> </span><span class="err">✓</span>
|
||||
<span class="w"> </span><span class="err">↓</span>
|
||||
<span class="n">job</span><span class="w"> </span><span class="n">status</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"completed"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3>10 Processor Stages</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Processor</th>
|
||||
<th>Depends On</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><code>Cut</code></td>
|
||||
<td>—</td>
|
||||
<td>Scene boundary detection (PySceneDetect)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><code>ASR</code></td>
|
||||
<td>Cut</td>
|
||||
<td>Automatic speech recognition (faster-whisper)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td><code>ASRX</code></td>
|
||||
<td>ASR</td>
|
||||
<td>Speaker diarization + ASR refinement</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><code>YOLO</code></td>
|
||||
<td>—</td>
|
||||
<td>Object detection (YOLOv8)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td><code>OCR</code></td>
|
||||
<td>—</td>
|
||||
<td>Optical character recognition</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><code>Face</code></td>
|
||||
<td>—</td>
|
||||
<td>Face detection + recognition (InsightFace + CoreML)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td><code>Pose</code></td>
|
||||
<td>—</td>
|
||||
<td>Pose estimation</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td><code>VisualChunk</code></td>
|
||||
<td>YOLO</td>
|
||||
<td>Visual object chunking</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td><code>Story</code></td>
|
||||
<td>ASRX + Cut + YOLO + Face</td>
|
||||
<td>Narrative scene summarization (LLM, with embedding)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td><code>5W1H</code></td>
|
||||
<td>Story</td>
|
||||
<td>Who/What/When/Where/Why extraction (LLM, with embedding)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>入庫 (Post-Processing / Ingestion)</h3>
|
||||
<p>These steps run after the 10 processors and are <strong>required for pipeline completion</strong>. The worker checks all of them before marking the job as done.</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Step</th>
|
||||
<th>Triggers When</th>
|
||||
<th>Verification</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td><strong>Rule 1 Sentence Chunking</strong></td>
|
||||
<td>ASR + ASRX done</td>
|
||||
<td><code>chunk</code> table has rows with <code>chunk_type = 'sentence'</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td><strong>Auto-Vectorize</strong></td>
|
||||
<td>Rule 1 done</td>
|
||||
<td><code>chunk.embedding</code> IS NOT NULL for sentence chunks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td><strong>Phase 1 Pack</strong></td>
|
||||
<td>Rule 1 done</td>
|
||||
<td><code>release_pack.py --phase 1</code> executed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td><strong>Rule 3 Scene Chunking</strong></td>
|
||||
<td>All 10 processors done + Cut + ASR</td>
|
||||
<td><code>chunk</code> table has rows with <code>chunk_type = 'cut'</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td><strong>Face Trace</strong></td>
|
||||
<td>All 10 processors done + Face</td>
|
||||
<td><code>face_detections.trace_id</code> IS NOT NULL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td><strong>Qdrant Face Sync</strong></td>
|
||||
<td>Face Trace done</td>
|
||||
<td>Qdrant face_embedding collection populated</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td><strong>Trace Chunks</strong></td>
|
||||
<td>Face Trace done</td>
|
||||
<td><code>chunk</code> table has rows with <code>chunk_type = 'trace'</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td><strong>TKG Builder</strong></td>
|
||||
<td>Face Trace done</td>
|
||||
<td><code>tkg_nodes</code> + <code>tkg_edges</code> tables have rows</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td><strong>TMDb Face Matching</strong></td>
|
||||
<td>TMDb enabled + Face done</td>
|
||||
<td><code>face_detections.identity_id</code> IS NOT NULL</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td><strong>Heuristic Scene Metadata</strong></td>
|
||||
<td>Face + YOLO done</td>
|
||||
<td><code>{file_uuid}.scene_meta.json</code> exists on disk</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td><strong>Identity Agent</strong></td>
|
||||
<td>Face + ASRX done</td>
|
||||
<td><code>identities</code> with <code>source = 'identity_agent'</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td><strong>5W1H Agent</strong></td>
|
||||
<td>Cut + ASR done</td>
|
||||
<td><code>chunk.summary_text</code> IS NOT NULL for cut chunks</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>13</td>
|
||||
<td><strong>Release Pack</strong></td>
|
||||
<td>5W1H Agent done</td>
|
||||
<td><code>release_pack.py --phase 2</code> executed</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Ingestion Status</h3>
|
||||
<p>Check real-time ingestion status for a file:</p>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/stats/ingestion-status/{file_uuid}"</span>
|
||||
</code></pre></div>
|
||||
|
||||
<p>Returns per-step <code>done</code> / <code>pending</code> status with detail counts.</p>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span><span class="s2">"http://localhost:3003/api/v1/stats/ingestion-status/bd80fec9c42afb0307eb28f22c64c76a"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'.steps[] | {name, status, detail}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h4>Response</h4>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"bd80fec9c42afb0307eb28f22c64c76a"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"steps"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rule1_sentence"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 sentence chunks"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"auto_vectorize"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 embedded"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"rule3_scene"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 scene chunks"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"face_trace"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 traces"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"trace_chunks"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 trace chunks"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tkg"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 nodes, 0 edges"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"identity_match"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 identities"</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"scene_metadata"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w"> </span><span class="p">},</span>
|
||||
<span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"5w1h"</span><span class="p">,</span><span class="w"> </span><span class="nt">"status"</span><span class="p">:</span><span class="w"> </span><span class="s2">"pending"</span><span class="p">,</span><span class="w"> </span><span class="nt">"detail"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0 scenes with 5W1H"</span><span class="w"> </span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">]</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3>Stats Endpoints</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Endpoint</th>
|
||||
<th>Auth</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>GET</td>
|
||||
<td><code>/api/v1/stats/sftpgo</code></td>
|
||||
<td>No</td>
|
||||
<td>SFTPGo service status</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>GET</td>
|
||||
<td><code>/api/v1/stats/ingestion-status/:file_uuid</code></td>
|
||||
<td>No</td>
|
||||
<td>Per-file ingestion checklist</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Configuration</h3>
|
||||
<h3><code>POST /api/v1/config/cache</code></h3>
|
||||
<p><strong>Auth</strong>: Required
|
||||
<strong>Scope</strong>: system-level</p>
|
||||
<p>Toggle the Redis cache on or off.</p>
|
||||
<h4>Request Parameters</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>enabled</code></td>
|
||||
<td>boolean</td>
|
||||
<td>Yes</td>
|
||||
<td><code>true</code> to enable, <code>false</code> to disable</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<div class="codehilite"><pre><span></span><code>curl<span class="w"> </span>-s<span class="w"> </span>-X<span class="w"> </span>POST<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/config/cache"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Content-Type: application/json"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"X-API-Key: </span><span class="nv">$KEY</span><span class="s2">"</span><span class="w"> </span><span class="se">\</span>
|
||||
<span class="w"> </span>-d<span class="w"> </span><span class="s1">'{"enabled": false}'</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3>Unmounted Routes</h3>
|
||||
<p>The following routes are defined in source code but are <strong>NOT</strong> currently mounted in the router:</p>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Endpoint</th>
|
||||
<th>Source file</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>/api/v1/search/persons</code></td>
|
||||
<td><code>universal_search.rs</code> (not mounted)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/api/v1/who</code></td>
|
||||
<td><code>who.rs</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/api/v1/who/candidates</code></td>
|
||||
<td><code>who.rs</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
207
deliverable_v1.1.0/html_docs/doc/12_agent.html
Normal file
207
deliverable_v1.1.0/html_docs/doc/12_agent.html
Normal file
@@ -0,0 +1,207 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>12 Agent - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<h1>Agent Endpoints</h1>
|
||||
<p>Agent endpoints provide AI-powered capabilities including translation, identity analysis, and 5W1H extraction.</p>
|
||||
<h2>POST /api/v1/agents/translate</h2>
|
||||
<p>Translate text between languages using Gemma4 (llama.cpp, port 8082).</p>
|
||||
<h3>Request</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Hello, welcome to Momentry Core."</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"target_language"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Traditional Chinese"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"source_language"</span><span class="p">:</span><span class="w"> </span><span class="s2">"English"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Type</th>
|
||||
<th>Required</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>text</code></td>
|
||||
<td>string</td>
|
||||
<td>✅</td>
|
||||
<td>Text to translate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>target_language</code></td>
|
||||
<td>string</td>
|
||||
<td>✅</td>
|
||||
<td>Target language name (e.g. "Traditional Chinese", "Japanese")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>source_language</code></td>
|
||||
<td>string</td>
|
||||
<td>❌</td>
|
||||
<td>Source language (default: "auto")</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Response</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"translated_text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"您好,歡迎使用 Momentry Core。"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"source_language_detected"</span><span class="p">:</span><span class="w"> </span><span class="s2">"English"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"model_used"</span><span class="p">:</span><span class="w"> </span><span class="s2">"google_gemma-4-26B-A4B-it-Q5_K_M.gguf"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3>Supported Language Pairs (tested)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
<th>Target</th>
|
||||
<th>Quality</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>English</td>
|
||||
<td>Traditional Chinese</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>English</td>
|
||||
<td>Japanese</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chinese</td>
|
||||
<td>English</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>English</td>
|
||||
<td>French</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chinese</td>
|
||||
<td>Japanese</td>
|
||||
<td>✅</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Model</h3>
|
||||
<ul>
|
||||
<li><strong>Model</strong>: Gemma4 26B (Q5_K_M)</li>
|
||||
<li><strong>Engine</strong>: llama.cpp at <code>localhost:8082</code></li>
|
||||
<li><strong>Endpoint</strong>: <code>/v1/chat/completions</code> (OpenAI-compatible)</li>
|
||||
<li><strong>Temperature</strong>: 0.1</li>
|
||||
<li><strong>Max tokens</strong>: 1024</li>
|
||||
</ul>
|
||||
<h3>Errors</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Condition</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>500</td>
|
||||
<td>LLM unreachable or response parse failure</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>401</td>
|
||||
<td>Missing/invalid auth</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr />
|
||||
<h2>POST /api/v1/agents/5w1h/analyze</h2>
|
||||
<p>Extract 5W1H (Who, What, When, Where, Why, How) from a scene. Uses Gemma4 LLM on port 8082.</p>
|
||||
<h3>Request</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3abeee81d94597629ed8cb943f182e94"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"scene_id"</span><span class="p">:</span><span class="w"> </span><span class="mi">42</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h3>Response</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"5w1h"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"who"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Cary Grant"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"what"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"discussing plans"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"when"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"1963"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"where"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Paris"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"why"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"vacation"</span><span class="p">],</span>
|
||||
<span class="w"> </span><span class="nt">"how"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"in person"</span><span class="p">]</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h2>POST /api/v1/agents/5w1h/batch</h2>
|
||||
<p>Batch analyze all scenes in a file for 5W1H extraction. Uses the pipeline's <code>parent_chunk_5w1h.py --mode llm</code>.</p>
|
||||
<h3>Request</h3>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"file_uuid"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3abeee81d94597629ed8cb943f182e94"</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h2>GET /api/v1/agents/5w1h/status</h2>
|
||||
<p>Get status of the 5W1H agent pipeline for a file.</p>
|
||||
<hr />
|
||||
<h2>Embedding Model</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Detail</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Model</strong></td>
|
||||
<td>EmbeddingGemma-300m</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Endpoint</strong></td>
|
||||
<td><code>POST /v1/embeddings</code> on port 11436</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Dimension</strong></td>
|
||||
<td>768</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Used by</strong></td>
|
||||
<td><code>parent_chunk_5w1h.py --embed</code>, story, 5W1H, search</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
29
deliverable_v1.1.0/html_docs/doc/index.html
Normal file
29
deliverable_v1.1.0/html_docs/doc/index.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Momentry API 文件</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 900px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 28px; margin-bottom: 8px; }
|
||||
p.subtitle { color: #666; margin-bottom: 24px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
tr { border-bottom: 1px solid #eee; }
|
||||
tr:last-child { border: none; }
|
||||
td { padding: 10px 0; }
|
||||
td.cn { width: 140px; font-weight: 600; color: #333; }
|
||||
td.en { color: #666; font-size: 14px; }
|
||||
a { color: #0066cc; text-decoration: none; display: block; }
|
||||
a:hover td { background: #f8f8f8; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Momentry API 文件</h1>
|
||||
<p class="subtitle">API 參考手冊 — 登入後可瀏覽各模組文件</p>
|
||||
<table><tr onclick="window.location='01_auth.html'" style="cursor:pointer"><td class="cn">安全認證</td><td class="en">Authentication</td></tr><tr onclick="window.location='02_health.html'" style="cursor:pointer"><td class="cn">健康檢查</td><td class="en">Health</td></tr><tr onclick="window.location='03_register.html'" style="cursor:pointer"><td class="cn">檔案註冊</td><td class="en">File Registration</td></tr><tr onclick="window.location='04_lookup.html'" style="cursor:pointer"><td class="cn">檔案屬性查詢</td><td class="en">File Lookup</td></tr><tr onclick="window.location='05_process.html'" style="cursor:pointer"><td class="cn">處理流程</td><td class="en">Processing</td></tr><tr onclick="window.location='06_search.html'" style="cursor:pointer"><td class="cn">搜尋功能</td><td class="en">Search</td></tr><tr onclick="window.location='07_identity.html'" style="cursor:pointer"><td class="cn">身份識別</td><td class="en">Identity</td></tr><tr onclick="window.location='08_identity_agent.html'" style="cursor:pointer"><td class="cn">智能身份綁定</td><td class="en">Smart Identity Binding</td></tr><tr onclick="window.location='08_media.html'" style="cursor:pointer"><td class="cn">串流與截圖</td><td class="en">Streaming & Thumbnails</td></tr><tr onclick="window.location='09_tmdb.html'" style="cursor:pointer"><td class="cn">TMDb 整合</td><td class="en">TMDb Integration</td></tr><tr onclick="window.location='10_pipeline.html'" style="cursor:pointer"><td class="cn">生產線</td><td class="en">Pipeline</td></tr><tr onclick="window.location='12_agent.html'" style="cursor:pointer"><td class="cn">智慧代理</td><td class="en">AI Agents</td></tr></table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
46
deliverable_v1.1.0/html_docs/doc/login.html
Normal file
46
deliverable_v1.1.0/html_docs/doc/login.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login - Momentry Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; height: 100vh; }
|
||||
.card { background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; width: 360px; }
|
||||
h1 { font-size: 24px; margin-bottom: 24px; text-align: center; }
|
||||
input { width: 100%; padding: 10px 12px; margin-bottom: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
|
||||
button { width: 100%; padding: 10px; background: #0066cc; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
|
||||
button:hover { background: #0052a3; }
|
||||
.error { color: #cc0000; font-size: 13px; margin-bottom: 12px; display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Momentry Docs</h1>
|
||||
<form id="loginForm">
|
||||
<input type="text" id="username" placeholder="Username" value="demo" required>
|
||||
<input type="password" id="password" placeholder="Password" value="demo" required>
|
||||
<div class="error" id="error">Invalid credentials</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('loginForm').onsubmit = async function(e) {
|
||||
e.preventDefault();
|
||||
const resp = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById('username').value,
|
||||
password: document.getElementById('password').value
|
||||
})
|
||||
});
|
||||
if (resp.ok) {
|
||||
window.location.href = '/doc/index.html';
|
||||
} else {
|
||||
document.getElementById('error').style.display = 'block';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
180
deliverable_v1.1.0/html_docs/doc_developer/11_error_codes.html
Normal file
180
deliverable_v1.1.0/html_docs/doc_developer/11_error_codes.html
Normal file
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>11 Error Codes - Momentry API Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||||
p { line-height: 1.6; margin: 8px 0; }
|
||||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||||
th { background: #f0f0f0; font-weight: 600; }
|
||||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||||
pre code { background: none; padding: 0; }
|
||||
a { color: #0066cc; }
|
||||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||||
.back:hover { color: #333; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
<!-- module: error_codes -->
|
||||
<!-- description: Standard API error codes -->
|
||||
<!-- depends: -->
|
||||
|
||||
<h2>Error Response Format</h2>
|
||||
<p>All API errors follow this JSON structure:</p>
|
||||
<div class="codehilite"><pre><span></span><code><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"success"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"error"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
|
||||
<span class="w"> </span><span class="nt">"code"</span><span class="p">:</span><span class="w"> </span><span class="s2">"E001_NOT_FOUND"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"message"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Resource not found"</span><span class="p">,</span>
|
||||
<span class="w"> </span><span class="nt">"details"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"resource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"file_uuid"</span><span class="p">,</span><span class="w"> </span><span class="nt">"value"</span><span class="p">:</span><span class="w"> </span><span class="s2">"abc"</span><span class="p">}</span>
|
||||
<span class="w"> </span><span class="p">}</span>
|
||||
<span class="p">}</span>
|
||||
</code></pre></div>
|
||||
|
||||
<h2>Error Code List</h2>
|
||||
<h3>Generic Errors (E0xx)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>HTTP</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>E001_NOT_FOUND</code></td>
|
||||
<td>404</td>
|
||||
<td>Resource not found (file, identity, chunk)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E002_DUPLICATE</code></td>
|
||||
<td>409</td>
|
||||
<td>Resource already exists</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E003_VALIDATION</code></td>
|
||||
<td>400</td>
|
||||
<td>Request parameter validation failed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E004_UNAUTHORIZED</code></td>
|
||||
<td>401</td>
|
||||
<td>Invalid API key or token</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E005_INTERNAL</code></td>
|
||||
<td>500</td>
|
||||
<td>Internal server error</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Processor Errors (E1xx)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>HTTP</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>E101_PROCESSOR_FAIL</code></td>
|
||||
<td>500</td>
|
||||
<td>Python script execution failed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E102_TIMEOUT</code></td>
|
||||
<td>504</td>
|
||||
<td>Processing timeout</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E103_RESUME_FAIL</code></td>
|
||||
<td>500</td>
|
||||
<td>Resume failed (checkpoint not found)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E104_NO_VIDEO</code></td>
|
||||
<td>400</td>
|
||||
<td>Video file path not found</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>Identity Errors (E2xx)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>HTTP</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>E201_FACE_NOT_FOUND</code></td>
|
||||
<td>404</td>
|
||||
<td>Face detection not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E202_MERGE_CONFLICT</code></td>
|
||||
<td>409</td>
|
||||
<td>Identity merge conflict</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E203_CANDIDATE_EMPTY</code></td>
|
||||
<td>404</td>
|
||||
<td>No candidates available for confirmation</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h3>TMDb Errors (E3xx)</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Code</th>
|
||||
<th>HTTP</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>E301_TMDB_NO_KEY</code></td>
|
||||
<td>400</td>
|
||||
<td><code>TMDB_API_KEY</code> environment variable not set</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E302_TMDB_UNREACHABLE</code></td>
|
||||
<td>502</td>
|
||||
<td>TMDb API unreachable or timed out</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E303_TMDB_CACHE_NOT_FOUND</code></td>
|
||||
<td>200</td>
|
||||
<td>No local TMDb cache; run prefetch first</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E304_TMDB_PROBE_FAILED</code></td>
|
||||
<td>500</td>
|
||||
<td>TMDb probe execution failed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>E305_TMDB_MOVIE_NOT_FOUND</code></td>
|
||||
<td>404</td>
|
||||
<td>No matching TMDb movie found from filename</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
29
deliverable_v1.1.0/html_docs/doc_developer/index.html
Normal file
29
deliverable_v1.1.0/html_docs/doc_developer/index.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Momentry API 文件</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||||
.container { max-width: 900px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||||
h1 { font-size: 28px; margin-bottom: 8px; }
|
||||
p.subtitle { color: #666; margin-bottom: 24px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
tr { border-bottom: 1px solid #eee; }
|
||||
tr:last-child { border: none; }
|
||||
td { padding: 10px 0; }
|
||||
td.cn { width: 140px; font-weight: 600; color: #333; }
|
||||
td.en { color: #666; font-size: 14px; }
|
||||
a { color: #0066cc; text-decoration: none; display: block; }
|
||||
a:hover td { background: #f8f8f8; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Momentry API 文件</h1>
|
||||
<p class="subtitle">API 參考手冊 — 登入後可瀏覽各模組文件</p>
|
||||
<table><tr onclick="window.location='11_error_codes.html'" style="cursor:pointer"><td class="cn">錯誤碼</td><td class="en">Error Codes</td></tr></table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
46
deliverable_v1.1.0/html_docs/doc_developer/login.html
Normal file
46
deliverable_v1.1.0/html_docs/doc_developer/login.html
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login - Momentry Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; height: 100vh; }
|
||||
.card { background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; width: 360px; }
|
||||
h1 { font-size: 24px; margin-bottom: 24px; text-align: center; }
|
||||
input { width: 100%; padding: 10px 12px; margin-bottom: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
|
||||
button { width: 100%; padding: 10px; background: #0066cc; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
|
||||
button:hover { background: #0052a3; }
|
||||
.error { color: #cc0000; font-size: 13px; margin-bottom: 12px; display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Momentry Docs</h1>
|
||||
<form id="loginForm">
|
||||
<input type="text" id="username" placeholder="Username" value="demo" required>
|
||||
<input type="password" id="password" placeholder="Password" value="demo" required>
|
||||
<div class="error" id="error">Invalid credentials</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('loginForm').onsubmit = async function(e) {
|
||||
e.preventDefault();
|
||||
const resp = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById('username').value,
|
||||
password: document.getElementById('password').value
|
||||
})
|
||||
});
|
||||
if (resp.ok) {
|
||||
window.location.href = '/doc/index.html';
|
||||
} else {
|
||||
document.getElementById('error').style.display = 'block';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
280
deliverable_v1.1.0/modules/01_auth.md
Normal file
280
deliverable_v1.1.0/modules/01_auth.md
Normal file
@@ -0,0 +1,280 @@
|
||||
<!-- module: auth -->
|
||||
<!-- description: Authentication — login, logout, JWT, session cookie, API key -->
|
||||
<!-- depends: -->
|
||||
|
||||
## Base URL
|
||||
|
||||
| Environment | URL | Purpose |
|
||||
|-------------|-----|---------|
|
||||
| Production | `http://localhost:3002` | Production deployment |
|
||||
| External (M5) | `https://m5api.momentry.ddns.net` | Remote access |
|
||||
|
||||
## Variables
|
||||
|
||||
All examples in this documentation use these environment variables:
|
||||
|
||||
```bash
|
||||
API="http://localhost:3002"
|
||||
KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All endpoints under `/api/v1/*` require authentication.
|
||||
The following endpoints are public (no auth needed):
|
||||
|
||||
- `GET /health`
|
||||
- `POST /api/v1/auth/login`
|
||||
- `POST /api/v1/auth/logout`
|
||||
|
||||
### Three Authentication Modes
|
||||
|
||||
The system supports three authentication methods, checked in **priority order** by the middleware:
|
||||
|
||||
```
|
||||
Middleware priority:
|
||||
1. Session Cookie (Portal/browser)
|
||||
2. JWT Bearer (API clients, CLI)
|
||||
3. API Key Header (legacy compatibility)
|
||||
4. API Key Query Param (?api_key=)
|
||||
```
|
||||
|
||||
| Mode | Transport | Expiry | Scope | Best for |
|
||||
|------|-----------|--------|-------|----------|
|
||||
| **Session Cookie** | `Cookie: session_id=<session_id>` | 24h | per-browser session | Portal (browser) |
|
||||
| **JWT** | `Authorization: Bearer <token>` | 1h | per-login token | API clients, CLI, scripts |
|
||||
| **API Key** | `X-API-Key: <key>` | 90d | fixed key for automation | Legacy scripts, WordPress |
|
||||
|
||||
---
|
||||
|
||||
### Login
|
||||
|
||||
**Default accounts & API keys:**
|
||||
|
||||
| Username | Password | API Key | Role |
|
||||
|----------|----------|---------|------|
|
||||
| `admin` | `admin` | — | admin |
|
||||
| `demo` | `demo` | `muser_demo_key_32chars_abcdef1234567890` | user |
|
||||
|
||||
The demo API key is set via `MOMENTRY_DEMO_API_KEY` env var and can be used in place of JWT for marcom integrations:
|
||||
|
||||
```bash
|
||||
# Using API key instead of JWT
|
||||
curl -s "$API/api/v1/files/scan" -H "X-API-Key: muser_demo_key_32chars_abcdef1234567890"
|
||||
```
|
||||
|
||||
```bash
|
||||
# Login as admin
|
||||
curl -s -X POST "$API/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "admin", "password": "admin"}'
|
||||
|
||||
# Login as demo user
|
||||
curl -s -X POST "$API/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "demo", "password": "demo"}'
|
||||
```
|
||||
|
||||
#### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"jwt": "eyJhbGciOiJIUzI1NiIs...",
|
||||
"api_key": "muser_...",
|
||||
"user": {
|
||||
"username": "admin",
|
||||
"role": "admin"
|
||||
},
|
||||
"expires_at": "2026-05-18T13:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `jwt` | string | JWT access token. Use as `Authorization: Bearer <jwt>`. Expires in 1 hour. |
|
||||
| `api_key` | string | Legacy API key. Use as `X-API-Key: <key>`. Good for 90 days. |
|
||||
| `user.username` | string | Username |
|
||||
| `user.role` | string | Role: `admin`, `user`, or `readonly` |
|
||||
| `expires_at` | string | ISO8601 timestamp of JWT expiration |
|
||||
|
||||
The login endpoint also sets a `Set-Cookie` header for browser-based clients:
|
||||
|
||||
```
|
||||
Set-Cookie: session_id=<session_id>; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400
|
||||
```
|
||||
|
||||
#### Error Response (401)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Invalid username or password"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Using JWT
|
||||
|
||||
JWT is preferred for API clients (CLI scripts, WordPress). It is validated by the middleware without a database lookup (stateless).
|
||||
|
||||
```bash
|
||||
# Login and capture JWT
|
||||
JWT=$(curl -s -X POST "$API/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin"}' | python3 -c "import json,sys;print(json.load(sys.stdin)['jwt'])")
|
||||
|
||||
# Use JWT for all subsequent requests
|
||||
curl -H "Authorization: Bearer $JWT" "$API/api/v1/files/scan"
|
||||
curl -H "Authorization: Bearer $JWT" "$API/api/v1/resource/tmdb"
|
||||
```
|
||||
|
||||
JWT is short-lived (1 hour). When it expires, request a new one via login.
|
||||
|
||||
---
|
||||
|
||||
### Using Session Cookie (Browser)
|
||||
|
||||
Browser-based clients (Portal) get a session cookie automatically after login. The browser sends the cookie with every request—no manual header needed.
|
||||
|
||||
```bash
|
||||
# Login captures the session cookie from Set-Cookie header
|
||||
curl -v -X POST "$API/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin"}' 2>&1 | grep "Set-Cookie"
|
||||
|
||||
# Browser automatically sends: Cookie: session_id=<session_id>
|
||||
# No manual header needed for subsequent requests
|
||||
```
|
||||
|
||||
The session cookie is HttpOnly (not accessible from JavaScript) and SameSite=Strict (protected against CSRF).
|
||||
|
||||
---
|
||||
|
||||
### Using Legacy API Key
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: $KEY" "$API/api/v1/files/scan"
|
||||
|
||||
# Also accepted via Bearer header (non-JWT format) or query parameter:
|
||||
curl -H "Authorization: Bearer $KEY" "$API/api/v1/files/scan"
|
||||
curl "$API/api/v1/files/scan?api_key=$KEY"
|
||||
```
|
||||
|
||||
API keys are validated via SHA256 hash lookup in the database. They are long-lived (90 days) and intended for automation.
|
||||
|
||||
### Obtaining an API Key (CLI)
|
||||
|
||||
```bash
|
||||
momentry api-key create "My API Key" --key-type user
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Logout
|
||||
|
||||
```bash
|
||||
# Logout using the session cookie (browser)
|
||||
curl -X POST "$API/api/v1/auth/logout" \
|
||||
-H "Cookie: session_id=<uuid>"
|
||||
```
|
||||
|
||||
#### What logout does
|
||||
|
||||
| Auth mode | Effect |
|
||||
|-----------|--------|
|
||||
| **Session Cookie** | Session deleted from database. Same cookie returns 401 on subsequent requests. |
|
||||
| **JWT** | JWT remains valid until expiry. (JWT is stateless — logout adds JWT to a blacklist only if API key mode is used.) |
|
||||
| **API Key** | API key remains valid. (Legacy keys are shared across sessions — revoking would break other clients.) |
|
||||
|
||||
#### Example: full session lifecycle
|
||||
|
||||
```bash
|
||||
# 1. Login
|
||||
SESSION_ID=$(curl -s -D - -X POST "$API/api/v1/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"admin"}' | grep "Set-Cookie" | sed 's/.*session_id=\([^;]*\).*/\1/')
|
||||
|
||||
# 2. Use session (works)
|
||||
curl -s -o /dev/null -w "HTTP %{http_code}\n" "$API/api/v1/resource/tmdb" \
|
||||
-H "Cookie: session_id=$SESSION_ID"
|
||||
# → HTTP 200
|
||||
|
||||
# 3. Logout
|
||||
curl -s -X POST "$API/api/v1/auth/logout" \
|
||||
-H "Cookie: session_id=$SESSION_ID"
|
||||
# → {"success": true}
|
||||
|
||||
# 4. Use session again (rejected)
|
||||
curl -s -o /dev/null -w "HTTP %{http_code}\n" "$API/api/v1/resource/tmdb" \
|
||||
-H "Cookie: session_id=$SESSION_ID"
|
||||
# → HTTP 401
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Authentication Flow Summary
|
||||
|
||||
```
|
||||
Login Request
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ 1. Check users │ ← users table (argon2 password verify)
|
||||
│ table │
|
||||
└──────┬───────────┘
|
||||
│
|
||||
┌───┴───┐
|
||||
│ match │
|
||||
└───┬───┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ 2. Create JWT │ ← 1h expiry, signed with JWT_SECRET
|
||||
├──────────────────┤
|
||||
│ 3. Create │ ← 24h expiry, stored in sessions table
|
||||
│ session │
|
||||
├──────────────────┤
|
||||
│ 4. Set-Cookie │ ← HttpOnly, SameSite=Strict, Path=/
|
||||
├──────────────────┤
|
||||
│ 5. Return │ ← JWT + api_key + user info to client
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
```
|
||||
Protected Request
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ Middleware checks: │
|
||||
│ │
|
||||
│ 1. Cookie session? │ → DB lookup session → get api_key → verify
|
||||
│ │
|
||||
│ 2. JWT Bearer? │ → verify JWT signature → decode claims
|
||||
│ │
|
||||
│ 3. X-API-Key? │ → SHA256 hash → DB lookup → verify
|
||||
│ │
|
||||
│ 4. ?api_key=? │ → same as #3
|
||||
│ │
|
||||
│ 5. None → 401 │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `401` | Missing or invalid authentication |
|
||||
| `401` | Session expired or logged out |
|
||||
| `401` | JWT expired |
|
||||
| `401` | API key revoked or inactive |
|
||||
|
||||
---
|
||||
|
||||
### Related
|
||||
|
||||
- `POST /api/v1/resource/tmdb/check` — test authentication + TMDb API connectivity
|
||||
- `GET /health/detailed` — view auth status (integrations section)
|
||||
147
deliverable_v1.1.0/modules/02_health.md
Normal file
147
deliverable_v1.1.0/modules/02_health.md
Normal file
@@ -0,0 +1,147 @@
|
||||
<!-- module: health -->
|
||||
<!-- description: Health check endpoints -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
## Health Check
|
||||
|
||||
### `GET /health`
|
||||
|
||||
**Auth**: Public
|
||||
**Scope**: system-level
|
||||
|
||||
Returns basic server health status — used by load balancers and monitoring.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl "$API/health" | jq '{status, version}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "1.0.0",
|
||||
"build_git_hash": "3a6c1865",
|
||||
"build_timestamp": "2026-05-16T13:38:15Z",
|
||||
"uptime_ms": 3015
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `status` | string | `ok` or `degraded` |
|
||||
| `version` | string | Semver version |
|
||||
| `build_git_hash` | string | Git commit hash |
|
||||
| `build_timestamp` | string | Binary build time |
|
||||
| `uptime_ms` | integer | Milliseconds since server start |
|
||||
|
||||
---
|
||||
|
||||
### `GET /health/detailed`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: system-level
|
||||
|
||||
Returns full system health including each service status, resource utilization, pipeline readiness, schema migration status, identity file sync status, and external integrations.
|
||||
|
||||
> Requires authentication (JWT, session cookie, or API key). The basic `/health` endpoint remains public for load balancer checks.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl "$API/health/detailed" | jq '{status, services, resources: {cpu: .resources.cpu_used_percent, memory: .resources.memory_used_percent}}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "1.0.0",
|
||||
"services": {
|
||||
"postgres": {"status": "ok", "latency_ms": 3},
|
||||
"redis": {"status": "ok", "latency_ms": 1},
|
||||
"qdrant": {"status": "ok", "latency_ms": 5}
|
||||
},
|
||||
"resources": {
|
||||
"cpu_used_percent": 12.5,
|
||||
"memory_available_mb": 32768,
|
||||
"memory_used_percent": 31.7
|
||||
},
|
||||
"pipeline": {
|
||||
"scripts_ready": true,
|
||||
"scripts_count": 345,
|
||||
"processors": {
|
||||
"asr": true,
|
||||
"yolo": true,
|
||||
"face": true,
|
||||
"pose": true,
|
||||
"ocr": true,
|
||||
"cut": true,
|
||||
"scene": true,
|
||||
"asrx": true,
|
||||
"visual_chunk": true
|
||||
},
|
||||
"models_ready": true,
|
||||
"models_count": 42,
|
||||
"scripts_integrity": {"matched": 332, "total": 345, "ok": false},
|
||||
"ffmpeg": true
|
||||
},
|
||||
"schema": {
|
||||
"table_exists": true,
|
||||
"applied": [{"filename": "migrate_add_users_table.sql"}],
|
||||
"required": [],
|
||||
"ok": true
|
||||
},
|
||||
"identities": {
|
||||
"directory_exists": true,
|
||||
"files_count": 3481,
|
||||
"index_ok": true,
|
||||
"db_count": 3481,
|
||||
"synced": true
|
||||
},
|
||||
"integrations": {
|
||||
"tmdb": {
|
||||
"api_key_configured": false,
|
||||
"enabled": false,
|
||||
"api_reachable": null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Response Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `status` | string | `ok` if all essential services healthy |
|
||||
| `services` | object | Per-service status (postgres, redis, qdrant) |
|
||||
| `services.*.status` | string | `ok`, `error`, or `degraded` |
|
||||
| `services.*.latency_ms` | int | Response time in milliseconds |
|
||||
| `resources` | object | CPU, memory usage |
|
||||
| `pipeline.scripts_ready` | boolean | Scripts directory accessible |
|
||||
| `pipeline.scripts_count` | int | Number of Python processor scripts |
|
||||
| `pipeline.processors` | object | Per-processor availability |
|
||||
| `pipeline.models_ready` | boolean | Models directory accessible |
|
||||
| `pipeline.scripts_integrity` | object | SHA256 checksum verification results |
|
||||
| `schema.ok` | boolean | All required migrations applied |
|
||||
| `identities.synced` | boolean | Identity file count matches DB count |
|
||||
| `integrations.tmdb` | object | TMDB API key config and reachability |
|
||||
|
||||
#### Health status rules
|
||||
|
||||
| Condition | status |
|
||||
|-----------|--------|
|
||||
| All services ok | `ok` |
|
||||
| Any service error | `degraded` |
|
||||
| Postgres or Redis error | `degraded` (server still responds) |
|
||||
|
||||
---
|
||||
|
||||
### Stats Endpoints
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/api/v1/stats/sftpgo` | No | SFTPGo service status |
|
||||
184
deliverable_v1.1.0/modules/03_register.md
Normal file
184
deliverable_v1.1.0/modules/03_register.md
Normal file
@@ -0,0 +1,184 @@
|
||||
<!-- module: register -->
|
||||
<!-- description: File registration — register, scan -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
## File Registration
|
||||
|
||||
### `POST /api/v1/files/register`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Register a video file for processing. Returns the file's metadata and UUID.
|
||||
|
||||
**New in v0.1.2**: Registration now **automatically triggers the processing pipeline** — no need to call `POST /api/v1/file/:file_uuid/process` separately. The system will:
|
||||
1. Register the file and run ffprobe
|
||||
2. Auto-run offline TMDb probe (reads local identity files, no API calls)
|
||||
3. Create a monitor job for the worker
|
||||
4. Worker starts all 10 processors (Cut → ASR → ASRX → YOLO → OCR → Face → Pose → VisualChunk → Story → 5W1H)
|
||||
|
||||
If the file already exists (same content hash), returns the existing record with `already_exists: true`.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `file_path` | string | Yes | — | Path to video file on disk |
|
||||
| `pattern` | string | No | — | Regex pattern for batch register (requires `file_path` to be a directory) |
|
||||
| `user_id` | integer | No | — | User ID to associate with registration |
|
||||
| `content_hash` | string | No | — | Pre-computed SHA-256 hash (skips computation) |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Register a single file
|
||||
curl -s -X POST "$API/api/v1/files/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"file_path": "/path/to/video.mp4"}'
|
||||
|
||||
# Batch register files matching a pattern in a directory
|
||||
curl -s -X POST "$API/api/v1/files/register" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"file_path": "/path/to/dir", "pattern": ".*\\.mp4$"}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "3a6c1865...",
|
||||
"file_name": "video.mp4",
|
||||
"file_path": "/path/to/video.mp4",
|
||||
"file_type": "video",
|
||||
"duration": 120.5,
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"fps": 24.0,
|
||||
"total_frames": 2892,
|
||||
"already_exists": false,
|
||||
"message": "File registered successfully"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | boolean | Always true on 200 |
|
||||
| `file_uuid` | string | 32-char hex UUID of the registered file |
|
||||
| `file_name` | string | File name (auto-renamed if name conflict) |
|
||||
| `file_path` | string | Canonical path on disk |
|
||||
| `file_type` | string | `"video"`, `"audio"`, or `"unknown"` |
|
||||
| `duration` | float | Duration in seconds |
|
||||
| `width` | integer | Video width in pixels |
|
||||
| `height` | integer | Video height in pixels |
|
||||
| `fps` | float | Frames per second |
|
||||
| `total_frames` | integer | Total frame count |
|
||||
| `already_exists` | boolean | True if same content was already registered |
|
||||
| `message` | string | Human-readable status |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `401` | Missing or invalid API key |
|
||||
| `400` | Invalid request body |
|
||||
| `404` | File path does not exist |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/files/scan`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Scan the filesystem directory and list all media files, showing which are registered, processing, or unregistered.
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `page` | integer | No | 1 | Page number (1-based) |
|
||||
| `page_size` | integer | No | all | Items per page (alias: `limit`) |
|
||||
| `limit` | integer | No | all | Max items (alias for `page_size`) |
|
||||
| `pattern` | string | No | — | Regex filter on file name (e.g., `.*\\.mp4$`) |
|
||||
| `sort_by` | string | No | `name` | Sort field: `name`, `size`, `modified`, `status` |
|
||||
| `sort_order` | string | No | `asc` | Sort direction: `asc` or `desc` |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Full scan
|
||||
curl -s "$API/api/v1/files/scan" -H "X-API-Key: $KEY" | jq '{total, registered_count, unregistered_count}'
|
||||
|
||||
# Paginated (page 1, 5 per page)
|
||||
curl -s "$API/api/v1/files/scan?page=1&page_size=5" -H "X-API-Key: $KEY" | jq '{page, total_pages, files: [.files[].file_name]}'
|
||||
|
||||
# Regex filter: only mp4 files
|
||||
curl -s "$API/api/v1/files/scan?pattern=.*\\.mp4$" -H "X-API-Key: $KEY" | jq '{filtered_total, files: [.files[].file_name]}'
|
||||
|
||||
# Sort by file size (largest first)
|
||||
curl -s "$API/api/v1/files/scan?sort_by=size&sort_order=desc&page_size=5" -H "X-API-Key: $KEY" | jq '[.files[] | {file_name, file_size}]'
|
||||
|
||||
# Sort by modified time (most recent first)
|
||||
curl -s "$API/api/v1/files/scan?sort_by=modified&sort_order=desc&page_size=5" -H "X-API-Key: $KEY" | jq '[.files[] | {file_name, modified_time}]'
|
||||
|
||||
# Sort by status
|
||||
curl -s "$API/api/v1/files/scan?sort_by=status&page_size=5" -H "X-API-Key: $KEY" | jq '[.files[] | {file_name, status}]'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"file_name": "video.mp4",
|
||||
"file_size": 12345678,
|
||||
"is_registered": true,
|
||||
"file_uuid": "3a6c1865...",
|
||||
"status": "completed",
|
||||
"registration_time": "2026-05-16T12:00:00Z",
|
||||
"job_id": 42
|
||||
}
|
||||
],
|
||||
"total": 107,
|
||||
"filtered_total": 80,
|
||||
"page": 1,
|
||||
"page_size": 20,
|
||||
"total_pages": 4,
|
||||
"registered_count": 26,
|
||||
"unregistered_count": 81
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `files` | array | Array of file info objects (paginated) |
|
||||
| `files[].file_name` | string | File name |
|
||||
| `files[].relative_path` | string | Path relative to scan root |
|
||||
| `files[].file_path` | string | Absolute path on disk |
|
||||
| `files[].file_size` | integer | File size in bytes |
|
||||
| `files[].modified_time` | string | Last modified timestamp (ISO8601) |
|
||||
| `files[].is_registered` | boolean | Whether file is registered in DB |
|
||||
| `files[].file_uuid` | string | 32-char hex UUID (only if registered) |
|
||||
| `files[].status` | string | `"completed"`, `"processing"`, `"registered"`, `"unregistered"`, or `null` |
|
||||
| `files[].registration_time` | string | DB registration timestamp (only if registered) |
|
||||
| `files[].job_id` | integer | Processing job ID (only if a job exists) |
|
||||
| `total` | integer | Total files found on disk (unfiltered) |
|
||||
| `filtered_total` | integer | Files matching regex filter |
|
||||
| `page` | integer | Current page number |
|
||||
| `page_size` | integer | Items per page |
|
||||
| `total_pages` | integer | Total pages |
|
||||
| `registered_count` | integer | Files registered in DB |
|
||||
| `unregistered_count` | integer | Files not yet registered |
|
||||
|
||||
#### Notes
|
||||
|
||||
| Feature | Behavior |
|
||||
|---------|----------|
|
||||
| **Regex** | Case-insensitive (`(?i)` prefix auto-applied). Applied to `file_name`. |
|
||||
| **Sort order** | Default (`sort_by=name`): registered files first, then alphabetically. `sort_by=status`: alphabetical by status string. |
|
||||
| **Pagination** | `page_size` and `limit` are aliases. Default: show all results. |
|
||||
| **Processing order** | `pattern` regex filter → `sort_by`/`sort_order` → `page`/`page_size` slice. |
|
||||
138
deliverable_v1.1.0/modules/04_lookup.md
Normal file
138
deliverable_v1.1.0/modules/04_lookup.md
Normal file
@@ -0,0 +1,138 @@
|
||||
<!-- module: lookup -->
|
||||
<!-- description: File lookup by name and unregistration -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
## File Lookup
|
||||
|
||||
### `GET /api/v1/files/lookup`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Search registered files by file name. Performs a case-insensitive LIKE search on the file name column. Returns basic info about matching files.
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file_name` | string | Yes | File name to search for (partial matches supported) |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Look up a specific file
|
||||
curl -s "$API/api/v1/files/lookup?file_name=video.mp4" \
|
||||
-H "X-API-Key: $KEY"
|
||||
|
||||
# Partial name search
|
||||
curl -s "$API/api/v1/files/lookup?file_name=charade" \
|
||||
-H "X-API-Key: $KEY" | jq '.matches[].file_name'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"file_name": "video.mp4",
|
||||
"exists": true,
|
||||
"matches": [
|
||||
{
|
||||
"file_uuid": "a03485a40b2df2d3",
|
||||
"file_name": "video.mp4",
|
||||
"file_type": "video",
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"next_name": "video (2).mp4"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `file_name` | string | Searched name |
|
||||
| `exists` | boolean | Exact name match exists |
|
||||
| `matches` | array | Array of matching registered files |
|
||||
| `matches[].file_uuid` | string | 32-char hex UUID |
|
||||
| `matches[].file_name` | string | Registered file name |
|
||||
| `matches[].file_type` | string | `"video"`, `"audio"`, or `null` |
|
||||
| `matches[].status` | string | Registration/processing status |
|
||||
| `next_name` | string | Suggested name for avoiding conflicts |
|
||||
|
||||
---
|
||||
|
||||
## Unregister
|
||||
|
||||
### `POST /api/v1/unregister`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Delete a registered file from the system. Supports single file by UUID, or batch by directory + regex pattern.
|
||||
|
||||
#### What gets deleted
|
||||
|
||||
| Removed (default) | Not removed |
|
||||
|---------|-------------|
|
||||
| Database records (videos, chunks, embeddings, processor_results, pre_chunks) | The original source video file on disk |
|
||||
| Processor output JSON files (`{uuid}.*.json`) — unless `delete_output_files: false` | Temp/working directories |
|
||||
| In-memory cache entries | |
|
||||
| MongoDB cached lists | |
|
||||
|
||||
> ⚠️ Database deletion is **irreversible**. To keep output files, set `"delete_output_files": false`.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
At least one mode must be specified: either `file_uuid` alone, or `file_path` + `pattern` together.
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `file_uuid` | string | * | — | Single file UUID to delete |
|
||||
| `file_path` | string | * | — | Directory path (for batch delete) |
|
||||
| `pattern` | string | * | — | Regex pattern (requires `file_path`) |
|
||||
| `delete_output_files` | boolean | No | `true` | If `true`, also delete processor output JSON files (`{uuid}.*.json`). Set to `false` to keep them. |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Delete a single file by UUID (default: also deletes output JSON files)
|
||||
curl -s -X POST "$API/api/v1/unregister" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"file_uuid": "'"$FILE_UUID"'"}'
|
||||
|
||||
# Keep output JSON files, only delete DB records
|
||||
curl -s -X POST "$API/api/v1/unregister" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"file_uuid": "'"$FILE_UUID"'", "delete_output_files": false}'
|
||||
|
||||
# Batch delete all mp4 files in a directory
|
||||
curl -s -X POST "$API/api/v1/unregister" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"file_path": "/path/to/dir", "pattern": ".*\\.mp4$"}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"file_uuid": "a03485a40b2df2d3",
|
||||
"message": "Video unregistered successfully"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | boolean | True if deletion succeeded |
|
||||
| `file_uuid` | string | UUID of the deleted file (single mode) |
|
||||
| `message` | string | Human-readable status |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `400` | Neither `file_uuid` nor `file_path`+`pattern` provided |
|
||||
| `404` | File UUID not found |
|
||||
| `401` | Missing or invalid API key |
|
||||
236
deliverable_v1.1.0/modules/05_process.md
Normal file
236
deliverable_v1.1.0/modules/05_process.md
Normal file
@@ -0,0 +1,236 @@
|
||||
<!-- module: process -->
|
||||
<!-- description: Processing pipeline — trigger, probe, progress, jobs -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
## Processing Pipeline
|
||||
|
||||
### `POST /api/v1/file/:file_uuid/process`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Trigger the processing pipeline for a registered file. Creates a monitor job that the worker picks up and processes sequentially. Returns immediately with the job info—processing runs asynchronously in the background.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `processors` | string[] | No | all | Specific processors to run: `["cut","asr","asrx","yolo","ocr","face","pose","visual_chunk","story","5w1h"]` |
|
||||
| `rules` | string[] | No | all | Rule names to apply (currently unused) |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# Run all processors
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/process" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" -d '{}'
|
||||
|
||||
# Run specific processors only
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/process" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"processors": ["asr", "face", "yolo"]}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"job_id": 42,
|
||||
"file_uuid": "3a6c1865...",
|
||||
"status": "processing",
|
||||
"pids": [12345, 12346],
|
||||
"message": "Processing triggered for video.mp4"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | boolean | Always true on 200 |
|
||||
| `job_id` | integer | Monitor job ID (for job tracking) |
|
||||
| `file_uuid` | string | 32-char hex UUID of the file |
|
||||
| `status` | string | `"processing"` |
|
||||
| `pids` | integer[] | Process IDs of started processors |
|
||||
| `message` | string | Human-readable status |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | File UUID not found |
|
||||
| `401` | Missing or invalid API key |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/file/:file_uuid/probe`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Get ffprobe metadata for a registered file. Returns video/audio stream info, codec details, duration, resolution, and frame rate.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/file/$FILE_UUID/probe" -H "X-API-Key: $KEY"
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "3a6c1865...",
|
||||
"file_name": "video.mp4",
|
||||
"file_size": 794863677,
|
||||
"duration": 120.5,
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"fps": 24.0,
|
||||
"total_frames": 2892,
|
||||
"cached": true,
|
||||
"format": {
|
||||
"filename": "/path/to/video.mp4",
|
||||
"format_name": "mov,mp4,m4a,3gp",
|
||||
"duration": "120.5",
|
||||
"size": "12345678",
|
||||
"bit_rate": "819200"
|
||||
},
|
||||
"streams": [
|
||||
{
|
||||
"index": 0,
|
||||
"codec_name": "h264",
|
||||
"codec_type": "video",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"r_frame_rate": "24/1",
|
||||
"duration": "120.5"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `file_uuid` | string | 32-char hex UUID |
|
||||
| `file_name` | string | File name |
|
||||
| `file_size` | integer | File size in bytes (from filesystem) |
|
||||
| `duration` | float | Duration in seconds |
|
||||
| `width` | integer | Video width in pixels |
|
||||
| `height` | integer | Video height in pixels |
|
||||
| `fps` | float | Frames per second |
|
||||
| `total_frames` | integer | Estimated total frames |
|
||||
| `cached` | boolean | True if result was from cached probe JSON |
|
||||
| `format` | object | Container format info (ffprobe format section) |
|
||||
| `streams` | array | Array of stream info objects |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/progress/:file_uuid`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Get real-time processing progress for a file via Redis pub/sub. Includes per-processor status, current/total frames, ETA, and system resource stats.
|
||||
|
||||
#### Pipeline Order
|
||||
|
||||
| Order | Processor | Dependencies | Description |
|
||||
|-------|-----------|-------------|-------------|
|
||||
| 1 | `cut` | — | Scene detection |
|
||||
| 2 | `asr` | cut | Speech-to-text (per scene) |
|
||||
| 3 | `asrx` | asr | Speaker diarization |
|
||||
| 4 | `yolo` | — | Object detection |
|
||||
| 5 | `ocr` | — | Text recognition |
|
||||
| 6 | `face` | — | Face detection & embedding |
|
||||
| 7 | `pose` | — | Pose estimation |
|
||||
| 8 | `visual_chunk` | yolo | Visual scene chunks |
|
||||
| 9 | `story` | asr, asrx, cut, yolo, face | Scene summaries (template) |
|
||||
| 10 | `5w1h` | story | 5W1H analysis (Gemma4 LLM) |
|
||||
|
||||
All processors except `story` and `5w1h` run concurrently when their dependencies are met. Story and 5W1H run sequentially after their prerequisites.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/progress/$FILE_UUID" -H "X-API-Key: $KEY" | jq '{overall_progress, processors: [.processors[] | {processor_type, status}]}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "3a6c1865...",
|
||||
"overall_progress": 71,
|
||||
"cpu_percent": 45.2,
|
||||
"gpu_percent": 30.1,
|
||||
"memory_percent": 62.4,
|
||||
"processors": [
|
||||
{"processor_type": "asr", "status": "complete", "progress": 100},
|
||||
{"processor_type": "yolo", "status": "running", "progress": 65},
|
||||
{"processor_type": "face", "status": "pending", "progress": 0}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `file_uuid` | string | 32-char hex UUID |
|
||||
| `overall_progress` | integer | Overall progress percentage (0–100) |
|
||||
| `processors` | array | Per-processor status list |
|
||||
| `processors[].processor_type` | string | Processor name (`asr`, `cut`, `yolo`, etc.) |
|
||||
| `processors[].status` | string | `"pending"`, `"running"`, `"complete"`, or `"failed"` |
|
||||
| `processors[].progress` | integer | Per-processor progress (0–100) |
|
||||
| `processors[].eta_seconds` | integer | Estimated seconds remaining (running processors) |
|
||||
| `processors[].current` | integer | Current frame count |
|
||||
| `processors[].total` | integer | Total frame count |
|
||||
| `cpu_percent` | float | Current CPU usage |
|
||||
| `gpu_percent` | float | Current GPU utilization |
|
||||
| `memory_percent` | float | Current memory usage |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/jobs`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: system-level
|
||||
|
||||
List all processing jobs (monitor jobs) in the system. Shows job status, which file each job is processing, and current processor info.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/jobs" -H "X-API-Key: $KEY" | jq '{count, jobs: [.jobs[] | {uuid, status}]}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"jobs": [
|
||||
{
|
||||
"id": 42,
|
||||
"uuid": "3a6c1865...",
|
||||
"status": "running",
|
||||
"current_processor": "yolo",
|
||||
"created_at": "2026-05-16T12:00:00Z",
|
||||
"started_at": "2026-05-16T12:01:00Z"
|
||||
}
|
||||
],
|
||||
"count": 15,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `jobs` | array | Array of job info objects |
|
||||
| `jobs[].id` | integer | Job ID |
|
||||
| `jobs[].uuid` | string | File UUID being processed |
|
||||
| `jobs[].status` | string | `"pending"`, `"running"`, `"completed"`, `"failed"` |
|
||||
| `jobs[].current_processor` | string | Currently active processor, or null |
|
||||
| `count` | integer | Total job count |
|
||||
| `page` | integer | Current page number |
|
||||
| `page_size` | integer | Jobs per page |
|
||||
145
deliverable_v1.1.0/modules/06_search.md
Normal file
145
deliverable_v1.1.0/modules/06_search.md
Normal file
@@ -0,0 +1,145 @@
|
||||
<!-- module: search -->
|
||||
<!-- description: Vector search, BM25, smart search, universal search, visual search -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
## Search APIs
|
||||
|
||||
### `POST /api/v1/search/smart`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Semantic vector search using EmbeddingGemma-300m. Generates a query embedding via EmbeddingGemma (port 11436), then searches pgvector `story_parent` and `llm_parent` chunks by cosine similarity.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `file_uuid` | string | Yes | — | File UUID to search within |
|
||||
| `query` | string | Yes | — | Search text |
|
||||
| `limit` | integer | No | 5 | Max results to return |
|
||||
| `page` | integer | No | 1 | Page number |
|
||||
| `page_size` | integer | No | 5 | Items per page |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/search/smart" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-d '{"file_uuid": "'"$FILE_UUID"'", "query": "Audrey Hepburn"}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Audrey Hepburn",
|
||||
"results": [
|
||||
{
|
||||
"parent_id": 1087822,
|
||||
"scene_order": 1087822,
|
||||
"start_frame": 104438,
|
||||
"end_frame": 104538,
|
||||
"fps": 24.0,
|
||||
"start_time": 4351.6,
|
||||
"end_time": 4355.76,
|
||||
"summary": "[4352s-4356s, 4s] Cast: Audrey Hepburn. Total: 2 lines, 10 words. Speakers: Audrey Hepburn (2 lines)",
|
||||
"similarity": 0.67
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"page_size": 5,
|
||||
"strategy": "semantic_vector_search"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/search/universal`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Multi-type BM25 full-text search across chunks, frames, and persons. Uses PostgreSQL `tsvector`.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `query` | string | Yes | — | Search text |
|
||||
| `file_uuid` | string | No | — | Restrict to specific file |
|
||||
| `types` | string[] | No | `["chunk","frame","person"]` | Search types |
|
||||
| `limit` | integer | No | 10 | Max results per type |
|
||||
| `page` | integer | No | 1 | Page number |
|
||||
| `page_size` | integer | No | 20 | Items per page |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/search/universal" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-d '{"file_uuid": "'"$FILE_UUID"'", "query": "Cary Grant"}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"type": "chunk",
|
||||
"chunk_id": "bd80fec92b0b6963d177a2c55bf713e2_2",
|
||||
"chunk_type": "story_child",
|
||||
"start_frame": 5103,
|
||||
"end_frame": 5127,
|
||||
"start_time": 212.64,
|
||||
"end_time": 213.64,
|
||||
"text": "[213s-214s] Cary Grant: \"Olá!\"",
|
||||
"score": 0.9
|
||||
}
|
||||
],
|
||||
"total": 20,
|
||||
"took_ms": 18
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/search/frames`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Search face detection frames by identity name or trace ID.
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/search/identity_text`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Search text chunks spoken by a specific identity.
|
||||
|
||||
---
|
||||
|
||||
### Visual Search
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/v1/search/visual` | Search visual chunks |
|
||||
| POST | `/api/v1/search/visual/class` | Search by object class |
|
||||
| POST | `/api/v1/search/visual/density` | Search by object density |
|
||||
| POST | `/api/v1/search/visual/combination` | Search by object combination |
|
||||
| POST | `/api/v1/search/visual/stats` | Visual chunk statistics |
|
||||
|
||||
#### Embedding Model
|
||||
|
||||
| Detail | Value |
|
||||
|--------|-------|
|
||||
| **Model** | EmbeddingGemma-300m |
|
||||
| **Endpoint** | `POST /api/v1/embeddings` on port 11436 |
|
||||
| **Dimension** | 768 |
|
||||
| **Storage** | pgvector (`chunk.embedding` column) |
|
||||
65
deliverable_v1.1.0/modules/08_identity_agent.md
Normal file
65
deliverable_v1.1.0/modules/08_identity_agent.md
Normal file
@@ -0,0 +1,65 @@
|
||||
<!-- module: identity_agent -->
|
||||
<!-- description: Identity agent — match from photo, match from trace -->
|
||||
<!-- depends: 01_auth, 07_identity -->
|
||||
|
||||
## Identity Agent
|
||||
|
||||
### `POST /api/v1/agents/identity/match-from-photo`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Upload a face photo to match against known identities. Detects face via InsightFace, extracts 512D embedding via CoreML FaceNet, then searches pgvector for the closest identity.
|
||||
|
||||
#### Request
|
||||
|
||||
`multipart/form-data` with field `image` (JPEG/PNG) and optional `file_uuid`.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/agents/identity/match-from-photo" \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-F "image=@/path/to/face.jpg" \
|
||||
-F "file_uuid=$FILE_UUID"
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"matches": [
|
||||
{
|
||||
"identity_uuid": "a9a90105...",
|
||||
"name": "Cary Grant",
|
||||
"similarity": 0.87
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/agents/identity/match-from-trace`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Match a face trace (tracked face across frames) against known identities. Samples 3 angles from the trace, generates embeddings, and searches pgvector.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file_uuid` | string | Yes | File containing the trace |
|
||||
| `trace_id` | integer | Yes | Face trace ID to match |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/agents/identity/match-from-trace" \
|
||||
-H "Authorization: Bearer $JWT" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"file_uuid": "'"$FILE_UUID"'", "trace_id": 10}'
|
||||
```
|
||||
109
deliverable_v1.1.0/modules/09_tmdb.md
Normal file
109
deliverable_v1.1.0/modules/09_tmdb.md
Normal file
@@ -0,0 +1,109 @@
|
||||
<!-- module: tmdb -->
|
||||
<!-- description: TMDb enrichment endpoints — prefetch, probe, resource, check -->
|
||||
<!-- depends: 01_auth, 03_register -->
|
||||
|
||||
## TMDb Enrichment
|
||||
|
||||
> **Offline operation**: TMDb prefetch now checks local identity files first (`identities/_index.json` + `*.tmdb.json`).
|
||||
> If local files exist, no external API call is made. Internet is only needed for initial data seeding.
|
||||
|
||||
### Overview
|
||||
|
||||
TMDb enrichment is an optional identity enrichment step that can be run after Pipeline face detection completes. The workflow is:
|
||||
|
||||
1. **Prefetch** (requires internet): Download movie cast data from TMDb API → cache to `{file_uuid}.tmdb.json`
|
||||
2. **Probe**: Read local cache → create identities for **all** cast members (`source='tmdb'`) + save `identity.json` + download profile image to `{OUTPUT}/identities/{uuid}/profile.jpg`
|
||||
3. **Match**: The worker automatically matches video faces against TMDb identities when `MOMENTRY_TMDB_PROBE_ENABLED=true`
|
||||
|
||||
### `POST /api/v1/agents/tmdb/prefetch`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Fetch TMDb cast data for a registered file and cache it locally. This is the only step requiring internet access.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `file_uuid` | string | Yes | File UUID to enrich |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/agents/tmdb/prefetch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"file_uuid": "'"$FILE_UUID"'"}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{"success": true, "file_uuid": "...", "cache_path": "/output/...tmdb.json"}
|
||||
```
|
||||
|
||||
### `POST /api/v1/file/:file_uuid/tmdb-probe`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: file-level
|
||||
|
||||
Read local TMDb cache and create/update identities. Requires prefetch to have been run first.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/file/$FILE_UUID/tmdb-probe" \
|
||||
-H "X-API-Key: $KEY" | jq '{identities_created, movie_title}'
|
||||
```
|
||||
|
||||
#### Response (200 — identities created)
|
||||
|
||||
```json
|
||||
{"success": true, "identities_created": 15, "movie_title": "Charade"}
|
||||
```
|
||||
|
||||
#### Response (200 — no cache)
|
||||
|
||||
```json
|
||||
{"success": false, "message": "No TMDb cache found. Run tmdb-prefetch first."}
|
||||
```
|
||||
|
||||
### `GET /api/v1/resource/tmdb`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: system-level
|
||||
|
||||
View TMDb resource status including configuration, identity counts, and cache file count.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/resource/tmdb" -H "X-API-Key: $KEY" \
|
||||
| jq '{identities_seeded, cache_files}'
|
||||
```
|
||||
|
||||
### `POST /api/v1/resource/tmdb/check`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: system-level
|
||||
|
||||
Ping the TMDb API to verify connectivity and measure latency.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/resource/tmdb/check" \
|
||||
-H "X-API-Key: $KEY" | jq '.status'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"api_key_configured": true,
|
||||
"enabled": false,
|
||||
"api_reachable": true,
|
||||
"api_latency_ms": 120
|
||||
}
|
||||
```
|
||||
178
deliverable_v1.1.0/modules/10_pipeline.md
Normal file
178
deliverable_v1.1.0/modules/10_pipeline.md
Normal file
@@ -0,0 +1,178 @@
|
||||
<!-- module: pipeline -->
|
||||
<!-- description: Pipeline processors, ingestion status, stats endpoints -->
|
||||
<!-- depends: 01_auth -->
|
||||
|
||||
## Pipeline
|
||||
|
||||
### Dependency Graph
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Processors["10 Processors"]
|
||||
Cut[Cut] --> ASR[ASR]
|
||||
ASR --> ASRX[ASRX]
|
||||
ASRX --> Story[Story]
|
||||
Cut --> Story
|
||||
YOLO[YOLO] --> VisualChunk[VisualChunk]
|
||||
VisualChunk --> Story
|
||||
Face[Face] --> Story
|
||||
Story --> FiveW1H[5W1H]
|
||||
OCR[OCR]
|
||||
Pose[Pose]
|
||||
end
|
||||
|
||||
subgraph Ingestion["入庫 (Post-Processing)"]
|
||||
ASR --> Rule1[Rule 1 Sentence]
|
||||
ASRX --> Rule1
|
||||
Rule1 --> Vectorize[Auto-Vectorize]
|
||||
Rule1 --> Phase1[Phase 1 Pack]
|
||||
|
||||
Cut --> Rule3[Rule 3 Scene]
|
||||
ASR --> Rule3
|
||||
|
||||
Face --> Trace[Face Trace]
|
||||
Trace --> Qdrant[Qdrant Sync]
|
||||
Trace --> TraceChunks[Trace Chunks]
|
||||
Trace --> TKG[TKG Builder]
|
||||
|
||||
Face --> TMDbMatch[TMDb Match]
|
||||
Face --> SceneMeta[Scene Metadata]
|
||||
YOLO --> SceneMeta
|
||||
Face --> IdentityAgent[Identity Agent]
|
||||
ASRX --> IdentityAgent
|
||||
|
||||
Cut --> Agent5W1H[5W1H Agent]
|
||||
ASR --> Agent5W1H
|
||||
Agent5W1H --> Phase2[Phase 2 Pack]
|
||||
end
|
||||
|
||||
style Processors fill:#1a1a2e,stroke:#e94560
|
||||
style Ingestion fill:#16213e,stroke:#0f3460
|
||||
```
|
||||
|
||||
### Pipeline Completion Flow
|
||||
|
||||
The pipeline is **not complete** until both the 10 processors AND the 入庫 (ingestion) steps have finished. The worker polls every 3 seconds and only marks the job as `completed` when all ingestion steps verify OK.
|
||||
|
||||
```
|
||||
10 processors done
|
||||
↓ (job status stays "running")
|
||||
Algorithm 1 Trigger: Rule 1 + Vectorize + Phase 1 Pack
|
||||
↓ (job runs in parallel)
|
||||
Algorithm 2 Trigger: Face Trace → TKG, Scene Metadata, Identity Agent, 5W1H Agent
|
||||
↓ (poll checks every 3s)
|
||||
Ingestion verification: rule1 ✓ vectorize ✓ rule3 ✓ face_trace ✓ tkg ✓ scene_meta ✓ 5w1h ✓
|
||||
↓
|
||||
job status = "completed"
|
||||
```
|
||||
|
||||
### 10 Processor Stages
|
||||
|
||||
| # | Processor | Depends On | Description |
|
||||
|---|-----------|------------|-------------|
|
||||
| 1 | `Cut` | — | Scene boundary detection (PySceneDetect) |
|
||||
| 2 | `ASR` | Cut | Automatic speech recognition (faster-whisper) |
|
||||
| 3 | `ASRX` | ASR | Speaker diarization + ASR refinement |
|
||||
| 4 | `YOLO` | — | Object detection (YOLOv8) |
|
||||
| 5 | `OCR` | — | Optical character recognition |
|
||||
| 6 | `Face` | — | Face detection + recognition (InsightFace + CoreML) |
|
||||
| 7 | `Pose` | — | Pose estimation |
|
||||
| 8 | `VisualChunk` | YOLO | Visual object chunking |
|
||||
| 9 | `Story` | ASRX + Cut + YOLO + Face | Narrative scene summarization (LLM, with embedding) |
|
||||
| 10 | `5W1H` | Story | Who/What/When/Where/Why extraction (LLM, with embedding) |
|
||||
|
||||
### 入庫 (Post-Processing / Ingestion)
|
||||
|
||||
These steps run after the 10 processors and are **required for pipeline completion**. The worker checks all of them before marking the job as done.
|
||||
|
||||
| # | Step | Triggers When | Verification |
|
||||
|---|------|--------------|-------------|
|
||||
| 1 | **Rule 1 Sentence Chunking** | ASR + ASRX done | `chunk` table has rows with `chunk_type = 'sentence'` |
|
||||
| 2 | **Auto-Vectorize** | Rule 1 done | `chunk.embedding` IS NOT NULL for sentence chunks |
|
||||
| 3 | **Phase 1 Pack** | Rule 1 done | `release_pack.py --phase 1` executed |
|
||||
| 4 | **Rule 3 Scene Chunking** | All 10 processors done + Cut + ASR | `chunk` table has rows with `chunk_type = 'cut'` |
|
||||
| 5 | **Face Trace** | All 10 processors done + Face | `face_detections.trace_id` IS NOT NULL |
|
||||
| 6 | **Qdrant Face Sync** | Face Trace done | Qdrant face_embedding collection populated |
|
||||
| 7 | **Trace Chunks** | Face Trace done | `chunk` table has rows with `chunk_type = 'trace'` |
|
||||
| 8 | **TKG Builder** | Face Trace done | `tkg_nodes` + `tkg_edges` tables have rows |
|
||||
| 9 | **TMDb Face Matching** | TMDb enabled + Face done | `face_detections.identity_id` IS NOT NULL |
|
||||
| 10 | **Heuristic Scene Metadata** | Face + YOLO done | `{file_uuid}.scene_meta.json` exists on disk |
|
||||
| 11 | **Identity Agent** | Face + ASRX done | `identities` with `source = 'identity_agent'` |
|
||||
| 12 | **5W1H Agent** | Cut + ASR done | `chunk.summary_text` IS NOT NULL for cut chunks |
|
||||
| 13 | **Release Pack** | 5W1H Agent done | `release_pack.py --phase 2` executed |
|
||||
|
||||
### Ingestion Status
|
||||
|
||||
Check real-time ingestion status for a file:
|
||||
|
||||
```bash
|
||||
curl "$API/api/v1/stats/ingestion-status/{file_uuid}"
|
||||
```
|
||||
|
||||
Returns per-step `done` / `pending` status with detail counts.
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3003/api/v1/stats/ingestion-status/bd80fec9c42afb0307eb28f22c64c76a" | jq '.steps[] | {name, status, detail}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "bd80fec9c42afb0307eb28f22c64c76a",
|
||||
"steps": [
|
||||
{ "name": "rule1_sentence", "status": "pending", "detail": "0 sentence chunks" },
|
||||
{ "name": "auto_vectorize", "status": "pending", "detail": "0 embedded" },
|
||||
{ "name": "rule3_scene", "status": "pending", "detail": "0 scene chunks" },
|
||||
{ "name": "face_trace", "status": "pending", "detail": "0 traces" },
|
||||
{ "name": "trace_chunks", "status": "pending", "detail": "0 trace chunks" },
|
||||
{ "name": "tkg", "status": "pending", "detail": "0 nodes, 0 edges" },
|
||||
{ "name": "identity_match", "status": "pending", "detail": "0 identities" },
|
||||
{ "name": "scene_metadata", "status": "pending", "detail": null },
|
||||
{ "name": "5w1h", "status": "pending", "detail": "0 scenes with 5W1H" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Stats Endpoints
|
||||
|
||||
| Method | Endpoint | Auth | Description |
|
||||
|--------|----------|------|-------------|
|
||||
| GET | `/api/v1/stats/sftpgo` | No | SFTPGo service status |
|
||||
| GET | `/api/v1/stats/ingestion-status/:file_uuid` | No | Per-file ingestion checklist |
|
||||
|
||||
### Configuration
|
||||
|
||||
### `POST /api/v1/config/cache`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: system-level
|
||||
|
||||
Toggle the Redis cache on or off.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `enabled` | boolean | Yes | `true` to enable, `false` to disable |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/config/cache" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-d '{"enabled": false}'
|
||||
```
|
||||
|
||||
### Unmounted Routes
|
||||
|
||||
The following routes are defined in source code but are **NOT** currently mounted in the router:
|
||||
|
||||
| Endpoint | Source file |
|
||||
|----------|-------------|
|
||||
| `/api/v1/search/persons` | `universal_search.rs` (not mounted) |
|
||||
| `/api/v1/who` | `who.rs` |
|
||||
| `/api/v1/who/candidates` | `who.rs` |
|
||||
57
deliverable_v1.1.0/modules/11_error_codes.md
Normal file
57
deliverable_v1.1.0/modules/11_error_codes.md
Normal file
@@ -0,0 +1,57 @@
|
||||
<!-- module: error_codes -->
|
||||
<!-- description: Standard API error codes -->
|
||||
<!-- depends: -->
|
||||
|
||||
## Error Response Format
|
||||
|
||||
All API errors follow this JSON structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "E001_NOT_FOUND",
|
||||
"message": "Resource not found",
|
||||
"details": {"resource": "file_uuid", "value": "abc"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Code List
|
||||
|
||||
### Generic Errors (E0xx)
|
||||
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `E001_NOT_FOUND` | 404 | Resource not found (file, identity, chunk) |
|
||||
| `E002_DUPLICATE` | 409 | Resource already exists |
|
||||
| `E003_VALIDATION` | 400 | Request parameter validation failed |
|
||||
| `E004_UNAUTHORIZED` | 401 | Invalid API key or token |
|
||||
| `E005_INTERNAL` | 500 | Internal server error |
|
||||
|
||||
### Processor Errors (E1xx)
|
||||
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `E101_PROCESSOR_FAIL` | 500 | Python script execution failed |
|
||||
| `E102_TIMEOUT` | 504 | Processing timeout |
|
||||
| `E103_RESUME_FAIL` | 500 | Resume failed (checkpoint not found) |
|
||||
| `E104_NO_VIDEO` | 400 | Video file path not found |
|
||||
|
||||
### Identity Errors (E2xx)
|
||||
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `E201_FACE_NOT_FOUND` | 404 | Face detection not found |
|
||||
| `E202_MERGE_CONFLICT` | 409 | Identity merge conflict |
|
||||
| `E203_CANDIDATE_EMPTY` | 404 | No candidates available for confirmation |
|
||||
|
||||
### TMDb Errors (E3xx)
|
||||
|
||||
| Code | HTTP | Description |
|
||||
|------|------|-------------|
|
||||
| `E301_TMDB_NO_KEY` | 400 | `TMDB_API_KEY` environment variable not set |
|
||||
| `E302_TMDB_UNREACHABLE` | 502 | TMDb API unreachable or timed out |
|
||||
| `E303_TMDB_CACHE_NOT_FOUND` | 200 | No local TMDb cache; run prefetch first |
|
||||
| `E304_TMDB_PROBE_FAILED` | 500 | TMDb probe execution failed |
|
||||
| `E305_TMDB_MOVIE_NOT_FOUND` | 404 | No matching TMDb movie found from filename |
|
||||
118
deliverable_v1.1.0/modules/12_agent.md
Normal file
118
deliverable_v1.1.0/modules/12_agent.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Agent Endpoints
|
||||
|
||||
Agent endpoints provide AI-powered capabilities including translation, identity analysis, and 5W1H extraction.
|
||||
|
||||
## POST /api/v1/agents/translate
|
||||
|
||||
Translate text between languages using Gemma4 (llama.cpp, port 8082).
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "Hello, welcome to Momentry Core.",
|
||||
"target_language": "Traditional Chinese",
|
||||
"source_language": "English"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `text` | string | ✅ | Text to translate |
|
||||
| `target_language` | string | ✅ | Target language name (e.g. "Traditional Chinese", "Japanese") |
|
||||
| `source_language` | string | ❌ | Source language (default: "auto") |
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"translated_text": "您好,歡迎使用 Momentry Core。",
|
||||
"source_language_detected": "English",
|
||||
"model_used": "google_gemma-4-26B-A4B-it-Q5_K_M.gguf"
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Language Pairs (tested)
|
||||
|
||||
| Source | Target | Quality |
|
||||
|--------|--------|---------|
|
||||
| English | Traditional Chinese | ✅ |
|
||||
| English | Japanese | ✅ |
|
||||
| Chinese | English | ✅ |
|
||||
| English | French | ✅ |
|
||||
| Chinese | Japanese | ✅ |
|
||||
|
||||
### Model
|
||||
|
||||
- **Model**: Gemma4 26B (Q5_K_M)
|
||||
- **Engine**: llama.cpp at `localhost:8082`
|
||||
- **Endpoint**: `/v1/chat/completions` (OpenAI-compatible)
|
||||
- **Temperature**: 0.1
|
||||
- **Max tokens**: 1024
|
||||
|
||||
### Errors
|
||||
|
||||
| Status | Condition |
|
||||
|--------|-----------|
|
||||
| 500 | LLM unreachable or response parse failure |
|
||||
| 401 | Missing/invalid auth |
|
||||
|
||||
---
|
||||
|
||||
## POST /api/v1/agents/5w1h/analyze
|
||||
|
||||
Extract 5W1H (Who, What, When, Where, Why, How) from a scene. Uses Gemma4 LLM on port 8082.
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "3abeee81d94597629ed8cb943f182e94",
|
||||
"scene_id": 42
|
||||
}
|
||||
```
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"5w1h": {
|
||||
"who": ["Cary Grant"],
|
||||
"what": ["discussing plans"],
|
||||
"when": ["1963"],
|
||||
"where": ["Paris"],
|
||||
"why": ["vacation"],
|
||||
"how": ["in person"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## POST /api/v1/agents/5w1h/batch
|
||||
|
||||
Batch analyze all scenes in a file for 5W1H extraction. Uses the pipeline's `parent_chunk_5w1h.py --mode llm`.
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"file_uuid": "3abeee81d94597629ed8cb943f182e94"
|
||||
}
|
||||
```
|
||||
|
||||
## GET /api/v1/agents/5w1h/status
|
||||
|
||||
Get status of the 5W1H agent pipeline for a file.
|
||||
|
||||
---
|
||||
|
||||
## Embedding Model
|
||||
|
||||
| Detail | Value |
|
||||
|--------|-------|
|
||||
| **Model** | EmbeddingGemma-300m |
|
||||
| **Endpoint** | `POST /v1/embeddings` on port 11436 |
|
||||
| **Dimension** | 768 |
|
||||
| **Used by** | `parent_chunk_5w1h.py --embed`, story, 5W1H, search |
|
||||
|
||||
63
deliverable_v1.1.0/modules/_template.md
Normal file
63
deliverable_v1.1.0/modules/_template.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# {Module Name} — API Workspace Module
|
||||
|
||||
> Use this template when adding or editing API endpoint documentation modules.
|
||||
|
||||
## Module Metadata
|
||||
|
||||
Every module MUST start with:
|
||||
|
||||
```markdown
|
||||
<!-- module: <short_name> -->
|
||||
<!-- description: One-line description of what this module covers -->
|
||||
<!-- depends: <comma-separated list of dependency module names> -->
|
||||
```
|
||||
|
||||
## Endpoint Template
|
||||
|
||||
Each endpoint MUST use this structure:
|
||||
|
||||
### `METHOD /path/to/endpoint`
|
||||
|
||||
**Auth**: Required / Optional / Public
|
||||
|
||||
**Scope**: file-level / identity-level / system-level
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `param1` | string | Yes | — | Description |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
# brief description of what this example demonstrates
|
||||
curl -s -X METHOD "$API/path" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"param1": "value"}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{ "success": true }
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | boolean | Always true on 200 |
|
||||
|
||||
#### Error Codes
|
||||
|
||||
| Code | HTTP | When |
|
||||
|------|------|------|
|
||||
| E0xx | 4xx | Description |
|
||||
|
||||
## Rules
|
||||
|
||||
1. Each module file covers ONE topic group (e.g., `09_tmdb.md` = all TMDb endpoints)
|
||||
2. Use `$API` and `$KEY` in all curl examples
|
||||
3. Use `$FILE_UUID`, `$IDENTITY_UUID` variables for UUID examples
|
||||
4. Module filename = `NN_topic.md` (NN = execution order, 01-99)
|
||||
5. `depends` metadata = which modules must be assembled before this one
|
||||
225
deliverable_v1.1.0/scripts/build_docs.py
Normal file
225
deliverable_v1.1.0/scripts/build_docs.py
Normal file
@@ -0,0 +1,225 @@
|
||||
#!/opt/homebrew/bin/python3.11
|
||||
"""Build HTML documentation from module source files."""
|
||||
import os, markdown, re, glob, shutil
|
||||
|
||||
MODULES_DIR = os.path.join(os.path.dirname(__file__), "..", "docs_v1.0", "API_WORKSPACE", "modules")
|
||||
DOC_DIR = os.path.join(os.path.dirname(__file__), "..", "docs_v1.0", "doc")
|
||||
DOC_DEV_DIR = os.path.join(os.path.dirname(__file__), "..", "docs_v1.0", "doc_developer")
|
||||
|
||||
# User-facing modules (no developer content)
|
||||
USER_MODULES = {
|
||||
"01_auth", "02_health", "03_register", "04_lookup", "05_process",
|
||||
"06_search", "07_identity", "08_identity_agent", "08_media",
|
||||
"09_tmdb", "10_pipeline", "12_agent",
|
||||
}
|
||||
|
||||
|
||||
def md_to_html(md_text: str) -> str:
|
||||
"""Convert Markdown to HTML."""
|
||||
html = markdown.markdown(md_text, extensions=['fenced_code', 'tables', 'codehilite'])
|
||||
# Wrap tables
|
||||
html = re.sub(r'<table>', '<table class="table">', html)
|
||||
return html
|
||||
|
||||
def build_index(files, dev=False):
|
||||
"""Build index.html."""
|
||||
links = []
|
||||
for fname in sorted(files):
|
||||
name = os.path.splitext(fname)[0]
|
||||
label = MODULE_LABELS.get(name, name.replace("_", " ").title())
|
||||
if "|" in label:
|
||||
cn, en = label.split("|", 1)
|
||||
else:
|
||||
cn, en = label, ""
|
||||
html_name = fname.replace(".md", ".html")
|
||||
links.append(f'<tr onclick="window.location=\'{html_name}\'" style="cursor:pointer"><td class="cn">{cn}</td><td class="en">{en}</td></tr>')
|
||||
|
||||
title = "Momentry API 開發者文件" if dev else "Momentry API 文件"
|
||||
subtitle = "開發者專用" if dev else "API 參考手冊 — 登入後可瀏覽各模組文件"
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="zh-TW">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{title}</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }}
|
||||
.container {{ max-width: 900px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }}
|
||||
h1 {{ font-size: 28px; margin-bottom: 8px; }}
|
||||
p.subtitle {{ color: #666; margin-bottom: 24px; }}
|
||||
table {{ width: 100%; border-collapse: collapse; }}
|
||||
tr {{ border-bottom: 1px solid #eee; }}
|
||||
tr:last-child {{ border: none; }}
|
||||
td {{ padding: 10px 0; }}
|
||||
td.cn {{ width: 140px; font-weight: 600; color: #333; }}
|
||||
td.en {{ color: #666; font-size: 14px; }}
|
||||
a {{ color: #0066cc; text-decoration: none; display: block; }}
|
||||
a:hover td {{ background: #f8f8f8; border-radius: 4px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>{title}</h1>
|
||||
<p class="subtitle">{subtitle}</p>
|
||||
<table>{"".join(links)}</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
MODULE_LABELS = {
|
||||
"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",
|
||||
"11_error_codes": "錯誤碼|Error Codes",
|
||||
"12_agent": "智慧代理|AI Agents",
|
||||
}
|
||||
|
||||
def build_html(md_text: str, title: str) -> str:
|
||||
"""Wrap MD content in HTML page."""
|
||||
content = md_to_html(md_text)
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{title} - Momentry API Docs</title>
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }}
|
||||
.container {{ max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }}
|
||||
h1 {{ font-size: 24px; margin: 24px 0 12px; }}
|
||||
h2 {{ font-size: 20px; margin: 20px 0 10px; color: #222; }}
|
||||
h3 {{ font-size: 16px; margin: 16px 0 8px; color: #444; }}
|
||||
p {{ line-height: 1.6; margin: 8px 0; }}
|
||||
table {{ border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }}
|
||||
th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
|
||||
th {{ background: #f0f0f0; font-weight: 600; }}
|
||||
code {{ background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }}
|
||||
pre {{ background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }}
|
||||
pre code {{ background: none; padding: 0; }}
|
||||
a {{ color: #0066cc; }}
|
||||
.back {{ display: inline-block; margin-bottom: 20px; color: #666; }}
|
||||
.back:hover {{ color: #333; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<a class="back" href="index.html">← Back to index</a>
|
||||
{content}
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
def login_page() -> str:
|
||||
return """<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login - Momentry Docs</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; display: flex; justify-content: center; align-items: center; height: 100vh; }
|
||||
.card { background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; width: 360px; }
|
||||
h1 { font-size: 24px; margin-bottom: 24px; text-align: center; }
|
||||
input { width: 100%; padding: 10px 12px; margin-bottom: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
|
||||
button { width: 100%; padding: 10px; background: #0066cc; color: white; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; }
|
||||
button:hover { background: #0052a3; }
|
||||
.error { color: #cc0000; font-size: 13px; margin-bottom: 12px; display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Momentry Docs</h1>
|
||||
<form id="loginForm">
|
||||
<input type="text" id="username" placeholder="Username" value="demo" required>
|
||||
<input type="password" id="password" placeholder="Password" value="demo" required>
|
||||
<div class="error" id="error">Invalid credentials</div>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('loginForm').onsubmit = async function(e) {
|
||||
e.preventDefault();
|
||||
const resp = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
username: document.getElementById('username').value,
|
||||
password: document.getElementById('password').value
|
||||
})
|
||||
});
|
||||
if (resp.ok) {
|
||||
window.location.href = '/doc/index.html';
|
||||
} else {
|
||||
document.getElementById('error').style.display = 'block';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
def main():
|
||||
# Clean and recreate doc dirs
|
||||
for d in [DOC_DIR, DOC_DEV_DIR]:
|
||||
if os.path.exists(d):
|
||||
shutil.rmtree(d)
|
||||
os.makedirs(d)
|
||||
|
||||
md_files = sorted(glob.glob(os.path.join(MODULES_DIR, "*.md")))
|
||||
if not md_files:
|
||||
print(f"No MD files found in {MODULES_DIR}")
|
||||
return
|
||||
|
||||
user_html = []
|
||||
dev_html = []
|
||||
for md_path in md_files:
|
||||
with open(md_path) as f:
|
||||
md_text = f.read()
|
||||
fname = os.path.basename(md_path)
|
||||
stem = os.path.splitext(fname)[0]
|
||||
|
||||
# Skip template
|
||||
if stem == "_template":
|
||||
continue
|
||||
|
||||
# Skip error codes (developer-only)
|
||||
if stem == "11_error_codes":
|
||||
dev_only = True
|
||||
else:
|
||||
dev_only = stem not in USER_MODULES
|
||||
|
||||
title = stem.replace("_", " ").title()
|
||||
html = build_html(md_text, title)
|
||||
|
||||
if dev_only:
|
||||
out_path = os.path.join(DOC_DEV_DIR, fname.replace(".md", ".html"))
|
||||
with open(out_path, "w") as f:
|
||||
f.write(html)
|
||||
dev_html.append(fname)
|
||||
print(f" [dev] {fname}")
|
||||
else:
|
||||
out_path = os.path.join(DOC_DIR, fname.replace(".md", ".html"))
|
||||
with open(out_path, "w") as f:
|
||||
f.write(html)
|
||||
user_html.append(fname)
|
||||
print(f" [doc] {fname}")
|
||||
|
||||
# Build indexes + login page
|
||||
for d, files, label in [(DOC_DIR, user_html, "User"), (DOC_DEV_DIR, dev_html, "Dev")]:
|
||||
index = build_index(files)
|
||||
with open(os.path.join(d, "index.html"), "w") as f:
|
||||
f.write(index)
|
||||
with open(os.path.join(d, "login.html"), "w") as f:
|
||||
f.write(login_page())
|
||||
print(f" {label}: {len(files)} pages -> {d}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
148
deliverable_v1.1.0/scripts/sync_dev_to_public.sh
Executable file
148
deliverable_v1.1.0/scripts/sync_dev_to_public.sh
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/bin/bash
|
||||
# sync_dev_to_public.sh — 比對 dev/public schema,同步 pipeline 資料
|
||||
# Usage: ./sync_dev_to_public.sh [check|sync] [file_uuid]
|
||||
|
||||
PSQL="/opt/homebrew/opt/libpq/bin/psql"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCHEMA="${MOMENTRY_DB_SCHEMA:-dev}"
|
||||
DB_URL="${DATABASE_URL:-postgres://accusys@localhost:5432/momentry}"
|
||||
MODE="${1:-check}"
|
||||
FILE_UUID="${2:-}"
|
||||
|
||||
TABLES=("videos" "chunk" "face_detections" "processor_results" "monitor_jobs"
|
||||
"identities" "identity_bindings" "tkg_nodes" "tkg_edges")
|
||||
|
||||
TARGET="public"
|
||||
|
||||
if [ -z "$FILE_UUID" ]; then
|
||||
echo "Usage: $0 [check|sync] <file_uuid>"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 check bd80fec92b0b6963d177a2c55bf713e2"
|
||||
echo " $0 sync bd80fec92b0b6963d177a2c55bf713e2"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Schema Sync: $SCHEMA → $TARGET ==="
|
||||
echo "File UUID: $FILE_UUID"
|
||||
echo "Mode: $MODE"
|
||||
echo ""
|
||||
|
||||
check_table() {
|
||||
local table=$1
|
||||
local col=$2
|
||||
local src_count dev_count pub_count
|
||||
|
||||
dev_count=$($PSQL -At "$DB_URL" -c "SELECT COUNT(*) FROM ${SCHEMA}.${table} WHERE ${col} = '${FILE_UUID}';" 2>/dev/null || echo "ERROR")
|
||||
pub_count=$($PSQL -At "$DB_URL" -c "SELECT COUNT(*) FROM ${TARGET}.${table} WHERE ${col} = '${FILE_UUID}';" 2>/dev/null || echo "ERROR")
|
||||
|
||||
if [ "$dev_count" = "ERROR" ] || [ "$pub_count" = "ERROR" ]; then
|
||||
echo " ⚠️ $table — query error (table may not exist in $TARGET)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$dev_count" -eq "$pub_count" ]; then
|
||||
echo " ✅ $table — $dev_count rows (match)"
|
||||
return 0
|
||||
else
|
||||
echo " ❌ $table — dev=$dev_count pub=$pub_count (MISMATCH)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
sync_table() {
|
||||
local table=$1
|
||||
local col=$2
|
||||
local src_count dev_count pub_count
|
||||
|
||||
dev_count=$($PSQL -At "$DB_URL" -c "SELECT COUNT(*) FROM ${SCHEMA}.${table} WHERE ${col} = '${FILE_UUID}';" 2>/dev/null || echo "0")
|
||||
pub_count=$($PSQL -At "$DB_URL" -c "SELECT COUNT(*) FROM ${TARGET}.${table} WHERE ${col} = '${FILE_UUID}';" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$dev_count" = "0" ]; then
|
||||
echo " ⏭️ $table — dev has 0 rows, skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
if [ "$dev_count" -eq "$pub_count" ]; then
|
||||
echo " ✅ $table — already synced ($dev_count rows)"
|
||||
return
|
||||
fi
|
||||
|
||||
echo " 🔄 Syncing $table: dev=$dev_count → pub=$pub_count ..."
|
||||
|
||||
# Delete existing public rows, insert from dev
|
||||
$PSQL "$DB_URL" -q -c "DELETE FROM ${TARGET}.${table} WHERE ${col} = '${FILE_UUID}';" 2>/dev/null || true
|
||||
|
||||
# Get columns list (excluding id for SERIAL)
|
||||
COLS=$($PSQL -At "$DB_URL" -c "
|
||||
SELECT string_agg(column_name, ', ' ORDER BY ordinal_position)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema='${SCHEMA}' AND table_name='${table}'
|
||||
AND column_name != 'id'
|
||||
AND is_updatable='YES';
|
||||
")
|
||||
|
||||
$PSQL "$DB_URL" -q -c "
|
||||
INSERT INTO ${TARGET}.${table} (${COLS})
|
||||
SELECT ${COLS}
|
||||
FROM ${SCHEMA}.${table}
|
||||
WHERE ${col} = '${FILE_UUID}';
|
||||
" 2>/dev/null && echo " ✅ $table synced" || echo " ❌ $table sync FAILED"
|
||||
}
|
||||
|
||||
echo "=== Checking Tables ==="
|
||||
echo ""
|
||||
MISMATCH=0
|
||||
for table in "${TABLES[@]}"; do
|
||||
# Determine the UUID column name for each table
|
||||
case "$table" in
|
||||
videos) col="file_uuid" ;;
|
||||
chunk) col="file_uuid" ;;
|
||||
face_detections) col="file_uuid" ;;
|
||||
processor_results) col="file_uuid" ;;
|
||||
monitor_jobs) col="uuid" ;;
|
||||
identities) col="uuid" ;; # identities.uuid is UUID type
|
||||
identity_bindings) col="uuid" ;;
|
||||
tkg_nodes) col="file_uuid" ;;
|
||||
tkg_edges) col="file_uuid" ;;
|
||||
*) col="file_uuid" ;;
|
||||
esac
|
||||
|
||||
if ! check_table "$table" "$col"; then
|
||||
MISMATCH=$((MISMATCH + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if [ "$MISMATCH" -eq 0 ]; then
|
||||
echo "✅ All tables in sync"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$MODE" != "sync" ]; then
|
||||
echo "⚠️ $MISMATCH table(s) have mismatches. Run '$0 sync $FILE_UUID' to fix."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Syncing Tables ==="
|
||||
echo ""
|
||||
for table in "${TABLES[@]}"; do
|
||||
case "$table" in
|
||||
videos) col="file_uuid" ;;
|
||||
chunk) col="file_uuid" ;;
|
||||
face_detections) col="file_uuid" ;;
|
||||
processor_results) col="file_uuid" ;;
|
||||
monitor_jobs) col="uuid" ;;
|
||||
identities) col="uuid" ;;
|
||||
identity_bindings) col="uuid" ;;
|
||||
tkg_nodes) col="file_uuid" ;;
|
||||
tkg_edges) col="file_uuid" ;;
|
||||
*) col="file_uuid" ;;
|
||||
esac
|
||||
sync_table "$table" "$col"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "✅ Sync complete"
|
||||
174
deliverable_v1.1.0/scripts/update_qdrant_uuid.py
Normal file
174
deliverable_v1.1.0/scripts/update_qdrant_uuid.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python3
|
||||
"""批量更新 Qdrant collection 中的 file_uuid (舊→新)"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
QDRANT_URL = "http://localhost:6333"
|
||||
|
||||
# UUID mapping: 舊 → 新
|
||||
UUID_MAP = {
|
||||
"aeed71342a899fe4b4c57b7d41bcb692": [
|
||||
"bd80fec92b0b6963d177a2c55bf713e2",
|
||||
],
|
||||
}
|
||||
|
||||
# Collections to process
|
||||
COLLECTIONS = [
|
||||
"momentry_dev_v1",
|
||||
"momentry_dev_stories",
|
||||
"momentry_dev_voice",
|
||||
"momentry_dev_rule1_v2",
|
||||
"momentry_dev_faces",
|
||||
"sentence_story",
|
||||
"sentence_summary",
|
||||
]
|
||||
|
||||
|
||||
def qdrant_get(path: str) -> dict:
|
||||
res = subprocess.run(
|
||||
["curl", "-s", "-X", "GET", f"{QDRANT_URL}{path}"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return json.loads(res.stdout) if res.stdout.strip() else {}
|
||||
|
||||
|
||||
def qdrant_post(path: str, body: dict) -> dict:
|
||||
tmp = "/tmp/qdrant_post.json"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(body, f)
|
||||
res = subprocess.run(
|
||||
["curl", "-s", "-X", "POST", f"{QDRANT_URL}{path}",
|
||||
"-H", "Content-Type: application/json", "-d", f"@{tmp}"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return json.loads(res.stdout) if res.stdout.strip() else {}
|
||||
|
||||
|
||||
def qdrant_put(path: str, body: dict) -> dict:
|
||||
tmp = "/tmp/qdrant_update.json"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(body, f)
|
||||
res = subprocess.run(
|
||||
["curl", "-s", "-X", "PUT", f"{QDRANT_URL}{path}",
|
||||
"-H", "Content-Type: application/json", "-d", f"@{tmp}"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
return json.loads(res.stdout) if res.stdout.strip() else {}
|
||||
|
||||
|
||||
def scroll_all(collection: str, filter_old: dict) -> list:
|
||||
"""Scroll all matching points from a collection"""
|
||||
points = []
|
||||
offset = None
|
||||
while True:
|
||||
body = {
|
||||
"limit": 1000,
|
||||
"with_payload": True,
|
||||
"with_vector": True,
|
||||
"filter": filter_old,
|
||||
}
|
||||
if offset:
|
||||
body["offset"] = offset
|
||||
result = qdrant_post(f"/collections/{collection}/points/scroll", body)
|
||||
batch = result.get("result", {}).get("points", [])
|
||||
points.extend(batch)
|
||||
next_offset = result.get("result", {}).get("next_page_offset")
|
||||
if next_offset is None:
|
||||
break
|
||||
offset = next_offset
|
||||
return points
|
||||
|
||||
|
||||
def update_points(collection: str, points: list, old_uuid: str, new_uuid: str):
|
||||
"""Update file_uuid in payload for the given points"""
|
||||
if not points:
|
||||
return 0
|
||||
|
||||
updated = []
|
||||
for p in points:
|
||||
pl = p.get("payload", {})
|
||||
# Check both 'uuid' and 'file_uuid' fields
|
||||
changed = False
|
||||
if pl.get("uuid") == old_uuid:
|
||||
pl["uuid"] = new_uuid
|
||||
changed = True
|
||||
if pl.get("file_uuid") == old_uuid:
|
||||
pl["file_uuid"] = new_uuid
|
||||
changed = True
|
||||
if changed:
|
||||
updated.append({
|
||||
"id": p["id"],
|
||||
"vector": p["vector"],
|
||||
"payload": pl,
|
||||
})
|
||||
|
||||
if not updated:
|
||||
return 0
|
||||
|
||||
# Update in batches of 500
|
||||
total = len(updated)
|
||||
for i in range(0, total, 500):
|
||||
batch = updated[i:i+500]
|
||||
result = qdrant_put(
|
||||
f"/collections/{collection}/points?wait=true",
|
||||
{"points": batch}
|
||||
)
|
||||
if result.get("status") != "ok":
|
||||
print(f" Error at {i}: {result}")
|
||||
return i
|
||||
return total
|
||||
|
||||
|
||||
def main():
|
||||
for collection in COLLECTIONS:
|
||||
# Check if collection exists
|
||||
info = qdrant_get(f"/collections/{collection}")
|
||||
if "result" not in info:
|
||||
continue
|
||||
|
||||
for old_uuid, new_uuids in UUID_MAP.items():
|
||||
for new_uuid in new_uuids:
|
||||
# Scroll all points with this old UUID
|
||||
filter_body = {
|
||||
"must": [
|
||||
{"should": [
|
||||
{"key": "uuid", "match": {"value": old_uuid}},
|
||||
{"key": "file_uuid", "match": {"value": old_uuid}},
|
||||
]}
|
||||
]
|
||||
}
|
||||
points = scroll_all(collection, filter_body)
|
||||
if not points:
|
||||
continue
|
||||
|
||||
print(f"{collection}: {len(points)} points with UUID {old_uuid[:8]}...")
|
||||
updated = update_points(collection, points, old_uuid, new_uuid)
|
||||
print(f" → {updated} points updated to {new_uuid[:8]}...")
|
||||
|
||||
# Verify
|
||||
print("\n=== Verification ===")
|
||||
for collection in COLLECTIONS:
|
||||
for old_uuid, new_uuids in UUID_MAP.items():
|
||||
for what, uuid in [("old", old_uuid), ("new", new_uuids[0])]:
|
||||
filter_body = {
|
||||
"must": [
|
||||
{"should": [
|
||||
{"key": "uuid", "match": {"value": uuid}},
|
||||
{"key": "file_uuid", "match": {"value": uuid}},
|
||||
]}
|
||||
]
|
||||
}
|
||||
result = qdrant_post(
|
||||
f"/collections/{collection}/points/count",
|
||||
{"filter": filter_body}
|
||||
)
|
||||
cnt = result.get("result", {}).get("count", 0)
|
||||
if cnt > 0:
|
||||
print(f" {collection}: {cnt} points with {what} UUID")
|
||||
print("✅ Done")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user