diff --git a/docs_v1.0/API_WORKSPACE/modules/10_pipeline.md b/docs_v1.0/API_WORKSPACE/modules/10_pipeline.md new file mode 100644 index 0000000..73b3bc8 --- /dev/null +++ b/docs_v1.0/API_WORKSPACE/modules/10_pipeline.md @@ -0,0 +1,117 @@ + + + + +## Pipeline + +### 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 | Narrative scene summarization (LLM, with embedding) | +| 10 | `5W1H` | Story | Who/What/When/Where/Why extraction (LLM, with embedding) | + +### Post-Processing (入庫) + +After all 10 processors complete, the pipeline runs the following storage & enrichment steps: + +| # | Step | Requires | Evidence | +|---|------|----------|----------| +| 1 | **Rule 1 Sentence Chunking** | ASR + ASRX | `chunk` table, `chunk_type = 'sentence'` | +| 2 | **Auto-Vectorize** | Rule 1 | `chunk.embedding` IS NOT NULL (pgvector) | +| 3 | **Rule 3 Scene Chunking** | Cut + ASR | `chunk` table, `chunk_type = 'cut'` | +| 4 | **Face Trace + DB Store** | Face | `face_detections.trace_id` IS NOT NULL | +| 5 | **Qdrant Face Sync** | Face Trace | Qdrant collection (face embeddings) | +| 6 | **Trace Chunks** | Face Trace | `chunk` table, `chunk_type = 'trace'` | +| 7 | **TKG Builder** | Face Trace | `tkg_nodes` + `tkg_edges` tables | +| 8 | **TMDb Face Matching** | Face + TMDb enabled | `face_detections.identity_id` IS NOT NULL | +| 9 | **Heuristic Scene Metadata** | Face + YOLO | `{file_uuid}.scene_meta.json` on disk | +| 10 | **Identity Agent** | Face + ASRX | `identities` with `source = 'identity_agent'` | +| 11 | **5W1H Agent** | Cut + ASR | `chunk.summary_text` IS NOT NULL (chunk_type = 'cut') | +| 12 | **Release Pack** | 5W1H Agent | `release_pack.py --phase 2` output | + +### 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/universal` | `universal_search.rs` | +| `/api/v1/search/frames` | `universal_search.rs` | +| `/api/v1/search/persons` | `universal_search.rs` | +| `/api/v1/who` | `who.rs` | +| `/api/v1/who/candidates` | `who.rs` | diff --git a/docs_v1.0/doc/01_auth.html b/docs_v1.0/doc/01_auth.html new file mode 100644 index 0000000..11a47c9 --- /dev/null +++ b/docs_v1.0/doc/01_auth.html @@ -0,0 +1,388 @@ + + + + +01 Auth - Momentry API Docs + + + +
+← Back to index + + + + +

Base URL

+ + + + + + + + + + + + + + + + + + + + +
EnvironmentURLPurpose
Productionhttp://localhost:3002Production deployment
External (M5)https://m5api.momentry.ddns.netRemote access
+

Variables

+

All examples in this documentation use these environment variables:

+
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):

+ +

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=)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ModeTransportExpiryScopeBest for
Session CookieCookie: session_id=<session_id>24hper-browser sessionPortal (browser)
JWTAuthorization: Bearer <token>1hper-login tokenAPI clients, CLI, scripts
API KeyX-API-Key: <key>90dfixed key for automationLegacy scripts, WordPress
+
+

Login

+

Default accounts & API keys:

+ + + + + + + + + + + + + + + + + + + + + + + +
UsernamePasswordAPI KeyRole
adminadminadmin
demodemomuser_demo_key_32chars_abcdef1234567890user
+

The demo API key is set via MOMENTRY_DEMO_API_KEY env var and can be used in place of JWT for marcom integrations:

+
# Using API key instead of JWT
+curl -s "$API/api/v1/files/scan" -H "X-API-Key: muser_demo_key_32chars_abcdef1234567890"
+
+ +
# 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

+
{
+  "success": true,
+  "jwt": "eyJhbGciOiJIUzI1NiIs...",
+  "api_key": "muser_...",
+  "user": {
+    "username": "admin",
+    "role": "admin"
+  },
+  "expires_at": "2026-05-18T13:00:00Z"
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
jwtstringJWT access token. Use as Authorization: Bearer <jwt>. Expires in 1 hour.
api_keystringLegacy API key. Use as X-API-Key: <key>. Good for 90 days.
user.usernamestringUsername
user.rolestringRole: admin, user, or readonly
expires_atstringISO8601 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)

+
{
+  "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).

+
# 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.

+
# 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

+
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)

+
momentry api-key create "My API Key" --key-type user
+
+ +
+

Logout

+
# Logout using the session cookie (browser)
+curl -X POST "$API/api/v1/auth/logout" \
+  -H "Cookie: session_id=<uuid>"
+
+ +

What logout does

+ + + + + + + + + + + + + + + + + + + + + +
Auth modeEffect
Session CookieSession deleted from database. Same cookie returns 401 on subsequent requests.
JWTJWT remains valid until expiry. (JWT is stateless — logout adds JWT to a blacklist only if API key mode is used.)
API KeyAPI key remains valid. (Legacy keys are shared across sessions — revoking would break other clients.)
+

Example: full session lifecycle

+
# 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

+ + + + + + + + + + + + + + + + + + + + + + + + + +
HTTPWhen
401Missing or invalid authentication
401Session expired or logged out
401JWT expired
401API key revoked or inactive
+
+

Related

+ +
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/02_health.html b/docs_v1.0/doc/02_health.html new file mode 100644 index 0000000..0ed4b92 --- /dev/null +++ b/docs_v1.0/doc/02_health.html @@ -0,0 +1,277 @@ + + + + +02 Health - Momentry API Docs + + + +
+← Back to index + + + + +

Health Check

+

GET /health

+

Auth: Public +Scope: system-level

+

Returns basic server health status — used by load balancers and monitoring.

+

Example

+
curl "$API/health" | jq '{status, version}'
+
+ +

Response (200)

+
{
+  "status": "ok",
+  "version": "1.0.0",
+  "build_git_hash": "3a6c1865",
+  "build_timestamp": "2026-05-16T13:38:15Z",
+  "uptime_ms": 3015
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusstringok or degraded
versionstringSemver version
build_git_hashstringGit commit hash
build_timestampstringBinary build time
uptime_msintegerMilliseconds 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

+
curl "$API/health/detailed" | jq '{status, services, resources: {cpu: .resources.cpu_used_percent, memory: .resources.memory_used_percent}}'
+
+ +

Response (200)

+
{
+  "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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
statusstringok if all essential services healthy
servicesobjectPer-service status (postgres, redis, qdrant)
services.*.statusstringok, error, or degraded
services.*.latency_msintResponse time in milliseconds
resourcesobjectCPU, memory usage
pipeline.scripts_readybooleanScripts directory accessible
pipeline.scripts_countintNumber of Python processor scripts
pipeline.processorsobjectPer-processor availability
pipeline.models_readybooleanModels directory accessible
pipeline.scripts_integrityobjectSHA256 checksum verification results
schema.okbooleanAll required migrations applied
identities.syncedbooleanIdentity file count matches DB count
integrations.tmdbobjectTMDB API key config and reachability
+

Health status rules

+ + + + + + + + + + + + + + + + + + + + + +
Conditionstatus
All services okok
Any service errordegraded
Postgres or Redis errordegraded (server still responds)
+
+

Stats Endpoints

+ + + + + + + + + + + + + + + + + +
MethodEndpointAuthDescription
GET/api/v1/stats/sftpgoNoSFTPGo service status
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/03_register.html b/docs_v1.0/doc/03_register.html new file mode 100644 index 0000000..f5f5bc8 --- /dev/null +++ b/docs_v1.0/doc/03_register.html @@ -0,0 +1,444 @@ + + + + +03 Register - Momentry API Docs + + + +
+← Back to index + + + + +

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/: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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
file_pathstringYesPath to video file on disk
patternstringNoRegex pattern for batch register (requires file_path to be a directory)
user_idintegerNoUser ID to associate with registration
content_hashstringNoPre-computed SHA-256 hash (skips computation)
+

Example

+
# 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)

+
{
+  "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"
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
successbooleanAlways true on 200
file_uuidstring32-char hex UUID of the registered file
file_namestringFile name (auto-renamed if name conflict)
file_pathstringCanonical path on disk
file_typestring"video", "audio", or "unknown"
durationfloatDuration in seconds
widthintegerVideo width in pixels
heightintegerVideo height in pixels
fpsfloatFrames per second
total_framesintegerTotal frame count
already_existsbooleanTrue if same content was already registered
messagestringHuman-readable status
+

Error Responses

+ + + + + + + + + + + + + + + + + + + + + +
HTTPWhen
401Missing or invalid API key
400Invalid request body
404File 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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
pageintegerNo1Page number (1-based)
page_sizeintegerNoallItems per page (alias: limit)
limitintegerNoallMax items (alias for page_size)
patternstringNoRegex filter on file name (e.g., .*\\.mp4$)
sort_bystringNonameSort field: name, size, modified, status
sort_orderstringNoascSort direction: asc or desc
+

Example

+
# 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)

+
{
+  "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
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
filesarrayArray of file info objects (paginated)
files[].file_namestringFile name
files[].relative_pathstringPath relative to scan root
files[].file_pathstringAbsolute path on disk
files[].file_sizeintegerFile size in bytes
files[].modified_timestringLast modified timestamp (ISO8601)
files[].is_registeredbooleanWhether file is registered in DB
files[].file_uuidstring32-char hex UUID (only if registered)
files[].statusstring"completed", "processing", "registered", "unregistered", or null
files[].registration_timestringDB registration timestamp (only if registered)
files[].job_idintegerProcessing job ID (only if a job exists)
totalintegerTotal files found on disk (unfiltered)
filtered_totalintegerFiles matching regex filter
pageintegerCurrent page number
page_sizeintegerItems per page
total_pagesintegerTotal pages
registered_countintegerFiles registered in DB
unregistered_countintegerFiles not yet registered
+

Notes

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FeatureBehavior
RegexCase-insensitive ((?i) prefix auto-applied). Applied to file_name.
Sort orderDefault (sort_by=name): registered files first, then alphabetically. sort_by=status: alphabetical by status string.
Paginationpage_size and limit are aliases. Default: show all results.
Processing orderpattern regex filter → sort_by/sort_orderpage/page_size slice.
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/04_lookup.html b/docs_v1.0/doc/04_lookup.html new file mode 100644 index 0000000..1ce9106 --- /dev/null +++ b/docs_v1.0/doc/04_lookup.html @@ -0,0 +1,291 @@ + + + + +04 Lookup - Momentry API Docs + + + +
+← Back to index + + + + +

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

+ + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
file_namestringYesFile name to search for (partial matches supported)
+

Example

+
# 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)

+
{
+  "file_name": "video.mp4",
+  "exists": true,
+  "matches": [
+    {
+      "file_uuid": "a03485a40b2df2d3",
+      "file_name": "video.mp4",
+      "file_type": "video",
+      "status": "completed"
+    }
+  ],
+  "next_name": "video (2).mp4"
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
file_namestringSearched name
existsbooleanExact name match exists
matchesarrayArray of matching registered files
matches[].file_uuidstring32-char hex UUID
matches[].file_namestringRegistered file name
matches[].file_typestring"video", "audio", or null
matches[].statusstringRegistration/processing status
next_namestringSuggested 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: falseTemp/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.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
file_uuidstring*Single file UUID to delete
file_pathstring*Directory path (for batch delete)
patternstring*Regex pattern (requires file_path)
delete_output_filesbooleanNotrueIf true, also delete processor output JSON files ({uuid}.*.json). Set to false to keep them.
+

Example

+
# 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)

+
{
+  "success": true,
+  "file_uuid": "a03485a40b2df2d3",
+  "message": "Video unregistered successfully"
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
successbooleanTrue if deletion succeeded
file_uuidstringUUID of the deleted file (single mode)
messagestringHuman-readable status
+

Error Responses

+ + + + + + + + + + + + + + + + + + + + + +
HTTPWhen
400Neither file_uuid nor file_path+pattern provided
404File UUID not found
401Missing or invalid API key
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/05_process.html b/docs_v1.0/doc/05_process.html new file mode 100644 index 0000000..69cba53 --- /dev/null +++ b/docs_v1.0/doc/05_process.html @@ -0,0 +1,505 @@ + + + + +05 Process - Momentry API Docs + + + +
+← Back to index + + + + +

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

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
processorsstring[]NoallSpecific processors to run: ["cut","asr","asrx","yolo","ocr","face","pose","visual_chunk","story","5w1h"]
rulesstring[]NoallRule names to apply (currently unused)
+

Example

+
# 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)

+
{
+  "success": true,
+  "job_id": 42,
+  "file_uuid": "3a6c1865...",
+  "status": "processing",
+  "pids": [12345, 12346],
+  "message": "Processing triggered for video.mp4"
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
successbooleanAlways true on 200
job_idintegerMonitor job ID (for job tracking)
file_uuidstring32-char hex UUID of the file
statusstring"processing"
pidsinteger[]Process IDs of started processors
messagestringHuman-readable status
+

Error Responses

+ + + + + + + + + + + + + + + + + +
HTTPWhen
404File UUID not found
401Missing 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

+
curl -s "$API/api/v1/file/$FILE_UUID/probe" -H "X-API-Key: $KEY"
+
+ +

Response (200)

+
{
+  "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"
+    }
+  ]
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
file_uuidstring32-char hex UUID
file_namestringFile name
file_sizeintegerFile size in bytes (from filesystem)
durationfloatDuration in seconds
widthintegerVideo width in pixels
heightintegerVideo height in pixels
fpsfloatFrames per second
total_framesintegerEstimated total frames
cachedbooleanTrue if result was from cached probe JSON
formatobjectContainer format info (ffprobe format section)
streamsarrayArray 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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OrderProcessorDependenciesDescription
1cutScene detection
2asrcutSpeech-to-text (per scene)
3asrxasrSpeaker diarization
4yoloObject detection
5ocrText recognition
6faceFace detection & embedding
7posePose estimation
8visual_chunkyoloVisual scene chunks
9storyasr, asrx, cut, yolo, faceScene summaries (template)
105w1hstory5W1H 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

+
curl -s "$API/api/v1/progress/$FILE_UUID" -H "X-API-Key: $KEY" | jq '{overall_progress, processors: [.processors[] | {processor_type, status}]}'
+
+ +

Response (200)

+
{
+  "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}
+  ]
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
file_uuidstring32-char hex UUID
overall_progressintegerOverall progress percentage (0–100)
processorsarrayPer-processor status list
processors[].processor_typestringProcessor name (asr, cut, yolo, etc.)
processors[].statusstring"pending", "running", "complete", or "failed"
processors[].progressintegerPer-processor progress (0–100)
processors[].eta_secondsintegerEstimated seconds remaining (running processors)
processors[].currentintegerCurrent frame count
processors[].totalintegerTotal frame count
cpu_percentfloatCurrent CPU usage
gpu_percentfloatCurrent GPU utilization
memory_percentfloatCurrent 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

+
curl -s "$API/api/v1/jobs" -H "X-API-Key: $KEY" | jq '{count, jobs: [.jobs[] | {uuid, status}]}'
+
+ +

Response (200)

+
{
+  "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
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
jobsarrayArray of job info objects
jobs[].idintegerJob ID
jobs[].uuidstringFile UUID being processed
jobs[].statusstring"pending", "running", "completed", "failed"
jobs[].current_processorstringCurrently active processor, or null
countintegerTotal job count
pageintegerCurrent page number
page_sizeintegerJobs per page
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/06_search.html b/docs_v1.0/doc/06_search.html new file mode 100644 index 0000000..9843461 --- /dev/null +++ b/docs_v1.0/doc/06_search.html @@ -0,0 +1,258 @@ + + + + +06 Search - Momentry API Docs + + + +
+← Back to index + + + + +

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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
uuidstringYesFile UUID to search within
querystringYesSearch text
pageintegerNo1Page number
page_sizeintegerNo5Items per page
+

Example

+
curl -s -X POST "$API/api/v1/search/smart" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer $JWT" \
+  -d '{"uuid": "'"$FILE_UUID"'", "query": "Audrey Hepburn"}'
+
+ +

Response (200)

+
{
+  "query": "Audrey Hepburn",
+  "results": [
+    {
+      "parent_id": 12345,
+      "start_time": 299.0,
+      "end_time": 300.0,
+      "summary": "[299s-300s, 1s] Cast: Audrey Hepburn. Total: 1 lines, 5 words...",
+      "similarity": 0.72
+    }
+  ],
+  "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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
querystringYesSearch text
uuidstringNoRestrict to specific file
typesstring[]No["chunk","frame","person"]Search types
pageintegerNo1Page number
page_sizeintegerNo20Items per page
+

Example

+
curl -s -X POST "$API/api/v1/search/universal" \
+  -H "Content-Type: application/json" \
+  -H "Authorization: Bearer $JWT" \
+  -d '{"uuid": "'"$FILE_UUID"'", "query": "Cary Grant"}'
+
+ +

Response (200)

+
{
+  "results": [
+    {
+      "type": "chunk",
+      "chunk_id": "uuid_1429",
+      "chunk_type": "story_child",
+      "start_time": 429.16,
+      "end_time": 430.5,
+      "text": "You could have the stamps.",
+      "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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MethodEndpointDescription
POST/api/v1/search/visualSearch visual chunks
POST/api/v1/search/visual/classSearch by object class
POST/api/v1/search/visual/densitySearch by object density
POST/api/v1/search/visual/combinationSearch by object combination
POST/api/v1/search/visual/statsVisual chunk statistics
+

Embedding Model

+ + + + + + + + + + + + + + + + + + + + + + + + + +
DetailValue
ModelEmbeddingGemma-300m
EndpointPOST /api/v1/embeddings on port 11436
Dimension768
Storagepgvector (chunk.embedding column)
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/07_identity.html b/docs_v1.0/doc/07_identity.html new file mode 100644 index 0000000..239e688 --- /dev/null +++ b/docs_v1.0/doc/07_identity.html @@ -0,0 +1,482 @@ + + + + +07 Identity - Momentry API Docs + + + +
+← Back to index + + + + +

Global Identities

+

GET /api/v1/identities

+

Auth: Required +Scope: identity-level

+

List all registered identities with pagination.

+

Example

+
curl -s "$API/api/v1/identities?page=1&page_size=20" -H "X-API-Key: $KEY" | jq '{count, identities: [.identities[] | {name}]}'
+
+ +
+

GET /api/v1/identity/:identity_uuid

+

Auth: Required +Scope: identity-level

+

Get detailed information for a specific identity, including metadata and TMDb references.

+

Example

+
curl -s "$API/api/v1/identity/$IDENTITY_UUID" -H "X-API-Key: $KEY"
+
+ +

Response (200)

+
{
+  "success": true,
+  "identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
+  "name": "Cary Grant",
+  "identity_type": "people",
+  "source": "tmdb",
+  "status": "confirmed",
+  "tmdb_id": 112,
+  "tmdb_profile": "https://image.tmdb.org/t/p/w185/abc.jpg",
+  "metadata": {},
+  "reference_data": {},
+  "created_at": "2026-05-16T12:00:00Z",
+  "updated_at": null
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
identity_uuidstringIdentity identifier
namestringIdentity name
identity_typestring"people" or null
sourcestring.json, auto, tmdb, user_defined, or merged
statusstring"confirmed", "pending", or "inactive"
tmdb_idintegerTMDb person ID (only if source = tmdb)
tmdb_profilestringTMDb profile image URL
metadataobjectMetadata JSON (tmdb_character, cast_order, etc.)
created_atstringCreation timestamp
+
+

DELETE /api/v1/identity/:identity_uuid

+

Auth: Required +Scope: identity-level

+

Delete an identity permanently.

+
+

GET /api/v1/identity/:identity_uuid/files

+

Auth: Required +Scope: identity-level

+

Get all files where this identity appears. Returns per-file summary including face count, confidence, and appearance time range.

+

Example

+
curl -s "$API/api/v1/identity/$IDENTITY_UUID/files" -H "X-API-Key: $KEY"
+
+ +
+

GET /api/v1/identity/:identity_uuid/faces

+

Auth: Required +Scope: identity-level

+

Get all face detection records associated with this identity.

+

Example

+
curl -s "$API/api/v1/identity/$IDENTITY_UUID/faces" -H "X-API-Key: $KEY"
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
file_uuidstringFile where face was detected
frame_numberintegerFrame number of detection
face_idstringFace ID (format: face_{frame_number})
confidencefloatDetection confidence
+
+

GET /api/v1/identity/:identity_uuid/chunks

+

Auth: Required +Scope: identity-level

+

Get all text chunks (sentences) spoken while this identity's face was on screen. Useful for finding what a person said.

+

Example

+
curl -s "$API/api/v1/identity/$IDENTITY_UUID/chunks" -H "X-API-Key: $KEY"
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
file_uuidstringFile identifier
chunk_idstringSentence chunk identifier
start_timefloatStart time in seconds
end_timefloatEnd time in seconds
textstringSpoken text content
+
+

POST /api/v1/identity/:identity_uuid/bind

+

Auth: Required +Scope: identity-level

+

Bind a face detection to an identity. Associates the face trace with the identity for future search and recognition.

+

Request Parameters

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
file_uuidstringYesFile where face is detected
face_idstringYesFace ID (format: {frame}_{idx})
+

Example

+
curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind" \
+  -H "X-API-Key: $KEY" \
+  -H "Content-Type: application/json" \
+  -d '{"file_uuid": "'"$FILE_UUID"'", "face_id": "1_5"}'
+
+ +
+

POST /api/v1/identity/:identity_uuid/unbind

+

Auth: Required +Scope: identity-level

+

Unbind a face detection from an identity. Removes the identity association from the face record.

+
+

GET /api/v1/identities/search

+

Auth: Required +Scope: identity-level

+

Search identities by name (ILIKE search). Returns matching identity records.

+

Example

+
curl -s "$API/api/v1/identities/search?q=Cary" -H "X-API-Key: $KEY"
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeDescription
namestringIdentity name
sourcestringIdentity source
tmdb_idintegerTMDb ID (if source = tmdb)
file_uuidstringAssociated file
+
+
+

POST /api/v1/identity/upload

+

Auth: Required +Scope: identity-level

+

Upload an identity.json file to create or update an identity. Accepts the same format as the identity.json files stored on disk.

+

If an identity with the same name already exists, it will be updated with the new values.

+

Request

+

The request body is an IdentityFile object:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
identity_uuidstringYesIdentity identifier
namestringYesIdentity display name
identity_typestringNo"people" or null
sourcestringNo.json, auto, tmdb, user_defined, or merged
statusstringNo"confirmed", "pending", or "inactive"
tmdb_idintegerNoTMDb person ID
tmdb_profilestringNoTMDb profile image URL
metadataobjectNoArbitrary metadata JSON
file_bindingsarrayNoArray of { file_uuid, trace_ids, face_count } (informational)
+

Example

+
curl -s -X POST "$API/api/v1/identity/upload" \
+  -H "X-API-Key: $KEY" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "version": 1,
+    "identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
+    "name": "Cary Grant",
+    "identity_type": "people",
+    "source": ".json",
+    "status": "confirmed",
+    "metadata": {},
+    "file_bindings": []
+  }'
+
+ +

Response (200)

+
{
+  "success": true,
+  "identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
+  "name": "Cary Grant",
+  "message": "Identity uploaded successfully"
+}
+
+ +
+
+

POST /api/v1/identity/:identity_uuid/profile-image

+

Auth: Required +Scope: identity-level

+

Upload a profile image (JPEG or PNG) for an identity. The image is saved to {output}/identities/{uuid}/profile.{ext}.

+

Uses multipart/form-data with field name image.

+

Example

+
curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/profile-image" \
+  -H "X-API-Key: $KEY" \
+  -F "image=@/path/to/photo.jpg"
+
+ +

Response (200)

+
{
+  "success": true,
+  "identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
+  "path": "/path/to/output/identities/.../profile.jpg",
+  "message": "Profile image saved: profile.jpg"
+}
+
+ +

Error Responses

+ + + + + + + + + + + + + + + + + + + + + +
HTTPWhen
400Missing image field or unsupported format
404Identity not found
415Unsupported image type (use JPEG or PNG)
+
+

GET /api/v1/identity/:identity_uuid/profile-image

+

Auth: Required +Scope: identity-level

+

Retrieve the profile image for an identity. Returns the raw image data with appropriate Content-Type header.

+
curl -s "$API/api/v1/identity/$IDENTITY_UUID/profile-image" \
+  -H "X-API-Key: $KEY" -o profile.jpg
+
+ + + + + + + + + + + + + + +
Response HeaderValue
content-typeimage/jpeg or image/png
+
+

GET /api/v1/signals/unbound

+

Auth: Required +Scope: identity-level

+

List unbound face signals — face detections that have not yet been assigned to any identity.

+

Example

+
curl -s "$API/api/v1/signals/unbound" -H "X-API-Key: $KEY"
+
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/08_identity_agent.html b/docs_v1.0/doc/08_identity_agent.html new file mode 100644 index 0000000..64a8bed --- /dev/null +++ b/docs_v1.0/doc/08_identity_agent.html @@ -0,0 +1,97 @@ + + + + +08 Identity Agent - Momentry API Docs + + + +
+← Back to index + + + + +

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

+
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)

+
{
+  "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

+ + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
file_uuidstringYesFile containing the trace
trace_idintegerYesFace trace ID to match
+

Example

+
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}'
+
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/08_media.html b/docs_v1.0/doc/08_media.html new file mode 100644 index 0000000..fae655d --- /dev/null +++ b/docs_v1.0/doc/08_media.html @@ -0,0 +1,303 @@ + + + + +08 Media - Momentry API Docs + + + +
+← Back to index + + + + +

Video Streaming & Frame Extraction

+

All video streaming endpoints support the following common query parameters:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
modestringNonormalnormal or debug (draws detection overlays)
audiostringNoonon or off
+
+

GET /api/v1/file/:file_uuid/video

+

Stream the full video file with range support for seeking.

+

Auth: Required +Scope: file-level

+

Response

+ +
+

GET /api/v1/file/:file_uuid/trace/:trace_id/video

+

Stream video with highlights for a specific face trace (follows a single person across frames with bounding box overlay).

+

Auth: Required +Scope: file-level

+
+

GET /api/v1/file/:file_uuid/video/bbox

+

Stream video with bounding box overlay for all detected objects/faces.

+

Auth: Required +Scope: file-level

+

Uses a built-in 5×7 bitmap font renderer to draw labels directly on video frames via FFmpeg drawtext filter.

+
+

GET /api/v1/file/:file_uuid/thumbnail

+

Extract a single frame from a video as JPEG image. Uses FFmpeg select filter.

+

Auth: Required +Scope: file-level

+

Query Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
frameintegerYesZero-based frame number to extract
xintegerNoCrop start X (left edge). Requires y, w, h.
yintegerNoCrop start Y (top edge). Requires x, w, h.
wintegerNoCrop width in pixels. Requires x, y, h.
hintegerNoCrop height in pixels. Requires x, y, w.
+

All four crop params (x, y, w, h) must be provided together or omitted.

+

Example

+
# Extract frame 1000 (full frame)
+curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000" \
+  -H "Authorization: Bearer $JWT" -o frame_1000.jpg
+
+# Extract and crop face region (x=320, y=240, w=160, h=160)
+curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000&x=320&y=240&w=160&h=160" \
+  -H "Authorization: Bearer $JWT" -o face_crop.jpg
+
+ +

Response

+ +

GET /api/v1/file/:file_uuid/clip

+

Extract a video clip (time range) as MPEG-TS stream. Uses FFmpeg -ss fast seek.

+

Auth: Required +Scope: file-level

+

Query Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDefaultDescription
start_frameintegerNo*Start frame (zero-based). Frame-accurate — use this for precision.
end_frameintegerNo*End frame (zero-based, inclusive). Requires start_frame.
start_timefloatNo*Start time in seconds. Approximate (FPS-dependent). Fallback if frames not given.
end_timefloatNo*End time in seconds. Approximate (FPS-dependent). Fallback if frames not given.
fpsfloatNovideo FPSOverride frames-per-second for frame↔time calculation. Defaults to video's detected FPS.
modestringNonormalnormal or debug (draws "CLIP" overlay)
audiostringNoonon or off
+

Either (start_frame+end_frame) OR (start_time+end_time) must be provided.

+

Example

+
# Clip by frame range (primary)
+curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_frame=0&end_frame=47" \
+  -H "Authorization: Bearer $JWT" -o clip.ts
+
+# Clip by time range (fallback)
+curl -s "$API/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_time=30&end_time=45" \
+  -H "Authorization: Bearer $JWT" -o clip.ts
+
+ +

Response

+ +

Technical Notes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DetailValue
BackendFFmpeg (ffmpeg-full)
Seek-ss before -i (fast keyframe seek)
FormatMPEG-TS (mpegts muxer, pipe-safe)
CodecH.264 + AAC
CacheCache-Control: public, max-age=86400 (24h)
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DetailValue
BackendFFmpeg (ffmpeg-full)
Filterselect=eq(n\,FRAME) to select frame, optional crop=W:H:X:Y
OutputSingle JPEG via pipe (image2pipe, mjpeg codec)
CacheCache-Control: public, max-age=86400 (24h)
Frame numberZero-based (frame=0 = first frame of video)
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/09_tmdb.html b/docs_v1.0/doc/09_tmdb.html new file mode 100644 index 0000000..0f48d89 --- /dev/null +++ b/docs_v1.0/doc/09_tmdb.html @@ -0,0 +1,123 @@ + + + + +09 Tmdb - Momentry API Docs + + + +
+← Back to index + + + + +

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. +
  3. Probe: Read local cache → create identities for all cast members (source='tmdb') + save identity.json + download profile image to {OUTPUT}/identities/{uuid}/profile.jpg
  4. +
  5. Match: The worker automatically matches video faces against TMDb identities when MOMENTRY_TMDB_PROBE_ENABLED=true
  6. +
+

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

+ + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
file_uuidstringYesFile UUID to enrich
+

Example

+
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)

+
{"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

+
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)

+
{"success": true, "identities_created": 15, "movie_title": "Charade"}
+
+ +

Response (200 — no cache)

+
{"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

+
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

+
curl -s -X POST "$API/api/v1/resource/tmdb/check" \
+  -H "X-API-Key: $KEY" | jq '.status'
+
+ +

Response

+
{
+  "api_key_configured": true,
+  "enabled": false,
+  "api_reachable": true,
+  "api_latency_ms": 120
+}
+
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/10_pipeline.html b/docs_v1.0/doc/10_pipeline.html new file mode 100644 index 0000000..cfda8a3 --- /dev/null +++ b/docs_v1.0/doc/10_pipeline.html @@ -0,0 +1,309 @@ + + + + +10 Pipeline - Momentry API Docs + + + +
+← Back to index + + + + +

Pipeline

+

10 Processor Stages

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#ProcessorDepends OnDescription
1CutScene boundary detection (PySceneDetect)
2ASRCutAutomatic speech recognition (faster-whisper)
3ASRXASRSpeaker diarization + ASR refinement
4YOLOObject detection (YOLOv8)
5OCROptical character recognition
6FaceFace detection + recognition (InsightFace + CoreML)
7PosePose estimation
8VisualChunkYOLOVisual object chunking
9StoryASRX + CutNarrative scene summarization (LLM, with embedding)
105W1HStoryWho/What/When/Where/Why extraction (LLM, with embedding)
+

Post-Processing (入庫)

+

After all 10 processors complete, the pipeline runs the following storage & enrichment steps:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#StepRequiresEvidence
1Rule 1 Sentence ChunkingASR + ASRXchunk table, chunk_type = 'sentence'
2Auto-VectorizeRule 1chunk.embedding IS NOT NULL (pgvector)
3Rule 3 Scene ChunkingCut + ASRchunk table, chunk_type = 'cut'
4Face Trace + DB StoreFaceface_detections.trace_id IS NOT NULL
5Qdrant Face SyncFace TraceQdrant collection (face embeddings)
6Trace ChunksFace Tracechunk table, chunk_type = 'trace'
7TKG BuilderFace Tracetkg_nodes + tkg_edges tables
8TMDb Face MatchingFace + TMDb enabledface_detections.identity_id IS NOT NULL
9Heuristic Scene MetadataFace + YOLO{file_uuid}.scene_meta.json on disk
10Identity AgentFace + ASRXidentities with source = 'identity_agent'
115W1H AgentCut + ASRchunk.summary_text IS NOT NULL (chunk_type = 'cut')
12Release Pack5W1H Agentrelease_pack.py --phase 2 output
+

Ingestion Status

+

Check real-time ingestion status for a file:

+
curl "$API/api/v1/stats/ingestion-status/{file_uuid}"
+
+ +

Returns per-step done / pending status with detail counts.

+

Example

+
curl "http://localhost:3003/api/v1/stats/ingestion-status/bd80fec9c42afb0307eb28f22c64c76a" | jq '.steps[] | {name, status, detail}'
+
+ +

Response

+
{
+  "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

+ + + + + + + + + + + + + + + + + + + + + + + +
MethodEndpointAuthDescription
GET/api/v1/stats/sftpgoNoSFTPGo service status
GET/api/v1/stats/ingestion-status/:file_uuidNoPer-file ingestion checklist
+

Configuration

+

POST /api/v1/config/cache

+

Auth: Required +Scope: system-level

+

Toggle the Redis cache on or off.

+

Request Parameters

+ + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
enabledbooleanYestrue to enable, false to disable
+

Example

+
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:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EndpointSource file
/api/v1/search/universaluniversal_search.rs
/api/v1/search/framesuniversal_search.rs
/api/v1/search/personsuniversal_search.rs
/api/v1/whowho.rs
/api/v1/who/candidateswho.rs
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/12_agent.html b/docs_v1.0/doc/12_agent.html new file mode 100644 index 0000000..79f83ee --- /dev/null +++ b/docs_v1.0/doc/12_agent.html @@ -0,0 +1,207 @@ + + + + +12 Agent - Momentry API Docs + + + +
+← Back to index +

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

+
{
+  "text": "Hello, welcome to Momentry Core.",
+  "target_language": "Traditional Chinese",
+  "source_language": "English"
+}
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldTypeRequiredDescription
textstringText to translate
target_languagestringTarget language name (e.g. "Traditional Chinese", "Japanese")
source_languagestringSource language (default: "auto")
+

Response

+
{
+  "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)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SourceTargetQuality
EnglishTraditional Chinese
EnglishJapanese
ChineseEnglish
EnglishFrench
ChineseJapanese
+

Model

+ +

Errors

+ + + + + + + + + + + + + + + + + +
StatusCondition
500LLM unreachable or response parse failure
401Missing/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

+
{
+  "file_uuid": "3abeee81d94597629ed8cb943f182e94",
+  "scene_id": 42
+}
+
+ +

Response

+
{
+  "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

+
{
+  "file_uuid": "3abeee81d94597629ed8cb943f182e94"
+}
+
+ +

GET /api/v1/agents/5w1h/status

+

Get status of the 5W1H agent pipeline for a file.

+
+

Embedding Model

+ + + + + + + + + + + + + + + + + + + + + + + + + +
DetailValue
ModelEmbeddingGemma-300m
EndpointPOST /v1/embeddings on port 11436
Dimension768
Used byparent_chunk_5w1h.py --embed, story, 5W1H, search
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/index.html b/docs_v1.0/doc/index.html new file mode 100644 index 0000000..62b15f8 --- /dev/null +++ b/docs_v1.0/doc/index.html @@ -0,0 +1,29 @@ + + + + +Momentry API 文件 + + + +
+

Momentry API 文件

+

API 參考手冊 — 登入後可瀏覽各模組文件

+
安全認證Authentication
健康檢查Health
檔案註冊File Registration
檔案屬性查詢File Lookup
處理流程Processing
搜尋功能Search
身份識別Identity
智能身份綁定Smart Identity Binding
串流與截圖Streaming & Thumbnails
TMDb 整合TMDb Integration
生產線Pipeline
智慧代理AI Agents
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc/login.html b/docs_v1.0/doc/login.html new file mode 100644 index 0000000..c199107 --- /dev/null +++ b/docs_v1.0/doc/login.html @@ -0,0 +1,46 @@ + + + + +Login - Momentry Docs + + + +
+

Momentry Docs

+
+ + +
Invalid credentials
+ +
+
+ + + \ No newline at end of file diff --git a/docs_v1.0/doc_developer/11_error_codes.html b/docs_v1.0/doc_developer/11_error_codes.html new file mode 100644 index 0000000..e1bd41c --- /dev/null +++ b/docs_v1.0/doc_developer/11_error_codes.html @@ -0,0 +1,180 @@ + + + + +11 Error Codes - Momentry API Docs + + + +
+← Back to index + + + + +

Error Response Format

+

All API errors follow this JSON structure:

+
{
+  "success": false,
+  "error": {
+    "code": "E001_NOT_FOUND",
+    "message": "Resource not found",
+    "details": {"resource": "file_uuid", "value": "abc"}
+  }
+}
+
+ +

Error Code List

+

Generic Errors (E0xx)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeHTTPDescription
E001_NOT_FOUND404Resource not found (file, identity, chunk)
E002_DUPLICATE409Resource already exists
E003_VALIDATION400Request parameter validation failed
E004_UNAUTHORIZED401Invalid API key or token
E005_INTERNAL500Internal server error
+

Processor Errors (E1xx)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeHTTPDescription
E101_PROCESSOR_FAIL500Python script execution failed
E102_TIMEOUT504Processing timeout
E103_RESUME_FAIL500Resume failed (checkpoint not found)
E104_NO_VIDEO400Video file path not found
+

Identity Errors (E2xx)

+ + + + + + + + + + + + + + + + + + + + + + + + + +
CodeHTTPDescription
E201_FACE_NOT_FOUND404Face detection not found
E202_MERGE_CONFLICT409Identity merge conflict
E203_CANDIDATE_EMPTY404No candidates available for confirmation
+

TMDb Errors (E3xx)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CodeHTTPDescription
E301_TMDB_NO_KEY400TMDB_API_KEY environment variable not set
E302_TMDB_UNREACHABLE502TMDb API unreachable or timed out
E303_TMDB_CACHE_NOT_FOUND200No local TMDb cache; run prefetch first
E304_TMDB_PROBE_FAILED500TMDb probe execution failed
E305_TMDB_MOVIE_NOT_FOUND404No matching TMDb movie found from filename
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc_developer/index.html b/docs_v1.0/doc_developer/index.html new file mode 100644 index 0000000..6a66aa7 --- /dev/null +++ b/docs_v1.0/doc_developer/index.html @@ -0,0 +1,29 @@ + + + + +Momentry API 文件 + + + +
+

Momentry API 文件

+

API 參考手冊 — 登入後可瀏覽各模組文件

+
錯誤碼Error Codes
+
+ + \ No newline at end of file diff --git a/docs_v1.0/doc_developer/login.html b/docs_v1.0/doc_developer/login.html new file mode 100644 index 0000000..c199107 --- /dev/null +++ b/docs_v1.0/doc_developer/login.html @@ -0,0 +1,46 @@ + + + + +Login - Momentry Docs + + + +
+

Momentry Docs

+
+ + +
Invalid credentials
+ +
+
+ + + \ No newline at end of file diff --git a/src/api/server.rs b/src/api/server.rs index 00eb4c4..aeafb74 100644 --- a/src/api/server.rs +++ b/src/api/server.rs @@ -9,7 +9,7 @@ use once_cell::sync::{Lazy, OnceCell}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use sha2::{Digest, Sha256}; -use sqlx::Row; +use sqlx::{PgPool, Row}; use std::time::Instant; use tower_http::cors::{Any, CorsLayer}; @@ -3676,7 +3676,8 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> { .route("/api/v1/auth/login", post(login)) .route("/api/v1/auth/logout", post(logout)) .route("/api/v1/stats/sftpgo", get(get_sftpgo_status)) - .route("/api/v1/stats/inference", get(get_inference_health)) + .route("/api/v1/stats/ingestion-status/:file_uuid", get(get_ingestion_status)) + .route("/api/v1/search/visual", post(search_visual_chunks)) .route( "/api/v1/search/visual/class", @@ -3758,86 +3759,73 @@ async fn get_sftpgo_status( } #[derive(Debug, Serialize)] -struct InferenceEngineStatus { - engine: String, - model: String, +struct IngestionStep { + name: String, status: String, - latency_ms: Option, - error: Option, + detail: Option, } #[derive(Debug, Serialize)] -struct InferenceHealthResponse { - ollama: InferenceEngineStatus, - llama_server: InferenceEngineStatus, +struct IngestionStatusResponse { + file_uuid: String, + steps: Vec, } -async fn get_inference_health() -> Result, StatusCode> { - let client = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(5)) - .build() - .unwrap(); +async fn get_ingestion_status( + State(state): State, + Path(file_uuid): Path, +) -> Result, StatusCode> { + let pool = state.db.pool(); + let chunk = schema::table_name("chunk"); + let fd = schema::table_name("face_detections"); - let ollama_start = std::time::Instant::now(); - let ollama_status = match client.get("http://localhost:11434/api/tags").send().await { - Ok(resp) if resp.status().is_success() => { - let latency = ollama_start.elapsed().as_millis() as u64; - InferenceEngineStatus { - engine: "Ollama".to_string(), - model: "nomic-embed-text".to_string(), - status: "ok".to_string(), - latency_ms: Some(latency), - error: None, + let scene_meta_path = format!("{}/{}.scene_meta.json", + crate::core::config::OUTPUT_DIR.as_str(), + file_uuid); + let scene_meta_ok = std::path::Path::new(&scene_meta_path).exists(); + + macro_rules! count_sql { + ($sql:expr) => { + sqlx::query_scalar::<_, i64>($sql) + .fetch_one(pool) + .await + .unwrap_or(0) + }; + } + + let sentence_count = count_sql!(&format!("SELECT COUNT(*) FROM {chunk} WHERE file_uuid = '{file_uuid}' AND chunk_type = 'sentence'")); + let sentence_embedded = count_sql!(&format!("SELECT COUNT(*) FROM {chunk} WHERE file_uuid = '{file_uuid}' AND chunk_type = 'sentence' AND embedding IS NOT NULL")); + let scene_count = count_sql!(&format!("SELECT COUNT(*) FROM {chunk} WHERE file_uuid = '{file_uuid}' AND chunk_type = 'cut'")); + let trace_count = count_sql!(&format!("SELECT COUNT(DISTINCT trace_id) FROM {fd} WHERE file_uuid = '{file_uuid}' AND trace_id IS NOT NULL")); + let trace_chunks = count_sql!(&format!("SELECT COUNT(*) FROM {chunk} WHERE file_uuid = '{file_uuid}' AND chunk_type = 'trace'")); + let identities = count_sql!(&format!("SELECT COUNT(DISTINCT identity_id) FROM {fd} WHERE file_uuid = '{file_uuid}' AND identity_id IS NOT NULL")); + let tkg_nodes = count_sql!(&format!("SELECT COUNT(*) FROM {} WHERE file_uuid = '{file_uuid}'", schema::table_name("tkg_nodes"))); + let tkg_edges = count_sql!(&format!("SELECT COUNT(*) FROM {} WHERE file_uuid = '{file_uuid}'", schema::table_name("tkg_edges"))); + let scene_5w1h = count_sql!(&format!("SELECT COUNT(*) FROM {chunk} WHERE file_uuid = '{file_uuid}' AND chunk_type = 'cut' AND summary_text IS NOT NULL AND summary_text != ''")); + + macro_rules! step { + ($name:expr, $done:expr, $detail:expr) => { + IngestionStep { + name: $name.into(), + status: if $done { "done" } else { "pending" }.into(), + detail: $detail, } - } - Ok(resp) => InferenceEngineStatus { - engine: "Ollama".to_string(), - model: "nomic-embed-text".to_string(), - status: "error".to_string(), - latency_ms: Some(ollama_start.elapsed().as_millis() as u64), - error: Some(format!("HTTP {}", resp.status())), - }, - Err(e) => InferenceEngineStatus { - engine: "Ollama".to_string(), - model: "nomic-embed-text".to_string(), - status: "error".to_string(), - latency_ms: None, - error: Some(e.to_string()), - }, - }; + }; + } - let llama_start = std::time::Instant::now(); - let llama_status = match client.get("http://localhost:8081/v1/models").send().await { - Ok(resp) if resp.status().is_success() => { - let latency = llama_start.elapsed().as_millis() as u64; - InferenceEngineStatus { - engine: "llama-server".to_string(), - model: "gemma4_e4b_q5".to_string(), - status: "ok".to_string(), - latency_ms: Some(latency), - error: None, - } - } - Ok(resp) => InferenceEngineStatus { - engine: "llama-server".to_string(), - model: "gemma4_e4b_q5".to_string(), - status: "error".to_string(), - latency_ms: Some(llama_start.elapsed().as_millis() as u64), - error: Some(format!("HTTP {}", resp.status())), - }, - Err(e) => InferenceEngineStatus { - engine: "llama-server".to_string(), - model: "gemma4_e4b_q5".to_string(), - status: "error".to_string(), - latency_ms: None, - error: Some(e.to_string()), - }, - }; + let steps = vec![ + step!("rule1_sentence", sentence_count > 0, Some(format!("{sentence_count} sentence chunks"))), + step!("auto_vectorize", sentence_embedded > 0, Some(format!("{sentence_embedded} embedded"))), + step!("rule3_scene", scene_count > 0, Some(format!("{scene_count} scene chunks"))), + step!("face_trace", trace_count > 0, Some(format!("{trace_count} traces"))), + step!("trace_chunks", trace_chunks > 0, Some(format!("{trace_chunks} trace chunks"))), + step!("tkg", tkg_nodes > 0 || tkg_edges > 0, Some(format!("{tkg_nodes} nodes, {tkg_edges} edges"))), + step!("identity_match", identities > 0, Some(format!("{identities} identities matched"))), + step!("scene_metadata", scene_meta_ok, None), + step!("5w1h", scene_5w1h > 0, Some(format!("{scene_5w1h} scenes with 5W1H"))), + ]; - Ok(Json(InferenceHealthResponse { - ollama: ollama_status, - llama_server: llama_status, - })) + Ok(Json(IngestionStatusResponse { file_uuid, steps })) } #[derive(Debug, Deserialize)]