Repository navigation
feat(detection): add DOODS2 request format for API detection - #627
Conversation
The "custom API endpoint" only ever changed where the request went; the
wire format was hard-coded to light-object-detect (multipart "file" upload
plus backend/confidence_threshold/return_image query params, x_min-style
reply). Pointing it at a DOODS2 server returned HTTP 422 on every frame.
Add a selectable request format:
- [api_detection] format = light-object-detect | doods2, plus
detector_name for DOODS2. Exposed through GET/POST /api/settings and the
Settings > Detection tab (format select, backend only for
light-object-detect, detector name only for DOODS2).
- Per-stream overrides via the api engine's config JSON
({"format","backend","detector_name"}) on the single-engine path.
- doods2 posts {"id","detector_name","detect":{"*":pct},"data":b64} as
JSON to the URL verbatim; the reply's top/left/bottom/right boxes and
0-100 confidence are converted to LightNVR's normalized 0-1 scale and a
non-empty "error" fails the request. Non-200 replies now log a body
preview so server-side rejections are diagnosable.
- Refactor api_detection.c so the frame and snapshot entry points share
one transport/parse/persist path instead of two ~350 line copies, and
expose the pure URL/body/response helpers for unit tests.
- New shared base64 encoder in src/utils.
- Align the default URL with light-object-detect's real endpoint
(/api/v1/detect) and document both wire contracts.
Closes #626
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Invalid-format requests partially modify runtime settings, and detection shutdown no longer releases cached JPEG encoders.
Review effort: Balanced
Findings: 2
Open (2)
What changed in this PR
Adds selectable DOODS2 support to LightNVR’s external detection pipeline, addressing #626’s incompatible request format.
Changes:
- Adds global settings and per-engine request overrides.
- Shares transport, response parsing, and persistence between detection paths.
- Adds configuration controls, documentation, and unit coverage.
| File | Description |
|---|---|
| web/public/locales/en.json | Adds format labels and endpoint guidance. |
| web/js/components/preact/StreamConfigModal.jsx | Removes the hard-coded endpoint display. |
| web/js/components/preact/SettingsView.jsx | Loads and saves detector options. |
| web/js/components/preact/settings/DetectionTab.jsx | Adds format-specific controls. |
| tests/unit/test_config.c | Tests format configuration handling. |
| tests/unit/test_api_detection.c | Tests request construction and response parsing. |
| src/web/api_handlers_settings.c | Exposes new detector settings. |
| src/video/unified_detection_thread.c | Applies per-engine request overrides. |
| src/video/api_detection.c | Implements DOODS2 and shared request handling. |
| src/utils/base64.c | Implements base64 encoding. |
| src/core/config.c | Loads and persists detector options. |
| README.md | Introduces DOODS2 support. |
| include/video/api_detection.h | Declares request options and helpers. |
| include/utils/base64.h | Declares base64 utilities. |
| include/core/config.h | Defines format types and configuration fields. |
| docs/DETECTION_ENGINES.md | Documents per-engine overrides. |
| docs/CONFIGURATION.md | Documents both detection protocols. |
| config/lightnvr.ini | Adds format and detector-name defaults. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // The global cleanup will happen at program shutdown | ||
|
|
||
| log_info("API detection system shutdown complete"); | ||
| log_info("API detection system shutdown"); |
| if (!config_set_api_detection_format(&g_config, api_detection_format->valuestring)) { | ||
| log_warn("Rejected invalid api_detection_format setting"); | ||
| cJSON_Delete(settings); | ||
| http_response_set_json_error(res, 400, "Invalid api_detection_format: expected light-object-detect or doods2"); | ||
| return; |
- Restore jpeg_encoder_cleanup_all() in shutdown_api_detection_system(); the refactor dropped it, leaving the cached encoder contexts allocated after shutdown with no remaining caller. - Preflight-validate api_detection_format in POST /api/settings before any g_config mutation so a bad format cannot leave earlier settings in the same request half-applied. - Use a literal format string for the DOODS2 body prefix (-Wformat-nonliteral). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
@matteius Thanks for this! I would love to test it. Is there a way to do it via Home Assistant? If not, I'll look into standing up a test server. |
|
@matteius I've installed the I've tried setting the stream to both options, both with the defaults as well as overriding the detection endpoint, and keep getting the following loggwd:
Also, does Light NVR send images to the API for detection or does it use a stream? Thanks! |

Summary
Fixes #626. The stream "custom API endpoint" only changed where LightNVR sent detection requests; the wire format was hard-coded to light-object-detect (multipart
fileupload plusbackend/confidence_threshold/return_imagequery params,x_min-style reply). Pointing it at a DOODS2 server got HTTP 422 on every frame, and nothing in the UI or docs said why.This adds a selectable request format and makes the API detector configurable globally and per stream.
What changed
Request format
[api_detection] format = light-object-detect | doods2anddetector_name(DOODS2 only) inlightnvr.ini, inGET/POST /api/settings(api_detection_format,api_detection_detector_name; an unknown format is rejected with 400), and in the Settings → Detection tab (format select; the backend select is shown only for light-object-detect, the detector name only for DOODS2).doods2posts{"id","detector_name","detect":{"*":pct},"data":<base64 JPEG>}as JSON to the URL verbatim, with no query parameters appended. The reply'stop/left/bottom/rightboxes and 0–100 confidence are converted to LightNVR's normalized 0–1 scale; a non-emptyerrorfield fails the request.apiengine'sconfigJSON ({"format","backend","detector_name"}) is honored on the single-engine dispatch path, settable throughPUT /api/streams/{name}/detection-engines.Refactor
api_detection.c: the frame and snapshot entry points now share one transport → parse → persist path instead of two ~350-line copies. The pure URL/body/response helpers are exposed for unit tests. light-object-detect behavior is unchanged.src/utils/base64.c.Docs and defaults
docs/CONFIGURATION.mddocuments both wire contracts;docs/DETECTION_ENGINES.mddocuments the per-engineconfigkeys; README mentions DOODS2./api/v1/detect). The stream modal no longer shows a hard-codedlocalhost:9001/detect.[api_detection]doc entriesconfidence_threshold/filter_classeswere never parsed by LightNVR; they are replaced with the keys that are.Testing
cmake --build buildfor all targets is clean, as iscd web && npx vite build.test_api_detection: 22 tests covering format parsing, option overlay from engine config, URL building for both formats, the DOODS2 body (base64, threshold scaling, JSON escaping, defaults), response parsing for both box shapes, error/null handling, and theMAX_DETECTIONScap.test_config: defaults, name normalization and rejection, ini save/load round-trip, unknown format falling back to the default.test_mp4_segment_boundaries, a known local FFmpeg header/library mismatch unrelated to this change.Follow-ups (not in this PR)
detectmap orregionsthrough engine config.🤖 Generated with Claude Code