Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions metro-ai-suite/smart-nvr/.coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[run]
branch = True
source = src, ui
omit =
ui/interface/interface.py
*/__init__.py

[report]
show_missing = True
skip_covered = True
exclude_lines =
pragma: no cover
if __name__ == .__main__.:
^\s*pass$
122 changes: 101 additions & 21 deletions metro-ai-suite/smart-nvr/build.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,50 @@
#!/bin/bash
#!/usr/bin/env bash
set -euo pipefail

# --- Color helpers (only if stdout is a TTY) ---
if [ -t 1 ]; then
YELLOW='\033[1;33m'; GREEN='\033[0;32m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
else
YELLOW=''; GREEN=''; RED=''; CYAN=''; NC=''
fi

usage() {
cat <<'EOF'
Build the nvr-event-router multi-mode image.

Environment variables:
REGISTRY_URL Optional registry prefix (e.g. registry.local:5000)
PROJECT_NAME Optional project namespace/repo (e.g. myteam)
TAG Image tag (default: latest)
ADD_COPYLEFT_SOURCES=true Include copyleft source collection layer
http_proxy / https_proxy / no_proxy Proxy settings passed as build args

Flags:
-t <tag> Override TAG
--push Push image after successful build
--nocache Disable layer cache
-h|--help Show this help
EOF
}

PUSH=false
NOCACHE=false

while [[ $# -gt 0 ]]; do
case "$1" in
-t)
TAG="$2"; shift 2 ;;
--push)
PUSH=true; shift ;;
--nocache)
NOCACHE=true; shift ;;
-h|--help)
usage; exit 0 ;;
*)
echo -e "${RED}Unknown argument: $1${NC}" >&2
usage; exit 1 ;;
esac
done

export REGISTRY_URL=${REGISTRY_URL:-}
export PROJECT_NAME=${PROJECT_NAME:-}
Expand All @@ -7,37 +53,71 @@ export TAG=${TAG:-latest}
[[ -n "$REGISTRY_URL" ]] && REGISTRY_URL="${REGISTRY_URL%/}/"
[[ -n "$PROJECT_NAME" ]] && PROJECT_NAME="${PROJECT_NAME%/}/"
REGISTRY="${REGISTRY_URL}${PROJECT_NAME}"

export REGISTRY="${REGISTRY:-}"

# Display info about the registry being used
if ! command -v docker >/dev/null 2>&1; then
echo -e "${RED}docker not found in PATH. Aborting.${NC}" >&2
exit 1
fi

DOCKERFILE_PATH="docker/Dockerfile"
if [ ! -f "$DOCKERFILE_PATH" ]; then
echo -e "${RED}Dockerfile not found at $DOCKERFILE_PATH${NC}" >&2
exit 1
fi

if [ -z "$REGISTRY" ]; then
echo -e "${YELLOW}Warning: No registry prefix set. Images will be tagged without a registry prefix.${NC}"
echo "Using local image names with tag: ${TAG}"
echo -e "${YELLOW}Warning: No registry prefix set. Images will be tagged locally.${NC}"
else
echo "Using registry prefix: ${REGISTRY}"
echo -e "${CYAN}Using registry prefix:${NC} ${REGISTRY}"
fi

# Set the tag value
tag="${REGISTRY}nvr-event-router:${TAG}"
IMAGE_NAME="${REGISTRY}nvr-event-router:${TAG}"
echo -e "${CYAN}Building image:${NC} ${IMAGE_NAME}"

echo "Building $tag image..."
BUILD_ARGS=( )

BUILD_ARGS=""
if [ -n "${http_proxy}" ]; then
BUILD_ARGS="${BUILD_ARGS} --build-arg http_proxy=${http_proxy}"
# Proxy build args (both lower + upper so Dockerfile ARGs can map either way)
for VAR in http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY; do
VAL="${!VAR:-}" || true
if [ -n "$VAL" ]; then
BUILD_ARGS+=("--build-arg" "${VAR}=${VAL}")
fi
done

# Copyleft toggle
if [ "${ADD_COPYLEFT_SOURCES:-}" = "true" ]; then
BUILD_ARGS+=("--build-arg" "COPYLEFT_SOURCES=true")
fi
if [ -n "${https_proxy}" ]; then
BUILD_ARGS="${BUILD_ARGS} --build-arg https_proxy=${https_proxy}"

# Optional no-cache
if $NOCACHE; then
BUILD_ARGS+=("--no-cache")
fi
if [ -n "${no_proxy}" ]; then
BUILD_ARGS="${BUILD_ARGS} --build-arg no_proxy=${no_proxy}"

echo -e "${CYAN}Docker build args:${NC} ${BUILD_ARGS[*]:-(none)}"

# Enable BuildKit if not already
export DOCKER_BUILDKIT=${DOCKER_BUILDKIT:-1}

set -x
docker build "${BUILD_ARGS[@]}" -t "${IMAGE_NAME}" -f "$DOCKERFILE_PATH" .
set +x

if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "^${IMAGE_NAME}$"; then
echo -e "${GREEN}Image ${IMAGE_NAME} built successfully.${NC}"
else
echo -e "${RED}Image ${IMAGE_NAME} build appears to have failed.${NC}" >&2
exit 1
fi

# Add copyleft sources build arg if environment variable is set
if [ "$ADD_COPYLEFT_SOURCES" = "true" ]; then
BUILD_ARGS="$BUILD_ARGS --build-arg COPYLEFT_SOURCES=true"
if $PUSH; then
if [ -z "$REGISTRY" ]; then
echo -e "${RED}Cannot push: REGISTRY_URL / PROJECT_NAME not set (image has no remote prefix).${NC}" >&2
exit 1
fi
echo -e "${CYAN}Pushing image:${NC} ${IMAGE_NAME}"
docker push "${IMAGE_NAME}"
fi

docker build ${BUILD_ARGS} -t "${tag}" -f docker/Dockerfile .
docker images | grep "$tag" && echo "Image ${tag} built successfully."
echo -e "${GREEN}Done.${NC}"
2 changes: 2 additions & 0 deletions metro-ai-suite/smart-nvr/docker/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ services:
tmpfs:
size: 1000000000 # 1GB cache
- ../resources/videos:/videos # Add volume for video files
- ../frigate-clips:/media/frigate/recordings # Shared volume for Frigate output clips
depends_on:
mqtt-broker:
condition: service_healthy
Expand All @@ -48,6 +49,7 @@ services:
- nvr-network
volumes:
- ../src:/app
- ../frigate-clips:/media/frigate/recordings # Shared volume for watcher to access Frigate recordings
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
depends_on:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 119 additions & 0 deletions metro-ai-suite/smart-nvr/docs/user-guide/api-docs/smart-nvr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,72 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
'/watchers/enable':
post:
summary: Enable/disable directory watcher for cameras
description: >-
Enable or disable directory watchers for one or more cameras. Accepts a list
of objects where each object maps a single camera name to a boolean flag
indicating whether the watcher should be enabled (true) or disabled (false).
Returns the merged mapping after persistence along with convenience lists
of enabled and disabled camera names.
operationId: set_camera_watchers_watchers_enable_post
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CameraWatcherRequest'
responses:
'200':
description: Updated watcher mapping
content:
application/json:
schema:
$ref: '#/components/schemas/CameraWatcherUpdateResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
get:
summary: Alias to fetch current watcher mapping (GET)
description: >-
Convenience alias for GET /watchers/mapping. Provided so clients that
probe the POST endpoint with a GET will receive the current mapping
state instead of an error.
operationId: get_camera_watcher_mapping_alias_watchers_enable_get
responses:
'200':
description: Current watcher mapping
content:
application/json:
schema:
$ref: '#/components/schemas/CameraWatcherMappingResponse'
'/watchers/mapping':
get:
summary: Get current camera watcher enable/disable mapping
description: >-
Returns the merged camera watcher mapping. Merge order / precedence:
1) Persisted Redis mapping (authoritative across restarts)
2) In-memory runtime overrides (reflects changes since process start)
If Redis is unavailable, only the in-memory mapping is returned and a
warning field MAY be included.
operationId: get_camera_watcher_mapping_watchers_mapping_get
responses:
'200':
description: Current watcher mapping (and optional warning)
content:
application/json:
schema:
$ref: '#/components/schemas/CameraWatcherMappingResponse'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
/:
get:
summary: Root
Expand Down Expand Up @@ -492,3 +558,56 @@ components:
- msg
- type
title: ValidationError
CameraWatcherRequest:
type: object
title: CameraWatcherRequest
required:
- cameras
properties:
cameras:
type: array
description: >-
List of per-camera mapping objects. Each object maps a single camera
name (string) to a boolean indicating whether its watcher should be
enabled (true) or disabled (false).
items:
type: object
additionalProperties:
type: boolean
CameraWatcherUpdateResponse:
type: object
title: CameraWatcherUpdateResponse
properties:
mapping:
type: object
description: Final persisted mapping of camera name to enabled flag.
additionalProperties:
type: boolean
enabled:
type: array
description: List of camera names whose watcher is enabled.
items:
type: string
disabled:
type: array
description: List of camera names whose watcher is disabled.
items:
type: string
required:
- mapping
- enabled
- disabled
CameraWatcherMappingResponse:
type: object
title: CameraWatcherMappingResponse
properties:
mapping:
type: object
description: Current merged mapping of camera name to enabled flag.
additionalProperties:
type: boolean
warning:
type: string
description: Optional warning if Redis lookup failed.
required:
- mapping
43 changes: 43 additions & 0 deletions metro-ai-suite/smart-nvr/docs/user-guide/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,49 @@ Re-run the application after [configuring](./get-started.md#step-2-configure-env
> - Requires VLM microservice to be running
> - Disabled by default for system stability

## Running Tests and Generating Coverage Report

To ensure the functionality of the microservice and measure test coverage, follow these steps:

1. **Install Dependencies**
Install the required dependencies, including development dependencies, using:

```bash
poetry install --with test
```

2. **Run Tests with Poetry**
Use the following command to run all tests:

```bash
poetry run pytest
```

3. **Run Tests with Coverage**
To collect coverage data while running tests, use:

```bash
poetry run pytest --cov=src --cov=ui --cov-report=term-missing:skip-covered
```

4. **Generate Coverage Report**
After running the tests, generate a coverage report:

```bash
poetry run coverage report -m
```

5. **Generate HTML Coverage Report (Optional)**
For a detailed view, generate an HTML report:

```bash
poetry run coverage html
```

Open the `htmlcov/index.html` file in your browser to view the report.

These steps will help you verify the functionality of the microservice and ensure adequate test coverage.

### Custom Build Configuration

If using custom [build flags](./how-to-build-from-source.md#customizing-the-build), ensure the same environment variables are set before running the setup script.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ This powerful automation feature enables intelligent event management and workfl

![Event-router](./_images/summary_event_response.png)

---

#### 4. **Configure Camera Streaming (Continuous Ingestion)**

Enable or disable continuous background processing ("watchers") per camera to automatically stream new video segments into the Video Search pipeline.

This feature allows selected cameras to be continuously monitored so their footage is proactively uploaded for Video Search indexing and managed centrally without restarting services or editing configuration files

- **Continuous Ingestion Watcher**: Monitors the NVR folder for each enabled camera and automatically sends new videos to the Video Search pipeline.
- **Real-Time Configuration**: Enable or disable cameras instantly via the UI without restarting any service.
- **Centralized Control**: Manage camera ingestion settings for all cameras in one place through the backend APIs (/watchers/mapping and /watchers/enable).
- **Persistent State Management**: Retains watcher settings across restarts when Redis persistence is active.

**Example Use Case**: Enable continuous ingestion for frequently used cameras to keep their footage always indexed for fast video search, while disabling low-priority ones to conserve resources.
![Continous-Streaming](./_images/configure_camera_streaming.png)

---

## Additional Resources

- **[Troubleshooting Guide](./Troubleshooting.md)** - Resolve common deployment and runtime issues
Expand Down
Loading