mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-07-20 18:59:01 +03:00
Compare commits
190
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce223e062 | ||
|
|
b79ad9871a | ||
|
|
8be7a97fa6 | ||
|
|
d7ad3ba699 | ||
|
|
e6601d50a6 | ||
|
|
efe585a920 | ||
|
|
f3a352ef3f | ||
|
|
ad968efd3e | ||
|
|
3fe91e20d0 | ||
|
|
bd52a1cc48 | ||
|
|
cb40343be7 | ||
|
|
b912a62e0b | ||
|
|
fcfab8ef14 | ||
|
|
e0d0b2a345 | ||
|
|
b72f5a986e | ||
|
|
63b1506dd6 | ||
|
|
90a18852ef | ||
|
|
b1c133bfd1 | ||
|
|
68e74c32e3 | ||
|
|
9ced2c25ee | ||
|
|
b76457e0af | ||
|
|
4626d91fbb | ||
|
|
77474ccfea | ||
|
|
8be5b9d8d0 | ||
|
|
ffed173d5a | ||
|
|
f70c142892 | ||
|
|
7c3b7f3c12 | ||
|
|
0756889d0e | ||
|
|
bb1f8757e6 | ||
|
|
38bc0397a6 | ||
|
|
1674058b85 | ||
|
|
9b9bde9491 | ||
|
|
fb3c72359f | ||
|
|
ec7d0c8f7b | ||
|
|
b7cdc1c614 | ||
|
|
d594e9d9a6 | ||
|
|
8343a96746 | ||
|
|
a4f077b128 | ||
|
|
b751025339 | ||
|
|
7e83d5de90 | ||
|
|
a08e2d7529 | ||
|
|
3f0ebb3577 | ||
|
|
c25a522fcc | ||
|
|
db9e64c598 | ||
|
|
570e21340a | ||
|
|
8073174c20 | ||
|
|
47a06c8b30 | ||
|
|
ae60197cb0 | ||
|
|
407817a3b1 | ||
|
|
08be019bed | ||
|
|
2dd05ca984 | ||
|
|
6fdd65ddb5 | ||
|
|
4b6fa49449 | ||
|
|
bc65713ae4 | ||
|
|
50f17e6852 | ||
|
|
e9ef4f978a | ||
|
|
2858662be9 | ||
|
|
88f944fe81 | ||
|
|
39a3667f39 | ||
|
|
2ed70bd693 | ||
|
|
90248ef243 | ||
|
|
7e0e0635b8 | ||
|
|
ec44398b1c | ||
|
|
d556ff8df2 | ||
|
|
3a09d01bbe | ||
|
|
0bdf5002a0 | ||
|
|
a4a592b4e6 | ||
|
|
66a2417229 | ||
|
|
555ef89800 | ||
|
|
01c82d6921 | ||
|
|
68e8afd35c | ||
|
|
5ef8b9b924 | ||
|
|
a576ad5218 | ||
|
|
8ea46e7c6c | ||
|
|
03f4f76b72 | ||
|
|
6ffb9f2c9e | ||
|
|
b470258d95 | ||
|
|
fac11286f5 | ||
|
|
50f7f11f0b | ||
|
|
f96127c264 | ||
|
|
2ae415be6b | ||
|
|
59faa4e088 | ||
|
|
3df7c22f4d | ||
|
|
f4cbbe806d | ||
|
|
cfb1420660 | ||
|
|
161f56b5d4 | ||
|
|
5ddf8bc1b0 | ||
|
|
dc2c48f6d7 | ||
|
|
6e5d55ff64 | ||
|
|
d439b09f90 | ||
|
|
3f7768a48f | ||
|
|
7881bea60f | ||
|
|
b0b00fe1d0 | ||
|
|
b1de5e2290 | ||
|
|
4fdc107987 | ||
|
|
a83809de54 | ||
|
|
43d97acd21 | ||
|
|
d968f00500 | ||
|
|
620923c27e | ||
|
|
32daf6f494 | ||
|
|
7413ce08d4 | ||
|
|
b712e1fbd9 | ||
|
|
c6eadfebb8 | ||
|
|
d9c1ea908d | ||
|
|
78fc472026 | ||
|
|
c8cfb9400a | ||
|
|
ca75f06456 | ||
|
|
bd1fc1cc72 | ||
|
|
e20fc521b1 | ||
|
|
19ec6fa245 | ||
|
|
f1e2240945 | ||
|
|
4e90d254ed | ||
|
|
c67170aa20 | ||
|
|
e9432d55e8 | ||
|
+23 |
1f154a0205 | ||
|
|
fb68e95725 | ||
|
|
ccd1e83ae9 | ||
|
|
d1bb3d94e5 | ||
|
|
c4b74c9148 | ||
|
|
0d4f1ec369 | ||
|
|
4ff7ab96dc | ||
|
|
d0f44de6bc | ||
|
|
5211590866 | ||
|
|
704ee9667c | ||
|
|
76a1230885 | ||
|
|
52a3301726 | ||
|
|
f448b259a2 | ||
|
|
ef9d7e07b7 | ||
|
|
814c497bef | ||
|
|
5bc15d4aa9 | ||
|
|
7ad233ef15 | ||
|
|
882b3a8ffd | ||
|
|
b6fd86a066 | ||
|
|
147cd5cc2b | ||
|
|
6a2b914b10 | ||
|
|
45213d0420 | ||
|
|
ba4a6a53d7 | ||
|
|
e90079ab2f | ||
|
|
edcf0b0d2c | ||
|
|
578d6391fb | ||
|
|
c9c9c1793d | ||
|
|
a3b10c308b | ||
|
|
540290bba7 | ||
|
|
b02d928056 | ||
|
|
057351e492 | ||
|
|
c7af51376b | ||
|
|
74cfa61952 | ||
|
|
81899ddf57 | ||
|
|
33e2b23f2e | ||
|
|
b5193c9978 | ||
|
|
93443773f4 | ||
|
|
57eb7db5c9 | ||
|
|
eb73277322 | ||
|
|
d3853688ed | ||
|
|
5fd8fc881a | ||
|
|
7000712454 | ||
|
|
72068b6823 | ||
|
|
04233c0574 | ||
|
|
1584400916 | ||
|
|
0d1189f26a | ||
|
|
29eff13a4c | ||
|
|
1162c01b3e | ||
|
|
27b7ef0a7a | ||
|
|
01a7ec1060 | ||
|
|
95b5b89ed9 | ||
|
|
a182385618 | ||
|
|
088e1ad7ef | ||
|
|
011ad8eda7 | ||
|
|
4171efcd79 | ||
|
|
0ea8924727 | ||
|
|
1a1994ca17 | ||
|
|
819e8de172 | ||
|
|
ea246384bf | ||
|
|
d8f70b7fed | ||
|
|
434ef358a2 | ||
|
|
fe269b77b8 | ||
|
|
77831304a7 | ||
|
|
1a6d04fde7 | ||
|
|
4a1b7a1629 | ||
|
|
8eace9c3e7 | ||
|
|
8fc1e97df5 | ||
|
|
0a332cada9 | ||
|
|
2cfb530dbf | ||
|
|
81b0d94793 | ||
|
|
67837f61d0 | ||
|
|
58c93c2e9e | ||
|
|
6b71feffab | ||
|
|
1c26bc289e | ||
|
|
0371b60c71 | ||
|
|
01392e03ac |
@@ -162,6 +162,7 @@ mpegts
|
||||
mqtt
|
||||
mse
|
||||
msenc
|
||||
muxing
|
||||
namedtuples
|
||||
nbytes
|
||||
nchw
|
||||
@@ -197,6 +198,8 @@ OWASP
|
||||
paddleocr
|
||||
paho
|
||||
passwordless
|
||||
PCMA
|
||||
PCMU
|
||||
popleft
|
||||
posthog
|
||||
postprocess
|
||||
@@ -222,7 +225,9 @@ radeontop
|
||||
rawvideo
|
||||
rcond
|
||||
RDONLY
|
||||
realmonitor
|
||||
rebranded
|
||||
recvonly
|
||||
referer
|
||||
reindex
|
||||
Reolink
|
||||
@@ -239,8 +244,11 @@ rocminfo
|
||||
rootfs
|
||||
rtmp
|
||||
RTSP
|
||||
rtsps
|
||||
rtspx
|
||||
ruamel
|
||||
scroller
|
||||
sendonly
|
||||
setproctitle
|
||||
setpts
|
||||
shms
|
||||
@@ -251,6 +259,7 @@ SNDMORE
|
||||
socs
|
||||
sqliteq
|
||||
sqlitevecq
|
||||
Srtp
|
||||
ssdlite
|
||||
statm
|
||||
stimeout
|
||||
|
||||
@@ -1,401 +0,0 @@
|
||||
# GitHub Copilot Instructions for Frigate NVR
|
||||
|
||||
This document provides coding guidelines and best practices for contributing to Frigate NVR, a complete and local NVR designed for Home Assistant with AI object detection.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Frigate NVR is a realtime object detection system for IP cameras that uses:
|
||||
|
||||
- **Backend**: Python 3.13+ with FastAPI, OpenCV, TensorFlow/ONNX
|
||||
- **Frontend**: React with TypeScript, Vite, TailwindCSS
|
||||
- **Architecture**: Multiprocessing design with ZMQ and MQTT communication
|
||||
- **Focus**: Minimal resource usage with maximum performance
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
When reviewing code, do NOT comment on:
|
||||
|
||||
- Missing imports - Static analysis tooling catches these
|
||||
- Code formatting - Ruff (Python) and Prettier (TypeScript/React) handle formatting
|
||||
- Minor style inconsistencies already enforced by linters
|
||||
|
||||
## Python Backend Standards
|
||||
|
||||
### Python Requirements
|
||||
|
||||
- **Compatibility**: Python 3.13+
|
||||
- **Language Features**: Use modern Python features:
|
||||
- Pattern matching
|
||||
- Type hints (comprehensive typing preferred)
|
||||
- f-strings (preferred over `%` or `.format()`)
|
||||
- Dataclasses
|
||||
- Async/await patterns
|
||||
|
||||
### Code Quality Standards
|
||||
|
||||
- **Formatting**: Ruff (configured in `pyproject.toml`)
|
||||
- **Linting**: Ruff with rules defined in project config
|
||||
- **Type Checking**: Use type hints consistently
|
||||
- **Testing**: unittest framework - use `python3 -u -m unittest` to run tests
|
||||
- **Language**: American English for all code, comments, and documentation
|
||||
|
||||
### Logging Standards
|
||||
|
||||
- **Logger Pattern**: Use module-level logger
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
```
|
||||
|
||||
- **Format Guidelines**:
|
||||
- No periods at end of log messages
|
||||
- No sensitive data (keys, tokens, passwords)
|
||||
- Use lazy logging: `logger.debug("Message with %s", variable)`
|
||||
- **Log Levels**:
|
||||
- `debug`: Development and troubleshooting information
|
||||
- `info`: Important runtime events (startup, shutdown, state changes)
|
||||
- `warning`: Recoverable issues that should be addressed
|
||||
- `error`: Errors that affect functionality but don't crash the app
|
||||
- `exception`: Use in except blocks to include traceback
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Exception Types**: Choose most specific exception available
|
||||
- **Try/Catch Best Practices**:
|
||||
- Only wrap code that can throw exceptions
|
||||
- Keep try blocks minimal - process data after the try/except
|
||||
- Avoid bare exceptions except in background tasks
|
||||
|
||||
Bad pattern:
|
||||
|
||||
```python
|
||||
try:
|
||||
data = await device.get_data() # Can throw
|
||||
# ❌ Don't process data inside try block
|
||||
processed = data.get("value", 0) * 100
|
||||
result = processed
|
||||
except DeviceError:
|
||||
logger.error("Failed to get data")
|
||||
```
|
||||
|
||||
Good pattern:
|
||||
|
||||
```python
|
||||
try:
|
||||
data = await device.get_data() # Can throw
|
||||
except DeviceError:
|
||||
logger.error("Failed to get data")
|
||||
return
|
||||
|
||||
# ✅ Process data outside try block
|
||||
processed = data.get("value", 0) * 100
|
||||
result = processed
|
||||
```
|
||||
|
||||
### Async Programming
|
||||
|
||||
- **External I/O**: All external I/O operations must be async
|
||||
- **Best Practices**:
|
||||
- Avoid sleeping in loops - use `asyncio.sleep()` not `time.sleep()`
|
||||
- Avoid awaiting in loops - use `asyncio.gather()` instead
|
||||
- No blocking calls in async functions
|
||||
- Use `asyncio.create_task()` for background operations
|
||||
- **Thread Safety**: Use proper synchronization for shared state
|
||||
|
||||
### Documentation Standards
|
||||
|
||||
- **Module Docstrings**: Concise descriptions at top of files
|
||||
```python
|
||||
"""Utilities for motion detection and analysis."""
|
||||
```
|
||||
- **Function Docstrings**: Required for public functions and methods
|
||||
|
||||
```python
|
||||
async def process_frame(frame: ndarray, config: Config) -> Detection:
|
||||
"""Process a video frame for object detection.
|
||||
|
||||
Args:
|
||||
frame: The video frame as numpy array
|
||||
config: Detection configuration
|
||||
|
||||
Returns:
|
||||
Detection results with bounding boxes
|
||||
"""
|
||||
```
|
||||
|
||||
- **Comment Style**:
|
||||
- Explain the "why" not just the "what"
|
||||
- Keep lines under 88 characters when possible
|
||||
- Use clear, descriptive comments
|
||||
|
||||
### File Organization
|
||||
|
||||
- **API Endpoints**: `frigate/api/` - FastAPI route handlers
|
||||
- **Configuration**: `frigate/config/` - Configuration parsing and validation
|
||||
- **Detectors**: `frigate/detectors/` - Object detection backends
|
||||
- **Events**: `frigate/events/` - Event management and storage
|
||||
- **Utilities**: `frigate/util/` - Shared utility functions
|
||||
|
||||
## Frontend (React/TypeScript) Standards
|
||||
|
||||
### Internationalization (i18n)
|
||||
|
||||
- **CRITICAL**: Never write user-facing strings directly in components
|
||||
- **Always use react-i18next**: Import and use the `t()` function
|
||||
|
||||
```tsx
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function MyComponent() {
|
||||
const { t } = useTranslation(["views/live"]);
|
||||
return <div>{t("camera_not_found")}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
- **Translation Files**: Add English strings to the appropriate json files in `web/public/locales/en`
|
||||
- **Namespaces**: Organize translations by feature/view (e.g., `views/live`, `common`, `views/system`)
|
||||
|
||||
### Code Quality
|
||||
|
||||
- **Linting**: ESLint (see `web/.eslintrc.cjs`)
|
||||
- **Formatting**: Prettier with Tailwind CSS plugin
|
||||
- **Type Safety**: TypeScript strict mode enabled
|
||||
- **Testing**: Vitest for unit tests
|
||||
|
||||
### Component Patterns
|
||||
|
||||
- **UI Components**: Use Radix UI primitives (in `web/src/components/ui/`)
|
||||
- **Styling**: TailwindCSS with `cn()` utility for class merging
|
||||
- **State Management**: React hooks (useState, useEffect, useCallback, useMemo)
|
||||
- **Data Fetching**: Custom hooks with proper loading and error states
|
||||
|
||||
### ESLint Rules
|
||||
|
||||
Key rules enforced:
|
||||
|
||||
- `react-hooks/rules-of-hooks`: error
|
||||
- `react-hooks/exhaustive-deps`: error
|
||||
- `no-console`: error (use proper logging or remove)
|
||||
- `@typescript-eslint/no-explicit-any`: warn (always use proper types instead of `any`)
|
||||
- Unused variables must be prefixed with `_`
|
||||
- Comma dangles required for multiline objects/arrays
|
||||
|
||||
### File Organization
|
||||
|
||||
- **Pages**: `web/src/pages/` - Route components
|
||||
- **Views**: `web/src/views/` - Complex view components
|
||||
- **Components**: `web/src/components/` - Reusable components
|
||||
- **Hooks**: `web/src/hooks/` - Custom React hooks
|
||||
- **API**: `web/src/api/` - API client functions
|
||||
- **Types**: `web/src/types/` - TypeScript type definitions
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### Backend Testing
|
||||
|
||||
- **Framework**: Python unittest
|
||||
- **Run Command**: `python3 -u -m unittest`
|
||||
- **Location**: `frigate/test/`
|
||||
- **Coverage**: Aim for comprehensive test coverage of core functionality
|
||||
- **Pattern**: Use `TestCase` classes with descriptive test method names
|
||||
```python
|
||||
class TestMotionDetection(unittest.TestCase):
|
||||
def test_detects_motion_above_threshold(self):
|
||||
# Test implementation
|
||||
```
|
||||
|
||||
### Test Best Practices
|
||||
|
||||
- Always have a way to test your work and confirm your changes
|
||||
- Write tests for bug fixes to prevent regressions
|
||||
- Test edge cases and error conditions
|
||||
- Mock external dependencies (cameras, APIs, hardware)
|
||||
- Use fixtures for test data
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Python Backend
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
python3 -u -m unittest
|
||||
|
||||
# Run specific test file
|
||||
python3 -u -m unittest frigate.test.test_ffmpeg_presets
|
||||
|
||||
# Check formatting (Ruff)
|
||||
ruff format --check frigate/
|
||||
|
||||
# Apply formatting
|
||||
ruff format frigate/
|
||||
|
||||
# Run linter
|
||||
ruff check frigate/
|
||||
```
|
||||
|
||||
### Frontend (from web/ directory)
|
||||
|
||||
```bash
|
||||
# Start dev server (AI agents should never run this directly unless asked)
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Run linter
|
||||
npm run lint
|
||||
|
||||
# Fix linting issues
|
||||
npm run lint:fix
|
||||
|
||||
# Format code
|
||||
npm run prettier:write
|
||||
```
|
||||
|
||||
### Docker Development
|
||||
|
||||
AI agents should never run these commands directly unless instructed.
|
||||
|
||||
```bash
|
||||
# Build local image
|
||||
make local
|
||||
|
||||
# Build debug image
|
||||
make debug
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### API Endpoint Pattern
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, Request
|
||||
from frigate.api.defs.tags import Tags
|
||||
|
||||
router = APIRouter(tags=[Tags.Events])
|
||||
|
||||
@router.get("/events")
|
||||
async def get_events(request: Request, limit: int = 100):
|
||||
"""Retrieve events from the database."""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
### Configuration Access
|
||||
|
||||
```python
|
||||
# Access Frigate configuration
|
||||
config: FrigateConfig = request.app.frigate_config
|
||||
camera_config = config.cameras["front_door"]
|
||||
```
|
||||
|
||||
### Database Queries
|
||||
|
||||
```python
|
||||
from frigate.models import Event
|
||||
|
||||
# Use Peewee ORM for database access
|
||||
events = (
|
||||
Event.select()
|
||||
.where(Event.camera == camera_name)
|
||||
.order_by(Event.start_time.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Avoid These
|
||||
|
||||
```python
|
||||
# Blocking operations in async functions
|
||||
data = requests.get(url) # ❌ Use async HTTP client
|
||||
time.sleep(5) # ❌ Use asyncio.sleep()
|
||||
|
||||
# Hardcoded strings in React components
|
||||
<div>Camera not found</div> # ❌ Use t("camera_not_found")
|
||||
|
||||
# Missing error handling
|
||||
data = await api.get_data() # ❌ No exception handling
|
||||
|
||||
# Bare exceptions in regular code
|
||||
try:
|
||||
value = await sensor.read()
|
||||
except Exception: # ❌ Too broad
|
||||
logger.error("Failed")
|
||||
|
||||
# Returning exceptions in JSON responses
|
||||
except ValueError as e:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": str(e)},
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Use These Instead
|
||||
|
||||
```python
|
||||
# Async operations
|
||||
import aiohttp
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
data = await response.json()
|
||||
|
||||
await asyncio.sleep(5) # ✅ Non-blocking
|
||||
|
||||
# Translatable strings in React
|
||||
const { t } = useTranslation();
|
||||
<div>{t("camera_not_found")}</div> # ✅ Translatable
|
||||
|
||||
# Proper error handling
|
||||
try:
|
||||
data = await api.get_data()
|
||||
except ApiException as err:
|
||||
logger.error("API error: %s", err)
|
||||
raise
|
||||
|
||||
# Specific exceptions
|
||||
try:
|
||||
value = await sensor.read()
|
||||
except SensorException as err: # ✅ Specific
|
||||
logger.exception("Failed to read sensor")
|
||||
|
||||
# Safe error responses
|
||||
except ValueError:
|
||||
logger.exception("Invalid parameters for API request")
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Invalid request parameters",
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## Project-Specific Conventions
|
||||
|
||||
### Configuration Files
|
||||
|
||||
- Main config: `config/config.yml`
|
||||
|
||||
### Directory Structure
|
||||
|
||||
- Backend code: `frigate/`
|
||||
- Frontend code: `web/`
|
||||
- Docker files: `docker/`
|
||||
- Documentation: `docs/`
|
||||
- Database migrations: `migrations/`
|
||||
|
||||
### Code Style Conformance
|
||||
|
||||
Always conform new and refactored code to the existing coding style in the project:
|
||||
|
||||
- Follow established patterns in similar files
|
||||
- Match indentation and formatting of surrounding code
|
||||
- Use consistent naming conventions (snake_case for Python, camelCase for TypeScript)
|
||||
- Maintain the same level of verbosity in comments and docstrings
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Documentation: https://docs.frigate.video
|
||||
- Main Repository: https://github.com/blakeblackshear/frigate
|
||||
- Home Assistant Integration: https://github.com/blakeblackshear/frigate-hass-integration
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
AGENTS.md
|
||||
@@ -26,7 +26,7 @@ _Please read the [contributing guidelines](https://github.com/blakeblackshear/fr
|
||||
|
||||
- This PR fixes or closes issue: fixes #
|
||||
- This PR is related to issue:
|
||||
- Link to discussion with maintainers (**required** for large/pinned features):
|
||||
- Link to discussion with maintainers (**required** for any large or "planned" features):
|
||||
|
||||
## For new features
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR description against template
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
script: |
|
||||
const maintainers = ['blakeblackshear', 'NickM-27', 'hawkeye217', 'dependabot[bot]', 'weblate'];
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
run: npm run e2e
|
||||
working-directory: ./web
|
||||
- name: Upload test artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: playwright-report
|
||||
@@ -125,5 +125,7 @@ jobs:
|
||||
run: devcontainer up --workspace-folder .
|
||||
- name: Run mypy in devcontainer
|
||||
run: devcontainer exec --workspace-folder . bash -lc "python3 -u -m mypy --config-file frigate/mypy.ini frigate"
|
||||
- name: Check API spec is up to date
|
||||
run: devcontainer exec --workspace-folder . bash -lc "python3 generate_api_auth_spec.py --check"
|
||||
- name: Run unit tests in devcontainer
|
||||
run: devcontainer exec --workspace-folder . bash -lc "python3 -u -m unittest"
|
||||
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
close-issue-message: ""
|
||||
days-before-stale: 30
|
||||
days-before-close: 3
|
||||
exempt-draft-pr: true
|
||||
exempt-issue-labels: "pinned,security"
|
||||
exempt-pr-labels: "pinned,security,dependencies"
|
||||
exempt-draft-pr: false
|
||||
exempt-issue-labels: "planned,security"
|
||||
exempt-pr-labels: "planned,security,dependencies"
|
||||
operations-per-run: 120
|
||||
- name: Print outputs
|
||||
env:
|
||||
|
||||
@@ -22,3 +22,8 @@ core
|
||||
!/web/**/*.ts
|
||||
.idea/*
|
||||
.ipynb_checkpoints
|
||||
|
||||
# Auto-generated Docker Compose Generator config files
|
||||
docs/src/components/DockerComposeGenerator/config/devices.ts
|
||||
docs/src/components/DockerComposeGenerator/config/hardware.ts
|
||||
docs/src/components/DockerComposeGenerator/config/ports.ts
|
||||
|
||||
@@ -0,0 +1,449 @@
|
||||
# Agent Instructions for Frigate NVR
|
||||
|
||||
This document provides coding guidelines and best practices for contributing to Frigate NVR, a complete and local NVR designed for Home Assistant with AI object detection.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Frigate NVR is a realtime object detection system for IP cameras that uses:
|
||||
|
||||
- **Backend**: Python 3.13+ with FastAPI, OpenCV, TensorFlow/ONNX
|
||||
- **Frontend**: React with TypeScript, Vite, TailwindCSS
|
||||
- **Architecture**: Multiprocessing design with ZMQ and MQTT communication
|
||||
- **Focus**: Minimal resource usage with maximum performance
|
||||
|
||||
## Code Review Guidelines
|
||||
|
||||
When reviewing code, do NOT comment on:
|
||||
|
||||
- Missing imports - Static analysis tooling catches these
|
||||
- Code formatting - Ruff (Python) and Prettier (TypeScript/React) handle formatting
|
||||
- Minor style inconsistencies already enforced by linters
|
||||
|
||||
## Python Backend Standards
|
||||
|
||||
### Python Requirements
|
||||
|
||||
- **Compatibility**: Python 3.13+
|
||||
- **Language Features**: Use modern Python features:
|
||||
- Pattern matching
|
||||
- Type hints (comprehensive typing preferred)
|
||||
- f-strings (preferred over `%` or `.format()`)
|
||||
- Dataclasses
|
||||
- Async/await patterns
|
||||
|
||||
### Code Quality Standards
|
||||
|
||||
- **Formatting**: Ruff (configured in `pyproject.toml`)
|
||||
- **Linting**: Ruff with rules defined in project config
|
||||
- **Type Checking**: Use type hints consistently
|
||||
- **Testing**: unittest framework - use `python3 -u -m unittest` to run tests
|
||||
- **Language**: American English for all code, comments, and documentation
|
||||
|
||||
### Logging Standards
|
||||
|
||||
- **Logger Pattern**: Use module-level logger
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
```
|
||||
|
||||
- **Format Guidelines**:
|
||||
- No periods at end of log messages
|
||||
- No sensitive data (keys, tokens, passwords)
|
||||
- Use lazy logging: `logger.debug("Message with %s", variable)`
|
||||
- **Log Levels**:
|
||||
- `debug`: Development and troubleshooting information
|
||||
- `info`: Important runtime events (startup, shutdown, state changes)
|
||||
- `warning`: Recoverable issues that should be addressed
|
||||
- `error`: Errors that affect functionality but don't crash the app
|
||||
- `exception`: Use in except blocks to include traceback
|
||||
|
||||
### Error Handling
|
||||
|
||||
- **Exception Types**: Choose most specific exception available
|
||||
- **Try/Catch Best Practices**:
|
||||
- Only wrap code that can throw exceptions
|
||||
- Keep try blocks minimal - process data after the try/except
|
||||
- Avoid bare exceptions except in background tasks
|
||||
|
||||
Bad pattern:
|
||||
|
||||
```python
|
||||
try:
|
||||
data = await device.get_data() # Can throw
|
||||
# ❌ Don't process data inside try block
|
||||
processed = data.get("value", 0) * 100
|
||||
result = processed
|
||||
except DeviceError:
|
||||
logger.error("Failed to get data")
|
||||
```
|
||||
|
||||
Good pattern:
|
||||
|
||||
```python
|
||||
try:
|
||||
data = await device.get_data() # Can throw
|
||||
except DeviceError:
|
||||
logger.error("Failed to get data")
|
||||
return
|
||||
|
||||
# ✅ Process data outside try block
|
||||
processed = data.get("value", 0) * 100
|
||||
result = processed
|
||||
```
|
||||
|
||||
### Async Programming
|
||||
|
||||
- **External I/O**: All external I/O operations must be async
|
||||
- **Best Practices**:
|
||||
- Avoid sleeping in loops - use `asyncio.sleep()` not `time.sleep()`
|
||||
- Avoid awaiting in loops - use `asyncio.gather()` instead
|
||||
- No blocking calls in async functions
|
||||
- Use `asyncio.create_task()` for background operations
|
||||
- **Thread Safety**: Use proper synchronization for shared state
|
||||
|
||||
### Documentation Standards
|
||||
|
||||
- **Module Docstrings**: Concise descriptions at top of files
|
||||
```python
|
||||
"""Utilities for motion detection and analysis."""
|
||||
```
|
||||
- **Function Docstrings**: Required for public functions and methods
|
||||
|
||||
```python
|
||||
async def process_frame(frame: ndarray, config: Config) -> Detection:
|
||||
"""Process a video frame for object detection.
|
||||
|
||||
Args:
|
||||
frame: The video frame as numpy array
|
||||
config: Detection configuration
|
||||
|
||||
Returns:
|
||||
Detection results with bounding boxes
|
||||
"""
|
||||
```
|
||||
|
||||
- **Comment Style**:
|
||||
- Explain the "why" not just the "what"
|
||||
- Keep lines under 88 characters when possible
|
||||
- Use clear, descriptive comments
|
||||
|
||||
### File Organization
|
||||
|
||||
- **API Endpoints**: `frigate/api/` - FastAPI route handlers
|
||||
- **Configuration**: `frigate/config/` - Configuration parsing and validation
|
||||
- **Detectors**: `frigate/detectors/` - Object detection backends
|
||||
- **Events**: `frigate/events/` - Event management and storage
|
||||
- **Utilities**: `frigate/util/` - Shared utility functions
|
||||
|
||||
## Frontend (React/TypeScript) Standards
|
||||
|
||||
### Internationalization (i18n)
|
||||
|
||||
- **CRITICAL**: Never write user-facing strings directly in components
|
||||
- **Always use react-i18next**: Import and use the `t()` function
|
||||
|
||||
```tsx
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function MyComponent() {
|
||||
const { t } = useTranslation(["views/live"]);
|
||||
return <div>{t("camera_not_found")}</div>;
|
||||
}
|
||||
```
|
||||
|
||||
- **Translation Files**: Add English strings to the appropriate json files in `web/public/locales/en`
|
||||
- **Namespaces**: Organize translations by feature/view (e.g., `views/live`, `common`, `views/system`)
|
||||
|
||||
### Code Quality
|
||||
|
||||
- **Linting**: ESLint (see `web/.eslintrc.cjs`)
|
||||
- **Formatting**: Prettier with Tailwind CSS plugin
|
||||
- **Type Safety**: TypeScript strict mode enabled
|
||||
|
||||
### Component Patterns
|
||||
|
||||
- **UI Components**: Use Radix UI primitives (in `web/src/components/ui/`)
|
||||
- **Styling**: TailwindCSS with `cn()` utility for class merging
|
||||
- **State Management**: React hooks (useState, useEffect, useCallback, useMemo)
|
||||
- **Data Fetching**: Custom hooks with proper loading and error states
|
||||
|
||||
### ESLint Rules
|
||||
|
||||
Key rules enforced:
|
||||
|
||||
- `react-hooks/rules-of-hooks`: error
|
||||
- `react-hooks/exhaustive-deps`: error
|
||||
- `no-console`: error (use proper logging or remove)
|
||||
- `@typescript-eslint/no-explicit-any`: warn (always use proper types instead of `any`)
|
||||
- Unused variables must be prefixed with `_`
|
||||
- Comma dangles required for multiline objects/arrays
|
||||
|
||||
### File Organization
|
||||
|
||||
- **Pages**: `web/src/pages/` - Route components
|
||||
- **Views**: `web/src/views/` - Complex view components
|
||||
- **Components**: `web/src/components/` - Reusable components
|
||||
- **Hooks**: `web/src/hooks/` - Custom React hooks
|
||||
- **API**: `web/src/api/` - API client functions
|
||||
- **Types**: `web/src/types/` - TypeScript type definitions
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
### Backend Testing
|
||||
|
||||
- **Framework**: Python unittest
|
||||
- **Run Command**: `python3 -u -m unittest`
|
||||
- **Location**: `frigate/test/`
|
||||
- **Coverage**: Aim for comprehensive test coverage of core functionality
|
||||
- **Pattern**: Use `TestCase` classes with descriptive test method names
|
||||
```python
|
||||
class TestMotionDetection(unittest.TestCase):
|
||||
def test_detects_motion_above_threshold(self):
|
||||
# Test implementation
|
||||
```
|
||||
|
||||
### Test Best Practices
|
||||
|
||||
- Always have a way to test your work and confirm your changes
|
||||
- Write tests for bug fixes to prevent regressions
|
||||
- Test edge cases and error conditions
|
||||
- Mock external dependencies (cameras, APIs, hardware)
|
||||
- Use fixtures for test data
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Python Backend
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
python3 -u -m unittest
|
||||
|
||||
# Run specific test file
|
||||
python3 -u -m unittest frigate.test.test_ffmpeg_presets
|
||||
|
||||
# Check formatting (Ruff)
|
||||
ruff format --check frigate/
|
||||
|
||||
# Apply formatting
|
||||
ruff format frigate/
|
||||
|
||||
# Run linter
|
||||
ruff check frigate/
|
||||
|
||||
# Type check
|
||||
python3 -u -m mypy --config-file frigate/mypy.ini frigate
|
||||
|
||||
# Regenerate the OpenAPI spec after adding, changing, or removing an API
|
||||
# endpoint or its auth dependency — outputs docs/static/frigate-api.yaml,
|
||||
# annotated with each endpoint's auth requirement (admin / any / camera /
|
||||
# public). NEVER edit that file by hand. CI runs the --check variant and fails
|
||||
# if it is out of date. (from repo root)
|
||||
python3 generate_api_auth_spec.py
|
||||
python3 generate_api_auth_spec.py --check
|
||||
```
|
||||
|
||||
### Frontend (from web/ directory)
|
||||
|
||||
```bash
|
||||
# Start dev server (AI agents should never run this directly unless asked)
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Run linter
|
||||
npm run lint
|
||||
|
||||
# Fix linting issues
|
||||
npm run lint:fix
|
||||
|
||||
# Format code
|
||||
npm run prettier:write
|
||||
|
||||
# E2E: first-time setup
|
||||
npm install
|
||||
npx playwright install chromium
|
||||
|
||||
# E2E: build the app and run all tests
|
||||
npm run e2e:build && npm run e2e
|
||||
|
||||
# E2E: interactive UI for debugging
|
||||
npm run e2e:ui
|
||||
|
||||
# E2E: run a specific spec
|
||||
npx playwright test --config e2e/playwright.config.ts e2e/specs/live.spec.ts
|
||||
|
||||
# E2E: filter by name, or run only desktop/mobile
|
||||
npx playwright test --config e2e/playwright.config.ts --grep="severity tab"
|
||||
npx playwright test --config e2e/playwright.config.ts --project=desktop
|
||||
|
||||
# E2E: regenerate mock data after backend model changes (from repo root)
|
||||
PYTHONPATH=. python3 web/e2e/fixtures/mock-data/generate-mock-data.py
|
||||
|
||||
# Regenerate config translations from Pydantic models — outputs to
|
||||
# web/public/locales/en/config/{global,cameras}.json. NEVER edit those
|
||||
# JSON files by hand; change the Pydantic field title/description and
|
||||
# re-run this script. (from repo root)
|
||||
python3 generate_config_translations.py
|
||||
|
||||
# Extract i18n keys from source into the locale files after adding
|
||||
# new t() calls. Use the :ci variant to verify the locale files are
|
||||
# in sync with source (fails if extraction would change anything).
|
||||
npm run i18n:extract
|
||||
npm run i18n:extract:ci
|
||||
```
|
||||
|
||||
### Docker Development
|
||||
|
||||
AI agents should never run these commands directly unless instructed.
|
||||
|
||||
```bash
|
||||
# Build local image
|
||||
make local
|
||||
|
||||
# Build debug image
|
||||
make debug
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### API Endpoint Pattern
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, Request
|
||||
from frigate.api.defs.tags import Tags
|
||||
|
||||
router = APIRouter(tags=[Tags.Events])
|
||||
|
||||
@router.get("/events")
|
||||
async def get_events(request: Request, limit: int = 100):
|
||||
"""Retrieve events from the database."""
|
||||
# Implementation
|
||||
```
|
||||
|
||||
After adding, changing, or removing an endpoint (or its auth dependency), regenerate the OpenAPI spec with `python3 generate_api_auth_spec.py` so `docs/static/frigate-api.yaml` stays in sync and the endpoint's auth requirement is documented. CI enforces this via the `--check` variant; never edit that file by hand.
|
||||
|
||||
### Configuration Access
|
||||
|
||||
```python
|
||||
# Access Frigate configuration
|
||||
config: FrigateConfig = request.app.frigate_config
|
||||
camera_config = config.cameras["front_door"]
|
||||
```
|
||||
|
||||
### Database Queries
|
||||
|
||||
```python
|
||||
from frigate.models import Event
|
||||
|
||||
# Use Peewee ORM for database access
|
||||
events = (
|
||||
Event.select()
|
||||
.where(Event.camera == camera_name)
|
||||
.order_by(Event.start_time.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
```
|
||||
|
||||
## Common Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Avoid These
|
||||
|
||||
```python
|
||||
# Blocking operations in async functions
|
||||
data = requests.get(url) # ❌ Use async HTTP client
|
||||
time.sleep(5) # ❌ Use asyncio.sleep()
|
||||
|
||||
# Hardcoded strings in React components
|
||||
<div>Camera not found</div> # ❌ Use t("camera_not_found")
|
||||
|
||||
# Missing error handling
|
||||
data = await api.get_data() # ❌ No exception handling
|
||||
|
||||
# Bare exceptions in regular code
|
||||
try:
|
||||
value = await sensor.read()
|
||||
except Exception: # ❌ Too broad
|
||||
logger.error("Failed")
|
||||
|
||||
# Returning exceptions in JSON responses
|
||||
except ValueError as e:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": str(e)},
|
||||
)
|
||||
```
|
||||
|
||||
### ✅ Use These Instead
|
||||
|
||||
```python
|
||||
# Async operations
|
||||
import aiohttp
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url) as response:
|
||||
data = await response.json()
|
||||
|
||||
await asyncio.sleep(5) # ✅ Non-blocking
|
||||
|
||||
# Translatable strings in React
|
||||
const { t } = useTranslation();
|
||||
<div>{t("camera_not_found")}</div> # ✅ Translatable
|
||||
|
||||
# Proper error handling
|
||||
try:
|
||||
data = await api.get_data()
|
||||
except ApiException as err:
|
||||
logger.error("API error: %s", err)
|
||||
raise
|
||||
|
||||
# Specific exceptions
|
||||
try:
|
||||
value = await sensor.read()
|
||||
except SensorException as err: # ✅ Specific
|
||||
logger.exception("Failed to read sensor")
|
||||
|
||||
# Safe error responses
|
||||
except ValueError:
|
||||
logger.exception("Invalid parameters for API request")
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Invalid request parameters",
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## WebSocket Broadcasts
|
||||
|
||||
Outbound WebSocket broadcasts go through a per-recipient classifier in `frigate/comms/ws.py` that enforces camera-level access. **The classifier is fail-closed: any topic it doesn't recognize is dropped for every client.** New outbound topics must be classified there or they'll silently disappear.
|
||||
|
||||
## Project-Specific Conventions
|
||||
|
||||
### Configuration Files
|
||||
|
||||
- Main config: `config/config.yml`
|
||||
|
||||
### Directory Structure
|
||||
|
||||
- Backend code: `frigate/`
|
||||
- Frontend code: `web/`
|
||||
- Docker files: `docker/`
|
||||
- Documentation: `docs/`
|
||||
- Database migrations: `migrations/`
|
||||
|
||||
### Code Style Conformance
|
||||
|
||||
Always conform new and refactored code to the existing coding style in the project:
|
||||
|
||||
- Follow established patterns in similar files
|
||||
- Match indentation and formatting of surrounding code
|
||||
- Use consistent naming conventions (snake_case for Python, camelCase for TypeScript)
|
||||
- Maintain the same level of verbosity in comments and docstrings
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- Documentation: https://docs.frigate.video
|
||||
- Main Repository: https://github.com/blakeblackshear/frigate
|
||||
- Home Assistant Integration: https://github.com/blakeblackshear/frigate-hass-integration
|
||||
+8
-3
@@ -10,11 +10,14 @@ If you've found a bug and want to fix it, go for it. Link to the relevant issue
|
||||
|
||||
### New features
|
||||
|
||||
Every new feature adds scope that the maintainers must test, maintain, and support long-term. Before writing code for a new feature:
|
||||
A pull request is more than just code — it's a request for the maintainers to review, integrate, and support the change long-term. We're selective about what we take on, and prioritize changes that align with the project's direction and can be responsibly maintained in the long term.
|
||||
|
||||
1. **Check for existing discussion.** Search [feature requests](https://github.com/blakeblackshear/frigate/issues) and [discussions](https://github.com/blakeblackshear/frigate/discussions) to see if it's been proposed or discussed. Pinned feature requests are on our radar — we plan to get to them, but we don't maintain a public roadmap or timeline. Check in with us first if you have interest in contributing to one.
|
||||
**Large or highly-requested features** raise the bar even higher. Popularity signals demand, but it doesn't pre-approve any particular implementation. The bigger the change, the higher the long-term cost, and the more important it is that we're aligned on scope and approach before any code is written. A large PR that lands without prior discussion is unlikely to be merged as-is, no matter how well it's implemented.
|
||||
|
||||
Before writing code for a new feature:
|
||||
|
||||
1. **Check for existing discussion.** Search [feature requests](https://github.com/blakeblackshear/frigate/issues) and [discussions](https://github.com/blakeblackshear/frigate/discussions) to see if it's been proposed or discussed. Feature requests tagged with "planned" are on our radar — we plan to get to them, but we don't maintain a public roadmap or timeline. Check in with us first if you have interest in contributing to one.
|
||||
2. **Start a discussion or feature request first.** This helps ensure your idea aligns with Frigate's direction before you invest time building it. Community interest in a feature request helps us gauge demand, though a great idea is a great idea even without a crowd behind it.
|
||||
3. **Be open to "no".** We try to be thoughtful about what we take on, and sometimes that means saying no to good code if the feature isn't the right fit for the project. These calls are sometimes subjective, and we won't always get them right. We're happy to discuss and reconsider.
|
||||
|
||||
## AI usage policy
|
||||
|
||||
@@ -39,6 +42,8 @@ We're not trying to gatekeep how you write code. Use whatever tools make you pro
|
||||
|
||||
Some honest context: when we review a PR, we're not just evaluating whether the code works today. We're evaluating whether we can maintain it, debug it, and extend it long-term — often without the original author's involvement. Code that the author doesn't deeply understand is code that nobody understands, and that's a liability.
|
||||
|
||||
One more thing worth saying directly: most maintainers already have access to the same AI tools you do. A PR that's entirely AI-generated — where the author can't explain the design, debug issues independently, or engage substantively in design discussions — doesn't offer something we couldn't produce ourselves. What makes a contribution genuinely valuable is the human judgment and domain understanding behind it, as well as the engagement during review that shapes it into something we can confidently take on long-term.
|
||||
|
||||
## Pull request guidelines
|
||||
|
||||
### Before submitting
|
||||
|
||||
@@ -265,8 +265,8 @@ ENV PATH="/usr/local/go2rtc/bin:/usr/local/tempio/bin:/usr/local/nginx/sbin:${PA
|
||||
RUN --mount=type=bind,source=docker/main/install_deps.sh,target=/deps/install_deps.sh \
|
||||
/deps/install_deps.sh
|
||||
|
||||
ENV DEFAULT_FFMPEG_VERSION="7.0"
|
||||
ENV INCLUDED_FFMPEG_VERSIONS="${DEFAULT_FFMPEG_VERSION}:5.0"
|
||||
ENV DEFAULT_FFMPEG_VERSION="8.0"
|
||||
ENV INCLUDED_FFMPEG_VERSIONS="${DEFAULT_FFMPEG_VERSION}:7.0:5.0"
|
||||
|
||||
RUN wget -q https://bootstrap.pypa.io/get-pip.py -O get-pip.py \
|
||||
&& sed -i 's/args.append("setuptools")/args.append("setuptools==77.0.3")/' get-pip.py \
|
||||
|
||||
@@ -52,9 +52,13 @@ if [[ "${TARGETARCH}" == "amd64" ]]; then
|
||||
tar -xf ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 amd64/bin/ffmpeg amd64/bin/ffprobe
|
||||
rm -rf ffmpeg.tar.xz
|
||||
mkdir -p /usr/lib/ffmpeg/7.0
|
||||
wget -qO ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2026-03-19-13-03/ffmpeg-n7.1.3-43-g5a1f107b4c-linux64-gpl-7.1.tar.xz"
|
||||
wget -qO ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linux64-gpl-7.0.tar.xz"
|
||||
tar -xf ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 amd64/bin/ffmpeg amd64/bin/ffprobe
|
||||
rm -rf ffmpeg.tar.xz
|
||||
mkdir -p /usr/lib/ffmpeg/8.0
|
||||
wget -qO ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2026-06-02-14-20/ffmpeg-n8.1.1-9-g58d4114d36-linux64-gpl-8.1.tar.xz"
|
||||
tar -xf ffmpeg.tar.xz -C /usr/lib/ffmpeg/8.0 --strip-components 1 amd64/bin/ffmpeg amd64/bin/ffprobe
|
||||
rm -rf ffmpeg.tar.xz
|
||||
fi
|
||||
|
||||
# ffmpeg -> arm64
|
||||
@@ -64,9 +68,13 @@ if [[ "${TARGETARCH}" == "arm64" ]]; then
|
||||
tar -xf ffmpeg.tar.xz -C /usr/lib/ffmpeg/5.0 --strip-components 1 arm64/bin/ffmpeg arm64/bin/ffprobe
|
||||
rm -f ffmpeg.tar.xz
|
||||
mkdir -p /usr/lib/ffmpeg/7.0
|
||||
wget -qO ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2026-03-19-13-03/ffmpeg-n7.1.3-43-g5a1f107b4c-linuxarm64-gpl-7.1.tar.xz"
|
||||
wget -qO ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2024-09-19-12-51/ffmpeg-n7.0.2-18-g3e6cec1286-linuxarm64-gpl-7.0.tar.xz"
|
||||
tar -xf ffmpeg.tar.xz -C /usr/lib/ffmpeg/7.0 --strip-components 1 arm64/bin/ffmpeg arm64/bin/ffprobe
|
||||
rm -f ffmpeg.tar.xz
|
||||
mkdir -p /usr/lib/ffmpeg/8.0
|
||||
wget -qO ffmpeg.tar.xz "https://github.com/NickM-27/FFmpeg-Builds/releases/download/autobuild-2026-06-02-14-20/ffmpeg-n8.1.1-9-g58d4114d36-linuxarm64-gpl-8.1.tar.xz"
|
||||
tar -xf ffmpeg.tar.xz -C /usr/lib/ffmpeg/8.0 --strip-components 1 arm64/bin/ffmpeg arm64/bin/ffprobe
|
||||
rm -f ffmpeg.tar.xz
|
||||
fi
|
||||
|
||||
# arch specific packages
|
||||
|
||||
@@ -5,11 +5,7 @@ from typing import Any
|
||||
from ruamel.yaml import YAML
|
||||
|
||||
sys.path.insert(0, "/opt/frigate")
|
||||
from frigate.const import (
|
||||
DEFAULT_FFMPEG_VERSION,
|
||||
INCLUDED_FFMPEG_VERSIONS,
|
||||
)
|
||||
from frigate.util.config import find_config_file
|
||||
from frigate.util.config import find_config_file, resolve_ffmpeg_path
|
||||
|
||||
sys.path.remove("/opt/frigate")
|
||||
|
||||
@@ -29,9 +25,4 @@ except FileNotFoundError:
|
||||
config: dict[str, Any] = {}
|
||||
|
||||
path = config.get("ffmpeg", {}).get("path", "default")
|
||||
if path == "default":
|
||||
print(f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg")
|
||||
elif path in INCLUDED_FFMPEG_VERSIONS:
|
||||
print(f"/usr/lib/ffmpeg/{path}/bin/ffmpeg")
|
||||
else:
|
||||
print(f"{path}/bin/ffmpeg")
|
||||
print(resolve_ffmpeg_path(path, "ffmpeg"))
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ruamel.yaml import YAML
|
||||
@@ -12,43 +11,16 @@ sys.path.insert(0, "/opt/frigate")
|
||||
from frigate.config.env import substitute_frigate_vars
|
||||
from frigate.const import (
|
||||
BIRDSEYE_PIPE,
|
||||
DEFAULT_FFMPEG_VERSION,
|
||||
INCLUDED_FFMPEG_VERSIONS,
|
||||
LIBAVFORMAT_VERSION_MAJOR,
|
||||
)
|
||||
from frigate.ffmpeg_presets import parse_preset_hardware_acceleration_encode
|
||||
from frigate.util.config import find_config_file
|
||||
from frigate.util.config import find_config_file, resolve_ffmpeg_path
|
||||
from frigate.util.services import is_restricted_go2rtc_source
|
||||
|
||||
sys.path.remove("/opt/frigate")
|
||||
|
||||
yaml = YAML()
|
||||
|
||||
# Check if arbitrary exec sources are allowed (defaults to False for security)
|
||||
allow_arbitrary_exec = None
|
||||
if "GO2RTC_ALLOW_ARBITRARY_EXEC" in os.environ:
|
||||
allow_arbitrary_exec = os.environ.get("GO2RTC_ALLOW_ARBITRARY_EXEC")
|
||||
elif (
|
||||
os.path.isdir("/run/secrets")
|
||||
and os.access("/run/secrets", os.R_OK)
|
||||
and "GO2RTC_ALLOW_ARBITRARY_EXEC" in os.listdir("/run/secrets")
|
||||
):
|
||||
allow_arbitrary_exec = (
|
||||
Path(os.path.join("/run/secrets", "GO2RTC_ALLOW_ARBITRARY_EXEC"))
|
||||
.read_text()
|
||||
.strip()
|
||||
)
|
||||
# check for the add-on options file
|
||||
elif os.path.isfile("/data/options.json"):
|
||||
with open("/data/options.json") as f:
|
||||
raw_options = f.read()
|
||||
options = json.loads(raw_options)
|
||||
allow_arbitrary_exec = options.get("go2rtc_allow_arbitrary_exec")
|
||||
|
||||
ALLOW_ARBITRARY_EXEC = allow_arbitrary_exec is not None and str(
|
||||
allow_arbitrary_exec
|
||||
).lower() in ("true", "1", "yes")
|
||||
|
||||
|
||||
config_file = find_config_file()
|
||||
|
||||
try:
|
||||
@@ -107,12 +79,7 @@ if go2rtc_config.get("rtsp", {}).get("password") is not None:
|
||||
|
||||
# ensure ffmpeg path is set correctly
|
||||
path = config.get("ffmpeg", {}).get("path", "default")
|
||||
if path == "default":
|
||||
ffmpeg_path = f"/usr/lib/ffmpeg/{DEFAULT_FFMPEG_VERSION}/bin/ffmpeg"
|
||||
elif path in INCLUDED_FFMPEG_VERSIONS:
|
||||
ffmpeg_path = f"/usr/lib/ffmpeg/{path}/bin/ffmpeg"
|
||||
else:
|
||||
ffmpeg_path = f"{path}/bin/ffmpeg"
|
||||
ffmpeg_path = resolve_ffmpeg_path(path, "ffmpeg")
|
||||
|
||||
if go2rtc_config.get("ffmpeg") is None:
|
||||
go2rtc_config["ffmpeg"] = {"bin": ffmpeg_path}
|
||||
@@ -128,18 +95,13 @@ if LIBAVFORMAT_VERSION_MAJOR < 59:
|
||||
go2rtc_config["ffmpeg"]["rtsp"] = rtsp_args
|
||||
|
||||
|
||||
def is_restricted_source(stream_source: str) -> bool:
|
||||
"""Check if a stream source is restricted (echo, expr, or exec)."""
|
||||
return stream_source.strip().startswith(("echo:", "expr:", "exec:"))
|
||||
|
||||
|
||||
for name in list(go2rtc_config.get("streams", {})):
|
||||
stream = go2rtc_config["streams"][name]
|
||||
|
||||
if isinstance(stream, str):
|
||||
try:
|
||||
formatted_stream = substitute_frigate_vars(stream)
|
||||
if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream):
|
||||
if is_restricted_go2rtc_source(formatted_stream):
|
||||
print(
|
||||
f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
||||
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
|
||||
@@ -158,7 +120,7 @@ for name in list(go2rtc_config.get("streams", {})):
|
||||
for i, stream_item in enumerate(stream):
|
||||
try:
|
||||
formatted_stream = substitute_frigate_vars(stream_item)
|
||||
if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream):
|
||||
if is_restricted_go2rtc_source(formatted_stream):
|
||||
print(
|
||||
f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. "
|
||||
f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources."
|
||||
|
||||
@@ -252,6 +252,7 @@ http {
|
||||
include proxy.conf;
|
||||
|
||||
proxy_cache api_cache;
|
||||
proxy_cache_key "$scheme$proxy_host$request_uri|$role|$groups|$user";
|
||||
proxy_cache_lock on;
|
||||
proxy_cache_use_stale updating;
|
||||
proxy_cache_valid 200 5s;
|
||||
|
||||
+11
-4
@@ -13,7 +13,7 @@ ARG ROCM
|
||||
|
||||
RUN apt update -qq && \
|
||||
apt install -y wget gpg && \
|
||||
wget -O rocm.deb https://repo.radeon.com/amdgpu-install/7.2/ubuntu/jammy/amdgpu-install_7.2.70200-1_all.deb && \
|
||||
wget -O rocm.deb https://repo.radeon.com/amdgpu-install/7.2.3/ubuntu/jammy/amdgpu-install_7.2.3.70203-1_all.deb && \
|
||||
apt install -y ./rocm.deb && \
|
||||
apt update && \
|
||||
apt install -qq -y rocm
|
||||
@@ -32,11 +32,14 @@ RUN echo /opt/rocm/lib|tee /opt/rocm-dist/etc/ld.so.conf.d/rocm.conf
|
||||
FROM deps AS deps-prelim
|
||||
|
||||
COPY docker/rocm/debian-backports.sources /etc/apt/sources.list.d/debian-backports.sources
|
||||
RUN apt-get update && \
|
||||
# install_deps.sh upgraded libstdc++6 from trixie for Battlemage; the matching
|
||||
# -dev package must also come from trixie or apt refuses to satisfy it.
|
||||
RUN echo "deb http://deb.debian.org/debian trixie main" > /etc/apt/sources.list.d/trixie.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y libnuma1 && \
|
||||
apt-get install -qq -y -t bookworm-backports mesa-va-drivers mesa-vulkan-drivers && \
|
||||
# Install C++ standard library headers for HIPRTC kernel compilation fallback
|
||||
apt-get install -qq -y libstdc++-12-dev && \
|
||||
apt-get install -qq -y -t trixie libstdc++-14-dev && \
|
||||
rm -f /etc/apt/sources.list.d/trixie.list && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /opt/frigate
|
||||
@@ -75,6 +78,10 @@ ENV MIGRAPHX_DISABLE_MIOPEN_FUSION=1
|
||||
ENV MIGRAPHX_DISABLE_SCHEDULE_PASS=1
|
||||
ENV MIGRAPHX_DISABLE_REDUCE_FUSION=1
|
||||
ENV MIGRAPHX_ENABLE_HIPRTC_WORKAROUNDS=1
|
||||
ENV MIOPEN_CUSTOM_CACHE_DIR=/config/model_cache/migraphx
|
||||
ENV MIOPEN_USER_DB_PATH=/config/model_cache/migraphx
|
||||
ENV AMD_COMGR_CACHE=1
|
||||
ENV AMD_COMGR_CACHE_DIR=/config/model_cache/migraphx
|
||||
|
||||
COPY --from=rocm-dist / /
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
onnxruntime-migraphx @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v7.2.0/onnxruntime_migraphx-1.23.1-cp311-cp311-linux_x86_64.whl
|
||||
onnxruntime-migraphx @ https://github.com/NickM-27/frigate-onnxruntime-rocm/releases/download/v7.2.3-1/onnxruntime_migraphx-1.24.4-cp311-cp311-linux_x86_64.whl
|
||||
@@ -1,5 +1,5 @@
|
||||
variable "ROCM" {
|
||||
default = "7.2.0"
|
||||
default = "7.2.3"
|
||||
}
|
||||
variable "HSA_OVERRIDE_GFX_VERSION" {
|
||||
default = ""
|
||||
|
||||
+63
-37
@@ -147,6 +147,13 @@ auth:
|
||||
# NOTE: changing this value will not automatically update password hashes, you
|
||||
# will need to change each user password for it to apply
|
||||
hash_iterations: 600000
|
||||
# Optional: Map roles to the list of cameras each role can access (default: none)
|
||||
# NOTE: An empty list grants the role access to all cameras. Roles defined here can be
|
||||
# referenced by proxy header role mapping or assigned to native users.
|
||||
roles:
|
||||
my_custom_role:
|
||||
- front_door
|
||||
- back_yard
|
||||
|
||||
# Optional: model modifications
|
||||
# NOTE: The default values are for the EdgeTPU detector.
|
||||
@@ -166,6 +173,9 @@ model:
|
||||
# Required: Object detection model input tensor format
|
||||
# Valid values are nhwc or nchw (default: shown below)
|
||||
input_tensor: nhwc
|
||||
# Optional: Data type of the model input tensor
|
||||
# Valid values are float, float_denorm, or int (default: shown below)
|
||||
input_dtype: int
|
||||
# Required: Object detection model type, currently only used with the OpenVINO detector
|
||||
# Valid values are ssd, yolox, yolonas (default: shown below)
|
||||
model_type: ssd
|
||||
@@ -196,11 +206,12 @@ audio:
|
||||
# - 500 - medium sensitivity
|
||||
# - 1000 - low sensitivity
|
||||
min_volume: 500
|
||||
# Optional: Number of threads to use for audio detection (default: shown below)
|
||||
num_threads: 2
|
||||
# Optional: Types of audio to listen for (default: shown below)
|
||||
listen:
|
||||
- bark
|
||||
- fire_alarm
|
||||
- scream
|
||||
- speech
|
||||
- yell
|
||||
# Optional: Filters to configure detection.
|
||||
@@ -257,7 +268,7 @@ birdseye:
|
||||
# More information about presets at https://docs.frigate.video/configuration/ffmpeg_presets
|
||||
ffmpeg:
|
||||
# Optional: ffmpeg binary path (default: shown below)
|
||||
# can also be set to `7.0` or `5.0` to specify one of the included versions
|
||||
# can also be set to `8.0` or `5.0` to specify one of the included versions
|
||||
# or can be set to any path that holds `bin/ffmpeg` & `bin/ffprobe`
|
||||
path: "default"
|
||||
# Optional: global ffmpeg args (default: shown below)
|
||||
@@ -469,6 +480,8 @@ review:
|
||||
- Animals in the garden
|
||||
# Optional: Preferred response language (default: English)
|
||||
preferred_language: English
|
||||
# Optional: Save thumbnails sent to the GenAI provider for review/debugging purposes (default: shown below)
|
||||
debug_save_thumbnails: False
|
||||
|
||||
# Optional: Motion configuration
|
||||
# NOTE: Can be overridden at the camera level
|
||||
@@ -500,6 +513,8 @@ motion:
|
||||
# - 30 - medium sensitivity
|
||||
# - 50 - low sensitivity
|
||||
contour_area: 10
|
||||
# Optional: Alpha blending factor used in frame differencing for motion calculation (default: shown below)
|
||||
delta_alpha: 0.2
|
||||
# Optional: Alpha value passed to cv2.accumulateWeighted when averaging frames to determine the background (default: shown below)
|
||||
# Higher values mean the current frame impacts the average a lot, and a new object will be averaged into the background faster.
|
||||
# Low values will cause things like moving shadows to be detected as motion for longer.
|
||||
@@ -572,6 +587,8 @@ record:
|
||||
timelapse_args: "-vf setpts=0.04*PTS -r 30"
|
||||
# Optional: Global hardware acceleration settings for timelapse exports. (default: inherit)
|
||||
hwaccel_args: auto
|
||||
# Optional: Maximum number of export jobs to process at the same time (default: shown below)
|
||||
max_concurrent: 3
|
||||
# Optional: Recording Preview Settings
|
||||
preview:
|
||||
# Optional: Quality of recording preview (default: shown below).
|
||||
@@ -638,6 +655,11 @@ snapshots:
|
||||
retain:
|
||||
# Required: Default retention days (default: shown below)
|
||||
default: 10
|
||||
# Optional: Mode for retention. (default: shown below)
|
||||
# all - save all snapshots regardless of activity
|
||||
# motion - save snapshots for any detected motion
|
||||
# active_objects - save snapshots for active/moving objects
|
||||
mode: motion
|
||||
# Optional: Per object retention days
|
||||
objects:
|
||||
person: 15
|
||||
@@ -714,28 +736,42 @@ lpr:
|
||||
enhancement: 0
|
||||
# Optional: Save plate images to /media/frigate/clips/lpr for debugging purposes (default: shown below)
|
||||
debug_save_plates: False
|
||||
# Optional: List of regex replacement rules to normalize detected plates (default: shown below)
|
||||
replace_rules: {}
|
||||
# Optional: List of regex replacement rules to normalize detected plates before matching (default: none)
|
||||
replace_rules:
|
||||
# Required: regex pattern to match in the detected plate
|
||||
- pattern: "O"
|
||||
# Required: string to replace the matched pattern with
|
||||
replacement: "0"
|
||||
|
||||
# Optional: Configuration for AI / LLM provider
|
||||
# Optional: Configuration for AI / LLM providers
|
||||
# WARNING: Depending on the provider, this will send thumbnails over the internet
|
||||
# to Google or OpenAI's LLMs to generate descriptions. GenAI features can be configured at
|
||||
# the camera level to enhance privacy for indoor cameras.
|
||||
# NOTE: genai is a map of named providers. Each key is a name you choose for the provider,
|
||||
# and each role (chat, descriptions, embeddings) may be assigned to exactly one provider.
|
||||
genai:
|
||||
# Required: Provider must be one of ollama, gemini, or openai
|
||||
provider: ollama
|
||||
# Required if provider is ollama. May also be used for an OpenAI API compatible backend with the openai provider.
|
||||
base_url: http://localhost::11434
|
||||
# Required if gemini or openai
|
||||
api_key: "{FRIGATE_GENAI_API_KEY}"
|
||||
# Required: The model to use with the provider.
|
||||
model: gemini-1.5-flash
|
||||
# Optional additional args to pass to the GenAI Provider (default: None)
|
||||
provider_options:
|
||||
keep_alive: -1
|
||||
# Optional: Options to pass during inference calls (default: {})
|
||||
runtime_options:
|
||||
temperature: 0.7
|
||||
# Required: name of the provider (chosen by you, used to reference it elsewhere)
|
||||
my_provider:
|
||||
# Required: Provider must be one of ollama, openai, azure_openai, gemini, or llamacpp
|
||||
provider: ollama
|
||||
# Required if provider is ollama. May also be used for an OpenAI API compatible backend with the openai provider.
|
||||
base_url: http://localhost::11434
|
||||
# Required if gemini or openai
|
||||
api_key: "{FRIGATE_GENAI_API_KEY}"
|
||||
# Required: The model to use with the provider.
|
||||
model: gemini-1.5-flash
|
||||
# Optional: Roles this provider handles (default: shown below)
|
||||
# Each role (chat, descriptions, embeddings) must be assigned to exactly one provider.
|
||||
roles:
|
||||
- chat
|
||||
- descriptions
|
||||
- embeddings
|
||||
# Optional additional args to pass to the GenAI Provider (default: None)
|
||||
provider_options:
|
||||
keep_alive: -1
|
||||
# Optional: Options to pass during inference calls (default: {})
|
||||
runtime_options:
|
||||
temperature: 0.7
|
||||
|
||||
# Optional: Configuration for audio transcription
|
||||
# NOTE: only the enabled option can be overridden at the camera level
|
||||
@@ -840,8 +876,8 @@ cameras:
|
||||
# Required: name of the camera
|
||||
back:
|
||||
# Optional: Enable/Disable the camera (default: shown below).
|
||||
# If disabled: config is used but no live stream and no capture etc.
|
||||
# Events/Recordings are still viewable.
|
||||
# When False, ffmpeg is not started and the camera is hidden from the UI
|
||||
# (except Camera Management). Re-enabling requires a Frigate restart.
|
||||
enabled: True
|
||||
# Optional: camera type used for some Frigate features (default: shown below)
|
||||
# Options are "generic" and "lpr"
|
||||
@@ -908,6 +944,9 @@ cameras:
|
||||
inertia: 3
|
||||
# Optional: Number of seconds that an object must loiter to be considered in the zone (default: shown below)
|
||||
loitering_time: 0
|
||||
# Optional: Minimum speed required for an object to be considered present in the zone (default: none)
|
||||
# In real-world units if distances are set. Used for speed-based zone triggers.
|
||||
speed_threshold: 2.5
|
||||
# Optional: List of objects that can trigger this zone (default: all tracked objects)
|
||||
objects:
|
||||
- person
|
||||
@@ -945,6 +984,9 @@ cameras:
|
||||
order: 0
|
||||
# Optional: Whether or not to show the camera in the Frigate UI (default: shown below)
|
||||
dashboard: True
|
||||
# Optional: Whether this camera is visible in review (the review page and its camera
|
||||
# filter, motion review, and the history view) (default: shown below)
|
||||
review: True
|
||||
|
||||
# Optional: connect to ONVIF camera
|
||||
# to enable PTZ controls.
|
||||
@@ -1083,22 +1125,6 @@ ui:
|
||||
# Optional: Set the time format used.
|
||||
# Options are browser, 12hour, or 24hour (default: shown below)
|
||||
time_format: browser
|
||||
# Optional: Set the date style for a specified length.
|
||||
# Options are: full, long, medium, short
|
||||
# Examples:
|
||||
# short: 2/11/23
|
||||
# medium: Feb 11, 2023
|
||||
# full: Saturday, February 11, 2023
|
||||
# (default: shown below).
|
||||
date_style: short
|
||||
# Optional: Set the time style for a specified length.
|
||||
# Options are: full, long, medium, short
|
||||
# Examples:
|
||||
# short: 8:14 PM
|
||||
# medium: 8:15:22 PM
|
||||
# full: 8:15:22 PM Mountain Standard Time
|
||||
# (default: shown below).
|
||||
time_style: medium
|
||||
# Optional: Set the unit system to either "imperial" or "metric" (default: metric)
|
||||
# Used in the UI and in MQTT topics
|
||||
unit_system: metric
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
id: advanced
|
||||
title: Advanced Options
|
||||
sidebar_label: Advanced Options
|
||||
id: system
|
||||
title: System
|
||||
---
|
||||
|
||||
import ConfigTabs from "@site/src/components/ConfigTabs";
|
||||
@@ -172,7 +171,7 @@ Custom models may also require different input tensor formats. The colorspace co
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detection model" /> to configure the model path, dimensions, and input format.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and open the **Custom Model** tab to configure the model path, dimensions, and input format.
|
||||
|
||||
| Field | Description |
|
||||
| --------------------------------------------- | ------------------------------------ |
|
||||
@@ -202,7 +201,7 @@ model:
|
||||
|
||||
:::warning
|
||||
|
||||
If the labelmap is customized then the labels used for alerts will need to be adjusted as well. See [alert labels](../configuration/review.md#restricting-alerts-to-specific-labels) for more info.
|
||||
If the labelmap is customized then the labels used for alerts will need to be adjusted as well. See [alert labels](../review.md#restricting-alerts-to-specific-labels) for more info.
|
||||
|
||||
:::
|
||||
|
||||
@@ -234,26 +233,16 @@ Some labels have special handling and modifications can disable functionality.
|
||||
|
||||
## Network Configuration
|
||||
|
||||
Changes to Frigate's internal network configuration can be made by bind mounting nginx.conf into the container. For example:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frigate:
|
||||
container_name: frigate
|
||||
...
|
||||
volumes:
|
||||
...
|
||||
- /path/to/your/nginx.conf:/usr/local/nginx/conf/nginx.conf
|
||||
```
|
||||
Frigate exposes a few networking options. IPv6 and the listen ports are set in the `networking` configuration (or from the Settings UI); more advanced changes require [customizing the bundled Nginx configuration](#customizing-the-nginx-configuration).
|
||||
|
||||
### Enabling IPv6
|
||||
|
||||
IPv6 is disabled by default. Enable it in the Frigate configuration.
|
||||
By default Frigate listens on IPv4 only. To also listen on IPv6 — on port `5000`, and on `8971` when TLS is configured — enable it in the `networking` configuration.
|
||||
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Networking" /> and expand **IPv6 configuration**, then enable **Enable IPv6**.
|
||||
Navigate to <NavPath path="Settings > System > Networking" /> and enable **IPv6**.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -261,7 +250,7 @@ Navigate to <NavPath path="Settings > System > Networking" /> and expand **IPv6
|
||||
```yaml
|
||||
networking:
|
||||
ipv6:
|
||||
enabled: True
|
||||
enabled: true
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
@@ -300,6 +289,20 @@ This setting is for advanced users. For the majority of use cases it's recommend
|
||||
|
||||
:::
|
||||
|
||||
### Customizing the Nginx configuration
|
||||
|
||||
More advanced changes to Frigate's internal network configuration can be made by bind mounting your own `nginx.conf` into the container. For example:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frigate:
|
||||
container_name: frigate
|
||||
...
|
||||
volumes:
|
||||
...
|
||||
- /path/to/your/nginx.conf:/usr/local/nginx/conf/nginx.conf
|
||||
```
|
||||
|
||||
## Base path
|
||||
|
||||
By default, Frigate runs at the root path (`/`). However some setups require to run Frigate under a custom path prefix (e.g. `/frigate`), especially when Frigate is located behind a reverse proxy that requires path-based routing.
|
||||
@@ -88,7 +88,7 @@ Volume is considered motion for recordings, this means when the `record -> retai
|
||||
|
||||
### Configuring Audio Events
|
||||
|
||||
The included audio model has over [500 different types](https://github.com/blakeblackshear/frigate/blob/dev/audio-labelmap.txt) of audio that can be detected, many of which are not practical. By default `bark`, `fire_alarm`, `scream`, `speech`, and `yell` are enabled but these can be customized.
|
||||
The included audio model has over [500 different types](https://github.com/blakeblackshear/frigate/blob/dev/audio-labelmap.txt) of audio that can be detected, many of which are not practical. By default `bark`, `fire_alarm`, `speech`, and `yell` are enabled but these can be customized.
|
||||
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
@@ -107,7 +107,6 @@ audio:
|
||||
listen:
|
||||
- bark
|
||||
- fire_alarm
|
||||
- scream
|
||||
- speech
|
||||
- yell
|
||||
```
|
||||
@@ -115,6 +114,70 @@ audio:
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
### Common Audio Labels
|
||||
|
||||
The labelmap includes hundreds of sound types. The labels below are the ones most users may find practical, grouped by what they're typically used for. Use the exact label string from the left column in your `listen` config, or search for the label in the Frigate UI directly.
|
||||
|
||||
Some labels cover several related sounds: `yell` is triggered by shouting, yelling, children shouting, and screaming; `crying` covers baby cries, sobbing, and whimpering; and `speech` covers ordinary talking and conversation.
|
||||
|
||||
**Safety and security**
|
||||
|
||||
| Label | Detects |
|
||||
| ---------------- | ---------------------------------- |
|
||||
| `yell` | Shouting, yelling, screaming |
|
||||
| `fire_alarm` | Fire and smoke alarm sirens |
|
||||
| `smoke_detector` | Smoke detector beeps |
|
||||
| `alarm` | General alarm sounds |
|
||||
| `car_alarm` | Car alarms |
|
||||
| `siren` | Emergency vehicle and civil sirens |
|
||||
| `glass` | Glass clinking |
|
||||
| `shatter` | Breaking glass |
|
||||
| `breaking` | Something breaking |
|
||||
| `gunshot` | Gunshots |
|
||||
| `explosion` | Explosions |
|
||||
|
||||
**People and activity**
|
||||
|
||||
| Label | Detects |
|
||||
| ----------- | ------------------------ |
|
||||
| `speech` | Talking and conversation |
|
||||
| `laughter` | Laughing |
|
||||
| `crying` | Baby crying and sobbing |
|
||||
| `cough` | Coughing |
|
||||
| `footsteps` | Footsteps and walking |
|
||||
| `knock` | Knocking on a door |
|
||||
| `doorbell` | Doorbell |
|
||||
| `ding-dong` | Doorbell chime |
|
||||
|
||||
**Pets and animals**
|
||||
|
||||
| Label | Detects |
|
||||
| ---------- | ---------------- |
|
||||
| `bark` | Dog barking |
|
||||
| `dog` | Other dog sounds |
|
||||
| `howl` | Howling |
|
||||
| `growling` | Growling |
|
||||
| `meow` | Cat meowing |
|
||||
| `cat` | Other cat sounds |
|
||||
| `hiss` | Hissing |
|
||||
|
||||
**Vehicles and driveway**
|
||||
|
||||
| Label | Detects |
|
||||
| ----------------- | -------------------- |
|
||||
| `car` | Passing cars |
|
||||
| `honk` | Car horns |
|
||||
| `truck` | Trucks |
|
||||
| `reversing_beeps` | Vehicle backup beeps |
|
||||
| `motorcycle` | Motorcycles |
|
||||
| `engine_starting` | Engines starting |
|
||||
|
||||
:::tip
|
||||
|
||||
Frequently-heard labels like `speech` can generate a lot of events, and each event could save a snapshot and recording based on your configuration, so start with a focused set — the defaults (`bark`, `fire_alarm`, `speech`, `yell`) plus a few of the safety labels above cover most needs — and expand from there. See the [full audio labelmap](https://github.com/blakeblackshear/frigate/blob/dev/audio-labelmap.txt) or the Frigate UI for every available type.
|
||||
|
||||
:::
|
||||
|
||||
### Audio Transcription
|
||||
|
||||
Frigate supports fully local audio transcription using either `sherpa-onnx` or OpenAI's open-source Whisper models via `faster-whisper`. The goal of this feature is to support Semantic Search for `speech` audio events. Frigate is not intended to act as a continuous, fully-automatic speech transcription service — automatically transcribing all speech (or queuing many audio events for transcription) requires substantial CPU (or GPU) resources and is impractical on most systems. For this reason, transcriptions for events are initiated manually from the UI or the API rather than being run continuously in the background.
|
||||
|
||||
@@ -167,7 +167,7 @@ A fast [detector](object_detectors.md) is recommended. CPU detectors will not pe
|
||||
|
||||
A full-frame zone in `required_zones` is not recommended, especially if you've calibrated your camera and there are `movement_weights` defined in the configuration file. Frigate will continue to autotrack an object that has entered one of the `required_zones`, even if it moves outside of that zone.
|
||||
|
||||
Some users have found it helpful to adjust the zone `inertia` value. See the [configuration reference](index.md).
|
||||
Some users have found it helpful to adjust the zone `inertia` value. See the [configuration reference](advanced/reference.md).
|
||||
|
||||
## Zooming
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ Additional cameras are simply added under the camera configuration section.
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > Camera configuration > Management" /> and use the add camera button to configure each additional camera.
|
||||
Navigate to <NavPath path="Settings > Global configuration > Camera management" /> and use the add camera button to configure each additional camera.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -143,6 +143,11 @@ If your ONVIF camera does not require authentication credentials, you may still
|
||||
|
||||
:::
|
||||
|
||||
If a camera connects but fails to authenticate, two optional fields can help:
|
||||
|
||||
- `tls_insecure`: Skips TLS certificate verification and sends the ONVIF password as plaintext (`PasswordText`) instead of a hashed digest (`PasswordDigest`). Some cameras reject the digest token and only accept plaintext. This weakens connection security, so only enable it on a trusted local network.
|
||||
- `ignore_time_mismatch`: ONVIF authentication tokens include a timestamp, and a camera will reject the token if its clock differs too much from Frigate's. Enabling this makes Frigate compensate for the time offset so authentication can still succeed. Running NTP on both the camera and the Frigate host is the recommended fix; only use this in a "safe" environment, as it slightly weakens token validation.
|
||||
|
||||
If your camera has multiple ONVIF profiles, you can specify which one to use for PTZ control with the `profile` option, matched by token or name. When not set, Frigate selects the first profile with a valid PTZ configuration. Check the Frigate debug logs (`frigate.ptz.onvif: debug`) to see available profile names and tokens for your camera.
|
||||
|
||||
An ONVIF-capable camera that supports relative movement within the field of view (FOV) can also be configured to automatically track moving objects and keep them in the center of the frame. For autotracking setup, see the [autotracking](autotracking.md) docs.
|
||||
@@ -174,7 +179,7 @@ The FeatureList on the [ONVIF Conformant Products Database](https://www.onvif.or
|
||||
| Hikvision DS-2DE3A404IWG-E/W | ✅ | ✅ | |
|
||||
| Reolink | ✅ | ❌ | |
|
||||
| Speco O8P32X | ✅ | ❌ | |
|
||||
| Sunba 405-D20X | ✅ | ❌ | Incomplete ONVIF support reported on original, and 4k models. All models are suspected incompatable. |
|
||||
| Sunba 405-D20X | ✅ | ❌ | Incomplete ONVIF support reported on original, and 4k models. All models are suspected incompatible. |
|
||||
| Tapo | ✅ | ❌ | Many models supported, ONVIF Service Port: 2020 |
|
||||
| Uniview IPC672LR-AX4DUPK | ✅ | ❌ | Firmware says FOV relative movement is supported, but camera doesn't actually move when sending ONVIF commands |
|
||||
| Uniview IPC6612SR-X33-VG | ✅ | ✅ | Leave `calibrate_on_startup` as `False`. A user has reported that zooming with `absolute` is working. |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
id: index
|
||||
id: config
|
||||
title: Frigate Configuration
|
||||
---
|
||||
|
||||
@@ -57,7 +57,7 @@ VS Code supports JSON schemas for automatically validating configuration files.
|
||||
|
||||
## Environment Variable Substitution
|
||||
|
||||
Frigate supports the use of environment variables starting with `FRIGATE_` **only** where specifically indicated in the [reference config](./reference.md). For example, the following values can be replaced at runtime by using environment variables:
|
||||
Frigate supports the use of environment variables starting with `FRIGATE_` **only** where specifically indicated in the [reference config](./advanced/reference.md). For example, the following values can be replaced at runtime by using environment variables:
|
||||
|
||||
```yaml
|
||||
mqtt:
|
||||
@@ -92,7 +92,7 @@ genai:
|
||||
|
||||
## Common configuration examples
|
||||
|
||||
Here are some common starter configuration examples. These can be configured through the Settings UI or via YAML. Refer to the [reference config](./reference.md) for detailed information about all config values.
|
||||
Here are some common starter configuration examples. These can be configured through the Settings UI or via YAML. Refer to the [reference config](./advanced/reference.md) for detailed information about all config values.
|
||||
|
||||
### Raspberry Pi Home Assistant App with USB Coral
|
||||
|
||||
@@ -110,10 +110,10 @@ Here are some common starter configuration examples. These can be configured thr
|
||||
|
||||
1. Navigate to <NavPath path="Settings > System > MQTT" /> and configure the MQTT connection to your Home Assistant Mosquitto broker
|
||||
2. Navigate to <NavPath path="Settings > Global configuration > FFmpeg" /> and set **Hardware acceleration arguments** to `Raspberry Pi (H.264)`
|
||||
3. Navigate to <NavPath path="Settings > System > Detector hardware" /> and add a detector with **Type** `EdgeTPU` and **Device** `usb`
|
||||
3. Navigate to <NavPath path="Settings > System > Detectors and model" /> and add a detector with **Type** `EdgeTPU` and **Device** `usb`
|
||||
4. Navigate to <NavPath path="Settings > Global configuration > Recording" /> and set **Enable recording** to on, **Motion retention > Retention days** to `7`, **Alert retention > Event retention > Retention days** to `30`, **Alert retention > Event retention > Retention mode** to `motion`, **Detection retention > Event retention > Retention days** to `30`, **Detection retention > Event retention > Retention mode** to `motion`
|
||||
5. Navigate to <NavPath path="Settings > Global configuration > Snapshots" /> and set **Enable snapshots** to on, **Snapshot retention > Default retention** to `30`
|
||||
6. Navigate to <NavPath path="Settings > Camera configuration > Management" /> and add your camera with the appropriate RTSP stream URL
|
||||
6. Navigate to <NavPath path="Settings > Global configuration > Camera management" /> and add your camera with the appropriate RTSP stream URL
|
||||
7. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> to add a motion mask for the camera timestamp
|
||||
|
||||
</TabItem>
|
||||
@@ -189,10 +189,10 @@ cameras:
|
||||
|
||||
1. Navigate to <NavPath path="Settings > System > MQTT" /> and set **Enable MQTT** to off
|
||||
2. Navigate to <NavPath path="Settings > Global configuration > FFmpeg" /> and set **Hardware acceleration arguments** to `VAAPI (Intel/AMD GPU)`
|
||||
3. Navigate to <NavPath path="Settings > System > Detector hardware" /> and add a detector with **Type** `EdgeTPU` and **Device** `usb`
|
||||
3. Navigate to <NavPath path="Settings > System > Detectors and model" /> and add a detector with **Type** `EdgeTPU` and **Device** `usb`
|
||||
4. Navigate to <NavPath path="Settings > Global configuration > Recording" /> and set **Enable recording** to on, **Motion retention > Retention days** to `7`, **Alert retention > Event retention > Retention days** to `30`, **Alert retention > Event retention > Retention mode** to `motion`, **Detection retention > Event retention > Retention days** to `30`, **Detection retention > Event retention > Retention mode** to `motion`
|
||||
5. Navigate to <NavPath path="Settings > Global configuration > Snapshots" /> and set **Enable snapshots** to on, **Snapshot retention > Default retention** to `30`
|
||||
6. Navigate to <NavPath path="Settings > Camera configuration > Management" /> and add your camera with the appropriate RTSP stream URL
|
||||
6. Navigate to <NavPath path="Settings > Global configuration > Camera management" /> and add your camera with the appropriate RTSP stream URL
|
||||
7. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> to add a motion mask for the camera timestamp
|
||||
|
||||
</TabItem>
|
||||
@@ -266,11 +266,11 @@ cameras:
|
||||
|
||||
1. Navigate to <NavPath path="Settings > System > MQTT" /> and configure the connection to your MQTT broker
|
||||
2. Navigate to <NavPath path="Settings > Global configuration > FFmpeg" /> and set **Hardware acceleration arguments** to `VAAPI (Intel/AMD GPU)`
|
||||
3. Navigate to <NavPath path="Settings > System > Detector hardware" /> and add a detector with **Type** `openvino` and **Device** `AUTO`
|
||||
4. Navigate to <NavPath path="Settings > System > Detection model" /> and configure the OpenVINO model path and settings
|
||||
3. Navigate to <NavPath path="Settings > System > Detectors and model" /> and add a detector with **Type** `openvino` and **Device** `AUTO`
|
||||
4. On the same page, in the **Custom Model** tab, configure the OpenVINO model path and settings
|
||||
5. Navigate to <NavPath path="Settings > Global configuration > Recording" /> and set **Enable recording** to on, **Motion retention > Retention days** to `7`, **Alert retention > Event retention > Retention days** to `30`, **Alert retention > Event retention > Retention mode** to `motion`, **Detection retention > Event retention > Retention days** to `30`, **Detection retention > Event retention > Retention mode** to `motion`
|
||||
6. Navigate to <NavPath path="Settings > Global configuration > Snapshots" /> and set **Enable snapshots** to on, **Snapshot retention > Default retention** to `30`
|
||||
7. Navigate to <NavPath path="Settings > Camera configuration > Management" /> and add your camera with the appropriate RTSP stream URL
|
||||
7. Navigate to <NavPath path="Settings > Global configuration > Camera management" /> and add your camera with the appropriate RTSP stream URL
|
||||
8. Navigate to <NavPath path="Settings > Camera configuration > Masks / Zones" /> to add a motion mask for the camera timestamp
|
||||
|
||||
</TabItem>
|
||||
@@ -149,9 +149,16 @@ For more detail, see [Frigate Tip: Best Practices for Training Face and Custom C
|
||||
- **The wizard is just the starting point**: You don't need to find and label every class upfront. Missing classes will naturally appear in Recent Classifications, and those images tend to be more valuable because they represent new conditions and edge cases.
|
||||
- **Problem framing**: Keep classes visually distinct and relevant to the chosen object types.
|
||||
- **Preprocessing**: Ensure examples reflect object crops similar to Frigate's boxes; keep the subject centered.
|
||||
- **Labels**: Keep label names short and consistent; include a `none` class if you plan to ignore uncertain predictions for sub labels.
|
||||
- **Crop size**: Aim for crops of at least 100×100 pixels (a 10,000 pixel area). Crops smaller than ~80×80 get stretched 3-7× by the model's 224×224 input resize and tend to collapse into a generic "blob" region of feature space where identity becomes unreliable. If most of your detections are small because the camera is far from the subject, consider repositioning the camera for closer crops.
|
||||
- **Class balance**: Aim to keep your largest class within ~3× the count of your smallest. Beyond that, the model becomes biased toward the dominant class and tends to default borderline predictions to it (the "everything looks like Buddy" failure mode).
|
||||
- **Threshold**: Tune `threshold` per model to reduce false assignments. Start at `0.8` and adjust based on validation.
|
||||
|
||||
:::tip `none` works differently from named classes
|
||||
|
||||
Named classes work best with visually uniform examples — every Buddy photo should look like Buddy. The `none` class needs the opposite: visual diversity across sizes, framings, and qualities, because at inference it has to absorb everything that isn't one of your named classes. Don't apply the same "only keep large, well-framed images" rule to `none` that you would to a named class. Mix in small crops, partial views, and false positives deliberately - otherwise the model has no signal for "small/ambiguous thing = not one of my known classes" and will force those crops into a named class by default.
|
||||
|
||||
:::
|
||||
|
||||
## Debugging Classification Models
|
||||
|
||||
To troubleshoot issues with object classification models, enable debug logging to see detailed information about classification attempts, scores, and consensus calculations.
|
||||
|
||||
@@ -19,7 +19,7 @@ Face recognition requires a one-time internet connection to download detection a
|
||||
|
||||
### Face Detection
|
||||
|
||||
When running a Frigate+ model (or any custom model that natively detects faces) should ensure that `face` is added to the [list of objects to track](../plus/#available-label-types) either globally or for a specific camera. This will allow face detection to run at the same time as object detection and be more efficient.
|
||||
When running a Frigate+ model (or any custom model that natively detects faces) should ensure that `face` is added to the [list of objects to track](../plus/index.md#available-label-types) either globally or for a specific camera. This will allow face detection to run at the same time as object detection and be more efficient.
|
||||
|
||||
When running a default COCO model or another model that does not include `face` as a detectable label, face detection will run via CV2 using a lightweight DNN model that runs on the CPU. In this case, you should _not_ define `face` in your list of objects to track.
|
||||
|
||||
@@ -171,7 +171,7 @@ When choosing images to include in the face training set it is recommended to al
|
||||
- If it is difficult to make out details in a persons face it will not be helpful in training.
|
||||
- Avoid images with extreme under/over-exposure.
|
||||
- Avoid blurry / pixelated images.
|
||||
- Avoid training on infrared (gray-scale). The models are trained on color images and will be able to extract features from gray-scale images.
|
||||
- Avoid training on infrared (gray-scale). The models are trained on color images and will not be able to extract features from gray-scale images.
|
||||
- Using images of people wearing hats / sunglasses may confuse the model.
|
||||
- Do not upload too many similar images at the same time, it is recommended to train no more than 4-6 similar images for each person to avoid over-fitting.
|
||||
|
||||
|
||||
@@ -49,15 +49,14 @@ You should have at least 8 GB of RAM available (or VRAM if running on GPU) to ru
|
||||
|
||||
### Model Types: Instruct vs Thinking
|
||||
|
||||
Most vision-language models are available as **instruct** models, which are fine-tuned to follow instructions and respond concisely to prompts. However, some models (such as certain Qwen-VL or minigpt variants) offer both **instruct** and **thinking** versions.
|
||||
Vision-language models come in **instruct** variants (fine-tuned to follow instructions and respond concisely), **thinking** variants (fine-tuned for free-form, speculative reasoning), and **hybrid** variants that support both modes per request. Most modern vision-language models are hybrid.
|
||||
|
||||
- **Instruct models** are always recommended for use with Frigate. These models generate direct, relevant, actionable descriptions that best fit Frigate's object and event summary use case.
|
||||
- **Reasoning / Thinking models** are fine-tuned for more free-form, open-ended, and speculative outputs, which are typically not concise and may not provide the practical summaries Frigate expects. For this reason, Frigate does **not** recommend or support using thinking models.
|
||||
Frigate manages reasoning per task automatically:
|
||||
|
||||
Some models are labeled as **hybrid** (capable of both thinking and instruct tasks). In these cases, it is recommended to disable reasoning / thinking, which is generally model specific (see your models documentation).
|
||||
- **Description tasks** (object descriptions, review descriptions, review summaries) are synthesis-only and benefit from concise, direct output, so Frigate disables thinking for these calls when the model exposes a per-request toggle.
|
||||
- **Chat** lets you toggle thinking on or off from the composer when the configured model supports it.
|
||||
|
||||
**Recommendation:**
|
||||
Always select the `-instruct` or documented instruct/tagged variant of any model you use in your Frigate configuration. If in doubt, refer to your model provider's documentation or model library for guidance on the correct model variant to use.
|
||||
You can use a pure instruct, hybrid, or thinking-capable model with Frigate — no extra configuration is required to disable thinking for descriptions.
|
||||
|
||||
### llama.cpp
|
||||
|
||||
@@ -201,7 +200,7 @@ Cloud Generative AI providers require an active internet connection to send imag
|
||||
|
||||
### Ollama Cloud
|
||||
|
||||
Ollama also supports [cloud models](https://ollama.com/cloud), where your local Ollama instance handles requests from Frigate, but model inference is performed in the cloud. Set up Ollama locally, sign in with your Ollama account, and specify the cloud model name in your Frigate config. For more details, see the Ollama cloud model [docs](https://docs.ollama.com/cloud).
|
||||
Ollama also supports [cloud models](https://ollama.com/cloud), where model inference is performed in the cloud. You can connect directly to Ollama Cloud by setting `base_url` to `https://ollama.com` and providing an API key. Alternatively, you can run Ollama locally and use a cloud model name so your local instance forwards requests to the cloud. For more details, see the Ollama cloud model [docs](https://docs.ollama.com/cloud).
|
||||
|
||||
#### Configuration
|
||||
|
||||
@@ -210,7 +209,8 @@ Ollama also supports [cloud models](https://ollama.com/cloud), where your local
|
||||
|
||||
1. Navigate to <NavPath path="Settings > Enrichments > Generative AI" />.
|
||||
- Set **Provider** to `ollama`
|
||||
- Set **Base URL** to your local Ollama address (e.g., `http://localhost:11434`)
|
||||
- Set **Base URL** to your local Ollama address (e.g., `http://localhost:11434`) or `https://ollama.com` for direct cloud inference
|
||||
- Set **API key** if required by your endpoint (e.g., when using `https://ollama.com`)
|
||||
- Set **Model** to the cloud model name
|
||||
|
||||
</TabItem>
|
||||
@@ -223,6 +223,16 @@ genai:
|
||||
model: cloud-model-name
|
||||
```
|
||||
|
||||
or when using Ollama Cloud directly
|
||||
|
||||
```yaml
|
||||
genai:
|
||||
provider: ollama
|
||||
base_url: https://ollama.com
|
||||
model: cloud-model-name
|
||||
api_key: your-api-key
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: go2rtc
|
||||
title: go2rtc
|
||||
---
|
||||
|
||||
import ConfigTabs from "@site/src/components/ConfigTabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
Frigate uses the bundled go2rtc to power a number of key features:
|
||||
|
||||
- WebRTC or MSE for live viewing with audio, higher resolutions and frame rates than the jsmpeg stream which is limited to the detect stream and does not support audio
|
||||
- Live stream support for cameras in Home Assistant Integration
|
||||
- RTSP relay for use with other consumers to reduce the number of connections to your camera streams
|
||||
|
||||
:::tip[Most users no longer need to configure go2rtc by hand]
|
||||
|
||||
The **camera setup wizard** is the recommended way to add cameras. Click **Add Camera** in <NavPath path="Settings > Global configuration > Camera management" />, and the wizard probes your camera and writes its configuration for you — including the go2rtc restream and the live stream mapping — so go2rtc is set up automatically.
|
||||
|
||||
This guide is mainly useful if you are **upgrading from an older version and have existing cameras that don't yet use go2rtc**, or if you want to fine-tune a stream by hand (for example, to transcode a codec your browser can't play). The [go2rtc troubleshooting guide](/troubleshooting/go2rtc) applies regardless of how your cameras were added.
|
||||
|
||||
:::
|
||||
|
||||
## Adding a go2rtc stream manually
|
||||
|
||||
If you added your cameras with the wizard, go2rtc is already configured — you can skip straight to [troubleshooting](/troubleshooting/go2rtc). The steps below are for upgrading users with existing cameras that aren't using go2rtc yet, or for anyone who prefers to configure a stream by hand.
|
||||
|
||||
Configure go2rtc to connect to your camera by adding the stream you want to use for live view. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#module-streams), not just rtsp.
|
||||
|
||||
:::tip
|
||||
|
||||
For the best experience, set the stream name under `go2rtc` to match the name of your camera so that Frigate will automatically map it and be able to use better live view options for the camera.
|
||||
|
||||
See [the live view docs](/configuration/live#setting-streams-for-live-ui) for more information.
|
||||
|
||||
:::
|
||||
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > go2rtc Streams" /> and click **Add stream**. Give the stream a name (use the camera's name so Frigate can auto-map it - for example, if your camera's name is `back`, use `back` as the go2rtc stream name), then paste the camera's stream URL into the **Source** field. Save the section.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
After adding this to the config, restart Frigate and try to watch the live stream for a single camera by clicking on it from the dashboard. It should look much clearer and more fluent than the original jsmpeg stream.
|
||||
|
||||
### Next steps
|
||||
|
||||
1. If the stream you added to go2rtc is also used by Frigate for the `record` or `detect` role, you can migrate your config to pull from the RTSP restream to reduce the number of connections to your camera as shown [here](/configuration/restream#reduce-connections-to-camera).
|
||||
2. You can [set up WebRTC](/configuration/live#webrtc-extra-configuration) if your camera supports two-way talk. Note that WebRTC only supports specific audio formats and may require opening ports on your router.
|
||||
3. If your camera supports two-way talk, you must configure your stream with `#backchannel=0` to prevent go2rtc from blocking other applications from accessing the camera's audio output. See [preventing go2rtc from blocking two-way audio](/configuration/restream#two-way-talk-restream) in the restream documentation.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If your stream won't play, has no audio, uses excessive CPU, or otherwise misbehaves, see the dedicated [go2rtc troubleshooting guide](/troubleshooting/go2rtc). It walks through how to isolate where the problem is and covers the most common issues — unsupported codecs, H.265/HEVC, audio, WebRTC and two-way talk, hardware-accelerated transcoding with FFmpeg 8, and camera-specific quirks.
|
||||
|
||||
## Homekit Configuration
|
||||
|
||||
To add camera streams to Homekit Frigate must be configured in docker to use `host` networking mode. Once that is done, you can use the go2rtc WebUI (accessed via port 1984, which is disabled by default) to share export a camera to Homekit. Any changes made will automatically be saved to `/config/go2rtc_homekit.yml`.
|
||||
@@ -72,7 +72,7 @@ Frigate can utilize most Intel integrated GPUs and Arc GPUs to accelerate video
|
||||
|
||||
:::note
|
||||
|
||||
The default driver is `iHD`. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file or [in the `config.yml` for HA App users](advanced.md#environment_vars).
|
||||
The default driver is `iHD`. You may need to change the driver to `i965` by adding the following environment variable `LIBVA_DRIVER_NAME=i965` to your docker-compose file or [in the `config.yml` for HA App users](advanced/system.md#environment_vars).
|
||||
|
||||
See [The Intel Docs](https://www.intel.com/content/www/us/en/support/articles/000005505/processors.html) to figure out what generation your CPU is.
|
||||
|
||||
@@ -136,90 +136,32 @@ ffmpeg:
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
### Configuring Intel GPU Stats in Docker
|
||||
### Configuring Intel GPU Stats
|
||||
|
||||
Additional configuration is needed for the Docker container to be able to access the `intel_gpu_top` command for GPU stats. There are two options:
|
||||
Frigate reads Intel GPU utilization directly from the kernel's per-client DRM usage counters exposed at `/proc/<pid>/fdinfo/<fd>`. This requires:
|
||||
|
||||
1. Run the container as privileged.
|
||||
2. Add the `CAP_PERFMON` capability (note: you might need to set the `perf_event_paranoid` low enough to allow access to the performance event system.)
|
||||
- Linux kernel **5.19 or newer** for the `i915` driver, or any release of the `xe` driver.
|
||||
- Frigate running with permission to read other processes' fdinfo. Running as root inside the container (the default) satisfies this; non-root setups may need `CAP_SYS_PTRACE`.
|
||||
|
||||
#### Run as privileged
|
||||
No `intel_gpu_top` binary, `CAP_PERFMON`, privileged mode, or `perf_event_paranoid` tuning is required.
|
||||
|
||||
This method works, but it gives more permissions to the container than are actually needed.
|
||||
#### Stats for SR-IOV or specific devices
|
||||
|
||||
##### Docker Compose - Privileged
|
||||
|
||||
```yaml
|
||||
services:
|
||||
frigate:
|
||||
...
|
||||
image: ghcr.io/blakeblackshear/frigate:stable
|
||||
# highlight-next-line
|
||||
privileged: true
|
||||
```
|
||||
|
||||
##### Docker Run CLI - Privileged
|
||||
|
||||
```bash {4}
|
||||
docker run -d \
|
||||
--name frigate \
|
||||
...
|
||||
--privileged \
|
||||
ghcr.io/blakeblackshear/frigate:stable
|
||||
```
|
||||
|
||||
#### CAP_PERFMON
|
||||
|
||||
Only recent versions of Docker support the `CAP_PERFMON` capability. You can test to see if yours supports it by running: `docker run --cap-add=CAP_PERFMON hello-world`
|
||||
|
||||
##### Docker Compose - CAP_PERFMON
|
||||
|
||||
```yaml {5,6}
|
||||
services:
|
||||
frigate:
|
||||
...
|
||||
image: ghcr.io/blakeblackshear/frigate:stable
|
||||
cap_add:
|
||||
- CAP_PERFMON
|
||||
```
|
||||
|
||||
##### Docker Run CLI - CAP_PERFMON
|
||||
|
||||
```bash {4}
|
||||
docker run -d \
|
||||
--name frigate \
|
||||
...
|
||||
--cap-add=CAP_PERFMON \
|
||||
ghcr.io/blakeblackshear/frigate:stable
|
||||
```
|
||||
|
||||
#### perf_event_paranoid
|
||||
|
||||
_Note: This setting must be changed for the entire system._
|
||||
|
||||
For more information on the various values across different distributions, see https://askubuntu.com/questions/1400874/what-does-perf-paranoia-level-four-do.
|
||||
|
||||
Depending on your OS and kernel configuration, you may need to change the `/proc/sys/kernel/perf_event_paranoid` kernel tunable. You can test the change by running `sudo sh -c 'echo 2 >/proc/sys/kernel/perf_event_paranoid'` which will persist until a reboot. Make it permanent by running `sudo sh -c 'echo kernel.perf_event_paranoid=2 >> /etc/sysctl.d/local.conf'`
|
||||
|
||||
#### Stats for SR-IOV or other devices
|
||||
|
||||
When using virtualized GPUs via SR-IOV, you need to specify the device path to use to gather stats from `intel_gpu_top`. This example may work for some systems using SR-IOV:
|
||||
If the host has more than one Intel GPU (e.g. an iGPU plus a discrete GPU, or SR-IOV virtual functions), pin stats collection to a specific device by setting `intel_gpu_device` to either its PCI bus address or a DRM card/render-node path:
|
||||
|
||||
```yaml
|
||||
telemetry:
|
||||
stats:
|
||||
intel_gpu_device: "sriov"
|
||||
intel_gpu_device: "0000:00:02.0"
|
||||
```
|
||||
|
||||
For other virtualized GPUs, try specifying the direct path to the device instead:
|
||||
|
||||
```yaml
|
||||
telemetry:
|
||||
stats:
|
||||
intel_gpu_device: "drm:/dev/dri/card0"
|
||||
intel_gpu_device: "/dev/dri/card1"
|
||||
```
|
||||
|
||||
If you are passing in a device path, make sure you've passed the device through to the container.
|
||||
When passing a device path, make sure the device is also passed through to the container.
|
||||
|
||||
## AMD-based CPUs
|
||||
|
||||
@@ -227,7 +169,7 @@ Frigate can utilize modern AMD integrated GPUs and AMD GPUs to accelerate video
|
||||
|
||||
### Configuring Radeon Driver
|
||||
|
||||
You need to change the driver to `radeonsi` by adding the following environment variable `LIBVA_DRIVER_NAME=radeonsi` to your docker-compose file or [in the `config.yml` for HA App users](advanced.md#environment_vars).
|
||||
You need to change the driver to `radeonsi` by adding the following environment variable `LIBVA_DRIVER_NAME=radeonsi` to your docker-compose file or [in the `config.yml` for HA App users](advanced/system.md#environment_vars).
|
||||
|
||||
### Via VAAPI
|
||||
|
||||
@@ -251,7 +193,7 @@ ffmpeg:
|
||||
|
||||
## NVIDIA GPUs
|
||||
|
||||
While older GPUs may work, it is recommended to use modern, supported GPUs. NVIDIA provides a [matrix of supported GPUs and features](https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new). If your card is on the list and supports CUVID/NVDEC, it will most likely work with Frigate for decoding. However, you must also use [a driver version that will work with FFmpeg](https://github.com/FFmpeg/nv-codec-headers/blob/master/README). Older driver versions may be missing symbols and fail to work, and older cards are not supported by newer driver versions. The only way around this is to [provide your own FFmpeg](/configuration/advanced#custom-ffmpeg-build) that will work with your driver version, but this is unsupported and may not work well if at all.
|
||||
While older GPUs may work, it is recommended to use modern, supported GPUs. NVIDIA provides a [matrix of supported GPUs and features](https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new). If your card is on the list and supports CUVID/NVDEC, it will most likely work with Frigate for decoding. However, you must also use [a driver version that will work with FFmpeg](https://github.com/FFmpeg/nv-codec-headers/blob/master/README). Older driver versions may be missing symbols and fail to work, and older cards are not supported by newer driver versions. The only way around this is to [provide your own FFmpeg](/configuration/advanced/system#custom-ffmpeg-build) that will work with your driver version, but this is unsupported and may not work well if at all.
|
||||
|
||||
A more complete list of cards and their compatible drivers is available in the [driver release readme](https://download.nvidia.com/XFree86/Linux-x86_64/525.85.05/README/supportedchips.html).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Frigate intelligently displays your camera streams on the Live view dashboard. B
|
||||
|
||||
### Live View technologies
|
||||
|
||||
Frigate intelligently uses three different streaming technologies to display your camera streams on the dashboard and the single camera view, switching between available modes based on network bandwidth, player errors, or required features like two-way talk. The highest quality and fluency of the Live view requires the bundled `go2rtc` to be configured as shown in the [step by step guide](/guides/configuring_go2rtc).
|
||||
Frigate intelligently uses three different streaming technologies to display your camera streams on the dashboard and the single camera view, switching between available modes based on network bandwidth, player errors, or required features like two-way talk. The highest quality and fluency of the Live view requires the bundled `go2rtc` to be [configured](/configuration/go2rtc).
|
||||
|
||||
The jsmpeg live view will use more browser and client GPU resources. Using go2rtc is highly recommended and will provide a superior experience.
|
||||
|
||||
@@ -88,8 +88,18 @@ Configure a "friendly name" for your stream followed by the go2rtc stream name.
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
1. Navigate to <NavPath path="Settings > Camera configuration > Live playback" />, then select your camera.
|
||||
- Under **Live stream names**, add entries mapping a friendly name to each go2rtc stream name (e.g., `Main Stream` mapped to `test_cam`, `Sub Stream` mapped to `test_cam_sub`).
|
||||
1. Navigate to <NavPath path="Settings > Camera configuration > Live playback" /> and select your camera.
|
||||
2. Under **Live stream names**, click **Add stream** to add a new entry.
|
||||
3. In the **Stream name** field, enter a friendly name that will appear in the Live UI's stream dropdown (e.g., `Main Stream`).
|
||||
4. In the **go2rtc stream** field, open the dropdown and select the go2rtc stream this name should map to (e.g., `test_cam`). The dropdown lists every stream configured under `go2rtc.streams`. If the go2rtc stream hasn't been created yet, you can type the name and choose **Use "..."** to save a custom value.
|
||||
5. Repeat for each additional stream you want to expose (e.g., `Sub Stream` → `test_cam_sub`).
|
||||
6. Use the trash icon on a row to remove a stream, then **Save** the section.
|
||||
|
||||
:::tip
|
||||
|
||||
Configure your go2rtc streams first under <NavPath path="Settings > System > go2rtc streams" /> so the dropdown is populated with valid options.
|
||||
|
||||
:::
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -257,19 +267,47 @@ cameras:
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
### Disabling cameras
|
||||
### Camera state
|
||||
|
||||
Cameras can be temporarily disabled through the Frigate UI and through [MQTT](/integrations/mqtt#frigatecamera_nameenabledset) to conserve system resources. When disabled, Frigate's ffmpeg processes are terminated — recording stops, object detection is paused, and the Live dashboard displays a blank image with a disabled message. Review items, tracked objects, and historical footage for disabled cameras can still be accessed via the UI.
|
||||
Each camera has three possible states, surfaced as a status selector in **Settings → Global configuration → Camera management**:
|
||||
|
||||
:::note
|
||||
- **On** — streams are processed normally. Object detection, recording, and Live view are active.
|
||||
- **Off** — Frigate's ffmpeg processes are paused. Recording stops, object detection is paused, and the Live dashboard displays a blank image with a "Camera is off" message. The camera is still visible in the Live dashboard and its past review items, tracked objects, and historical footage remain accessible via the UI. The Off state persists across Frigate restarts via a `.runtime_state.json` file alongside `config.yml` (see [Runtime toggle persistence](#runtime-toggle-persistence)).
|
||||
- **Disabled** — the change is saved to your configuration file (`enabled: False`). The camera stops immediately, Frigate stops ffmpeg processes, and all live and historical UI elements for the camera are no longer visible but remains retained on disk. The camera is still listed in **Settings → Global configuration → Camera management** so it can be re-enabled. **A restart of Frigate is required to bring a disabled camera back to On.**
|
||||
|
||||
Disabling a camera via the Frigate UI or MQTT is temporary and does not persist through restarts of Frigate.
|
||||
#### Turning a camera on or off
|
||||
|
||||
:::
|
||||
Turning a camera off is temporary and does not require a restart. The available controls are:
|
||||
|
||||
For restreamed cameras, go2rtc remains active but does not use system resources for decoding or processing unless there are active external consumers (such as the Advanced Camera Card in Home Assistant using a go2rtc source).
|
||||
- The power button in the single-camera Live view header
|
||||
- The right-click context menu on a camera tile on the Live dashboard
|
||||
- The Camera management settings pane (status set to **Off**)
|
||||
- The mobile settings drawer on the single-camera Live view (admin users only)
|
||||
- The [MQTT topic](/integrations/mqtt#frigatecamera_nameenabledset) `frigate/<camera_name>/enabled/set` with payload `ON` or `OFF`
|
||||
- The Home Assistant integration via the [`camera.turn_on` / `camera.turn_off` actions](/integrations/home-assistant#camera-api)
|
||||
|
||||
Note that disabling a camera through the config file (`enabled: False`) removes all related UI elements, including historical footage access. To retain access while disabling the camera, keep it enabled in the config and use the UI or MQTT to disable it temporarily.
|
||||
#### Disabling a camera
|
||||
|
||||
Disabling a camera saves the change to your configuration file. Navigate to **Settings → Global configuration → Camera management** and set the camera's status to **Disabled**. Runtime processing stops immediately; the change persists across restarts.
|
||||
|
||||
Re-enabling a disabled camera requires a restart of Frigate so that the ffmpeg processes and other camera-scoped resources can be initialized. The UI will prompt you to restart when you switch a disabled camera back to On.
|
||||
|
||||
#### Restream behavior
|
||||
|
||||
For both Off and Disabled cameras, go2rtc remains active but does not use system resources for decoding or processing unless there are active external consumers (such as the Advanced Camera Card in Home Assistant using a go2rtc source).
|
||||
|
||||
#### Choosing Off versus Disabled
|
||||
|
||||
If you want a camera's historical data (review items, tracked objects, footage) to stay accessible in the UI while you stop processing, set the camera to **Off**. If you want the camera fully removed from the Live dashboard, review filters, and other UI surfaces, set it to **Disabled**. The Disabled state still keeps the camera in Camera management so it can be re-enabled later; if you want to remove all traces of a camera including its configuration, delete it via Camera management instead.
|
||||
|
||||
#### Runtime toggle persistence
|
||||
|
||||
The Live view toggles for **camera on/off**, **detect**, **recordings**, **snapshots**, and **audio detection** — along with the equivalent MQTT `/set` topics — write the new state to `.runtime_state.json` next to your `config.yml`. The file is replayed on Frigate startup so your last-known toggle states survive a restart. Two interactions worth knowing:
|
||||
|
||||
- **Settings UI saves win.** When you save a field through **Settings → Global configuration**, the matching entry is cleared from `.runtime_state.json` so the new value in your config file is the durable source.
|
||||
- **Switching profiles clears all runtime overrides.** Activating or deactivating a [profile](/configuration/profiles) is treated as a deliberate state change, so the file is wiped to avoid stale overrides replaying on top of the new profile.
|
||||
|
||||
If you hand-edit `config.yml` while runtime overrides exist, the overrides will still replay on restart. Delete `.runtime_state.json` to reset to the YAML-defined defaults.
|
||||
|
||||
### Live player error messages
|
||||
|
||||
|
||||
@@ -197,3 +197,7 @@ This option is handy when you want to prevent large transient changes from trigg
|
||||
When the skip threshold is exceeded, **no motion is reported** for that frame, meaning **nothing is recorded** for that frame. That means you can miss something important, like a PTZ camera auto-tracking an object or activity while the camera is moving. If you prefer to guarantee that every frame is saved, leave this unset and accept occasional recordings containing scene noise — they typically only take up a few megabytes and are quick to scan in the timeline UI.
|
||||
|
||||
:::
|
||||
|
||||
## Reviewing Detected Motion
|
||||
|
||||
To review what the detector picked up — or to search past recordings for motion in a specific region — see [Reviewing Motion](/usage/review#reviewing-motion) on the Review page.
|
||||
|
||||
@@ -91,7 +91,7 @@ See [common Edge TPU troubleshooting steps](/troubleshooting/edgetpu) if the Edg
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `usb`.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `usb`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -111,7 +111,7 @@ detectors:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add** to add multiple detectors, specifying `usb:0` and `usb:1` as the device for each.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add** to add multiple detectors, specifying `usb:0` and `usb:1` as the device for each.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -136,7 +136,7 @@ _warning: may have [compatibility issues](https://github.com/blakeblackshear/fri
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then leave the device field empty.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then leave the device field empty.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -156,7 +156,7 @@ detectors:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `pci`.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `pci`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -176,7 +176,7 @@ detectors:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add** to add multiple detectors, specifying `pci:0` and `pci:1` as the device for each.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add** to add multiple detectors, specifying `pci:0` and `pci:1` as the device for each.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -199,7 +199,7 @@ detectors:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add** to add multiple detectors with different device types (e.g., `usb` and `pci`).
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add** to add multiple detectors with different device types (e.g., `usb` and `pci`).
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -246,7 +246,7 @@ After placing the downloaded files for the tflite model and labels in your confi
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `usb`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure the model settings:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **EdgeTPU** from the detector type dropdown and click **Add**, then set device to `usb`. Then on the same page, in the **Custom Model** tab, configure the model settings:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------- |
|
||||
@@ -309,7 +309,7 @@ Use this configuration for YOLO-based models. When no custom model path or URL i
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure the model settings:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then on the same page, in the **Custom Model** tab, configure the model settings:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ----------------------- |
|
||||
@@ -365,7 +365,7 @@ For SSD-based models, provide either a model path or URL to your compiled SSD mo
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure the model settings:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then on the same page, in the **Custom Model** tab, configure the model settings:
|
||||
|
||||
| Field | Value |
|
||||
| --------------------------------------- | ------ |
|
||||
@@ -410,7 +410,7 @@ The Hailo detector supports all YOLO models compiled for Hailo hardware that inc
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure the model settings to match your custom model dimensions and format.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **Hailo-8/Hailo-8L** from the detector type dropdown and click **Add**, then set device to `PCIe`. Then on the same page, in the **Custom Model** tab, configure the model settings to match your custom model dimensions and format.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -465,7 +465,7 @@ When using many cameras one detector may not be enough to keep up. Multiple dete
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **OpenVINO** from the detector type dropdown and click **Add** to add multiple detectors, each targeting `GPU` or `NPU`.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **OpenVINO** from the detector type dropdown and click **Add** to add multiple detectors, each targeting `GPU` or `NPU`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -494,7 +494,7 @@ detectors:
|
||||
| [YOLO-NAS](#yolo-nas) | ✅ | ✅ | |
|
||||
| [MobileNet v2](#ssdlite-mobilenet-v2) | ✅ | ✅ | Fast and lightweight model, less accurate than larger models |
|
||||
| [YOLOX](#yolox) | ✅ | ? | |
|
||||
| [D-FINE](#d-fine) | ❌ | ❌ | |
|
||||
| [D-FINE / DEIMv2](#d-fine--deimv2) | ❌ | ❌ | |
|
||||
|
||||
#### SSDLite MobileNet v2
|
||||
|
||||
@@ -508,7 +508,7 @@ Use the model configuration shown below when using the OpenVINO detector with th
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------ |
|
||||
@@ -558,7 +558,7 @@ After placing the downloaded onnx model in your config folder, use the following
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------------- |
|
||||
@@ -620,7 +620,7 @@ After placing the downloaded onnx model in your config folder, use the following
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU` (or `NPU`). Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | -------------------------------------------------------- |
|
||||
@@ -660,7 +660,7 @@ Note that the labelmap uses a subset of the complete COCO label set that has onl
|
||||
|
||||
#### RF-DETR
|
||||
|
||||
[RF-DETR](https://github.com/roboflow/rf-detr) is a DETR based model. The ONNX exported models are supported, but not included by default. See [the models section](#downloading-rf-detr-model) for more informatoin on downloading the RF-DETR model for use in Frigate.
|
||||
[RF-DETR](https://github.com/roboflow/rf-detr) is a DETR based model. The ONNX exported models are supported, but not included by default. See [the models section](#downloading-rf-detr-model) for more information on downloading the RF-DETR model for use in Frigate.
|
||||
|
||||
:::warning
|
||||
|
||||
@@ -676,7 +676,7 @@ After placing the downloaded onnx model in your `config/model_cache` folder, use
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `GPU`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| --------------------------------------- | --------------------------------- |
|
||||
@@ -710,13 +710,13 @@ model:
|
||||
|
||||
</details>
|
||||
|
||||
#### D-FINE
|
||||
#### D-FINE / DEIMv2
|
||||
|
||||
[D-FINE](https://github.com/Peterande/D-FINE) is a DETR based model. The ONNX exported models are supported, but not included by default. See [the models section](#downloading-d-fine-model) for more information on downloading the D-FINE model for use in Frigate.
|
||||
[D-FINE](https://github.com/Peterande/D-FINE) and [DEIMv2](https://github.com/Intellindust-AI-Lab/DEIMv2) are DETR based models that share the same ONNX input/output format. The ONNX exported models are supported, but not included by default. See the models section for downloading [D-FINE](#downloading-d-fine-model) or [DEIMv2](#downloading-deimv2-model) for use in Frigate.
|
||||
|
||||
:::warning
|
||||
|
||||
Currently D-FINE models only run on OpenVINO in CPU mode, GPUs currently fail to compile the model
|
||||
Currently D-FINE / DEIMv2 models only run on OpenVINO in CPU mode, GPUs currently fail to compile the model
|
||||
|
||||
:::
|
||||
|
||||
@@ -728,7 +728,7 @@ After placing the downloaded onnx model in your config/model_cache folder, use t
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `CPU`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **OpenVINO** from the detector type dropdown and click **Add**, then set device to `CPU`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ---------------------------------- |
|
||||
@@ -766,6 +766,31 @@ Note that the labelmap uses a subset of the complete COCO label set that has onl
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DEIMv2 Setup & Config</summary>
|
||||
|
||||
After placing the downloaded onnx model in your `config/model_cache` folder, you can use the following configuration:
|
||||
|
||||
```yaml
|
||||
detectors:
|
||||
ov:
|
||||
type: openvino
|
||||
device: CPU
|
||||
|
||||
model:
|
||||
model_type: dfine
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/deimv2_hgnetv2_n.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
```
|
||||
|
||||
Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects.
|
||||
|
||||
</details>
|
||||
|
||||
## Apple Silicon detector
|
||||
|
||||
The NPU in Apple Silicon can't be accessed from within a container, so the [Apple Silicon detector client](https://github.com/frigate-nvr/apple-silicon-detector) must first be setup. It is recommended to use the Frigate docker image with `-standard-arm64` suffix, for example `ghcr.io/blakeblackshear/frigate:stable-standard-arm64`.
|
||||
@@ -782,7 +807,7 @@ Using the detector config below will connect to the client:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://host.docker.internal:5555`.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://host.docker.internal:5555`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -816,7 +841,7 @@ When Frigate is started with the following config it will connect to the detecto
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://host.docker.internal:5555`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ZMQ IPC** from the detector type dropdown and click **Add**, then set the endpoint to `tcp://host.docker.internal:5555`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | -------------------------------------------------------- |
|
||||
@@ -947,7 +972,7 @@ The AMD GPU kernel is known problematic especially when converting models to mxr
|
||||
|
||||
See [ONNX supported models](#supported-models) for supported models, there are some caveats:
|
||||
|
||||
- D-FINE models are not supported
|
||||
- D-FINE / DEIMv2 models are not supported
|
||||
- YOLO-NAS models are known to not run well on integrated GPUs
|
||||
|
||||
## ONNX
|
||||
@@ -977,7 +1002,7 @@ When using many cameras one detector may not be enough to keep up. Multiple dete
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ONNX** from the detector type dropdown and click **Add** to add multiple detectors.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ONNX** from the detector type dropdown and click **Add** to add multiple detectors.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -997,13 +1022,13 @@ detectors:
|
||||
|
||||
### ONNX Supported Models
|
||||
|
||||
| Model | Nvidia GPU | AMD GPU | Notes |
|
||||
| ----------------------------- | ---------- | ------- | --------------------------------------------------- |
|
||||
| [YOLOv9](#yolo-v3-v4-v7-v9-2) | ✅ | ✅ | Supports CUDA Graphs for optimal Nvidia performance |
|
||||
| [RF-DETR](#rf-detr) | ✅ | ❌ | Supports CUDA Graphs for optimal Nvidia performance |
|
||||
| [YOLO-NAS](#yolo-nas-1) | ⚠️ | ⚠️ | Not supported by CUDA Graphs |
|
||||
| [YOLOX](#yolox-1) | ✅ | ✅ | Supports CUDA Graphs for optimal Nvidia performance |
|
||||
| [D-FINE](#d-fine) | ⚠️ | ❌ | Not supported by CUDA Graphs |
|
||||
| Model | Nvidia GPU | AMD GPU | Notes |
|
||||
| ------------------------------------ | ---------- | ------- | --------------------------------------------------- |
|
||||
| [YOLOv9](#yolo-v3-v4-v7-v9-2) | ✅ | ✅ | Supports CUDA Graphs for optimal Nvidia performance |
|
||||
| [RF-DETR](#rf-detr) | ✅ | ⚠️ | Supports CUDA Graphs for optimal Nvidia performance |
|
||||
| [YOLO-NAS](#yolo-nas-1) | ⚠️ | ⚠️ | Not supported by CUDA Graphs |
|
||||
| [YOLOX](#yolox-1) | ✅ | ✅ | Supports CUDA Graphs for optimal Nvidia performance |
|
||||
| [D-FINE / DEIMv2](#d-fine--deimv2-1) | ⚠️ | ❌ | Not supported by CUDA Graphs |
|
||||
|
||||
There is no default model provided, the following formats are supported:
|
||||
|
||||
@@ -1025,7 +1050,7 @@ After placing the downloaded onnx model in your config folder, use the following
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ONNX** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------------- |
|
||||
@@ -1084,7 +1109,7 @@ After placing the downloaded onnx model in your config folder, use the following
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ONNX** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | -------------------------------------------------------- |
|
||||
@@ -1133,7 +1158,7 @@ After placing the downloaded onnx model in your config folder, use the following
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ONNX** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | -------------------------------------------------------- |
|
||||
@@ -1182,7 +1207,7 @@ After placing the downloaded onnx model in your `config/model_cache` folder, use
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ONNX** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| --------------------------------------- | --------------------------------- |
|
||||
@@ -1215,9 +1240,9 @@ model:
|
||||
|
||||
</details>
|
||||
|
||||
#### D-FINE
|
||||
#### D-FINE / DEIMv2
|
||||
|
||||
[D-FINE](https://github.com/Peterande/D-FINE) is a DETR based model. The ONNX exported models are supported, but not included by default. See [the models section](#downloading-d-fine-model) for more information on downloading the D-FINE model for use in Frigate.
|
||||
[D-FINE](https://github.com/Peterande/D-FINE) and [DEIMv2](https://github.com/Intellindust-AI-Lab/DEIMv2) are DETR based models that share the same ONNX input/output format. The ONNX exported models are supported, but not included by default. See the models section for downloading [D-FINE](#downloading-d-fine-model) or [DEIMv2](#downloading-deimv2-model) for use in Frigate.
|
||||
|
||||
<details>
|
||||
<summary>D-FINE Setup & Config</summary>
|
||||
@@ -1227,7 +1252,7 @@ After placing the downloaded onnx model in your `config/model_cache` folder, use
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **ONNX** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **ONNX** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------- |
|
||||
@@ -1262,6 +1287,28 @@ model:
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>DEIMv2 Setup & Config</summary>
|
||||
|
||||
After placing the downloaded onnx model in your `config/model_cache` folder, you can use the following configuration:
|
||||
|
||||
```yaml
|
||||
detectors:
|
||||
onnx:
|
||||
type: onnx
|
||||
|
||||
model:
|
||||
model_type: dfine
|
||||
width: 640
|
||||
height: 640
|
||||
input_tensor: nchw
|
||||
input_dtype: float
|
||||
path: /config/model_cache/deimv2_hgnetv2_n.onnx
|
||||
labelmap_path: /labelmap/coco-80.txt
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Note that the labelmap uses a subset of the complete COCO label set that has only 80 objects.
|
||||
|
||||
## CPU Detector (not recommended)
|
||||
@@ -1281,7 +1328,7 @@ A TensorFlow Lite model is provided in the container at `/cpu_model.tflite` and
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **CPU** from the detector type dropdown and click **Add**. Configure the number of threads and click **Add** again to add additional CPU detectors as needed (one per camera is recommended).
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **CPU** from the detector type dropdown and click **Add**. Configure the number of threads and click **Add** again to add additional CPU detectors as needed (one per camera is recommended).
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -1317,7 +1364,7 @@ To integrate CodeProject.AI into Frigate, configure the detector as follows:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **DeepStack** from the detector type dropdown and click **Add**. Set the API URL to point to your CodeProject.AI server (e.g., `http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection`).
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **DeepStack** from the detector type dropdown and click **Add**. Set the API URL to point to your CodeProject.AI server (e.g., `http://<your_codeproject_ai_server_ip>:<port>/v1/vision/detection`).
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -1356,7 +1403,7 @@ To configure the MemryX detector, use the following example configuration:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -1376,7 +1423,7 @@ detectors:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **MemryX** from the detector type dropdown and click **Add** to add multiple detectors, specifying `PCIe:0`, `PCIe:1`, `PCIe:2`, etc. as the device for each.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **MemryX** from the detector type dropdown and click **Add** to add multiple detectors, specifying `PCIe:0`, `PCIe:1`, `PCIe:2`, etc. as the device for each.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -1405,7 +1452,7 @@ MemryX `.dfp` models are automatically downloaded at runtime, if enabled, to the
|
||||
|
||||
#### YOLO-NAS
|
||||
|
||||
The [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model included in this detector is downloaded from the [Models Section](#downloading-yolo-nas-model) and compiled to DFP with [mx_nc](https://developer.memryx.com/tools/neural_compiler.html#usage).
|
||||
The [YOLO-NAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model included in this detector is downloaded from the [Models Section](#downloading-yolo-nas-model) and compiled to DFP with [mx_nc](https://developer.memryx.com/2p1/tools/neural_compiler.html#usage).
|
||||
|
||||
**Note:** The default model for the MemryX detector is YOLO-NAS 320x320.
|
||||
|
||||
@@ -1420,7 +1467,7 @@ Below is the recommended configuration for using the **YOLO-NAS** (small) model
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------------- |
|
||||
@@ -1459,7 +1506,7 @@ model:
|
||||
|
||||
#### YOLOv9
|
||||
|
||||
The YOLOv9s model included in this detector is downloaded from [the original GitHub](https://github.com/WongKinYiu/yolov9) like in the [Models Section](#yolov9-1) and compiled to DFP with [mx_nc](https://developer.memryx.com/tools/neural_compiler.html#usage).
|
||||
The YOLOv9s model included in this detector is downloaded from [the original GitHub](https://github.com/WongKinYiu/yolov9) like in the [Models Section](#yolov9-1) and compiled to DFP with [mx_nc](https://developer.memryx.com/2p1/tools/neural_compiler.html#usage).
|
||||
|
||||
##### Configuration
|
||||
|
||||
@@ -1468,7 +1515,7 @@ Below is the recommended configuration for using the **YOLOv9** (small) model wi
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------------- |
|
||||
@@ -1515,7 +1562,7 @@ Below is the recommended configuration for using the **YOLOX** (small) model wit
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ----------------------- |
|
||||
@@ -1562,7 +1609,7 @@ Below is the recommended configuration for using the **SSDLite MobileNet v2** mo
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **MemryX** from the detector type dropdown and click **Add**, then set device to `PCIe:0`. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ----------------------- |
|
||||
@@ -1601,19 +1648,39 @@ model:
|
||||
|
||||
#### Using a Custom Model
|
||||
|
||||
To use your own model:
|
||||
To use your own custom model, first compile it into a [.dfp](https://developer.memryx.com/2p1/specs/files.html#dataflow-program) file, which is the format used by MemryX.
|
||||
|
||||
1. Package your compiled model into a `.zip` file.
|
||||
#### Compile the Model
|
||||
|
||||
2. The `.zip` must contain the compiled `.dfp` file.
|
||||
Custom models must be compiled using **MemryX SDK 2.1**.
|
||||
|
||||
3. Depending on the model, the compiler may also generate a cropped post-processing network. If present, it will be named with the suffix `_post.onnx`.
|
||||
Before compiling your model, install the MemryX Neural Compiler tools from the
|
||||
[Install Tools](https://developer.memryx.com/2p1/get_started/install_tools.html) page on the **host**.
|
||||
|
||||
4. Bind-mount the `.zip` file into the container and specify its path using `model.path` in your config.
|
||||
> **Note:** It is recommended to compile the model on the host machine, or on another separate machine, rather than inside the Frigate Docker container. Installing the compiler inside Docker may conflict with container packages. It is recommended to create a Python virtual environment and install the compiler there.
|
||||
|
||||
5. Update the `labelmap_path` to match your custom model's labels.
|
||||
Once the SDK 2.1 environment is set up, follow the
|
||||
[MemryX Compiler](https://developer.memryx.com/2p1/tools/neural_compiler.html#usage) documentation to compile your model.
|
||||
|
||||
For detailed instructions on compiling models, refer to the [MemryX Compiler](https://developer.memryx.com/tools/neural_compiler.html#usage) docs and [Tutorials](https://developer.memryx.com/tutorials/tutorials.html).
|
||||
Example:
|
||||
|
||||
```bash
|
||||
mx_nc -m yolonas.onnx -c 4 --autocrop -v --dfp_fname yolonas.dfp
|
||||
```
|
||||
|
||||
For detailed instructions on compiling models, refer to the [MemryX Compiler](https://developer.memryx.com/2p1/tools/neural_compiler.html#usage) docs and [Tutorials](https://developer.memryx.com/2p1/tutorials/tutorials.html).
|
||||
|
||||
#### Package the Compiled Model
|
||||
|
||||
1. Package your compiled model into a `.zip` file.
|
||||
|
||||
2. The `.zip` file must contain the compiled `.dfp` file.
|
||||
|
||||
3. Depending on the model, the compiler may also generate a cropped post-processing network. If present, it will be named with the suffix `_post.onnx`.
|
||||
|
||||
4. Bind-mount the `.zip` file into the container and specify its path using `model.path` in your config.
|
||||
|
||||
5. Update `labelmap_path` to match your custom model's labels.
|
||||
|
||||
```yaml
|
||||
# The detector automatically selects the default model if nothing is provided in the config.
|
||||
@@ -1701,7 +1768,7 @@ Use the config below to work with generated TRT models:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **TensorRT** from the detector type dropdown and click **Add**, then set the device to `0` (the default GPU index). Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **TensorRT** from the detector type dropdown and click **Add**, then set the device to `0` (the default GPU index). Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------------------------ |
|
||||
@@ -1758,7 +1825,7 @@ Use the model configuration shown below when using the synaptics detector with t
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **Synaptics** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **Synaptics** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ---------------------------- |
|
||||
@@ -1812,7 +1879,7 @@ When using many cameras one detector may not be enough to keep up. Multiple dete
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **RKNN** from the detector type dropdown and click **Add** to add multiple detectors, each with `num_cores` set to `0` for automatic selection.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **RKNN** from the detector type dropdown and click **Add** to add multiple detectors, each with `num_cores` set to `0` for automatic selection.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -1854,7 +1921,7 @@ This `config.yml` shows all relevant options to configure the detector and expla
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **RKNN** from the detector type dropdown and click **Add**. Set `num_cores` to `0` for automatic selection (increase for better performance on multicore NPUs, e.g., set to `3` on rk3588).
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **RKNN** from the detector type dropdown and click **Add**. Set `num_cores` to `0` for automatic selection (increase for better performance on multicore NPUs, e.g., set to `3` on rk3588).
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -1891,7 +1958,7 @@ The inference time was determined on a rk3588 with 3 NPU cores.
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ----------------------------------------------------------------------- |
|
||||
@@ -1937,7 +2004,7 @@ The pre-trained YOLO-NAS weights from DeciAI are subject to their license and ca
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | -------------------------------------------------- |
|
||||
@@ -1977,7 +2044,7 @@ model: # required
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ---------------------------------------------- |
|
||||
@@ -2071,7 +2138,7 @@ Once completed, configure the detector as follows:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **DeGirum** from the detector type dropdown and click **Add**. Set the location to your AI server (e.g., service name, container name, or `host:port`), the zoo to `degirum/public`, and provide your authentication token if needed.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **DeGirum** from the detector type dropdown and click **Add**. Set the location to your AI server (e.g., service name, container name, or `host:port`), the zoo to `degirum/public`, and provide your authentication token if needed.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -2114,7 +2181,7 @@ It is also possible to eliminate the need for an AI server and run the hardware
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **DeGirum** from the detector type dropdown and click **Add**. Set the location to `@local`, the zoo to `degirum/public`, and provide your authentication token.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **DeGirum** from the detector type dropdown and click **Add**. Set the location to `@local`, the zoo to `degirum/public`, and provide your authentication token.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -2151,7 +2218,7 @@ If you do not possess whatever hardware you want to run, there's also the option
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **DeGirum** from the detector type dropdown and click **Add**. Set the location to `@cloud`, the zoo to `degirum/public`, and provide your authentication token.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **DeGirum** from the detector type dropdown and click **Add**. Set the location to `@cloud`, the zoo to `degirum/public`, and provide your authentication token.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -2207,7 +2274,7 @@ Use the model configuration shown below when using the axengine detector with th
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and select **AXEngine NPU** from the detector type dropdown and click **Add**. Then navigate to <NavPath path="Settings > System > Detection model" /> and configure:
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and select **AXEngine NPU** from the detector type dropdown and click **Add**. Then on the same page, in the **Custom Model** tab, configure:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ----------------------- |
|
||||
@@ -2274,6 +2341,49 @@ COPY --from=build /dfine/output/dfine_${MODEL_SIZE}_obj2coco.onnx /dfine-${MODEL
|
||||
EOF
|
||||
```
|
||||
|
||||
### Downloading DEIMv2 Model
|
||||
|
||||
[DEIMv2](https://github.com/Intellindust-AI-Lab/DEIMv2) can be exported as ONNX by running the command below. Pretrained weights are available on Hugging Face for two backbone families:
|
||||
|
||||
- **HGNetv2** (smaller/faster): `atto`, `femto`, `pico`, `n`
|
||||
- **DINOv3** (larger/more accurate): `s`, `m`, `l`, `x`
|
||||
|
||||
Set `BACKBONE` and `MODEL_SIZE` in the first line to match your desired variant. Hugging Face model names use uppercase (e.g. `HGNetv2_N`, `DINOv3_S`), while config files use lowercase (e.g. `hgnetv2_n`, `dinov3_s`).
|
||||
|
||||
```sh
|
||||
docker build . --rm --build-arg BACKBONE=hgnetv2 --build-arg MODEL_SIZE=n --output . -f- <<'EOF'
|
||||
FROM python:3.11-slim AS build
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y git libgl1 libglib2.0-0 && rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.8.0 /uv /bin/
|
||||
WORKDIR /deimv2
|
||||
RUN git clone https://github.com/Intellindust-AI-Lab/DEIMv2.git .
|
||||
# Install CPU-only PyTorch first to avoid pulling CUDA variant
|
||||
RUN uv pip install --no-cache --system torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
||||
RUN uv pip install --no-cache --system -r requirements.txt
|
||||
RUN uv pip install --no-cache --system onnx safetensors huggingface_hub
|
||||
RUN mkdir -p output
|
||||
ARG BACKBONE
|
||||
ARG MODEL_SIZE
|
||||
# Download from Hugging Face and convert safetensors to pth
|
||||
RUN python3 -c "\
|
||||
from huggingface_hub import hf_hub_download; \
|
||||
from safetensors.torch import load_file; \
|
||||
import torch; \
|
||||
backbone = '${BACKBONE}'.replace('hgnetv2','HGNetv2').replace('dinov3','DINOv3'); \
|
||||
size = '${MODEL_SIZE}'.upper(); \
|
||||
st = load_file(hf_hub_download('Intellindust/DEIMv2_' + backbone + '_' + size + '_COCO', 'model.safetensors')); \
|
||||
torch.save({'model': st}, 'output/deimv2.pth')"
|
||||
RUN sed -i "s/data = torch.rand(2/data = torch.rand(1/" tools/deployment/export_onnx.py
|
||||
# HuggingFace safetensors omits frozen constants that the model constructor initializes
|
||||
RUN sed -i "s/cfg.model.load_state_dict(state)/cfg.model.load_state_dict(state, strict=False)/" tools/deployment/export_onnx.py
|
||||
RUN python3 tools/deployment/export_onnx.py -c configs/deimv2/deimv2_${BACKBONE}_${MODEL_SIZE}_coco.yml -r output/deimv2.pth
|
||||
FROM scratch
|
||||
ARG BACKBONE
|
||||
ARG MODEL_SIZE
|
||||
COPY --from=build /deimv2/output/deimv2.onnx /deimv2_${BACKBONE}_${MODEL_SIZE}.onnx
|
||||
EOF
|
||||
```
|
||||
|
||||
### Downloading RF-DETR Model
|
||||
|
||||
RF-DETR can be exported as ONNX by running the command below. You can copy and paste the whole thing to your terminal and execute, altering `MODEL_SIZE=Nano` in the first line to `Nano`, `Small`, or `Medium` size.
|
||||
|
||||
@@ -158,4 +158,4 @@ Models for both CPU and EdgeTPU (Coral) are bundled in the image. You can use yo
|
||||
- EdgeTPU Model: `/edgetpu_model.tflite`
|
||||
- Labels: `/labelmap.txt`
|
||||
|
||||
You also need to update the [model config](advanced.md#model) if they differ from the defaults.
|
||||
You also need to update the [model config](advanced/system.md#model) if they differ from the defaults.
|
||||
|
||||
@@ -33,10 +33,10 @@ The easiest way to define profiles is to use the Frigate UI. Profiles can also b
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
1. **Create a profile** — Navigate to <NavPath path="Settings > Camera configuration > Profiles" />. Click the **Add Profile** button, enter a name (and optionally a profile ID).
|
||||
1. **Create a profile** — Navigate to <NavPath path="Settings > Global configuration > Profiles" />. Click the **Add Profile** button, enter a name (and optionally a profile ID).
|
||||
2. **Configure overrides** — Navigate to a camera configuration section (e.g. Motion detection, Record, Notifications). In the top right, two buttons will appear - choose a camera and a profile from the profile selector to edit overrides for that camera and section. Only the fields you change will be stored as overrides — fields that require a restart are hidden since profiles are applied at runtime. You can click the **Remove Profile Override** button to clear overrides.
|
||||
3. **Activate a profile** — Use the **Profiles** option in Frigate's main menu to choose a profile. Alternatively, in Settings, navigate to <NavPath path="Settings > Camera configuration > Profiles" />, then choose a profile in the Active Profile dropdown to activate it. The active profile is also shown in the status bar at the bottom of the screen on desktop browsers.
|
||||
4. **Delete a profile** — Navigate to <NavPath path="Settings > Camera configuration > Profiles" />, then click the trash icon for a profile. This removes the profile definition and all camera overrides associated with it.
|
||||
3. **Activate a profile** — Use the **Profiles** option in Frigate's main menu to choose a profile. Alternatively, in Settings, navigate to <NavPath path="Settings > Global configuration > Profiles" />, then choose a profile in the Active Profile dropdown to activate it. The active profile is also shown in the status bar at the bottom of the screen on desktop browsers.
|
||||
4. **Delete a profile** — Navigate to <NavPath path="Settings > Global configuration > Profiles" />, then click the trash icon for a profile. This removes the profile definition and all camera overrides associated with it.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -126,7 +126,11 @@ Only the fields you explicitly set in a profile override are applied. All other
|
||||
|
||||
## Activating Profiles
|
||||
|
||||
Profiles can be activated and deactivated from the Frigate UI. Open the Settings cog and select **Profiles** from the submenu to see all defined profiles. From there you can activate any profile or deactivate the current one. The active profile is indicated in the UI so you always know which profile is in effect.
|
||||
Profiles can be activated and deactivated via the Frigate UI, [MQTT](/integrations/mqtt#frigateprofileset), or the Home Assistant integration.
|
||||
|
||||
In the Frigate UI, open the Settings cog and select **Profiles** from the submenu to see all defined profiles. From there you can activate any profile or deactivate the current one. The active profile is indicated in the UI so you always know which profile is in effect.
|
||||
|
||||
Activating or deactivating a profile clears any [runtime toggle overrides](/configuration/live#runtime-toggle-persistence) so the profile's settings aren't silently undone by a stale toggle from before the switch.
|
||||
|
||||
## Example: Home / Away Setup
|
||||
|
||||
@@ -135,10 +139,10 @@ A common use case is having different detection and notification settings based
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
1. Navigate to <NavPath path="Settings > Camera configuration > Profiles" /> and create two profiles: **Home** and **Away**.
|
||||
1. Navigate to <NavPath path="Settings > Global configuration > Profiles" /> and create two profiles: **Home** and **Away**.
|
||||
2. From to the Camera configuration section in Settings, choose the **front_door** camera, and select the **Away** profile from the profile dropdown. Then, enable notifications from the Notifications pane, and set alert labels to `person` and `car` from the Review pane. Then, from the profile dropdown choose **Home** profile, then navigate to Notifications to disable notifications.
|
||||
3. For the **indoor_cam** camera, perform similar steps - configure the **Away** profile to enable the camera, detection, and recording. Configure the **Home** profile to disable the camera entirely for privacy.
|
||||
4. Activate the desired profile from <NavPath path="Settings > Camera configuration > Profiles" /> or from the **Profiles** option in Frigate's main menu.
|
||||
4. Activate the desired profile from <NavPath path="Settings > Global configuration > Profiles" /> or from the **Profiles** option in Frigate's main menu.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -207,3 +211,27 @@ In this example:
|
||||
- **Away profile**: The front door camera enables notifications and tracks specific alert labels. The indoor camera is fully enabled with detection and recording.
|
||||
- **Home profile**: The front door camera disables notifications. The indoor camera is completely disabled for privacy.
|
||||
- **No profile active**: All cameras use their base configuration values.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Can I define a zone or mask in a profile but not have it in the base config?
|
||||
|
||||
No. Profiles are pure overrides. Every zone and mask defined under a profile must reference an entry that already exists on the base camera config. Configurations that introduce profile-only zones or masks are rejected at startup.
|
||||
|
||||
If you want a zone or mask to be active only under a specific profile, define it on the base config with `enabled: false`, then enable it in that profile's overrides.
|
||||
|
||||
### How do I revert a profile zone or mask override back to the base configuration?
|
||||
|
||||
Delete the override. In the Frigate UI, edit the profile and use the "Revert override" action (the trash can icon) on the zone or mask. The base entry is left untouched, and once the override is removed the profile inherits the base values for that zone or mask.
|
||||
|
||||
### Can multiple profiles be active at the same time?
|
||||
|
||||
No. Only one profile can be active at a time. Activating a new profile automatically deactivates the current one.
|
||||
|
||||
### What happens to my profile overrides if I delete a zone or mask from the base?
|
||||
|
||||
When you delete a base zone or mask in the Frigate UI, any profile overrides for that entry are deleted automatically as part of the same operation. If you remove a base entry by editing your config file directly and leave a profile override behind, the config will fail validation at startup until the orphaned override is removed as well.
|
||||
|
||||
### Why are some settings missing when I configure a profile override?
|
||||
|
||||
Fields that require a Frigate restart to take effect cannot be overridden by profiles, since profiles are applied at runtime without restarting. Those fields are hidden when editing a profile override and can only be changed on the base configuration.
|
||||
|
||||
@@ -11,6 +11,12 @@ Recordings can be enabled and are stored at `/media/frigate/recordings`. The fol
|
||||
|
||||
New recording segments are written from the camera stream to cache, they are only moved to disk if they match the setup recording retention policy.
|
||||
|
||||
:::tip
|
||||
|
||||
To keep a specific clip beyond your retention window, [export](/usage/exports) it rather than increasing retention for the whole camera. Exports are saved separately and are never removed by retention.
|
||||
|
||||
:::
|
||||
|
||||
H265 recordings can be viewed in Chrome 108+, Edge and Safari only. All other browsers require recordings to be encoded with H264.
|
||||
|
||||
## Common recording configurations
|
||||
@@ -195,7 +201,7 @@ Pre and post capture footage is included in the **recording timeline**, visible
|
||||
|
||||
## Will Frigate delete old recordings if my storage runs out?
|
||||
|
||||
As of Frigate 0.12 if there is less than an hour left of storage, the oldest 2 hours of recordings will be deleted.
|
||||
If there is less than an hour left of storage, the oldest hour of recordings will be deleted and a message will be printed in the Frigate logs. This emergency cleanup deletes the oldest recordings first regardless of retention settings to reclaim space as quickly as possible.
|
||||
|
||||
## Configuring Recording Retention
|
||||
|
||||
|
||||
@@ -236,7 +236,7 @@ Enabling arbitrary exec sources allows execution of arbitrary commands through g
|
||||
|
||||
## Advanced Restream Configurations
|
||||
|
||||
The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-exec) source in go2rtc can be used for custom ffmpeg commands. An example is below:
|
||||
The [exec](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-exec) source in go2rtc can be used for custom ffmpeg commands and other applications. An example is below:
|
||||
|
||||
:::warning
|
||||
|
||||
@@ -244,16 +244,11 @@ The `exec:`, `echo:`, and `expr:` sources are disabled by default for security.
|
||||
|
||||
:::
|
||||
|
||||
:::warning
|
||||
|
||||
The `exec:`, `echo:`, and `expr:` sources are disabled by default for security. You must set `GO2RTC_ALLOW_ARBITRARY_EXEC=true` to use them. See [Security: Restricted Stream Sources](#security-restricted-stream-sources) for more information.
|
||||
|
||||
:::
|
||||
|
||||
NOTE: The output will need to be passed with two curly braces `{{output}}`
|
||||
NOTE: RTSP output will need to be passed with two curly braces `{{output}}`, whereas pipe output must be passed without curly braces.
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
stream1: exec:ffmpeg -hide_banner -re -stream_loop -1 -i /media/BigBuckBunny.mp4 -c copy -rtsp_transport tcp -f rtsp {{output}}
|
||||
stream2: exec:rpicam-vid -t 0 --libav-format h264 -o -
|
||||
```
|
||||
|
||||
@@ -23,7 +23,7 @@ In 0.14 and later, all of that is bundled into a single review item which starts
|
||||
|
||||
## Alerts and Detections
|
||||
|
||||
Not every segment of video captured by Frigate may be of the same level of interest to you. Video of people who enter your property may be a different priority than those walking by on the sidewalk. For this reason, Frigate 0.14 categorizes review items as _alerts_ and _detections_. By default, all person and car objects are considered alerts. You can refine categorization of your review items by configuring required zones for them.
|
||||
Not every segment of video captured by Frigate may be of the same level of interest to you. Video of people who enter your property may be a different priority than those walking by on the sidewalk. For this reason, Frigate categorizes review items as _alerts_ and _detections_. By default, all person and car objects are considered alerts. You can refine categorization of your review items by configuring required zones for them.
|
||||
|
||||
:::note
|
||||
|
||||
@@ -130,3 +130,7 @@ By default a review item will be created if any `review -> alerts -> labels` and
|
||||
Because zones don't apply to audio, audio labels will always be marked as a detection by default.
|
||||
|
||||
:::
|
||||
|
||||
## Reviewing Motion
|
||||
|
||||
The Review page can also surface periods of motion that didn't produce a tracked object, and lets you search past recordings for motion in a region you draw. See [Reviewing Motion](/usage/review#reviewing-motion) in the Usage docs for how to use **Motion Previews** and **Motion Search**, and [Tuning Motion Detection](motion_detection.md) for configuring the underlying motion detector.
|
||||
|
||||
@@ -222,12 +222,7 @@ See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_
|
||||
|
||||
## Usage and Best Practices
|
||||
|
||||
1. Semantic Search is used in conjunction with the other filters available on the Explore page. Use a combination of traditional filtering and Semantic Search for the best results.
|
||||
2. Use the thumbnail search type when searching for particular objects in the scene. Use the description search type when attempting to discern the intent of your object.
|
||||
3. Because of how the AI models Frigate uses have been trained, the comparison between text and image embedding distances generally means that with multi-modal (`thumbnail` and `description`) searches, results matching `description` will appear first, even if a `thumbnail` embedding may be a better match. Play with the "Search Type" setting to help find what you are looking for. Note that if you are generating descriptions for specific objects or zones only, this may cause search results to prioritize the objects with descriptions even if the the ones without them are more relevant.
|
||||
4. Make your search language and tone closely match exactly what you're looking for. If you are using thumbnail search, **phrase your query as an image caption**. Searching for "red car" may not work as well as "red sedan driving down a residential street on a sunny day".
|
||||
5. Semantic search on thumbnails tends to return better results when matching large subjects that take up most of the frame. Small things like "cat" tend to not work well.
|
||||
6. Experiment! Find a tracked object you want to test and start typing keywords and phrases to see what works for you.
|
||||
For tips on getting the best results from Semantic Search — choosing between thumbnail and description search, phrasing queries effectively, and combining search with the other Explore filters — see [Usage and best practices](/usage/explore#usage-and-best-practices) in the Usage docs.
|
||||
|
||||
## Triggers
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ title: Camera setup
|
||||
|
||||
Cameras configured to output H.264 video and AAC audio will offer the most compatibility with all features of Frigate and Home Assistant. H.265 has better compression, but less compatibility. Firefox 134+/136+/137+ (Windows/Mac/Linux & Android), Chrome 108+, Safari and Edge are the only browsers able to play H.265 and only support a limited number of H.265 profiles. Ideally, cameras should be configured directly for the desired resolutions and frame rates you want to use in Frigate. Reducing frame rates within Frigate will waste CPU resources decoding extra frames that are discarded. There are three different goals that you want to tune your stream configurations around.
|
||||
|
||||
- **Detection**: This is the only stream that Frigate will decode for processing. Also, this is the stream where snapshots will be generated from. The resolution for detection should be tuned for the size of the objects you want to detect. See [Choosing a detect resolution](#choosing-a-detect-resolution) for more details. The recommended frame rate is 5fps, but may need to be higher (10fps is the recommended maximum for most users) for very fast moving objects. Higher resolutions and frame rates will drive higher CPU usage on your server.
|
||||
- **Detection**: This is the only stream that Frigate will decode for processing. Also, this is the stream where snapshots will be generated from. The resolution for detection should be tuned for the size of the objects you want to detect. See [Choosing a detect resolution](#choosing-a-detect-resolution) for more details. The default frame rate of 5fps is correct for almost all cameras and rarely needs to be changed; see [Choosing a detect frame rate](#choosing-a-detect-frame-rate). Higher resolutions and frame rates will drive higher CPU usage on your server.
|
||||
|
||||
- **Recording**: This stream should be the resolution you wish to store for reference. Typically, this will be the highest resolution your camera supports. I recommend setting this feed in your camera's firmware to 15 fps.
|
||||
|
||||
@@ -25,6 +25,44 @@ Larger resolutions **do** improve performance if the objects are very small in t
|
||||
|
||||

|
||||
|
||||
### Choosing a detect frame rate
|
||||
|
||||
`detect.fps` controls how many times per second Frigate runs object detection — it does **not** need to match your camera's frame rate. The default of **5** is correct for the vast majority of cameras.
|
||||
|
||||
:::warning
|
||||
|
||||
Most users who raise `detect.fps` above the default don't need to. Increasing it consumes more CPU/GPU (detection load scales directly with the frame rate) while providing **no benefit to tracking** once objects are already being followed smoothly. Leave it at **5** unless you have a specific scene that fails the test below, and confirm any change actually helps in the debug view.
|
||||
|
||||
:::
|
||||
|
||||
#### Why 5 is enough for almost everyone
|
||||
|
||||
Frigate follows an object by matching its bounding box from one detection frame to the next, which requires the object to be detected often enough while it is on screen. At 5 fps this is satisfied in normal scenes: an object crossing a yard, porch, driveway, or walkway is in view for several seconds and produces ~15 or more detections, which is more than enough for a reliable track and a good snapshot. This includes fast subjects such as a running person or a bolting pet, which on a wide-angle view remain on screen for several seconds.
|
||||
|
||||
A higher rate helps only when an object crosses the **entire frame in less than two seconds**, which is determined by camera framing rather than object speed - for example, a camera aimed down a street at fast cross-traffic. In those scenes 5 fps may produce too few detections to hold a track. Cameras covering normal approaches and open areas are unaffected.
|
||||
|
||||
#### Checking whether a higher rate is needed
|
||||
|
||||
Estimate how long an object is visible as it crosses the area of interest, aiming for roughly 8–10 detections during the pass:
|
||||
|
||||
> **`detect.fps` ≈ 10 ÷ (seconds the object is in view)**
|
||||
|
||||
Most objects — people walking or running, pets, and vehicles in a yard, driveway, or walkway — stay in view for two seconds or more, so the default of 5 fps is correct. Slowly try raising it to 10 (the recommended maximum) in increments only when objects routinely cross the entire frame in about a second, such as a camera aimed at a street or sidewalk with fast cross-traffic. Objects that transit in under a second cannot be tracked reliably at any practical rate, so reposition the camera instead.
|
||||
|
||||
:::tip
|
||||
|
||||
If the formula calls for more than 10, the fix is **camera placement, not frame rate**. Angle the camera so objects move toward it rather than across the view, or aim it where traffic slows. A higher `detect.fps` increases CPU load proportionally without producing more detections of a too-brief object.
|
||||
|
||||
:::
|
||||
|
||||
#### Verify in the debug view
|
||||
|
||||
Confirm any change in the Debug view or Debug Replay. Watch a typical object cross the scene: if its bounding box follows it smoothly while visible, the rate is sufficient. A box that jumps erratically, drops out, or splits one object into multiple events indicates the rate should be increased one step.
|
||||
|
||||
#### Dedicated LPR cameras
|
||||
|
||||
A dedicated license plate recognition camera is the most common reason to use something higher than 5 fps: the camera is highly zoomed, the plate is small, and it moves at full vehicle speed, so it transits the frame quickly. However, the same ceiling applies: above 10 fps is unnecessary, and **placement matters most**: aim LPR cameras where vehicles slow down, such as gates, driveways, and parking entrances. A tight view of a fast through-road will not likely read plates reliably at any frame rate. See [License Plate Recognition](/configuration/license_plate_recognition) for details.
|
||||
|
||||
### Example Camera Configuration
|
||||
|
||||
For the Dahua/Loryta 5442 camera, I use the following settings:
|
||||
|
||||
@@ -5,20 +5,40 @@ title: Glossary
|
||||
|
||||
The glossary explains terms commonly used in Frigate's documentation.
|
||||
|
||||
## Alert
|
||||
|
||||
The higher-priority of the two [review item](#review-item) severities, the other being a [detection](#detection). By default a review item is an alert when it involves a `person` or `car`; the qualifying [labels](#label) and [zones](#zone) can be configured. [See the review docs for more info](/configuration/review)
|
||||
|
||||
## Attribute
|
||||
|
||||
A property detected on an [object](#object) that exists alongside its [label](#label). Unlike a [sub label](#sub-label), an object can carry several attributes at once. Some attributes come directly from the object detection [model](#model) — for example `face`, `license_plate`, or delivery carrier logos such as `amazon`, `ups`, and `fedex` — while others come from a [custom object classification model](/configuration/custom_classification/object_classification) configured with the `attribute` type. Attributes are visible in the Tracked Object Details pane in Explore, in `frigate/events` MQTT messages, and through the HTTP API.
|
||||
|
||||
## Bounding Box
|
||||
|
||||
A box returned from the object detection model that outlines an object in the frame. These have multiple colors depending on object type in the debug live view.
|
||||
A box returned by the object detection [model](#model) that outlines a detected [object](#object) in the frame. In the Debug view, bounding boxes are colored by object [label](#label).
|
||||
|
||||
### Bounding Box Colors
|
||||
|
||||
- At startup different colors will be assigned to each object label
|
||||
- A dark blue thin line indicates that object is not detected at this current point in time
|
||||
- A gray thin line indicates that object is detected as being stationary
|
||||
- A thick line indicates that object is the subject of autotracking (when enabled).
|
||||
- A thick line indicates that object is the subject of autotracking (when enabled)
|
||||
|
||||
## Class
|
||||
|
||||
The categories a classification [model](#model) is trained to distinguish between. Each class is a distinct visual category the model predicts, plus a `none` class for inputs that don't fit any category. For example, a custom object classification model for `person` objects might use the classes `delivery_person`, `resident`, and `none`. The predicted class is applied to the [object](#object) as either a [sub label](#sub-label) or an [attribute](#attribute), depending on the model's configuration. [See the object classification docs for more info](/configuration/custom_classification/object_classification)
|
||||
|
||||
## Detection
|
||||
|
||||
The lower-priority of the two [review item](#review-item) severities, the other being an [alert](#alert). By default, any review item that does not qualify as an alert is a detection; the qualifying [labels](#label) and [zones](#zone) can be configured. Despite the name, a detection is a category of review item — not the same as the object detection performed by the [model](#model). [See the review docs for more info](/configuration/review)
|
||||
|
||||
## False Positive
|
||||
|
||||
An incorrect detection of an object type. For example a dog being detected as a person, a chair being detected as a dog, etc. A person being detected in an area you want to ignore is not a false positive.
|
||||
An incorrect result from the object detection [model](#model), where it assigns the wrong [label](#label) to something in the frame — for example a dog identified as a person, or a chair identified as a dog. A person correctly identified in an area you want to ignore is not a false positive.
|
||||
|
||||
## Label
|
||||
|
||||
The type assigned to a detected [object](#object) by the object detection [model](#model), drawn from the model's labelmap — for example `person`, `car`, or `dog`. Frigate tracks `person` by default; additional labels are tracked by adding them to the objects configuration. [See the available objects docs for the full list](/configuration/objects)
|
||||
|
||||
## Mask
|
||||
|
||||
@@ -26,44 +46,56 @@ There are two types of masks in Frigate. [See the mask docs for more info](/conf
|
||||
|
||||
### Motion Mask
|
||||
|
||||
Motion masks prevent detection of [motion](#motion) in masked areas from triggering Frigate to run object detection, but do not prevent objects from being detected if object detection runs due to motion in nearby areas. For example: camera timestamps, skies, the tops of trees, etc.
|
||||
A motion mask stops [motion](#motion) in the masked area from triggering object detection. It does not stop an object from being detected when object detection runs because of motion in a nearby area. Use motion masks for parts of the frame that change constantly but never contain objects you care about — camera timestamps, the sky, the tops of trees, and so on.
|
||||
|
||||
### Object Mask
|
||||
|
||||
Object filter masks drop any bounding boxes where the bottom center (overlap doesn't matter) is in the masked area. It forces them to be considered a [false positive](#false-positive) so that they are ignored.
|
||||
An object filter mask drops any [bounding box](#bounding-box) whose bottom center falls inside the masked area (overlap elsewhere doesn't matter). The object is forced to be treated as a [false positive](#false-positive) and ignored.
|
||||
|
||||
## Min Score
|
||||
|
||||
The lowest score that an object can be detected with during tracking, any detection with a lower score will be assumed to be a false positive
|
||||
The lowest score a detected object can have to be kept during tracking. Anything scoring below the minimum is assumed to be a [false positive](#false-positive) and discarded.
|
||||
|
||||
## Model
|
||||
|
||||
A machine learning model that Frigate uses to detect or classify objects. The object detection model locates [objects](#object) in each frame and returns their [labels](#label) and [bounding boxes](#bounding-box). Additional enrichment models run on tracked objects to add detail: face recognition, license plate recognition, bird classification, custom object and state classification, and the embedding models used for semantic search. [See the object detectors docs for more info](/configuration/object_detectors)
|
||||
|
||||
## Motion
|
||||
|
||||
When pixels in the current camera frame are different than previous frames. When many nearby pixels are different in the current frame they grouped together and indicated with a red motion box in the live debug view. [See the motion detection docs for more info](/configuration/motion_detection)
|
||||
A change in pixels between the current camera frame and previous frames. When many nearby pixels change together, they are grouped and shown as a red motion box in the debug live view. [See the motion detection docs for more info](/configuration/motion_detection)
|
||||
|
||||
## Object
|
||||
|
||||
Something Frigate can detect and follow in a camera frame, identified by its [label](#label) (for example a person or a car). The object types Frigate watches for are set in the `objects` configuration. Once an object is detected and followed across frames it becomes a [tracked object](#tracked-object-event-in-previous-versions), which may also carry a [sub label](#sub-label) and [attributes](#attribute). [See the available objects docs for more info](/configuration/objects)
|
||||
|
||||
## Region
|
||||
|
||||
A portion of the camera frame that is sent to object detection, regions can be sent due to motion, active objects, or occasionally for stationary objects. These are represented by green boxes in the debug live view.
|
||||
A portion of the camera frame sent to the object detection [model](#model). Regions are selected because of [motion](#motion), active objects, or occasionally to recheck stationary objects, and are shown as green boxes in the debug live view.
|
||||
|
||||
## Review Item
|
||||
|
||||
A review item is a time period where any number of events/tracked objects were active. [See the review docs for more info](/configuration/review)
|
||||
A period of time during which one or more [tracked objects](#tracked-object-event-in-previous-versions) were active, grouped together for review. Each review item is categorized as either an [alert](#alert) or a [detection](#detection). [See the review docs for more info](/configuration/review)
|
||||
|
||||
## Snapshot Score
|
||||
|
||||
The score shown in a snapshot is the score of that object at that specific moment in time.
|
||||
The object's score at the specific moment the snapshot was captured.
|
||||
|
||||
## Sub Label
|
||||
|
||||
A more specific identity assigned to a [tracked object](#tracked-object-event-in-previous-versions) in addition to its [label](#label). A `person` may get the name of a recognized face, a `car` may get the name of a known license plate, and a `bird` may get its species. An object can have only one sub label at a time. Sub labels are produced by face recognition, license plate recognition, bird classification, custom object classification configured with the `sub label` type, and semantic search triggers.
|
||||
|
||||
## Threshold
|
||||
|
||||
The threshold is the median score that an object must reach in order to be considered a true positive.
|
||||
The median score an object must reach to be considered a true positive.
|
||||
|
||||
## Top Score
|
||||
|
||||
The top score for an object is the highest median score for an object.
|
||||
The highest median score an object reached over its lifetime.
|
||||
|
||||
## Tracked Object ("event" in previous versions)
|
||||
|
||||
The time period starting when a tracked object entered the frame and ending when it left the frame, including any time that the object remained still. Tracked objects are saved when it is considered a [true positive](#threshold) and meets the requirements for a snapshot or recording to be saved.
|
||||
An [object](#object) followed from the moment it enters the frame until it leaves, including any time it stays still. A tracked object is saved once it is considered a [true positive](#threshold) and meets the requirements for a snapshot or recording.
|
||||
|
||||
## Zone
|
||||
|
||||
Zones are areas of interest, zones can be used for notifications and for limiting the areas where Frigate will create a [review item](#review-item). [See the zone docs for more info](/configuration/zones)
|
||||
A user-defined area of interest within the camera frame. Zones can be used for notifications and to limit where Frigate creates a [review item](#review-item). [See the zone docs for more info](/configuration/zones)
|
||||
|
||||
@@ -223,10 +223,11 @@ Apple Silicon can not run within a container, so a ZMQ proxy is utilized to comm
|
||||
|
||||
With the [ROCm](../configuration/object_detectors.md#amdrocm-gpu-detector) detector Frigate can take advantage of many discrete AMD GPUs.
|
||||
|
||||
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time |
|
||||
| --------- | --------------------------- | ------------------------- |
|
||||
| AMD 780M | t-320: ~ 14 ms s-320: 20 ms | 320: ~ 25 ms 640: ~ 50 ms |
|
||||
| AMD 8700G | | 320: ~ 20 ms 640: ~ 40 ms |
|
||||
| Name | YOLOv9 Inference Time | YOLO-NAS Inference Time | RF-DETR Inference Time |
|
||||
| -------------- | --------------------------- | ------------------------- | ---------------------- |
|
||||
| AMD 780M | t-320: ~ 14 ms s-320: 20 ms | 320: ~ 25 ms 640: ~ 50 ms | |
|
||||
| AMD 8700G | | 320: ~ 20 ms 640: ~ 40 ms | |
|
||||
| AMD 9060XT 16G | t-320: ~ 4 ms s-320: 5 ms | 320: ~ 6 ms | Nano-320: ~ 90 ms |
|
||||
|
||||
## Community Supported Detectors
|
||||
|
||||
|
||||
@@ -4,12 +4,15 @@ title: Installation
|
||||
---
|
||||
|
||||
import ShmCalculator from '@site/src/components/ShmCalculator'
|
||||
import DockerComposeGenerator from '@site/src/components/DockerComposeGenerator'
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
Frigate is a Docker container that can be run on any Docker host including as a [Home Assistant App](https://www.home-assistant.io/apps/). Note that the Home Assistant App is **not** the same thing as the integration. The [integration](/integrations/home-assistant) is required to integrate Frigate into Home Assistant, whether you are running Frigate as a standalone Docker container or as a Home Assistant App.
|
||||
|
||||
:::tip
|
||||
|
||||
If you already have Frigate installed as a Home Assistant App, check out the [getting started guide](../guides/getting_started#configuring-frigate) to configure Frigate.
|
||||
If you already have Frigate installed as a Home Assistant App, check out the [getting started guide](../guides/getting_started.md#configuring-frigate) to configure Frigate.
|
||||
|
||||
:::
|
||||
|
||||
@@ -286,7 +289,7 @@ The MemryX MX3 Accelerator is available in the M.2 2280 form factor (like an NVM
|
||||
|
||||
#### Installation
|
||||
|
||||
To get started with MX3 hardware setup for your system, refer to the [Hardware Setup Guide](https://developer.memryx.com/get_started/hardware_setup.html).
|
||||
To get started with MX3 hardware setup for your system, refer to the [Hardware Setup Guide](https://developer.memryx.com/2p1/get_started/install_hardware.html).
|
||||
|
||||
Then follow these steps for installing the correct driver/runtime configuration:
|
||||
|
||||
@@ -295,6 +298,12 @@ Then follow these steps for installing the correct driver/runtime configuration:
|
||||
3. Run the script with `./user_installation.sh`
|
||||
4. **Restart your computer** to complete driver installation.
|
||||
|
||||
:::warning
|
||||
|
||||
For manual setup, use **MemryX SDK 2.1** only. Other SDK versions are not supported for this setup. See the [SDK 2.1 documentation](https://developer.memryx.com/2p1/index.html)
|
||||
|
||||
:::
|
||||
|
||||
#### Setup
|
||||
|
||||
To set up Frigate, follow the default installation instructions, for example: `ghcr.io/blakeblackshear/frigate:stable`
|
||||
@@ -468,6 +477,16 @@ Finally, configure [hardware object detection](/configuration/object_detectors#a
|
||||
|
||||
Running through Docker with Docker Compose is the recommended install method.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="domestic" label="Docker Compose Generator" default>
|
||||
|
||||
Generate a Frigate Docker Compose configuration based on your hardware and requirements.
|
||||
|
||||
<DockerComposeGenerator/>
|
||||
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="original" label="Example Docker Compose File">
|
||||
```yaml
|
||||
services:
|
||||
frigate:
|
||||
@@ -501,6 +520,10 @@ services:
|
||||
environment:
|
||||
FRIGATE_RTSP_PASSWORD: "password"
|
||||
```
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
**Docker CLI**
|
||||
|
||||
If you can't use Docker Compose, you can run the container with something similar to this:
|
||||
|
||||
@@ -577,7 +600,7 @@ There are several variants of the App available:
|
||||
|
||||
If you are using hardware acceleration for ffmpeg, you **may** need to use the _Full Access_ variant of the App. This is because the Frigate App runs in a container with limited access to the host system. The _Full Access_ variant allows you to disable _Protection mode_ and give Frigate full access to the host system.
|
||||
|
||||
You can also edit the Frigate configuration file through the [VS Code App](https://github.com/hassio-addons/addon-vscode) or similar. In that case, the configuration file will be at `/addon_configs/<addon_directory>/config.yml`, where `<addon_directory>` is specific to the variant of the Frigate App you are running. See the list of directories [here](../configuration/index.md#accessing-app-config-dir).
|
||||
You can also edit the Frigate configuration file through the [VS Code App](https://github.com/hassio-addons/addon-vscode) or similar. In that case, the configuration file will be at `/addon_configs/<addon_directory>/config.yml`, where `<addon_directory>` is specific to the variant of the Frigate App you are running. See the list of directories [here](../configuration/config.md#accessing-app-config-dir).
|
||||
|
||||
## Kubernetes
|
||||
|
||||
@@ -726,7 +749,7 @@ Failure to remap port 5000 on the host will result in the WebUI and all API endp
|
||||
|
||||
:::
|
||||
|
||||
Docker containers on macOS can be orchestrated by either [Docker Desktop](https://docs.docker.com/desktop/setup/install/mac-install/) or [OrbStack](https://orbstack.dev) (native swift app). The difference in inference speeds is negligable, however CPU, power consumption and container start times will be lower on OrbStack because it is a native Swift application.
|
||||
Docker containers on macOS can be orchestrated by either [Docker Desktop](https://docs.docker.com/desktop/setup/install/mac-install/) or [OrbStack](https://orbstack.dev) (native Swift app). The difference in inference speeds is negligible, however CPU, power consumption and container start times will be lower on OrbStack because it is a native Swift application.
|
||||
|
||||
To allow Frigate to use the Apple Silicon Neural Engine / Processing Unit (NPU) the host must be running [Apple Silicon Detector](../configuration/object_detectors.md#apple-silicon-detector) on the host (outside Docker)
|
||||
|
||||
@@ -745,7 +768,7 @@ services:
|
||||
- /path/to/your/recordings:/recordings
|
||||
ports:
|
||||
- "8971:8971"
|
||||
# If exposing on macOS map to a diffent host port like 5001 or any orher port with no conflicts
|
||||
# If exposing on macOS map to a different host port like 5001 or any other port with no conflicts
|
||||
# - "5001:5000" # Internal unauthenticated access. Expose carefully.
|
||||
- "8554:8554" # RTSP feeds
|
||||
extra_hosts:
|
||||
|
||||
@@ -32,7 +32,7 @@ The following models are downloaded automatically the first time their associate
|
||||
| [License plate recognition](/configuration/license_plate_recognition) | PaddleOCR (detection, classification, recognition) + YOLOv9 plate detector | GitHub |
|
||||
| [Bird classification](/configuration/bird_classification) | MobileNetV2 bird model + label map | GitHub |
|
||||
| [Custom classification](/configuration/custom_classification/state_classification) (training) | MobileNetV2 ImageNet base weights (via Keras) | Google storage |
|
||||
| [Audio transcription](/configuration/advanced) | Whisper or Sherpa-ONNX streaming model | HuggingFace / OpenAI |
|
||||
| [Audio transcription](/configuration/advanced/system) | Whisper or Sherpa-ONNX streaming model | HuggingFace / OpenAI |
|
||||
|
||||
### Hardware-Specific Detector Models
|
||||
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
---
|
||||
id: configuring_go2rtc
|
||||
title: Configuring go2rtc
|
||||
---
|
||||
|
||||
Use of the bundled go2rtc is optional. You can still configure FFmpeg to connect directly to your cameras. However, adding go2rtc to your configuration is required for the following features:
|
||||
|
||||
- WebRTC or MSE for live viewing with audio, higher resolutions and frame rates than the jsmpeg stream which is limited to the detect stream and does not support audio
|
||||
- Live stream support for cameras in Home Assistant Integration
|
||||
- RTSP relay for use with other consumers to reduce the number of connections to your camera streams
|
||||
|
||||
## Setup a go2rtc stream
|
||||
|
||||
First, you will want to configure go2rtc to connect to your camera stream by adding the stream you want to use for live view in your Frigate config file. Avoid changing any other parts of your config at this step. Note that go2rtc supports [many different stream types](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#module-streams), not just rtsp.
|
||||
|
||||
:::tip
|
||||
|
||||
For the best experience, you should set the stream name under `go2rtc` to match the name of your camera so that Frigate will automatically map it and be able to use better live view options for the camera.
|
||||
|
||||
See [the live view docs](../configuration/live.md#setting-streams-for-live-ui) for more information.
|
||||
|
||||
:::
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
```
|
||||
|
||||
After adding this to the config, restart Frigate and try to watch the live stream for a single camera by clicking on it from the dashboard. It should look much clearer and more fluent than the original jsmpeg stream.
|
||||
|
||||
### What if my video doesn't play?
|
||||
|
||||
- Check Logs:
|
||||
- Access the go2rtc logs in the Frigate UI under Logs in the sidebar.
|
||||
- If go2rtc is having difficulty connecting to your camera, you should see some error messages in the log.
|
||||
|
||||
- Check go2rtc Web Interface: if you don't see any errors in the logs, try viewing the camera through go2rtc's web interface.
|
||||
- Navigate to port 1984 in your browser to access go2rtc's web interface.
|
||||
- If using Frigate through Home Assistant, enable the web interface at port 1984.
|
||||
- If using Docker, forward port 1984 before accessing the web interface.
|
||||
- Click `stream` for the specific camera to see if the camera's stream is being received.
|
||||
|
||||
- Check Video Codec:
|
||||
- If the camera stream works in go2rtc but not in your browser, the video codec might be unsupported.
|
||||
- If using H265, switch to H264. Refer to [video codec compatibility](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#codecs-madness) in go2rtc documentation.
|
||||
- If unable to switch from H265 to H264, or if the stream format is different (e.g., MJPEG), re-encode the video using [FFmpeg parameters](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-ffmpeg). It supports rotating and resizing video feeds and hardware acceleration. Keep in mind that transcoding video from one format to another is a resource intensive task and you may be better off using the built-in jsmpeg view.
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
- "ffmpeg:back#video=h264#hardware"
|
||||
```
|
||||
|
||||
- Switch to FFmpeg if needed:
|
||||
- Some camera streams may need to use the ffmpeg module in go2rtc. This has the downside of slower startup times, but has compatibility with more stream types.
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
```
|
||||
|
||||
- If you can see the video but do not have audio, this is most likely because your camera's audio stream codec is not AAC.
|
||||
- If possible, update your camera's audio settings to AAC in your camera's firmware.
|
||||
- If your cameras do not support AAC audio, you will need to tell go2rtc to re-encode the audio to AAC on demand if you want audio. This will use additional CPU and add some latency. To add AAC audio on demand, you can update your go2rtc config as follows:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
- "ffmpeg:back#audio=aac"
|
||||
```
|
||||
|
||||
If you need to convert **both** the audio and video streams, you can use the following:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
- "ffmpeg:back#video=h264#audio=aac#hardware"
|
||||
```
|
||||
|
||||
When using the ffmpeg module, you would add AAC audio like this:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- "ffmpeg:rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2#video=copy#audio=copy#audio=aac#hardware"
|
||||
```
|
||||
|
||||
:::warning
|
||||
|
||||
To access the go2rtc stream externally when utilizing the Frigate App (for
|
||||
instance through VLC), you must first enable the RTSP Restream port.
|
||||
You can do this by visiting the Frigate App configuration page within Home
|
||||
Assistant and revealing the hidden options under the "Show disabled ports"
|
||||
section.
|
||||
|
||||
:::
|
||||
|
||||
### Next steps
|
||||
|
||||
1. If the stream you added to go2rtc is also used by Frigate for the `record` or `detect` role, you can migrate your config to pull from the RTSP restream to reduce the number of connections to your camera as shown [here](/configuration/restream#reduce-connections-to-camera).
|
||||
2. You can [set up WebRTC](/configuration/live#webrtc-extra-configuration) if your camera supports two-way talk. Note that WebRTC only supports specific audio formats and may require opening ports on your router.
|
||||
3. If your camera supports two-way talk, you must configure your stream with `#backchannel=0` to prevent go2rtc from blocking other applications from accessing the camera's audio output. See [preventing go2rtc from blocking two-way audio](/configuration/restream#two-way-talk-restream) in the restream documentation.
|
||||
|
||||
## Homekit Configuration
|
||||
|
||||
To add camera streams to Homekit Frigate must be configured in docker to use `host` networking mode. Once that is done, you can use the go2rtc WebUI (accessed via port 1984, which is disabled by default) to share export a camera to Homekit. Any changes made will automatically be saved to `/config/go2rtc_homekit.yml`.
|
||||
@@ -144,7 +144,7 @@ At this point you should be able to start Frigate and a basic config will be cre
|
||||
|
||||
### Step 2: Add a camera
|
||||
|
||||
Click the **Add Camera** button in <NavPath path="Settings > Camera configuration > Management" /> to use the camera setup wizard to get your first camera added into Frigate.
|
||||
Click the **Add Camera** button in <NavPath path="Settings > Global configuration > Camera management" /> to use the camera setup wizard to get your first camera added into Frigate.
|
||||
|
||||
### Step 3: Configure hardware acceleration (recommended)
|
||||
|
||||
@@ -192,7 +192,7 @@ cameras:
|
||||
|
||||
### Step 4: Configure detectors
|
||||
|
||||
By default, Frigate will use a single CPU detector.
|
||||
By default, Frigate will use a single OpenVINO detector running on the CPU.
|
||||
|
||||
In many cases, the integrated graphics on Intel CPUs provides sufficient performance for typical Frigate setups. If you have an Intel processor, you can follow the configuration below.
|
||||
|
||||
@@ -204,8 +204,8 @@ You need to refer to **Configure hardware acceleration** above to enable the con
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
1. Navigate to <NavPath path="Settings > System > Detector hardware" /> and add a detector with **Type** `OpenVINO` and **Device** `GPU`
|
||||
2. Navigate to <NavPath path="Settings > System > Detection model" /> and configure the model settings for OpenVINO:
|
||||
1. Navigate to <NavPath path="Settings > System > Detectors and model" /> and add a detector with **Type** `OpenVINO` and **Device** `GPU`
|
||||
2. On the same page, in the **Custom Model** tab, configure the model settings for OpenVINO:
|
||||
|
||||
| Field | Value |
|
||||
| ---------------------------------------- | ------------------------------------------ |
|
||||
@@ -273,7 +273,7 @@ services:
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
Navigate to <NavPath path="Settings > System > Detector hardware" /> and add a detector with **Type** `EdgeTPU` and **Device** `usb`.
|
||||
Navigate to <NavPath path="Settings > System > Detectors and model" /> and add a detector with **Type** `EdgeTPU` and **Device** `usb`.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
@@ -301,7 +301,7 @@ cameras:
|
||||
|
||||
More details on available detectors can be found [here](../configuration/object_detectors.md).
|
||||
|
||||
Restart Frigate and you should start seeing detections for `person`. If you want to track other objects, they can be configured in <NavPath path="Settings > Global configuration > Objects" /> or via the [configuration file reference](../configuration/reference.md).
|
||||
Restart Frigate and you should start seeing detections for `person`. If you want to track other objects, they can be configured in <NavPath path="Settings > Global configuration > Objects" /> or via the [configuration file reference](../configuration/advanced/reference.md).
|
||||
|
||||
### Step 5: Setup motion masks
|
||||
|
||||
@@ -388,21 +388,20 @@ If you only plan to use Frigate for recording, it is still recommended to define
|
||||
|
||||
:::
|
||||
|
||||
By default, Frigate will retain video of all tracked objects for 10 days. The full set of options for recording can be found [here](../configuration/reference.md).
|
||||
By default, Frigate will retain video of all tracked objects for 10 days. The full set of options for recording can be found [here](../configuration/advanced/reference.md).
|
||||
|
||||
### Step 7: Complete config
|
||||
|
||||
At this point you have a complete config with basic functionality.
|
||||
|
||||
- View [common configuration examples](../configuration/index.md#common-configuration-examples) for a list of common configuration examples.
|
||||
- View [full config reference](../configuration/reference.md) for a complete list of configuration options.
|
||||
- View [common configuration examples](../configuration/config.md#common-configuration-examples) for a list of common configuration examples.
|
||||
- View [full config reference](../configuration/advanced/reference.md) for a complete list of configuration options.
|
||||
|
||||
### Follow up
|
||||
|
||||
Now that you have a working install, you can use the following documentation for additional features:
|
||||
|
||||
1. [Configuring go2rtc](configuring_go2rtc.md) - Additional live view options and RTSP relay
|
||||
2. [Zones](../configuration/zones.md)
|
||||
3. [Review](../configuration/review.md)
|
||||
4. [Masks](../configuration/masks.md)
|
||||
5. [Home Assistant Integration](../integrations/home-assistant.md) - Integrate with Home Assistant
|
||||
1. [Zones](../configuration/zones.md)
|
||||
2. [Review](../configuration/review.md)
|
||||
3. [Masks](../configuration/masks.md)
|
||||
4. [Home Assistant Integration](../integrations/home-assistant.md) - Integrate with Home Assistant
|
||||
|
||||
@@ -12,7 +12,7 @@ Before setting up a reverse proxy, check if any of the built-in functionality in
|
||||
|-|-|
|
||||
|TLS|Please see the `tls` [configuration option](../configuration/tls.md)|
|
||||
|Authentication|Please see the [authentication](../configuration/authentication.md) documentation|
|
||||
|IPv6|[Enabling IPv6](../configuration/advanced.md#enabling-ipv6)
|
||||
|IPv6|[Enabling IPv6](../configuration/advanced/system.md#enabling-ipv6)
|
||||
|
||||
**Note about TLS**
|
||||
When using a reverse proxy, the TLS session is usually terminated at the proxy, sending the internal request over plain HTTP. If this is the desired behavior, TLS must first be disabled in Frigate, or you will encounter an HTTP 400 error: "The plain HTTP request was sent to HTTPS port."
|
||||
|
||||
@@ -195,7 +195,7 @@ For clips to be castable to media devices, audio is required and may need to be
|
||||
|
||||
## Camera API
|
||||
|
||||
To disable a camera dynamically
|
||||
To turn a camera off (pauses Frigate's processing of the stream; does not persist across Frigate restarts; see [Camera state](/configuration/live#camera-state)):
|
||||
|
||||
```
|
||||
action: camera.turn_off
|
||||
@@ -204,7 +204,7 @@ target:
|
||||
entity_id: camera.back_deck_cam # your Frigate camera entity ID
|
||||
```
|
||||
|
||||
To enable a camera that has been disabled dynamically
|
||||
To turn a camera back on:
|
||||
|
||||
```
|
||||
action: camera.turn_on
|
||||
@@ -213,6 +213,12 @@ target:
|
||||
entity_id: camera.back_deck_cam # your Frigate camera entity ID
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
These actions toggle Frigate's runtime On/Off state. To permanently disable a camera, set its status to **Disabled** in **Settings → Camera Management** in the Frigate UI.
|
||||
|
||||
:::
|
||||
|
||||
## Notification API
|
||||
|
||||
Many people do not want to expose Frigate to the web, so the integration creates some public API endpoints that can be used for notifications.
|
||||
|
||||
@@ -306,7 +306,7 @@ Publishes the current health status of each role that is enabled (`audio`, `dete
|
||||
|
||||
- `online`: Stream is running and being processed
|
||||
- `offline`: Stream is offline and is being restarted
|
||||
- `disabled`: Camera is currently disabled
|
||||
- `disabled`: Camera is currently turned off (either at runtime via the `enabled/set` topic, or persistently via the configuration file). See [Camera state](/configuration/live#camera-state) for the distinction.
|
||||
|
||||
### `frigate/<camera_name>/<object_name>`
|
||||
|
||||
@@ -368,15 +368,15 @@ The published value is the detected state class name (e.g., `open`, `closed`, `o
|
||||
|
||||
### `frigate/<camera_name>/enabled/set`
|
||||
|
||||
Topic to turn Frigate's processing of a camera on and off. Expected values are `ON` and `OFF`.
|
||||
Topic to turn Frigate's processing of a camera on or off at runtime. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)). To permanently change the configured value, use **Settings → Global configuration → Camera management** in the Frigate UI. See [Camera state](/configuration/live#camera-state) for the difference between turning a camera off and disabling it.
|
||||
|
||||
### `frigate/<camera_name>/enabled/state`
|
||||
|
||||
Topic with current state of processing for a camera. Published values are `ON` and `OFF`.
|
||||
Topic with current runtime state of processing for a camera. Published values are `ON` and `OFF`.
|
||||
|
||||
### `frigate/<camera_name>/detect/set`
|
||||
|
||||
Topic to turn object detection for a camera on and off. Expected values are `ON` and `OFF`.
|
||||
Topic to turn object detection for a camera on and off. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)).
|
||||
|
||||
### `frigate/<camera_name>/detect/state`
|
||||
|
||||
@@ -384,7 +384,7 @@ Topic with current state of object detection for a camera. Published values are
|
||||
|
||||
### `frigate/<camera_name>/audio/set`
|
||||
|
||||
Topic to turn audio detection for a camera on and off. Expected values are `ON` and `OFF`.
|
||||
Topic to turn audio detection for a camera on and off. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)).
|
||||
|
||||
### `frigate/<camera_name>/audio/state`
|
||||
|
||||
@@ -392,7 +392,7 @@ Topic with current state of audio detection for a camera. Published values are `
|
||||
|
||||
### `frigate/<camera_name>/recordings/set`
|
||||
|
||||
Topic to turn recordings for a camera on and off. Expected values are `ON` and `OFF`.
|
||||
Topic to turn recordings for a camera on and off. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)).
|
||||
|
||||
### `frigate/<camera_name>/recordings/state`
|
||||
|
||||
@@ -400,7 +400,7 @@ Topic with current state of recordings for a camera. Published values are `ON` a
|
||||
|
||||
### `frigate/<camera_name>/snapshots/set`
|
||||
|
||||
Topic to turn snapshots for a camera on and off. Expected values are `ON` and `OFF`.
|
||||
Topic to turn snapshots for a camera on and off. Expected values are `ON` and `OFF`. The change is persisted across Frigate restarts (see [Runtime toggle persistence](/configuration/live#runtime-toggle-persistence)).
|
||||
|
||||
### `frigate/<camera_name>/snapshots/state`
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ id: plus
|
||||
title: Frigate+
|
||||
---
|
||||
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
For more information about how to use Frigate+ to improve your model, see the [Frigate+ docs](/plus/).
|
||||
|
||||
:::info
|
||||
@@ -57,7 +59,7 @@ You can view all of your submitted images at [https://plus.frigate.video](https:
|
||||
|
||||
Once you have [requested your first model](../plus/first_model.md) and gotten your own model ID, it can be used with a special model path. No other information needs to be configured for Frigate+ models because it fetches the remaining config from Frigate+ automatically.
|
||||
|
||||
You can either choose the new model from the Frigate+ pane in the Settings page of the Frigate UI, or manually set the model at the root level in your config:
|
||||
You can either choose the new model from the <NavPath path="Settings > System > Detectors and model" /> pane in the Frigate UI (the **Frigate+ Model** tab), or manually set the model at the root level in your config:
|
||||
|
||||
```yaml
|
||||
detectors: ...
|
||||
|
||||
@@ -39,6 +39,10 @@ This is a fork (with fixed errors and new features) of [original Double Take](ht
|
||||
|
||||
[Frigate telegram](https://github.com/OldTyT/frigate-telegram) makes it possible to send events from Frigate to Telegram. Events are sent as a message with a text description, video, and thumbnail.
|
||||
|
||||
## [kiosk-monitor](https://github.com/extremeshok/kiosk-monitor)
|
||||
|
||||
[kiosk-monitor](https://github.com/extremeshok/kiosk-monitor) is a Raspberry Pi watchdog that runs Chromium fullscreen on a Frigate dashboard (optionally with VLC on a second monitor for an RTSP camera stream), auto-restarts on frozen screens or unreachable URLs, and ships a Birdseye-aware Chromium helper that auto-sizes the grid to the display.
|
||||
|
||||
## [Periscope](https://github.com/maksz42/periscope)
|
||||
|
||||
[Periscope](https://github.com/maksz42/periscope) is a lightweight Android app that turns old devices into live viewers for Frigate. It works on Android 2.2 and above, including Android TV. It supports authentication and HTTPS.
|
||||
|
||||
@@ -24,7 +24,7 @@ Video decoding is one of the most CPU-intensive tasks in Frigate. While an AI ac
|
||||
|
||||
### Configuration
|
||||
|
||||
Frigate provides preset configurations for common hardware acceleration scenarios. Set up `hwaccel_args` based on your hardware in your [configuration](../configuration/reference) as described in the [getting started guide](../guides/getting_started).
|
||||
Frigate provides preset configurations for common hardware acceleration scenarios. Set up `hwaccel_args` based on your hardware in your [configuration](../configuration/advanced/reference) as described in the [getting started guide](../guides/getting_started).
|
||||
|
||||
### Troubleshooting Hardware Acceleration
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ id: dummy-camera
|
||||
title: Analyzing Object Detection
|
||||
---
|
||||
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
Frigate provides several tools for investigating object detection and tracking behavior: reviewing recorded detections through the UI, using the built-in Debug Replay feature, and manually setting up a dummy camera for advanced scenarios.
|
||||
|
||||
## Reviewing Detections in the UI
|
||||
@@ -37,6 +39,8 @@ The per-clip variation is typically quite low and is mostly an artifact of keyfr
|
||||
|
||||
Debug Replay lets you re-run Frigate's detection pipeline against a section of recorded video without manually configuring a dummy camera. It automatically extracts the recording, creates a temporary camera with the same detection settings as the original, and loops the clip through the pipeline so you can observe detections in real time.
|
||||
|
||||
Debug Replay isn't intended to be a one-stop pane for all Frigate diagnostics or a comprehensive debugging environment for every Frigate feature. It merely makes it easier to spin up a "dummy camera" and perform some common adjustments in real-time. You'll still need to use the normal tools (logs, an MQTT client, etc) to debug your feature.
|
||||
|
||||
### When to use
|
||||
|
||||
- Reproducing a detection or tracking issue from a specific time range
|
||||
@@ -49,11 +53,25 @@ Only one replay session can be active at a time. If a session is already running
|
||||
|
||||
:::
|
||||
|
||||
### Starting Debug Replay
|
||||
|
||||
Debug Replay can be started from several places in the UI. The starting point determines the time range that gets replayed.
|
||||
|
||||
- **History — Actions menu.** Navigate to <NavPath path="History > {camera}" />, open the **Actions** menu in the toolbar, and choose **Debug Replay**. From here you can pick a preset (**Last 1 Minute**, **Last 5 Minutes**), select a range directly on the timeline with **From Timeline**, or enter exact start and end times with **Custom**. This is the most flexible option and the best choice when you want to add padding around a detection. On mobile, the same options appear in the Actions drawer.
|
||||
- **History — Detail Stream event menu.** While viewing a review item in the Detail Stream, open the menu on a tracked object's event card and choose **Debug Replay**. The replay range is set automatically to that object's start and end times.
|
||||
- **Explore — search result menu.** From an Explore card, open the kebab menu and choose **Debug Replay**. The range is taken from the tracked object's lifecycle.
|
||||
- **Explore — Tracking Details Actions menu.** Open a tracked object's **Tracking Details** dialog, then choose **Debug Replay** from the Actions menu. Same automatic range as the search result menu.
|
||||
- **Exports — export card menu.** From <NavPath path="Exports" />, open the menu on an export and choose **Debug Replay** to loop the exported clip through the detection pipeline for the camera it was exported from.
|
||||
|
||||
The Detail Stream, Explore, and Exports entry points use the underlying recording or export's bounds with a small amount of padding. This can be convenient for quick checks, but if a detection is short or you want extra "settle" time for motion and the detector, start the replay from the History Actions menu instead and widen the range manually.
|
||||
|
||||
### Variables to consider
|
||||
|
||||
- The replay will not always produce identical results to the original run. Different frames may be selected on replay, which can change detections and tracking.
|
||||
- Motion detection depends on the exact frames used; small frame shifts can change motion regions and therefore what gets passed to the detector.
|
||||
- Object detection is not fully deterministic: models and post-processing can yield slightly different results across runs.
|
||||
- In cases where a detection is short and a replay may only be a small number of frames, it is recommended to manually add some padding before and after the detection so that the motion and object detectors have time to settle into the scene. Rather than starting Debug Replay from Explore, navigate to History for your camera, choose Debug Replay from the Actions menu, and click the "From Timeline" or "Custom" option.
|
||||
- The replay camera inherits the source camera's zones. Any automations that trigger on those zone names will fire for the replay camera as well. This can be helpful when debugging zone behavior, but may be unexpected. You can add a condition on the source camera's name in your automation if you want to exclude replay triggers.
|
||||
|
||||
Treat the replay as a close approximation rather than an exact reproduction. Run multiple loops and examine the debug overlays and logs to understand the behavior.
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ If you see repeated "On connect called" messages in your logs, check for another
|
||||
|
||||
### Error: Database Is Locked
|
||||
|
||||
SQLite does not work well on a network share, if the `/media` folder is mapped to a network share then [this guide](../configuration/advanced.md#database) should be used to move the database to a location on the internal drive.
|
||||
SQLite does not work well on a network share, if the `/media` folder is mapped to a network share then [this guide](../configuration/advanced/system.md#database) should be used to move the database to a location on the internal drive.
|
||||
|
||||
### Unable to publish to MQTT: client is not connected
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
---
|
||||
id: go2rtc
|
||||
title: Troubleshooting go2rtc
|
||||
---
|
||||
|
||||
import ConfigTabs from "@site/src/components/ConfigTabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
This page covers common problems with the bundled [go2rtc](/configuration/go2rtc) and how to resolve them, whether your cameras were added with the setup wizard or configured by hand.
|
||||
|
||||
When a stream won't play or behaves oddly, the most important first step is to figure out **where** in the pipeline it breaks. Frigate's live view is a chain — _camera → go2rtc → your browser_ — and each stage fails for different reasons. Work through the checks below in order, then jump to the matching problem category.
|
||||
|
||||
## Start by isolating the problem
|
||||
|
||||
### 1. Read the go2rtc logs
|
||||
|
||||
Access the go2rtc logs in the Frigate UI under <NavPath path="System Logs" /> in the sidebar (select the **go2rtc** tab). If go2rtc cannot connect to your camera you will usually see a clear error here — `401 Unauthorized` (bad or incorrectly encoded credentials), `Connection refused` / `timeout` (wrong IP, port, or the camera is at its connection limit), or `404 Not Found` (wrong RTSP path, or the referenced stream name does not exist).
|
||||
|
||||
### 2. Test the stream in the go2rtc web interface
|
||||
|
||||
If the logs look clean, open go2rtc's own web interface on port `1984`. This is the single most useful diagnostic, because it takes Frigate's UI out of the equation entirely.
|
||||
|
||||
- If using Frigate through Home Assistant, enable the web interface at port `1984` (it is disabled by default — see [Home Assistant ports](#home-assistant-and-port-access)).
|
||||
- If using Docker, forward port `1984` before accessing the web interface.
|
||||
|
||||
Open the stream page for your camera (`http://<frigate_host>:1984/stream.html?src=back`) and try each player link:
|
||||
|
||||
- **If nothing plays here**, the problem is between the camera and go2rtc (codec, credentials, or transport), _not_ your browser. Fix it at the source before touching anything in Frigate.
|
||||
- **If a player works here but Frigate's live view does not**, the problem is browser/codec related — compare the **MSE** and **WebRTC** links. Frigate prefers MSE and only attempts WebRTC when MSE fails (or for two-way talk). If `mode=mse` plays but `mode=webrtc` does not, you have a [WebRTC codec problem](#webrtc-and-two-way-talk); if neither plays, your browser cannot decode the codec (commonly H.265 — see [H.265 / HEVC cameras](#h265--hevc-cameras)).
|
||||
|
||||
### 3. Inspect the negotiated codecs
|
||||
|
||||
You can view detailed stream info — including the exact video and audio codecs go2rtc negotiated with the camera — at `http://frigate_ip:5000/api/go2rtc/streams` (or `http://frigate_ip:5000/api/go2rtc/streams/back` for a single camera). This is the authoritative answer to "what is my camera actually sending?" and is far more reliable than guessing from the camera's web UI. It also shows whether the audio track is `sendonly`/`recvonly`, which matters for [two-way talk](#webrtc-and-two-way-talk).
|
||||
|
||||
### 4. Fix the codec with the FFmpeg module
|
||||
|
||||
If the camera plays in go2rtc but not in your browser, the video or audio codec is unsupported. Browsers can reliably play **H.264** video and **AAC** audio; many cannot play H.265/HEVC, and some camera audio (G.711/PCM, MJPEG containers, etc.) is not playable at all. The fix is to have go2rtc re-encode the stream on demand using its FFmpeg module.
|
||||
|
||||
In the Frigate UI this is the **Use compatibility mode (ffmpeg)** toggle on a stream source; in YAML it is the `ffmpeg:` prefix on the source URL.
|
||||
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
1. Navigate to <NavPath path="Settings > System > go2rtc Streams" /> and expand your camera's stream.
|
||||
2. On the source you want to convert, click the **Use compatibility mode (ffmpeg)** button (the sliders icon next to the URL). This routes the source through go2rtc's FFmpeg module and reveals the transcoding options.
|
||||
3. Set **Video** to **Transcode to H.264** if your browser can't play the camera's video codec (e.g. H.265). Leave it on **Copy** to pass the video through untouched — this is much cheaper and should be your default whenever only the audio needs converting.
|
||||
4. Set **Audio** to **Transcode to AAC** (for MSE) or **Transcode to Opus** (for WebRTC) if the camera's audio codec is unsupported. Leave it on **Copy** to keep the original, or **Exclude** to drop audio entirely.
|
||||
5. When transcoding **video**, set **Hardware acceleration** to **Automatic (recommended)** so the encode runs on your GPU instead of the CPU. See [hardware-accelerated transcoding](#hardware-accelerated-transcoding-with-ffmpeg-8) for an important FFmpeg 8 caveat.
|
||||
6. **Save** the section, then reload the live view.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
# transcode video to H.264 on the GPU; only needed if the browser can't play the source codec
|
||||
- "ffmpeg:back#video=h264#hardware"
|
||||
```
|
||||
|
||||
To convert audio only (leaving video untouched), or to convert both:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2
|
||||
- "ffmpeg:back#audio=aac" # audio only — preferred when the video already plays
|
||||
# or, to convert both video and audio:
|
||||
# - "ffmpeg:back#video=h264#audio=aac#hardware"
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
:::warning
|
||||
|
||||
The `#`-modifiers (`#video=`, `#audio=`, `#hardware`, `#backchannel=0`, …) **only take effect on a source that is prefixed with `ffmpeg:`**. Adding them to a bare `rtsp://…#audio=opus` source does nothing — go2rtc ignores them. Likewise, when a source references another stream by name (e.g. `ffmpeg:back#audio=aac`), the name must match the stream key **exactly** (it is case sensitive), or the transcode is silently never produced. This is the single most common configuration mistake. In the Frigate UI, the **Use compatibility mode (ffmpeg)** toggle adds the `ffmpeg:` prefix for you.
|
||||
|
||||
:::
|
||||
|
||||
Transcoding video is resource intensive. Always prefer `#video=copy` (the **Copy** option) and only convert the track that is actually unsupported. If you must transcode video and have no hardware encoder available, the built-in jsmpeg view may be the better option.
|
||||
|
||||
## Live view is black, buffering, or stuck in "low-bandwidth mode"
|
||||
|
||||
When the live view shows a black screen, spins forever, or repeatedly drops to the lower-quality jsmpeg player ("low-bandwidth mode"), the stream almost always contains something the browser cannot decode over MSE — usually H.265 video or a non-AAC audio track. Confirm this in the go2rtc web UI (port `1984`): if MSE won't play there, Frigate can't play it either, since it uses the same pipeline.
|
||||
|
||||
The fix is to produce an **H.264 + AAC** stream, either by changing your camera's firmware codecs or by transcoding in go2rtc (see [Fix the codec with the FFmpeg module](#4-fix-the-codec-with-the-ffmpeg-module)). A few other things worth checking:
|
||||
|
||||
- **Set the camera's I-frame (keyframe) interval to match its frame rate** (or "1x" on Reolink), and avoid "smart"/"+" codecs like _H.264+_ or _H.265+_. A long keyframe interval delays the first decodable frame past Frigate's startup timeout, which forces the fallback to jsmpeg. See [camera settings recommendations](/configuration/live#camera-settings-recommendations).
|
||||
- **A spinner that never clears, even though video plays in VLC**, is often an unplayable _audio_ track stalling playback. Drop or transcode the audio (see below).
|
||||
- **Remote/VPN viewing that buffers** while the LAN is fine is usually latency/jitter exceeding MSE's startup buffer — set up [WebRTC](/configuration/live#webrtc-extra-configuration), which drops late frames instead of buffering.
|
||||
|
||||
The general live-view behavior (smart streaming, the MSE → WebRTC → jsmpeg fallback chain, and how to read browser console errors) is documented in detail in the [Live view FAQ](/configuration/live#live-view-faq).
|
||||
|
||||
## H.265 / HEVC cameras
|
||||
|
||||
H.265/HEVC playback in the browser is unreliable and version-dependent. WebRTC does not support H.265 on some browsers, and MSE/HEVC support varies by browser, OS, and whether a hardware decoder is present. An H.265 stream that plays fine in VLC, the go2rtc web UI, and Frigate's recordings can still be blank in a live view.
|
||||
|
||||
For dependable live viewing, use **H.264** for the stream the live view consumes:
|
||||
|
||||
- Point the live view at the camera's H.264 **substream** and keep the H.265 main stream for recording only, or
|
||||
- Transcode H.265 → H.264 in go2rtc with the FFmpeg module and `#hardware` (software HEVC transcoding is very CPU heavy).
|
||||
|
||||
Treat browser HEVC playback as best-effort. See also [H.265 cameras via Safari](/configuration/camera_specific#h265-cameras-via-safari).
|
||||
|
||||
## No audio in Live view
|
||||
|
||||
Live view audio has strict codec requirements that differ by player: **MSE requires AAC, PCMA, or PCMU**, and **WebRTC requires Opus, PCMA, or PCMU**. Many cameras default to a codec outside these sets (or to PCM/G.711), so the player loads video only and no audio control appears.
|
||||
|
||||
The most robust approach is to provide both an AAC track (for MSE) and an Opus track (for WebRTC) on the same stream by transcoding audio with the FFmpeg module while copying the video:
|
||||
|
||||
<ConfigTabs>
|
||||
<TabItem value="ui">
|
||||
|
||||
1. Navigate to <NavPath path="Settings > System > go2rtc Streams" /> and expand the camera's stream.
|
||||
2. Add a second **Source** that references the stream by name (e.g. the URL `ffmpeg:back`), enable **Use compatibility mode (ffmpeg)**, and set **Audio** to **Transcode to Opus** for WebRTC support.
|
||||
3. Keep the original source as **Source 1** so MSE can use the camera's AAC (or transcode the first source's audio to AAC if the camera doesn't provide it).
|
||||
4. **Save** the section.
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml">
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- rtsp://user:password@10.0.10.10:554/cam/realmonitor?channel=1&subtype=2 # video + AAC for MSE
|
||||
- "ffmpeg:back#audio=opus" # adds an Opus track for WebRTC
|
||||
```
|
||||
|
||||
If the camera's native audio isn't AAC either, transcode both:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
streams:
|
||||
back:
|
||||
- "ffmpeg:rtsp://user:password@10.0.10.10:554/live0#video=copy#audio=aac" # video copy + AAC for MSE
|
||||
- "ffmpeg:back#audio=opus" # Opus for WebRTC
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</ConfigTabs>
|
||||
|
||||
Setting the camera firmware to AAC (and H.264) avoids transcoding entirely and is always preferable when the camera supports it. For more detail and examples, see [Audio Support](/configuration/live#audio-support).
|
||||
|
||||
## WebRTC and two-way talk
|
||||
|
||||
WebRTC is only attempted when MSE fails or when using a camera's two-way talk feature; the "All Cameras" dashboard never uses it. When it doesn't work, the cause is almost always one of:
|
||||
|
||||
- **Codec mismatch** — WebRTC cannot carry H.265 or AAC. The stream backing the WebRTC view must provide Opus (or PCMA/PCMU) audio and H.264 video. Add an `ffmpeg:back#audio=opus` source as shown above.
|
||||
- **Port `8555` not reachable, or no candidates set** — WebRTC needs port `8555` (both TCP and UDP) open and a reachable candidate advertised. On Docker installs running on a custom/overlay network, go2rtc may advertise unreachable container IPs as ICE candidates; setting `webrtc.filters.candidates: []` and supplying only your host's LAN IP resolves this. See [WebRTC extra configuration](/configuration/live#webrtc-extra-configuration).
|
||||
- **Two-way talk** additionally requires a secure context (HTTPS or the authenticated port `8971`, because browsers block microphone access on plain HTTP). The camera's RTSP backchannel must also be handled correctly — go2rtc seizes the backchannel by default, which blocks two-way audio for other consumers and can inject static. Disable it on the primary stream with `#backchannel=0` and use a separate dedicated stream for talk, as documented in [preventing go2rtc from blocking two-way audio](/configuration/restream#two-way-talk-restream).
|
||||
|
||||
## High CPU usage
|
||||
|
||||
If go2rtc is using a lot of CPU, it is almost always transcoding in software. An FFmpeg source with a codec modifier like `#video=h264` or `#audio=aac` but **no** `#hardware` re-encodes on the CPU. (Frigate's `ffmpeg.hwaccel_args` only applies to Frigate's own detect/record processes — it does _not_ accelerate go2rtc's transcodes.)
|
||||
|
||||
To keep CPU usage down:
|
||||
|
||||
- Only transcode the track that is genuinely unsupported, and use `#video=copy` to pass video through untouched whenever possible.
|
||||
- When you must transcode video, always add `#hardware` (the **Automatic** hardware option in the UI) so the encode runs on the GPU. Note the [FFmpeg 8 device requirement](#hardware-accelerated-transcoding-with-ffmpeg-8) below.
|
||||
- Don't restream a high-resolution main stream just to feed the live view — even with `#video=copy`, muxing a 4K/8MP+ stream is inherently expensive. Use the camera's lower-resolution substream for live and detect, and let Frigate pull the main stream directly for recording.
|
||||
|
||||
## Connection, authentication, and complex passwords
|
||||
|
||||
If go2rtc logs `401 Unauthorized` for a URL that works in VLC, the password almost certainly contains reserved URL characters. **Frigate URL-encodes passwords for its own `cameras.ffmpeg.inputs`, but it does not touch what you write under `go2rtc.streams`** — go2rtc parses that URL itself. You must URL-encode special characters yourself in the `go2rtc.streams` section (`@` → `%40`, `#` → `%23`, `?` → `%3F`, `%` → `%25`, etc.).
|
||||
|
||||
Note the asymmetry: under `cameras.ffmpeg.inputs` you should use the **raw** password (Frigate encodes it for you) — pre-encoding it there causes a double-encode and fails. See [Handling Complex Passwords](/configuration/restream#handling-complex-passwords).
|
||||
|
||||
Repeated `401`/`Connection refused` errors can also mean the camera hit its **concurrent connection limit** or triggered a login lockout. Routing all roles through a single [RTSP restream](/configuration/restream#reduce-connections-to-camera) means the camera only ever sees one connection from go2rtc.
|
||||
|
||||
## Stream names must match everywhere
|
||||
|
||||
A surprising number of "the better live options aren't available" or `404 Not Found` problems come down to a name mismatch. The same string must be used consistently:
|
||||
|
||||
- the **go2rtc stream key** (`go2rtc.streams.<name>`),
|
||||
- any `ffmpeg:<name>#…` source that references it,
|
||||
- the camera's restream input path (`rtsp://127.0.0.1:8554/<name>`), and
|
||||
- the camera name itself (so Frigate auto-maps it for MSE/WebRTC) — or an explicit `live -> streams` mapping pointing at the go2rtc stream **name** (never a path).
|
||||
|
||||
If you rename or remove a go2rtc stream while experimenting and the live stream selector then shows a blank entry, clear your browser's site data for the Frigate URL — the selected stream is cached per-device in local storage.
|
||||
|
||||
## Camera-specific behavior
|
||||
|
||||
Several camera brands have well-known quirks with go2rtc. Rather than repeat them here, see the [camera-specific configuration](/configuration/camera_specific) page, which covers them in detail. The highlights:
|
||||
|
||||
- **Reolink** — RTSP is unreliable on many models; the **http-flv** stream through the FFmpeg module is recommended, and you must enable HTTP/RTMP in the camera and **reboot** it. 6MP+ models stream H.265 over http-flv-enhanced, which requires FFmpeg 8.0. See [Reolink Cameras](/configuration/camera_specific#reolink-cameras).
|
||||
- **TP-Link Tapo** — use go2rtc's native `tapo://` source for stability and two-way audio; a stale RTSP credential can often be revived by clicking play once in the go2rtc web UI.
|
||||
- **Ubiquiti/UniFi Protect** — use the `rtspx://` scheme (not `rtsps://…?enableSrtp`).
|
||||
- **Amcrest/Dahua** — use the `/cam/realmonitor?channel=1&subtype=N` scheme, where `subtype=0` is the main stream. See [Amcrest & Dahua](/configuration/camera_specific#amcrest--dahua).
|
||||
|
||||
## Non-RTSP sources and the FFmpeg module
|
||||
|
||||
go2rtc's native zero-copy handling only supports well-formed RTSP H.264/H.265. Anything else — MJPEG, HTTP/HTTP-FLV, RTMP, or unusual codecs — must be handed to the FFmpeg module by prefixing the source with `ffmpeg:`. This is also necessary for some camera streams to be parsed at all, at the cost of slightly slower startup. MJPEG and other non-H.264 sources additionally need `#video=h264` (with `#hardware`) before they can be used for the `record`, `detect`, or restream roles. See [MJPEG Cameras](/configuration/camera_specific#mjpeg-cameras) for a complete example.
|
||||
|
||||
## Hardware-accelerated transcoding with FFmpeg 8
|
||||
|
||||
Frigate 0.18 ships **FFmpeg 8.0** as the default, and FFmpeg 8 is stricter about hardware-accelerated filtering than earlier versions. Whenever go2rtc transcodes video with hardware acceleration (any source using `#hardware`, `#hardware=vaapi`, or the **Automatic** hardware option in the UI), it builds a filter chain that uploads frames to the GPU with the `hwupload` filter. FFmpeg 8 now refuses to do this unless it is told **which device** to use — earlier versions selected one automatically. The result is that an otherwise-working transcode fails to start, the live view never loads, and go2rtc logs:
|
||||
|
||||
```
|
||||
[hwupload] A hardware device reference is required to upload frames to.
|
||||
[AVFilterGraph] Error initializing filters
|
||||
Error opening output files: Invalid argument
|
||||
```
|
||||
|
||||
The fix is to tell go2rtc's bundled FFmpeg which hardware device to use via the `go2rtc -> ffmpeg -> global` option. For **VAAPI**-based acceleration — which covers most Intel and AMD GPUs, and is what go2rtc selects automatically on that hardware — point it at your render device:
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
ffmpeg:
|
||||
global: "-vaapi_device /dev/dri/renderD128"
|
||||
streams:
|
||||
back:
|
||||
- "ffmpeg:rtsp://user:password@10.0.10.10:554/live0#video=h264#hardware"
|
||||
```
|
||||
|
||||
`/dev/dri/renderD128` is the usual render node; on a system with more than one GPU you may need `renderD129` (or higher), and the device must be passed into the container (e.g. `devices: - /dev/dri:/dev/dri` in Docker Compose).
|
||||
|
||||
If you use a **different hardware acceleration backend**, you will likely need to specify its device in the same way, using the option that matches that backend instead of `-vaapi_device`. See the [go2rtc FFmpeg source documentation](https://github.com/AlexxIT/go2rtc/tree/v1.9.13#source-ffmpeg) and the upstream report ([go2rtc issue #1984](https://github.com/AlexxIT/go2rtc/issues/1984)) for background and other examples.
|
||||
|
||||
:::tip
|
||||
|
||||
If you don't transcode in go2rtc with hardware acceleration, this does not affect you. If you want to avoid the change entirely, you can pin Frigate (and the go2rtc it bundles) back to FFmpeg 7.0 by setting `ffmpeg -> path: "7.0"` in your config.
|
||||
|
||||
:::
|
||||
|
||||
## Home Assistant and port access
|
||||
|
||||
When running Frigate as a Home Assistant add-on, the go2rtc API (port `1984`), the RTSP restream (port `8554`), and WebRTC (port `8555`) are **disabled and hidden by default**. To use them — for example to reach the go2rtc web interface for troubleshooting, or to open a go2rtc stream externally in an app like VLC — go to <NavPath path="Settings > Add-ons > Frigate > Configuration > Network" />, click **Show disabled ports**, enable the port you need, and save. Use the host's IP address rather than an mDNS name like `homeassistant.local`.
|
||||
|
||||
If live view works in the Frigate UI but not in Home Assistant, the most common cause is the go2rtc stream name not matching the camera name — name the primary go2rtc stream exactly like the camera, or add a `live -> streams` mapping, so the integration can resolve the restream.
|
||||
@@ -121,6 +121,12 @@ If segments are only ~1 second instead of ~10 seconds, the camera is sending cor
|
||||
- **Changing codec, bitrate, or resolution mid-stream** — Any encoding changes during an active stream can cause unpredictable segment splitting.
|
||||
- **Camera firmware bugs** — Check for firmware updates from your camera manufacturer.
|
||||
|
||||
:::tip
|
||||
|
||||
You don't have to run `ffprobe` by hand to catch this. Open a camera's **Camera Probe Info** dialog (the info icon on the System → Metrics → Cameras page) and check the **Keyframe analysis** section. It probes the record stream and flags sparse or variable keyframes, which is what smart/"+" codecs (H.264+/H.265+) and long keyframe intervals produce.
|
||||
|
||||
:::
|
||||
|
||||
### Step 4: Check for a stuck detector
|
||||
|
||||
If the detect stream is not processing frames, segments will accumulate. Common causes:
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
id: explore
|
||||
title: Explore
|
||||
---
|
||||
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
**Explore** is where you browse and search every **tracked object** Frigate has saved. By default it groups recent objects by label; when [Semantic Search](/configuration/semantic_search) is enabled, you can also search by natural-language description or visual similarity. Selecting any object opens a detail pane with its snapshot, lifecycle, and metadata.
|
||||
|
||||
This page describes how to _use_ the Explore view. For how the underlying features are _configured_, see [Semantic Search](/configuration/semantic_search) and [Generative AI descriptions](/configuration/genai/genai_objects).
|
||||
|
||||
## Browsing tracked objects
|
||||
|
||||
The default view shows your most recent tracked objects grouped into rows by label — _Person_, _Car_, _Dog_, and so on — each row labeled with the object type and a count. The arrow at the end of a row opens the full, filterable grid for that label.
|
||||
|
||||
Clicking a thumbnail opens its [detail dialog](#tracked-object-details); right-clicking or long-pressing a thumbnail opens an [actions menu](#actions-and-bulk-selection). You can switch to a denser grid layout and adjust the number of columns from the view's settings.
|
||||
|
||||
## Searching
|
||||
|
||||
When [Semantic Search](/configuration/semantic_search) is enabled, a search bar appears that combines two things in one input:
|
||||
|
||||
- **Natural-language search** — type a free-text query and press Enter to run a semantic search over your tracked objects.
|
||||
- **Filter tokens** — type a `key:` to get suggestions, then a value, to add a structured filter. Each filter becomes a removable chip, and you can chain several together.
|
||||
|
||||
You can save a search with the star icon and reload it later, and clear everything with the clear-search icon. A help popover explains the token syntax, for example:
|
||||
|
||||
```
|
||||
cameras:front_door label:person before:01012024 time_range:3:00PM-4:00PM
|
||||
```
|
||||
|
||||
### Filter reference
|
||||
|
||||
The most common filter tokens are:
|
||||
|
||||
| Filter | Description |
|
||||
| ---------------------------- | ---------------------------------------------------------------------------------- |
|
||||
| **Cameras** | Limit to one or more cameras. |
|
||||
| **Labels** | Object labels (person, car, etc.). |
|
||||
| **Sub Labels** | Recognized sub labels (e.g. a recognized face or name). |
|
||||
| **Attributes** | Classification attributes applied to the object. |
|
||||
| **Recognized License Plate** | Match a recognized plate. |
|
||||
| **Zones** | Objects that entered specific zones. |
|
||||
| **Before / After** | Restrict to a date range. |
|
||||
| **Time Range** | Restrict to a time of day (`HH:MM-HH:MM`). |
|
||||
| **Min / Max Score** | Restrict by the object's confidence score. |
|
||||
| **Min / Max Speed** | Restrict by estimated speed (when speed estimation is configured). |
|
||||
| **Has Snapshot / Has Clip** | Only objects that saved a snapshot or recording. |
|
||||
| **Submitted to Frigate+** | Only objects already submitted (when Frigate+ is enabled). |
|
||||
| **Search Type** | Whether semantic search matches the object's **Thumbnail** or its **Description**. |
|
||||
|
||||
### Sorting
|
||||
|
||||
When a filter or search is active, a **Sort** control lets you order results by **date**, **object score**, or **estimated speed** (ascending or descending). When a semantic query or similarity search is active, results can also be ordered by **relevance**.
|
||||
|
||||
### Thumbnail and description search
|
||||
|
||||
- The **Search Type** setting controls whether a text query is matched against each object's **thumbnail** or its **description**. Each result indicates which one it matched and the confidence.
|
||||
|
||||
Natural-language search, thumbnail search, and description search all require [Semantic Search](/configuration/semantic_search) to be enabled.
|
||||
|
||||
## Tracked Object Details
|
||||
|
||||
Selecting an object opens the **Tracked Object Details** dialog. Use the arrows (or the left/right keys) to step to the previous or next object. The dialog has two tabs:
|
||||
|
||||
- **Snapshot** or **Thumbnail** — the saved snapshot (or thumbnail).
|
||||
- **Tracking Details** — the object's lifecycle, available when the object has a recording. It lists each significant moment (detected, entered a zone, became active or stationary, left, and so on); clicking a moment plays that part of the recording with the bounding box overlaid. A settings popover lets you show all zones and adjust the annotation offset.
|
||||
|
||||
The details pane shows the object's **label**, **scores**, **camera**, **timestamp**, estimated **speed**, any **recognized license plate** and **classification attributes**, and its **description**. Admins can edit the sub label, license plate, and attributes inline.
|
||||
|
||||
The **description** can be edited by hand, and — when [Generative AI descriptions](/configuration/genai/genai_objects) are enabled and the object's lifecycle has ended — regenerated from the snapshot or from thumbnails. For `speech` objects, a **Transcribe** action is available when audio transcription is enabled. When [Frigate+](/integrations/plus) is enabled, admins can submit a snapshot to improve their model directly from this pane.
|
||||
|
||||
## Actions and bulk selection
|
||||
|
||||
Right-clicking or long-pressing an object (in the grid or its thumbnail) opens an actions menu with options to **download** the video, snapshot, or a clean snapshot; **view tracking details**; **find similar**; **add a trigger**; **view in History**; and **delete the tracked object**.
|
||||
|
||||
:::note
|
||||
|
||||
Deleting a tracked object removes its snapshot, embeddings, and tracking-details entries, but the recorded footage of that object in [History](/usage/history) is **not** deleted.
|
||||
|
||||
:::
|
||||
|
||||
To act on many objects at once, Ctrl/Cmd-click or right-click to start a selection (selected tiles gain a blue ring), then use the toolbar to select all, clear the selection, or delete (admins).
|
||||
|
||||
## Semantic Search - Usage and best practices {#usage-and-best-practices}
|
||||
|
||||
1. Semantic Search is used in conjunction with the other filters available on the Explore page. Use a combination of traditional filtering and Semantic Search for the best results.
|
||||
2. Use the thumbnail search type when searching for particular objects in the scene. Use the description search type when attempting to discern the intent of your object.
|
||||
3. Because of how the AI models Frigate uses have been trained, the comparison between text and image embedding distances generally means that with multi-modal (`thumbnail` and `description`) searches, results matching `description` will appear first, even if a `thumbnail` embedding may be a better match. Play with the "Search Type" setting to help find what you are looking for. Note that if you are generating descriptions for specific objects or zones only, this may cause search results to prioritize the objects with descriptions even if the the ones without them are more relevant.
|
||||
4. Make your search language and tone closely match exactly what you're looking for. If you are using thumbnail search, **phrase your query as an image caption**. Searching for "red car" may not work as well as "red sedan driving down a residential street on a sunny day".
|
||||
5. Semantic search on thumbnails tends to return better results when matching large subjects that take up most of the frame. Small things like "cat" tend to not work well.
|
||||
6. Experiment! Find a tracked object you want to test and start typing keywords and phrases to see what works for you.
|
||||
|
||||
## Triggers
|
||||
|
||||
From an object's actions menu, **Add trigger** sets up a per-camera trigger that uses Semantic Search to automate an action (a notification, sub label, or attribute) whenever a similar object appears. Triggers require Semantic Search and are managed under <NavPath path="Settings > Enrichments > Triggers" />. See [Triggers](/configuration/semantic_search#triggers) for full configuration and best practices.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
id: exports
|
||||
title: Exports
|
||||
---
|
||||
|
||||
**Exports** are how you keep a specific piece of footage permanently.
|
||||
|
||||
Frigate's recordings are governed by your [retention settings](/configuration/record): once footage ages past its retention window — or, depending on your configuration, once it is only kept where motion, alerts, or detections occurred — it is deleted to free up disk space. An **export** saves a copy of a chosen time range to a separate location that is **never removed by retention**, so it stays available until you delete it yourself.
|
||||
|
||||
This is the answer to the common question _"how do I stop Frigate from deleting an important clip?"_ Instead of increasing retention for an entire camera (which uses far more storage to protect a single moment), export just the footage you want to keep.
|
||||
|
||||
:::tip
|
||||
|
||||
Exports are stored under `/media/frigate/exports`, separate from your recordings, and are not counted against or removed by recording retention. They remain on disk until you delete them, so be aware that they accumulate over time.
|
||||
|
||||
:::
|
||||
|
||||
## Creating an export
|
||||
|
||||
There are a few ways to create an export:
|
||||
|
||||
- **From Review** — select (right click or long-press) an individual review item directly, and choose Export from the header menu. You can also select multiple review items and export them all at once, optionally grouping them into a [case](#cases).
|
||||
- **From History** — open the **Actions** menu and choose **Export**. You can export a preset duration (the last 1, 4, 8, 12, or 24 hours), enter a custom start and end time, or select a range directly on the timeline. A **multi-camera** option lets you export the same time range across several cameras at once.
|
||||
|
||||
In every case you can give the export a name. Frigate then saves the footage from your recordings as a single video file. Larger ranges take time to process; the export is marked _in progress_ until it finishes, and you can keep using Frigate while it runs.
|
||||
|
||||
## Managing exports
|
||||
|
||||
All of your exports live on the **Exports** page, reachable from the main navigation, where you can search for one by name. Each export offers the following actions:
|
||||
|
||||
- **Play** it in the browser,
|
||||
- **Download** it to save the footage outside of Frigate,
|
||||
- **Share** it — copies a direct link to the export (or uses your device's share sheet),
|
||||
- **Rename** it, and
|
||||
- **Delete** it — deleting is the only way an export is removed.
|
||||
|
||||
You can also select multiple exports at once to **delete** them in bulk, or to **add them to** (or **remove them from**) a [case](#cases).
|
||||
|
||||
## Cases
|
||||
|
||||
A **case** groups related exports together — for example, all the clips from a single incident across multiple cameras. On the **Exports** page you can create a case with a name and description, add existing exports to it (or create a new case while exporting), and **download the entire case as a single archive** to hand off as one package.
|
||||
|
||||
Exports that don't belong to a case appear under **Uncategorized Exports**. Deleting a case lets you either keep its exports (they move back to uncategorized) or delete them along with the case.
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: history
|
||||
title: History
|
||||
---
|
||||
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
**History** is Frigate's full-resolution recording viewer. Unlike Live, Review, and Explore, there is no menu item for it — you reach it from within another view, then scrub the timeline, switch cameras, inspect a tracked object's lifecycle, and export or share any moment.
|
||||
|
||||
This page describes how to _use_ the History view. For how recordings are _configured_ (retention, pre/post capture), see [Recording](/configuration/record).
|
||||
|
||||
## Opening History
|
||||
|
||||
You can open History from several places:
|
||||
|
||||
- **From [Review](/usage/review):** clicking a review item opens its recording, scrubbed to just before the activity on that camera.
|
||||
- **From [Live](/usage/live):** the **History** button in a camera's single-camera view opens that camera about 30 seconds in the past.
|
||||
- **From a share link:** opening a shared timestamp link (see [Share Timestamp](#the-actions-menu) below) jumps straight to that camera and moment.
|
||||
|
||||
Use the **Back** button to return where you came from, or the **Live** button to jump to the current camera's live view.
|
||||
|
||||
## Timeline, Events, and Detail
|
||||
|
||||
A toggle (a drawer on mobile) switches the side panel between three modes:
|
||||
|
||||
- **Timeline** — a scrubbable vertical timeline of the selected camera, annotated with a motion line, review-item markers, and gaps where no recording exists.
|
||||
- **Events** — a scrollable list of the camera's review items for the time range; clicking one seeks the player to it.
|
||||
- **Detail** — the [tracking details inspector](#the-detail-view) for the objects in view.
|
||||
|
||||
While you are selecting a range to export, the panel temporarily switches to Timeline.
|
||||
|
||||
## Scrubbing and previews
|
||||
|
||||
Drag the timeline handlebar to move through time; the main player and any secondary camera previews scrub together so everything stays in sync. Press the zoom buttons on the timeline to change its zoom level (from coarse to fine segments). Sections of the timeline with no recordings are shown as gaps.
|
||||
|
||||
On desktop, when more than one camera is available, a **row of secondary previews** shows the other cameras at the same moment. Clicking one of them makes it the main camera at the current timestamp, so you can follow activity across cameras without losing your place. On mobile, use the camera drawer to switch cameras.
|
||||
|
||||
## Filtering and the calendar
|
||||
|
||||
You can filter History by **cameras** and **date**. The calendar behaves the same as it does in [Review](/usage/review#filtering-and-the-calendar): an **underline** under a day means recordings exist for that day, and a **colored dot** (red for unreviewed alerts, orange for unreviewed detections) marks days with unreviewed activity.
|
||||
|
||||
## The Detail view
|
||||
|
||||
The **Detail** mode turns the side panel into a tracking details inspector. It lists one card per review item, each showing the item's severity, start time, the object labels involved, a count of tracked objects, and the duration. The active card is highlighted as the video plays, and clicking a card seeks to it.
|
||||
|
||||
Expanding a card reveals the **lifecycle** of each tracked object — a row for each significant moment (detected, entered a zone, became active, became stationary, left, and so on), with a progress line that follows the current playback position. Hovering a row shows that moment's score, ratio, and area, and clicking a row seeks the video to that exact timestamp.
|
||||
|
||||
The **Detail View Settings** at the bottom let you toggle whether the active item's objects expand automatically, and adjust the **annotation offset** — a fine timing correction that aligns the bounding-box overlays with the recorded video when your camera's snapshot and recording timestamps drift. Admins can save the offset to the camera's configuration.
|
||||
|
||||
## The Actions menu
|
||||
|
||||
On desktop, the **Actions** menu (the film icon) collects the things you can do with the footage you are viewing:
|
||||
|
||||
- **Export** — save a clip of a chosen time range so it is never removed by retention. The dialog pre-selects the last hour; adjust the range or drag the timeline handles, then export. See [Exports](/usage/exports) for managing and downloading exports.
|
||||
- **Share Timestamp** — generate a link to the current moment (or a custom timestamp) to share with another Frigate user. This is an internal link, not a public share URL.
|
||||
- **Motion Search** — scan this camera's recordings for changes in a region you draw. This is the same tool documented under [Reviewing Motion](/usage/review#motion-search).
|
||||
- **Debug Replay** (admins) — replay a recorded range back through Frigate's detection pipeline to see how it would be processed.
|
||||
|
||||
You can also capture an instant snapshot of the current frame, and submit a frame to [Frigate+](/integrations/plus) directly from the player (admins only).
|
||||
|
||||
## AI review summaries
|
||||
|
||||
When [Generative AI review](/configuration/genai/genai_review) is configured, Frigate can generate a title, description, and threat classification for review items and surface them as you scrub through History. A review item that has an AI summary exposes its details in a few places:
|
||||
|
||||
- **Over the video** — when the item is on screen, a popup appears over the player.
|
||||
- **In the Events side panel** — items with a summary show the title below the thumbnail.
|
||||
- **In the Detail side panel** — the item's card shows the title alongside its tracking details.
|
||||
|
||||
Clicking any of these opens the **AI Analysis** dialog with the generated detail and any flagged concerns for that item.
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
id: live
|
||||
title: Live View
|
||||
---
|
||||
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
**Live view** is Frigate's real-time dashboard and the page you land on by default. It shows all of your cameras at a glance, streams your most recent alerts across the top, and lets you open any camera in a full-resolution single-camera view with audio, two-way talk, PTZ, and on-demand recording controls.
|
||||
|
||||
This page describes how to _use_ the Live view. For how to _configure_ live streaming — go2rtc, stream selection, smart streaming, WebRTC, and audio — see the [Live View configuration](/configuration/live) docs.
|
||||
|
||||
## The dashboard at a glance
|
||||
|
||||
The default **All Cameras** dashboard shows every camera, with a filmstrip of recent **alerts** scrolling across the top. Clicking an alert opens it in [Review](/usage/review); each card also has a check button to mark it reviewed without leaving the dashboard.
|
||||
|
||||
By default Frigate uses **smart streaming**: a camera's image updates roughly once per minute while nothing is happening, and switches to a full live stream the moment activity is detected. This conserves bandwidth and resources. You can change this per camera or per group (see [Streaming settings](#streaming-settings-and-the-right-click-menu) below), and the behavior is explained in detail under [Live view technologies](/configuration/live#live-view-technologies).
|
||||
|
||||
On mobile, a toggle in the header switches between a **grid** layout and a single-column **list** layout. On desktop a **fullscreen** button is available in the lower-right corner.
|
||||
|
||||
## Switching dashboards and camera groups
|
||||
|
||||
The icon rail (top-left on desktop, a horizontal strip on mobile) switches between dashboards:
|
||||
|
||||
- The **home** icon is the **All Cameras** dashboard, which shows every camera enabled for the dashboard.
|
||||
- Each **camera group** you create appears as its own icon. Selecting a group shows only that group's cameras.
|
||||
|
||||
Camera groups are useful for organizing cameras by location (for example, _Front of House_ or _Backyard_) and for giving each group its own dashboard layout and streaming preferences.
|
||||
|
||||
You can also view [Birdseye](/configuration/birdseye) on the dashboard, or open it directly at `http://<frigate_host>:5000/#birdseye`. Clicking a camera inside the Birdseye view jumps to that camera's live feed.
|
||||
|
||||
## Creating and editing camera groups
|
||||
|
||||
Admins can manage groups from the pencil icon next to the group rail, which opens the **Camera Groups** dialog. From there you can add a group, or edit and delete existing ones. When creating a group you choose:
|
||||
|
||||
- a **Name** (spaces are converted to underscores),
|
||||
- the **cameras** to include — each camera has a toggle and a gear that opens its [streaming settings](#streaming-settings-and-the-right-click-menu), and
|
||||
- an **icon** used for the group's button in the rail.
|
||||
|
||||
Deleting a group also clears any custom layout you saved for it.
|
||||
|
||||
## Rearranging a camera group layout
|
||||
|
||||
On desktop and tablet, each camera group has its own freely-arrangeable grid. Enter **Edit Layout** mode from the layout button in the lower-right corner: camera tiles gain a drag handle and corner resize handles. Drag a tile to reposition it and drag a corner to resize it (the aspect ratio is preserved). Exit edit mode to save. The layout is stored in your browser per device, so each device can have its own arrangement.
|
||||
|
||||
The default **All Cameras** dashboard is not manually arrangeable — it automatically sizes tiles based on each camera's aspect ratio (wide cameras span two columns, tall cameras span two rows).
|
||||
|
||||
## Reading the tile indicators
|
||||
|
||||
Each camera tile surfaces its current state with a few overlays:
|
||||
|
||||
- A **pulsing red dot** in the corner means **motion is currently detected** on that camera.
|
||||
- A **red outline** around the tile means an **active tracked object** is on that camera.
|
||||
- A small **label chip** lists the object types currently detected (for example, _Person_, _Car_).
|
||||
- A **camera-name label** appears when you have enabled always-on camera names, or when a camera is offline or disabled.
|
||||
- A **Stream Offline** or **Camera is off** placeholder appears when no frames are being received or the camera has been turned off.
|
||||
|
||||
You can optionally overlay live streaming statistics (stream type, bandwidth, latency, and frame counts) on a tile to diagnose playback issues.
|
||||
|
||||
## Streaming settings and the right-click menu
|
||||
|
||||
Right-clicking (or long-pressing) a camera tile opens a context menu with quick controls: an **audio volume** control for streams that support audio, **Mute / Unmute all cameras**, **show or hide streaming statistics**, the **debug view**, **notification** options, and — for admins — turning the camera on or off.
|
||||
|
||||
A **Low-bandwidth mode** notice may also appear in the context menu with a **Reset** option appears when Frigate has fallen back to the lower-quality jsmpeg stream — see the [Live view FAQ](/configuration/live#live-view-faq) for why this happens.
|
||||
|
||||
For non-default groups, the context menu also exposes **Streaming Settings** for that camera, which let you choose:
|
||||
|
||||
- the **stream** to display (the dropdown lists the streams you configured under [`live -> streams`](/configuration/live#setting-streams-for-live-ui), and indicates whether audio is available),
|
||||
- the **streaming method** — **No Streaming**, **Smart Streaming** (recommended), or **Continuous Streaming** (higher bandwidth), and
|
||||
- **compatibility mode**, for devices that have trouble rendering the default player.
|
||||
|
||||
These settings are saved per group and per device in your browser, not in your config file.
|
||||
|
||||
## The single-camera view
|
||||
|
||||
Clicking a camera tile opens its full-resolution single-camera view. The top bar provides:
|
||||
|
||||
- **Back** (also the `Esc` key) to return to the dashboard,
|
||||
- **History** to jump to the [recordings](/usage/history) for this camera, starting about 30 seconds in the past,
|
||||
- **Fullscreen** and **Picture-in-Picture** (if supported by your browser),
|
||||
- **Two-way talk** (the microphone button — requires a supported camera and WebRTC; keyboard shortcut `t`), and
|
||||
- **Camera audio muting** (the speaker button; keyboard shortcut `m`).
|
||||
|
||||
You can pinch or scroll to zoom into the feed. A **settings** gear provides a **stream** selector (with audio and two-way-talk availability indicators), **Play in background**, **Show stats**, and a **Debug view** that overlays Frigate's detection regions and bounding boxes.
|
||||
|
||||
:::tip
|
||||
|
||||
Two-way talk and camera audio have specific codec and port requirements. See [Audio Support](/configuration/live#audio-support) and [WebRTC](/configuration/live#webrtc-extra-configuration) for setup details.
|
||||
|
||||
:::
|
||||
|
||||
## Camera controls
|
||||
|
||||
Admins get a row of toggles in the single-camera view (a settings drawer on mobile) to turn camera features on and off in real time:
|
||||
|
||||
- **Camera** on/off,
|
||||
- **Object detection**,
|
||||
- **Recording** (only available when recording is enabled in the camera's config),
|
||||
- **Snapshots**,
|
||||
- **Audio detection**,
|
||||
- **Live audio transcription** (when audio detection is enabled), and
|
||||
- **Autotracking** (for [autotracking-capable PTZ cameras](/configuration/autotracking)).
|
||||
|
||||
These toggles change runtime behavior immediately. Whether a change persists across a restart depends on the feature — see the relevant configuration page.
|
||||
|
||||
## On-demand recording and snapshots
|
||||
|
||||
The single-camera view can capture footage on demand:
|
||||
|
||||
- **Start on-demand recording** begins a manual recording based on the camera's recording retention settings (the button pulses while active). If recording is disabled for the camera, only a snapshot is saved. Use **End on-demand recording** to stop.
|
||||
- **Download instant snapshot** saves a still image of the current frame.
|
||||
|
||||
See [Recording](/configuration/record) and [Snapshots](/configuration/snapshots) for how retention is configured, and [Exports](/usage/exports) for keeping a clip permanently.
|
||||
|
||||
## PTZ controls
|
||||
|
||||
For ONVIF cameras that support it, a control panel provides pan/tilt arrows, **zoom**, **focus**, and saved **presets**. You can also enable a **click-to-move / drag-to-zoom** overlay: click a point in the frame to center the camera there, or drag a box to pan and zoom to that area (dragging top-left to bottom-right zooms in, the reverse zooms out).
|
||||
|
||||
For continuous, automatic tracking of a moving object, see [Autotracking](/configuration/autotracking).
|
||||
@@ -0,0 +1,140 @@
|
||||
---
|
||||
id: review
|
||||
title: Review
|
||||
---
|
||||
|
||||
import NavPath from "@site/src/components/NavPath";
|
||||
|
||||
**Review** is where you triage what happened on your cameras. It groups activity into **review items** — segments of time on a single camera that bundle together the objects and audio that were active at once — and sorts them into **Alerts**, **Detections**, and **Motion**. From here you can scrub through activity, mark items as reviewed, filter, export, and jump to the full recording in [History](/usage/history).
|
||||
|
||||
This page describes how to _use_ the Review view. For how alerts and detections are _configured_ (labels, zones, required zones, retention), see the [Review configuration](/configuration/review) docs.
|
||||
|
||||
:::info
|
||||
|
||||
Review items are only created for a camera when **recording is enabled** for that camera. See [Recording](/configuration/record).
|
||||
|
||||
:::
|
||||
|
||||
## Alerts, Detections, and Motion
|
||||
|
||||
Not every segment of video captured by Frigate is of the same level of interest. The people who enter your property may be a higher priority than those just walking by on the sidewalk. For this reason, Frigate sorts **review items** by importance into **alerts** and **detections**, with a separate **Motion** category for significant motion.
|
||||
|
||||
The toggle at the top of the page switches between these three severities. One is always selected.
|
||||
|
||||
| Tab | Indicator color | What it shows |
|
||||
| -------------- | --------------- | ---------------------------------------------------------------------------------------------------------------- |
|
||||
| **Alerts** | dark red | The activity you most want to see. By default, all `person` and `car` tracked objects are alerts. |
|
||||
| **Detections** | orange | Everything else Frigate tracked that wasn't promoted to an alert. |
|
||||
| **Motion** | yellow | Periods of significant motion, with the ability to filter to periods which did **not** produce a tracked object. |
|
||||
|
||||
This same color coding is used for the ring around a selected item and the dots on the calendar. How an object is categorized as an alert vs. a detection — and how required zones refine that — is covered in [Alerts and Detections](/configuration/review#alerts-and-detections).
|
||||
|
||||
The **Alerts** and **Detections** tabs show a count next to their label. With **Show Reviewed** turned off (the default), this is the number of items still left to review; with it on, the count reflects every item in the selected time range.
|
||||
|
||||
## Marking items as reviewed
|
||||
|
||||
Review items are shown as a grid of thumbnail cards next to a vertical activity timeline. Hovering a card (desktop) or swiping to the right (mobile) plays a short preview inline.
|
||||
|
||||
- **Clicking** a card opens its recording in [History](/usage/history) and marks the item as reviewed.
|
||||
- The object chip on each card is **gray** when the item is unreviewed and turns **green** once it has been reviewed.
|
||||
- The **Mark these items as reviewed** button marks everything currently shown as reviewed at once.
|
||||
|
||||
Reviewed state is tracked per user, so marking an item reviewed does not hide it for other users.
|
||||
|
||||
## Selecting and acting on multiple items
|
||||
|
||||
To act on several items at once, start a selection by **Ctrl/Cmd-clicking** a card (desktop) or **long-pressing** one (mobile). Selected cards gain a colored ring matching their severity. Keyboard shortcuts speed this up: `Ctrl+A` selects all, `R` marks the selection reviewed, and `Esc` clears it.
|
||||
|
||||
With items selected, an action bar appears with options to:
|
||||
|
||||
- **Export** the selected items (a single item exports directly; multiple items open the batch [export](/usage/exports) dialog),
|
||||
- **Mark as reviewed** or **Mark as unreviewed**, and
|
||||
- **Delete** them (admins only).
|
||||
|
||||
## Filtering and the calendar
|
||||
|
||||
Use the filter controls in the header to narrow what's shown. The available filters depend on the tab: Alerts and Detections can be filtered by **cameras**, **date**, **labels**, **zones**, and whether items are already reviewed; the Motion tab can be filtered by **cameras**, **date**, and **motion only**.
|
||||
|
||||
The **calendar** filter lets you jump to a specific day (it shows **Last 24 Hours** until you pick one). On each day:
|
||||
|
||||
- An **underline** under the day number means **recordings exist** for that day. Days without recordings are dimmed.
|
||||
- A **colored dot** under the day number means there is **unreviewed activity** that day — a **red dot** for unreviewed alerts, or an **orange dot** for unreviewed detections when there are no unreviewed alerts. Motion is not represented by a dot.
|
||||
|
||||
Future dates are disabled, and the week start and time zone follow your configuration.
|
||||
|
||||
## Reviewing Motion
|
||||
|
||||
The Review page also can show periods of motion that didn't produce a tracked object, and provides a way to search past recordings for motion in a specific region. These tools complement the alerts and detections workflow above — see [Tuning Motion Detection](/configuration/motion_detection) for how the underlying motion detector is configured.
|
||||
|
||||
The **Motion** tab itself shows a multi-camera grid scrubbed to a shared point in time, with a draggable timeline and a playback-speed selector. A camera tile gains a colored ring when a review item or significant motion overlaps the current time, and clicking a tile opens that camera's recording at that moment. Each camera's options menu (the kebab in the corner of its tile) is where you open **Motion Previews** and **Motion Search**, described below.
|
||||
|
||||
### Motion Previews
|
||||
|
||||
The Motion Previews pane shows preview clips for periods of significant motion that did not produce a tracked object. It is useful for spotting things that motion detection picked up but object detection did not, which can help validate tuning or catch missed objects.
|
||||
|
||||
On the <NavPath path="Review > Motion" /> page, click the kebab menu on a camera and choose **Motion Previews**. Each card represents a continuous range of motion-only activity and plays back the recorded preview for that range. A heatmap overlay dims areas of the frame with no motion so the moving regions stand out.
|
||||
|
||||
The pane provides a few controls:
|
||||
|
||||
- **Speed** — speeds up or slows down all of the preview clips at once.
|
||||
- **Dim** — controls how strongly non-motion areas are darkened by the heatmap overlay. Higher values increase motion area visibility.
|
||||
- **Filter** — opens a 16×16 grid overlaid on a snapshot of the camera. Select one or more cells to only show clips with motion in those regions. This is helpful for filtering out motion in areas like a busy street while keeping motion in your driveway.
|
||||
|
||||
Clicking a preview clip seeks the recording player to that timestamp so you can review the full footage.
|
||||
|
||||
### Motion Search
|
||||
|
||||
Motion Search lets you scan recorded footage for changes inside a region of interest you draw on the camera. Unlike Motion Previews, which surfaces what Frigate's motion detector flagged in real time, Motion Search re-analyzes the saved recordings, so it can find changes that were missed (for example, an object that appeared while motion detection was paused by `lightning_threshold`, or in a region that is normally motion-masked).
|
||||
|
||||
To start a search, open the Actions menu in [History](/usage/history) or click the kebab menu on a camera in the <NavPath path="Review > Motion" /> page and choose **Motion Search**. In the dialog:
|
||||
|
||||
1. Pick the camera and time range to scan. In the date pickers, days that have recordings available are underlined.
|
||||
2. Draw a polygon on the camera frame to define the region of interest.
|
||||
3. Adjust the search parameters if needed:
|
||||
|
||||
| Field | Description |
|
||||
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Sensitivity Threshold** | Per-pixel luminance change required to count as motion inside the ROI. Behaves like Frigate's motion detection `threshold` setting. |
|
||||
| **Minimum Change Area** | Minimum size of a single moving region, as a percentage of the ROI, for a frame to count as significant. Raise it to ignore small movements (leaves, distant motion); lower it when your subject covers only a small slice of the ROI. Every result shows the percentage it scored, so you can use those values to tune this. |
|
||||
| **Maximum Results** | Maximum number of matching timestamps to return. The search stops once it reaches this many results, so a lower value finishes sooner while a higher value scans further into the range. |
|
||||
| **Parallel mode** | Decode multiple recording ranges at the same time. Speeds up large time ranges at the cost of higher decoding and CPU usage. |
|
||||
|
||||
Motion Search samples each recording's keyframes automatically, so there is no frame-rate or sampling setting to tune.
|
||||
|
||||
Once running, Frigate scans the recording segments that overlap the time range and reports timestamps where changes were detected inside the polygon, along with the percentage of the ROI that changed. Clicking a result seeks the player to that moment so you can review what happened.
|
||||
|
||||
The results panel shows the time range being scanned, a live progress bar with the timestamp currently being analyzed, and the running result count. A collapsible **Search Metrics** section reports how many segments were scanned and processed, how many were skipped because no motion was recorded in the ROI (using the stored motion heatmap), how many frames were decoded, and the total search time. Skipping segments with no recorded motion in the selected ROI is what makes searching long time ranges practical.
|
||||
|
||||
#### Common use cases
|
||||
|
||||
Frigate's main use case is to record and surface tracked objects, so Motion Search is most useful for the cases where object detection produced nothing — there is no object to find in Explore, but you suspect something happened.
|
||||
|
||||
- **Locating an unattributed change.** You know something appeared, disappeared, or moved in a window of footage — a package now gone, a gate left open — but no detection points to it. A search returns the candidate timestamps instead of scrubbing the timeline by hand.
|
||||
- **An object that was never detected.** Something Frigate doesn't have a model label for, an object too small or distant to be detected, or movement in a region where detection isn't running. The activity left no tracked object but did change the pixels, so a search can still find it.
|
||||
- **Activity while detection was effectively paused.** Changes that occurred while object detection was disabled, motion was suppressed by `skip_motion_threshold`, or inside an area covered by a motion mask, won't appear as review items or tracked objects but can be recovered by searching the recordings directly.
|
||||
|
||||
#### Examples
|
||||
|
||||
These show how to choose the ROI and **Minimum Change Area** for two common goals. Minimum Change Area is the size of a single moving region as a percentage of the ROI you draw, so the right value depends on how much of the ROI your subject — and its movement between samples — covers.
|
||||
|
||||
Because samples are a second or more apart, a moving subject usually appears in two places at once in the comparison, so even ordinary motion often scores tens of percent and a low threshold lets in almost everything. The most reliable approach is to **run a search, look at the percentage each result scored, and set Minimum Change Area just below the values for the events you care about.** The default is 20%; the suggestions below are starting points.
|
||||
|
||||
- **When did this item first appear (or disappear)?** A package was dropped off, a car parked, or a trash can was moved, and you want the exact moment. Draw a **tight ROI** around the spot the item occupies and **raise Minimum Change Area** (start around 40–60%). Because the item fills most of a tight ROI, its arrival or removal is a large change, while smaller nearby motion (shadows, a passing pedestrian) stays below the threshold. The **earliest result** is when it appeared; if you only care about that moment, a low Maximum Results finishes faster. If you get no hits, the ROI is probably looser than the item — lower the threshold or tighten the ROI.
|
||||
- **What's been getting into the garden?** Something has been trampling a flower bed overnight and no object was ever tracked. Draw a **looser ROI** covering the whole bed and use a **lower Minimum Change Area than the case above** — start near the 20% default and lower it (toward 5–10%) only if a small or distant subject is missed, since it covers just a slice of a large region. Expect more results to scan through — step through the timestamps and jump to each to see what triggered it. If wind-blown plants add noise, raise Minimum Change Area or the Sensitivity Threshold.
|
||||
|
||||
#### Expected performance
|
||||
|
||||
Motion Search analyzes the saved recordings on demand rather than reading a pre-built index, so a search over a long range takes longer than browsing Motion Previews. Cost scales mainly with how much footage has to be examined: segments with no recorded motion in your ROI are skipped using the stored motion heatmap (shown as "segments skipped" in the status panel), so a quiet range finishes quickly while a busy one takes longer.
|
||||
|
||||
To increase the speed of searches:
|
||||
|
||||
- Draw a tight ROI. Because **Minimum Change Area** is measured as a percentage of the region you draw, a tight ROI around where you expect the change makes the object fill a larger share of the area, so it clears the threshold more easily. A loose ROI makes the same object a small fraction of the region, so it can fall below the threshold and be missed — forcing you to lower Minimum Change Area, which lets in more noise.
|
||||
- Narrow the time range to the window you care about, so there is less footage to examine.
|
||||
- Lower **Maximum Results** when you only need the first few hits. Because the search stops once it reaches that many results, a smaller value lets a busy range finish early instead of scanning the whole window.
|
||||
- Use Parallel mode to shorten wall-clock time on multi-core systems, at the cost of higher decoding and CPU usage while it runs.
|
||||
|
||||
## AI review summaries
|
||||
|
||||
When [Generative AI review](/configuration/genai/genai_review) is configured, Frigate can generate a title, description, and threat classification for review items and surface them automatically in Review and History. Clicking the summary chip opens an **AI Analysis** dialog with the generated detail and any flagged concerns.
|
||||
|
||||
In Review, an additional icon appears on unreviewed items that the AI classified as **suspicious** (Level 1) or **critical** (Level 2), so the activity that most warrants attention stands out before you open it. The icon goes away once the item has been reviewed.
|
||||
Generated
+11
-4
@@ -14,9 +14,11 @@
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@inkeep/docusaurus": "^2.0.16",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^4.5.1",
|
||||
"docusaurus-theme-openapi-docs": "^4.5.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
@@ -5747,6 +5749,11 @@
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/js-yaml": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://mirrors.tencent.com/npm/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@@ -10964,9 +10971,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -12883,7 +12890,7 @@
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"resolved": "https://mirrors.tencent.com/npm/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
+5
-2
@@ -3,9 +3,10 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build:config": "node scripts/build-config.mjs",
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "npm run regen-docs && docusaurus start --host 0.0.0.0",
|
||||
"build": "npm run regen-docs && docusaurus build",
|
||||
"start": "npm run build:config && npm run regen-docs && docusaurus start --host 0.0.0.0",
|
||||
"build": "npm run build:config && npm run regen-docs && docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
@@ -23,9 +24,11 @@
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@inkeep/docusaurus": "^2.0.16",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-openapi-docs": "^4.5.1",
|
||||
"docusaurus-theme-openapi-docs": "^4.5.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Build script: reads config.yaml and generates TypeScript files
|
||||
* for the Docker Compose Generator.
|
||||
*
|
||||
* Usage: node scripts/build-config.mjs
|
||||
*/
|
||||
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CONFIG_DIR = path.resolve(__dirname, "../src/components/DockerComposeGenerator/config");
|
||||
const YAML_PATH = path.join(CONFIG_DIR, "config.yaml");
|
||||
|
||||
// Read & parse YAML
|
||||
const raw = fs.readFileSync(YAML_PATH, "utf8");
|
||||
const config = yaml.load(raw);
|
||||
|
||||
if (!config.devices || !config.hardware || !config.ports) {
|
||||
console.error("config.yaml must contain 'devices', 'hardware', and 'ports' sections.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a .ts file from a section of the YAML config.
|
||||
*/
|
||||
function generateTsFile(sectionName, items, typeName, varName, mapVarName, yamlFilename) {
|
||||
const jsonItems = JSON.stringify(items, null, 2);
|
||||
// Indent JSON to fit inside the array literal
|
||||
const indented = jsonItems
|
||||
.split("\n")
|
||||
.map((line, i) => (i === 0 ? line : " " + line))
|
||||
.join("\n");
|
||||
|
||||
const content = `/**
|
||||
* AUTO-GENERATED FILE — do not edit directly.
|
||||
* Source: ${yamlFilename}
|
||||
* To update, edit the YAML file and run: npm run build:config
|
||||
*/
|
||||
|
||||
import type { ${typeName} } from "./types";
|
||||
|
||||
export const ${varName}: ${typeName}[] = ${indented};
|
||||
|
||||
/** Lookup map for quick access by ID */
|
||||
export const ${mapVarName}: Map<string, ${typeName}> = new Map(${varName}.map((item) => [item.id, item]));
|
||||
`;
|
||||
|
||||
const outPath = path.join(CONFIG_DIR, `${sectionName}.ts`);
|
||||
fs.writeFileSync(outPath, content, "utf8");
|
||||
console.log(` ✓ Generated ${sectionName}.ts (${items.length} items)`);
|
||||
}
|
||||
|
||||
console.log("Building config from config.yaml...");
|
||||
|
||||
generateTsFile("devices", config.devices, "DeviceConfig", "devices", "deviceMap", "config.yaml");
|
||||
generateTsFile("hardware", config.hardware, "HardwareOption", "hardwareOptions", "hardwareMap", "config.yaml");
|
||||
generateTsFile("ports", config.ports, "PortConfig", "ports", "portMap", "config.yaml");
|
||||
|
||||
console.log("Done!");
|
||||
@@ -63,8 +63,8 @@ SYSTEM_NAV: dict[str, tuple[str, str]] = {
|
||||
"environment_vars": ("System", "Environment variables"),
|
||||
"telemetry": ("System", "Telemetry"),
|
||||
"birdseye": ("System", "Birdseye"),
|
||||
"detectors": ("System", "Detector hardware"),
|
||||
"model": ("System", "Detection model"),
|
||||
"detectors": ("System", "Detectors and model"),
|
||||
"model": ("System", "Detectors and model"),
|
||||
}
|
||||
|
||||
# All known top-level config section keys
|
||||
|
||||
+115
-79
@@ -17,91 +17,126 @@ const sidebars: SidebarsConfig = {
|
||||
],
|
||||
Guides: [
|
||||
"guides/getting_started",
|
||||
"guides/configuring_go2rtc",
|
||||
"guides/ha_notifications",
|
||||
"guides/ha_network_storage",
|
||||
"guides/reverse_proxy",
|
||||
],
|
||||
Configuration: {
|
||||
"Configuration Files": [
|
||||
"configuration/index",
|
||||
"configuration/reference",
|
||||
{
|
||||
type: "link",
|
||||
label: "Go2RTC Configuration Reference",
|
||||
href: "https://github.com/AlexxIT/go2rtc/tree/v1.9.13#configuration",
|
||||
} as PropSidebarItemLink,
|
||||
],
|
||||
Detectors: [
|
||||
"configuration/object_detectors",
|
||||
"configuration/audio_detectors",
|
||||
],
|
||||
Enrichments: [
|
||||
"configuration/semantic_search",
|
||||
"configuration/face_recognition",
|
||||
"configuration/license_plate_recognition",
|
||||
"configuration/bird_classification",
|
||||
{
|
||||
type: "category",
|
||||
label: "Custom Classification",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Custom Classification",
|
||||
description: "Configuration for custom classification models",
|
||||
Usage: [
|
||||
"usage/live",
|
||||
"usage/review",
|
||||
"usage/history",
|
||||
"usage/explore",
|
||||
"usage/exports",
|
||||
],
|
||||
Configuration: [
|
||||
"configuration/config",
|
||||
{
|
||||
type: "category",
|
||||
label: "Detectors",
|
||||
items: [
|
||||
"configuration/object_detectors",
|
||||
"configuration/audio_detectors",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Enrichments",
|
||||
items: [
|
||||
"configuration/semantic_search",
|
||||
"configuration/face_recognition",
|
||||
"configuration/license_plate_recognition",
|
||||
"configuration/bird_classification",
|
||||
{
|
||||
type: "category",
|
||||
label: "Custom Classification",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Custom Classification",
|
||||
description: "Configuration for custom classification models",
|
||||
},
|
||||
items: [
|
||||
"configuration/custom_classification/state_classification",
|
||||
"configuration/custom_classification/object_classification",
|
||||
],
|
||||
},
|
||||
items: [
|
||||
"configuration/custom_classification/state_classification",
|
||||
"configuration/custom_classification/object_classification",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Generative AI",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Generative AI",
|
||||
description: "Generative AI Features",
|
||||
{
|
||||
type: "category",
|
||||
label: "Generative AI",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Generative AI",
|
||||
description: "Generative AI Features",
|
||||
},
|
||||
items: [
|
||||
"configuration/genai/genai_config",
|
||||
"configuration/genai/genai_review",
|
||||
"configuration/genai/genai_objects",
|
||||
],
|
||||
},
|
||||
items: [
|
||||
"configuration/genai/genai_config",
|
||||
"configuration/genai/genai_review",
|
||||
"configuration/genai/genai_objects",
|
||||
],
|
||||
},
|
||||
],
|
||||
Cameras: [
|
||||
"configuration/cameras",
|
||||
"configuration/review",
|
||||
"configuration/record",
|
||||
"configuration/snapshots",
|
||||
"configuration/motion_detection",
|
||||
"configuration/birdseye",
|
||||
"configuration/live",
|
||||
"configuration/restream",
|
||||
"configuration/autotracking",
|
||||
"configuration/camera_specific",
|
||||
],
|
||||
Objects: [
|
||||
"configuration/object_filters",
|
||||
"configuration/masks",
|
||||
"configuration/zones",
|
||||
"configuration/objects",
|
||||
"configuration/stationary_objects",
|
||||
],
|
||||
"Hardware Acceleration": [
|
||||
"configuration/hardware_acceleration_video",
|
||||
"configuration/hardware_acceleration_enrichments",
|
||||
],
|
||||
"Extra Configuration": [
|
||||
"configuration/authentication",
|
||||
"configuration/notifications",
|
||||
"configuration/profiles",
|
||||
"configuration/ffmpeg_presets",
|
||||
"configuration/pwa",
|
||||
"configuration/tls",
|
||||
"configuration/advanced",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Cameras",
|
||||
items: [
|
||||
"configuration/cameras",
|
||||
"configuration/review",
|
||||
"configuration/record",
|
||||
"configuration/snapshots",
|
||||
"configuration/motion_detection",
|
||||
"configuration/birdseye",
|
||||
"configuration/live",
|
||||
"configuration/restream",
|
||||
"configuration/autotracking",
|
||||
"configuration/camera_specific",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Objects",
|
||||
items: [
|
||||
"configuration/object_filters",
|
||||
"configuration/masks",
|
||||
"configuration/zones",
|
||||
"configuration/objects",
|
||||
"configuration/stationary_objects",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Hardware Acceleration",
|
||||
items: [
|
||||
"configuration/hardware_acceleration_video",
|
||||
"configuration/hardware_acceleration_enrichments",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Extra Configuration",
|
||||
items: [
|
||||
"configuration/authentication",
|
||||
"configuration/notifications",
|
||||
"configuration/profiles",
|
||||
"configuration/go2rtc",
|
||||
"configuration/ffmpeg_presets",
|
||||
"configuration/pwa",
|
||||
"configuration/tls",
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Advanced Configuration",
|
||||
items: [
|
||||
"configuration/advanced/system",
|
||||
"configuration/advanced/reference",
|
||||
{
|
||||
type: "link",
|
||||
label: "Go2RTC Configuration Reference",
|
||||
href: "https://github.com/AlexxIT/go2rtc/tree/v1.9.13#configuration",
|
||||
} as PropSidebarItemLink,
|
||||
],
|
||||
},
|
||||
],
|
||||
Integrations: [
|
||||
"integrations/plus",
|
||||
"integrations/home-assistant",
|
||||
@@ -130,6 +165,7 @@ const sidebars: SidebarsConfig = {
|
||||
],
|
||||
Troubleshooting: [
|
||||
"troubleshooting/faqs",
|
||||
"troubleshooting/go2rtc",
|
||||
"troubleshooting/recordings",
|
||||
"troubleshooting/dummy-camera",
|
||||
{
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import React from "react";
|
||||
import Admonition from "@theme/Admonition";
|
||||
import DeviceSelector from "./components/DeviceSelector";
|
||||
import HardwareOptions from "./components/HardwareOptions";
|
||||
import PortConfigSection from "./components/PortConfig";
|
||||
import StoragePaths from "./components/StoragePaths";
|
||||
import NvidiaGpuConfig from "./components/NvidiaGpuConfig";
|
||||
import OtherOptions from "./components/OtherOptions";
|
||||
import GeneratedOutput from "./components/GeneratedOutput";
|
||||
import { useConfigGenerator } from "./hooks/useConfigGenerator";
|
||||
import styles from "./styles.module.css";
|
||||
|
||||
/**
|
||||
* Simple markdown-link-to-React renderer for help text.
|
||||
* Only supports [text](url) syntax — no nested brackets.
|
||||
*/
|
||||
function renderHelpText(text: string): React.ReactNode {
|
||||
const parts = text.split(/(\[[^\]]+\]\([^)]+\))/g);
|
||||
return parts.map((part, i) => {
|
||||
const match = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
||||
if (match) {
|
||||
return (
|
||||
<a key={i} href={match[2]}>
|
||||
{match[1]}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <React.Fragment key={i}>{part}</React.Fragment>;
|
||||
});
|
||||
}
|
||||
|
||||
export default function DockerComposeGenerator() {
|
||||
const {
|
||||
deviceId, device, hardwareEnabled,
|
||||
portEnabled,
|
||||
nvidiaGpuCount, nvidiaGpuDeviceId,
|
||||
configPath, mediaPath, rtspPassword, timezone, shmSize,
|
||||
shmSizeError, gpuDeviceIdError, configPathError, mediaPathError,
|
||||
hasAnyHardware, generatedYaml,
|
||||
selectDevice, toggleHardware, togglePort,
|
||||
handleShmSizeChange, handleConfigPathChange, handleMediaPathChange,
|
||||
handleNvidiaGpuCountChange, handleNvidiaGpuDeviceIdChange,
|
||||
setRtspPassword, setTimezone, isHardwareDisabled,
|
||||
} = useConfigGenerator();
|
||||
|
||||
return (
|
||||
<div className={styles.generator}>
|
||||
<div className={styles.card}>
|
||||
<DeviceSelector selectedId={deviceId} onSelect={selectDevice} />
|
||||
|
||||
{device.helpText && (
|
||||
<Admonition type={device.helpType || "info"}>
|
||||
{renderHelpText(device.helpText)}
|
||||
</Admonition>
|
||||
)}
|
||||
|
||||
{device.needsNvidiaConfig && (
|
||||
<NvidiaGpuConfig
|
||||
gpuCount={nvidiaGpuCount}
|
||||
gpuDeviceId={nvidiaGpuDeviceId}
|
||||
gpuDeviceIdError={gpuDeviceIdError}
|
||||
onGpuCountChange={handleNvidiaGpuCountChange}
|
||||
onGpuDeviceIdChange={handleNvidiaGpuDeviceIdChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<HardwareOptions
|
||||
deviceId={deviceId}
|
||||
hardwareEnabled={hardwareEnabled}
|
||||
onToggle={toggleHardware}
|
||||
isDisabled={isHardwareDisabled}
|
||||
/>
|
||||
|
||||
<StoragePaths
|
||||
configPath={configPath}
|
||||
mediaPath={mediaPath}
|
||||
configPathError={configPathError}
|
||||
mediaPathError={mediaPathError}
|
||||
onConfigPathChange={handleConfigPathChange}
|
||||
onMediaPathChange={handleMediaPathChange}
|
||||
/>
|
||||
|
||||
<PortConfigSection
|
||||
portEnabled={portEnabled}
|
||||
onTogglePort={togglePort}
|
||||
/>
|
||||
|
||||
<OtherOptions
|
||||
rtspPassword={rtspPassword}
|
||||
timezone={timezone}
|
||||
shmSize={shmSize}
|
||||
shmSizeError={shmSizeError}
|
||||
onRtspPasswordChange={setRtspPassword}
|
||||
onTimezoneChange={setTimezone}
|
||||
onShmSizeChange={handleShmSizeChange}
|
||||
/>
|
||||
|
||||
<GeneratedOutput
|
||||
yaml={generatedYaml}
|
||||
configPath={configPath}
|
||||
mediaPath={mediaPath}
|
||||
hasAnyHardware={hasAnyHardware}
|
||||
deviceId={deviceId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import React from "react";
|
||||
import { useColorMode } from "@docusaurus/theme-common";
|
||||
import { devices } from "../config";
|
||||
import type { DeviceConfig } from "../config";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
interface Props {
|
||||
selectedId: string;
|
||||
onSelect: (id: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the icon type from the icon string:
|
||||
* - Starts with "<svg" → inline SVG
|
||||
* - Starts with "/" or "http" → image URL/path
|
||||
* - Otherwise → emoji text
|
||||
*/
|
||||
function getIconType(icon: string): "svg" | "image" | "emoji" {
|
||||
const trimmed = icon.trim();
|
||||
if (trimmed.startsWith("<svg")) return "svg";
|
||||
if (trimmed.startsWith("/") || trimmed.startsWith("http://") || trimmed.startsWith("https://")) return "image";
|
||||
return "emoji";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the style object contains background-* properties,
|
||||
* indicating the image should be rendered as a CSS background-image
|
||||
* rather than an <img> tag.
|
||||
*/
|
||||
function hasBackgroundProps(style: React.CSSProperties | undefined): boolean {
|
||||
if (!style) return false;
|
||||
return Object.keys(style).some((key) => {
|
||||
const k = key.toLowerCase().replace(/-/g, "");
|
||||
return k === "backgroundsize" || k === "backgroundposition" || k === "backgroundrepeat" || k === "backgroundimage";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a style object to CSS custom properties (e.g. { width: "24px" } → { "--svg-width": "24px" })
|
||||
* so they can be consumed by CSS rules targeting child elements like <svg>.
|
||||
*/
|
||||
function toCssVars(style: React.CSSProperties | undefined, prefix: string): React.CSSProperties {
|
||||
if (!style) return {};
|
||||
const vars: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(style)) {
|
||||
const cssKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||||
vars[`--${prefix}-${cssKey}`] = value;
|
||||
}
|
||||
return vars as React.CSSProperties;
|
||||
}
|
||||
|
||||
function DeviceIcon({ device }: { device: DeviceConfig }) {
|
||||
const { isDarkTheme } = useColorMode();
|
||||
const iconStr = isDarkTheme && device.iconDark ? device.iconDark : device.icon;
|
||||
const iconStyle = (isDarkTheme && device.iconDarkStyle
|
||||
? device.iconDarkStyle
|
||||
: device.iconStyle) as React.CSSProperties | undefined;
|
||||
const svgStyle = (isDarkTheme && device.svgDarkStyle
|
||||
? device.svgDarkStyle
|
||||
: device.svgStyle) as React.CSSProperties | undefined;
|
||||
|
||||
const iconType = getIconType(iconStr);
|
||||
|
||||
if (iconType === "svg") {
|
||||
return (
|
||||
<div
|
||||
className={styles.deviceIconSvg}
|
||||
style={{ ...iconStyle, ...toCssVars(svgStyle, "svg") }}
|
||||
dangerouslySetInnerHTML={{ __html: iconStr }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (iconType === "image") {
|
||||
// When iconStyle contains background-* properties, render as background-image
|
||||
// on the container div instead of an <img> tag, enabling background-size/position control.
|
||||
if (hasBackgroundProps(iconStyle)) {
|
||||
return (
|
||||
<div
|
||||
className={styles.deviceIconImage}
|
||||
style={{
|
||||
backgroundImage: `url(${iconStr})`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "center",
|
||||
backgroundSize: "contain",
|
||||
...iconStyle,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles.deviceIconImage}>
|
||||
<img src={iconStr} alt={device.name} style={iconStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.deviceIcon} style={iconStyle}>
|
||||
{iconStr}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function DeviceCard({
|
||||
device,
|
||||
active,
|
||||
onClick,
|
||||
}: {
|
||||
device: DeviceConfig;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`${styles.deviceCard} ${active ? styles.deviceCardActive : ""}`}
|
||||
onClick={onClick}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") onClick();
|
||||
}}
|
||||
>
|
||||
<DeviceIcon device={device} />
|
||||
<div className={styles.deviceName}>{device.name}</div>
|
||||
<div className={styles.deviceDesc}>{device.description}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DeviceSelector({ selectedId, onSelect }: Props) {
|
||||
return (
|
||||
<div className={styles.formSection}>
|
||||
<h4>Device Type</h4>
|
||||
<div className={styles.deviceGrid}>
|
||||
{devices.map((d) => (
|
||||
<DeviceCard
|
||||
key={d.id}
|
||||
device={d}
|
||||
active={selectedId === d.id}
|
||||
onClick={() => onSelect(d.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import CodeBlock from "@theme/CodeBlock";
|
||||
import Admonition from "@theme/Admonition";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
interface Props {
|
||||
yaml: string;
|
||||
configPath: string;
|
||||
mediaPath: string;
|
||||
hasAnyHardware: boolean;
|
||||
deviceId: string;
|
||||
}
|
||||
|
||||
export default function GeneratedOutput({
|
||||
yaml,
|
||||
configPath,
|
||||
mediaPath,
|
||||
hasAnyHardware,
|
||||
deviceId,
|
||||
}: Props) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
navigator.clipboard.writeText(yaml).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
});
|
||||
}, [yaml]);
|
||||
|
||||
return (
|
||||
<div className={styles.resultSection}>
|
||||
<div className={styles.resultHeader}>
|
||||
<h4>Generated Configuration</h4>
|
||||
<button className="button button--primary button--sm" onClick={handleCopy}>
|
||||
{copied ? "Copied!" : "Copy"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!configPath && (
|
||||
<Admonition type="tip">
|
||||
<p>You haven't specified a config file directory. You may want to modify the default path.</p>
|
||||
</Admonition>
|
||||
)}
|
||||
{!mediaPath && (
|
||||
<Admonition type="tip">
|
||||
<p>You haven't specified a recording storage directory. You may want to modify the default path.</p>
|
||||
</Admonition>
|
||||
)}
|
||||
{deviceId === "stable" && !hasAnyHardware && (
|
||||
<Admonition type="warning">
|
||||
<p>You haven't selected any hardware acceleration. Please check if you have supported hardware available.</p>
|
||||
</Admonition>
|
||||
)}
|
||||
|
||||
<CodeBlock language="yaml" title="docker-compose.yml">
|
||||
{yaml}
|
||||
</CodeBlock>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import { hardwareOptions } from "../config";
|
||||
import type { HardwareOption } from "../config";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
interface Props {
|
||||
deviceId: string;
|
||||
hardwareEnabled: Record<string, boolean>;
|
||||
onToggle: (hwId: string) => void;
|
||||
isDisabled: (hwId: string) => boolean;
|
||||
}
|
||||
|
||||
function renderDescription(text: string): React.ReactNode {
|
||||
const parts = text.split(/(\[[^\]]+\]\([^)]+\))/g);
|
||||
return parts.map((part, i) => {
|
||||
const match = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
||||
if (match) {
|
||||
return <a key={i} href={match[2]}>{match[1]}</a>;
|
||||
}
|
||||
return <React.Fragment key={i}>{part}</React.Fragment>;
|
||||
});
|
||||
}
|
||||
|
||||
function HardwareCheckbox({
|
||||
hw, disabled, checked, onToggle,
|
||||
}: {
|
||||
hw: HardwareOption; disabled: boolean; checked: boolean; onToggle: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className={styles.hardwareItem}>
|
||||
<label className={`${styles.checkboxLabel} ${disabled ? styles.checkboxDisabled : ""}`}>
|
||||
<input type="checkbox" checked={checked} onChange={onToggle} disabled={disabled} />
|
||||
<span>{hw.label}</span>
|
||||
</label>
|
||||
{checked && hw.description && (
|
||||
<div className={styles.hardwareDescription}>{renderDescription(hw.description)}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HardwareOptions({ deviceId, hardwareEnabled, onToggle, isDisabled }: Props) {
|
||||
return (
|
||||
<div className={styles.formSection}>
|
||||
<h4>Generic Hardware Devices</h4>
|
||||
{deviceId !== "stable" && (
|
||||
<p className={styles.helpText}>
|
||||
Some options have been auto-configured based on your device type.
|
||||
</p>
|
||||
)}
|
||||
<div className={styles.checkboxGrid}>
|
||||
{hardwareOptions.map((hw) => {
|
||||
const disabled = isDisabled(hw.id);
|
||||
const checked = disabled ? false : !!hardwareEnabled[hw.id];
|
||||
return (
|
||||
<HardwareCheckbox key={hw.id} hw={hw} disabled={disabled} checked={checked} onToggle={() => onToggle(hw.id)} />
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from "react";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
interface Props {
|
||||
gpuCount: string;
|
||||
gpuDeviceId: string;
|
||||
gpuDeviceIdError: boolean;
|
||||
onGpuCountChange: (value: string) => void;
|
||||
onGpuDeviceIdChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function NvidiaGpuConfig({
|
||||
gpuCount,
|
||||
gpuDeviceId,
|
||||
gpuDeviceIdError,
|
||||
onGpuCountChange,
|
||||
onGpuDeviceIdChange,
|
||||
}: Props) {
|
||||
const showDeviceId = gpuCount !== "";
|
||||
|
||||
return (
|
||||
<div className={styles.nvidiaConfig}>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-gpu-count" className={styles.label}>
|
||||
GPU count:
|
||||
</label>
|
||||
<input
|
||||
id="dcg-gpu-count"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
className={styles.input}
|
||||
value={gpuCount}
|
||||
placeholder="all"
|
||||
onChange={(e) => onGpuCountChange(e.target.value.replace(/\D/g, ""))}
|
||||
/>
|
||||
</div>
|
||||
{showDeviceId && (
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-gpu-device-id" className={styles.label}>
|
||||
GPU device IDs (required, comma-separated):
|
||||
</label>
|
||||
<input
|
||||
id="dcg-gpu-device-id"
|
||||
type="text"
|
||||
className={`${styles.input} ${gpuDeviceIdError ? styles.inputError : ""}`}
|
||||
value={gpuDeviceId}
|
||||
placeholder="0"
|
||||
onChange={(e) => onGpuDeviceIdChange(e.target.value)}
|
||||
/>
|
||||
{gpuDeviceIdError ? (
|
||||
<p className={styles.helpText}>
|
||||
⚠️ GPU device IDs are required when GPU count is a number
|
||||
</p>
|
||||
) : (
|
||||
<p className={styles.helpText}>
|
||||
Single GPU: 0 | Multiple GPUs: 0,1,2
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { useMemo } from "react";
|
||||
import CodeInline from "@theme/CodeInline";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
const AUTO_TIMEZONE_VALUE = "__auto__";
|
||||
|
||||
function getTimezoneList(): string[] {
|
||||
if (typeof Intl !== "undefined") {
|
||||
const intl = Intl as typeof Intl & {
|
||||
supportedValuesOf?: (key: string) => string[];
|
||||
};
|
||||
const supported = intl.supportedValuesOf?.("timeZone");
|
||||
if (supported && supported.length > 0) {
|
||||
return [...supported].sort();
|
||||
}
|
||||
}
|
||||
|
||||
const fallback = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
return fallback ? [fallback] : ["UTC"];
|
||||
}
|
||||
|
||||
interface Props {
|
||||
rtspPassword: string;
|
||||
timezone: string;
|
||||
shmSize: string;
|
||||
shmSizeError: boolean;
|
||||
onRtspPasswordChange: (value: string) => void;
|
||||
onTimezoneChange: (value: string) => void;
|
||||
onShmSizeChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function OtherOptions({
|
||||
rtspPassword,
|
||||
timezone,
|
||||
shmSize,
|
||||
shmSizeError,
|
||||
onRtspPasswordChange,
|
||||
onTimezoneChange,
|
||||
onShmSizeChange,
|
||||
}: Props) {
|
||||
const timezones = useMemo(() => getTimezoneList(), []);
|
||||
const systemTimezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone || "Etc/UTC";
|
||||
const selectedValue = timezone || AUTO_TIMEZONE_VALUE;
|
||||
|
||||
return (
|
||||
<div className={styles.formSection}>
|
||||
<h4>Other Options</h4>
|
||||
<div className={styles.formGrid}>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-timezone" className={styles.label}>
|
||||
Timezone:
|
||||
</label>
|
||||
<select
|
||||
id="dcg-timezone"
|
||||
className={`${styles.input} ${styles.select}`}
|
||||
value={selectedValue}
|
||||
onChange={(e) =>
|
||||
onTimezoneChange(
|
||||
e.target.value === AUTO_TIMEZONE_VALUE ? "" : e.target.value
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value={AUTO_TIMEZONE_VALUE}>
|
||||
Use browser timezone ({systemTimezone})
|
||||
</option>
|
||||
{timezones.map((tz) => (
|
||||
<option key={tz} value={tz}>
|
||||
{tz}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-shm-size" className={styles.label}>
|
||||
Shared memory (SHM):
|
||||
</label>
|
||||
<input
|
||||
id="dcg-shm-size"
|
||||
type="text"
|
||||
className={`${styles.input} ${shmSizeError ? styles.inputError : ""}`}
|
||||
value={shmSize}
|
||||
placeholder="512mb"
|
||||
onChange={(e) => onShmSizeChange(e.target.value)}
|
||||
/>
|
||||
{shmSizeError ? (
|
||||
<p className={styles.helpText}>
|
||||
⚠️ Invalid format. Use a number followed by a unit (e.g. 512mb, 1gb)
|
||||
</p>
|
||||
) : (
|
||||
<p className={styles.helpText}>
|
||||
See{" "}
|
||||
<a href="/frigate/installation#calculating-required-shm-size">
|
||||
calculating required SHM size
|
||||
</a>{" "}
|
||||
for the correct value.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-rtsp-password" className={styles.label}>
|
||||
RTSP password:
|
||||
</label>
|
||||
<input
|
||||
id="dcg-rtsp-password"
|
||||
type="text"
|
||||
className={styles.input}
|
||||
value={rtspPassword}
|
||||
placeholder="password"
|
||||
onChange={(e) => onRtspPasswordChange(e.target.value)}
|
||||
/>
|
||||
<p className={styles.helpText}>
|
||||
Optional. You can specify{" "}
|
||||
<CodeInline>{"{FRIGATE_RTSP_PASSWORD}"}</CodeInline>{" "}
|
||||
in the config file to reference camera stream passwords. This is NOT
|
||||
the Frigate login password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from "react";
|
||||
import Admonition from "@theme/Admonition";
|
||||
import { ports } from "../config";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
interface Props {
|
||||
portEnabled: Record<string, boolean>;
|
||||
onTogglePort: (portId: string) => void;
|
||||
}
|
||||
|
||||
function PortItem({
|
||||
port,
|
||||
enabled,
|
||||
onToggle,
|
||||
}: {
|
||||
port: typeof ports[number];
|
||||
enabled: boolean;
|
||||
onToggle: () => void;
|
||||
}) {
|
||||
const showWarning = port.warningContent && (
|
||||
port.warningWhen === "checked" ? enabled :
|
||||
port.warningWhen === "unchecked" ? !enabled : enabled
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.hardwareItem}>
|
||||
<label className={`${styles.checkboxLabel} ${port.locked ? styles.checkboxDisabled : ""}`}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={onToggle}
|
||||
disabled={port.locked}
|
||||
/>
|
||||
<span>
|
||||
{port.locked && "🔒 "}
|
||||
Port {port.host}
|
||||
{port.protocol !== "tcp" && `/${port.protocol}`}
|
||||
</span>
|
||||
</label>
|
||||
{port.description && (
|
||||
<div className={styles.hardwareDescription}>{port.description}</div>
|
||||
)}
|
||||
{showWarning && (
|
||||
<Admonition type={port.warningType || "warning"}>
|
||||
{port.warningContent}
|
||||
</Admonition>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PortConfigSection({
|
||||
portEnabled,
|
||||
onTogglePort,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className={styles.formSection}>
|
||||
<h4>Port Configuration</h4>
|
||||
<div className={styles.checkboxGrid}>
|
||||
{ports.map((port) => (
|
||||
<PortItem
|
||||
key={port.id}
|
||||
port={port}
|
||||
enabled={!!portEnabled[port.id]}
|
||||
onToggle={() => onTogglePort(port.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import styles from "../styles.module.css";
|
||||
|
||||
interface Props {
|
||||
configPath: string;
|
||||
mediaPath: string;
|
||||
configPathError: boolean;
|
||||
mediaPathError: boolean;
|
||||
onConfigPathChange: (value: string) => void;
|
||||
onMediaPathChange: (value: string) => void;
|
||||
}
|
||||
|
||||
export default function StoragePaths({
|
||||
configPath,
|
||||
mediaPath,
|
||||
configPathError,
|
||||
mediaPathError,
|
||||
onConfigPathChange,
|
||||
onMediaPathChange,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className={styles.formSection}>
|
||||
<h4>Storage Paths</h4>
|
||||
<div className={styles.formGrid}>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-config-path" className={styles.label}>
|
||||
Config / DB / model cache directory (on your host):
|
||||
</label>
|
||||
<input
|
||||
id="dcg-config-path"
|
||||
type="text"
|
||||
className={`${styles.input} ${configPathError ? styles.inputError : ""}`}
|
||||
value={configPath}
|
||||
placeholder="/path/to/your/config"
|
||||
onChange={(e) => onConfigPathChange(e.target.value)}
|
||||
/>
|
||||
{configPathError && (
|
||||
<p className={styles.helpText}>
|
||||
⚠️ Path contains invalid characters. Only letters, numbers,
|
||||
underscores, hyphens, slashes, and dots are allowed.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.formGroup}>
|
||||
<label htmlFor="dcg-media-path" className={styles.label}>
|
||||
Recording storage directory (on your host):
|
||||
</label>
|
||||
<input
|
||||
id="dcg-media-path"
|
||||
type="text"
|
||||
className={`${styles.input} ${mediaPathError ? styles.inputError : ""}`}
|
||||
value={mediaPath}
|
||||
placeholder="/path/to/your/storage"
|
||||
onChange={(e) => onMediaPathChange(e.target.value)}
|
||||
/>
|
||||
{mediaPathError && (
|
||||
<p className={styles.helpText}>
|
||||
⚠️ Path contains invalid characters. Only letters, numbers,
|
||||
underscores, hyphens, slashes, and dots are allowed.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
export { devices, deviceMap } from "./devices";
|
||||
export { hardwareOptions, hardwareMap } from "./hardware";
|
||||
export { ports, portMap } from "./ports";
|
||||
|
||||
export type {
|
||||
DeviceConfig,
|
||||
DeviceMapping,
|
||||
VolumeMapping,
|
||||
HardwareOption,
|
||||
PortConfig,
|
||||
NvidiaDeployConfig,
|
||||
} from "./types";
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Type definitions for the Docker Compose Generator configuration.
|
||||
* All device, hardware, and port options are declaratively defined
|
||||
* so that adding a new device only requires editing config files.
|
||||
*/
|
||||
|
||||
/** A single device mapping entry (e.g. /dev/dri:/dev/dri) */
|
||||
export interface DeviceMapping {
|
||||
/** Host device path */
|
||||
host: string;
|
||||
/** Container device path (defaults to host if omitted) */
|
||||
container?: string;
|
||||
/** Inline comment for this device line */
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
/** A single volume mapping entry */
|
||||
export interface VolumeMapping {
|
||||
/** Host path */
|
||||
host: string;
|
||||
/** Container path */
|
||||
container: string;
|
||||
/** Whether the mount is read-only */
|
||||
readOnly?: boolean;
|
||||
/** Inline comment */
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
/** NVIDIA deploy configuration for docker-compose */
|
||||
export interface NvidiaDeployConfig {
|
||||
/** "all" or a specific number */
|
||||
count: string;
|
||||
/** Specific GPU device IDs (when count is a number) */
|
||||
deviceIds?: string[];
|
||||
}
|
||||
|
||||
/** Full device type definition */
|
||||
export interface DeviceConfig {
|
||||
/** Unique identifier, e.g. "intel" */
|
||||
id: string;
|
||||
/** Display name, e.g. "Intel GPU" */
|
||||
name: string;
|
||||
/** Short description */
|
||||
description: string;
|
||||
/**
|
||||
* Icon for the device card. Supports:
|
||||
* - Emoji string (e.g. "🖥️")
|
||||
* - Image URL or static path (e.g. "/img/intel.svg", "https://example.com/icon.png")
|
||||
* - Inline SVG markup (e.g. "<svg>...</svg>")
|
||||
*/
|
||||
icon: string;
|
||||
/**
|
||||
* Additional CSS properties applied to the icon element.
|
||||
* - For image-type icons: if any `background-*` property (e.g. `background-size`,
|
||||
* `background-position`) is present, the image is rendered as a CSS `background-image`
|
||||
* on the container div, enabling full background positioning control.
|
||||
* Otherwise the image is rendered as an `<img>` tag and styles apply to it.
|
||||
* - For emoji/SVG icons: styles apply to the container div.
|
||||
*/
|
||||
iconStyle?: Record<string, string>;
|
||||
/**
|
||||
* Additional CSS properties applied directly to the inner `<svg>` element
|
||||
* when the icon is an inline SVG. Use this to override the default
|
||||
* `width: 100%; height: 100%` or set `fill`, `transform`, etc.
|
||||
* Ignored for emoji and image-type icons.
|
||||
*/
|
||||
svgStyle?: Record<string, string>;
|
||||
/**
|
||||
* Icon for dark mode. Same format as `icon`. When provided, this icon
|
||||
* replaces `icon` when the user is in dark mode.
|
||||
*/
|
||||
iconDark?: string;
|
||||
/** Additional CSS properties for the dark mode icon container */
|
||||
iconDarkStyle?: Record<string, string>;
|
||||
/**
|
||||
* SVG-specific styles for dark mode. Same as `svgStyle` but applied
|
||||
* when dark mode is active. Merged over `svgStyle` in dark mode.
|
||||
*/
|
||||
svgDarkStyle?: Record<string, string>;
|
||||
/** Docker image tag, e.g. "stable" */
|
||||
imageTag: string;
|
||||
/**
|
||||
* Image tag suffix appended to the base tag.
|
||||
* e.g. "-standard-arm64" produces "stable-standard-arm64"
|
||||
*/
|
||||
imageTagSuffix?: string;
|
||||
/** Hardware option IDs to auto-enable when this device is selected */
|
||||
autoHardware: string[];
|
||||
/** Help text shown as an admonition when this device is selected */
|
||||
helpText?: string;
|
||||
/** Admonition type for help text */
|
||||
helpType?: "info" | "warning" | "danger";
|
||||
/** Device mappings always added for this device type */
|
||||
devices?: DeviceMapping[];
|
||||
/** Volume mappings always added for this device type */
|
||||
volumes?: VolumeMapping[];
|
||||
/** Extra environment variables for this device type */
|
||||
env?: Record<string, string>;
|
||||
/** NVIDIA deploy config (only for tensorrt) */
|
||||
nvidiaDeploy?: NvidiaDeployConfig;
|
||||
/** Runtime setting, e.g. "nvidia" for Jetson */
|
||||
runtime?: string;
|
||||
/** Extra hosts entries, e.g. "host.docker.internal:host-gateway" */
|
||||
extraHosts?: string[];
|
||||
/** Security options, e.g. ["apparmor=unconfined"] */
|
||||
securityOpt?: string[];
|
||||
/** Whether this device type needs the NVIDIA GPU config UI */
|
||||
needsNvidiaConfig?: boolean;
|
||||
}
|
||||
|
||||
/** Generic hardware acceleration option definition */
|
||||
export interface HardwareOption {
|
||||
/** Unique identifier, e.g. "usbCoral" */
|
||||
id: string;
|
||||
/** Display label */
|
||||
label: string;
|
||||
/**
|
||||
* Description shown below the checkbox when this option is enabled.
|
||||
* Supports markdown link syntax: [text](url)
|
||||
*/
|
||||
description?: string;
|
||||
/** Device IDs that disable this option */
|
||||
disabledWhen?: string[];
|
||||
/** Device mappings added when this option is enabled */
|
||||
devices?: DeviceMapping[];
|
||||
/** Volume mappings added when this option is enabled */
|
||||
volumes?: VolumeMapping[];
|
||||
/** Extra environment variables */
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
/** Port definition */
|
||||
export interface PortConfig {
|
||||
/** Unique identifier (also the default host port as string) */
|
||||
id: string;
|
||||
/** Host port number */
|
||||
host: number;
|
||||
/** Container port number */
|
||||
container: number;
|
||||
/** Protocol */
|
||||
protocol?: "tcp" | "udp";
|
||||
/** Description of the port's purpose */
|
||||
description: string;
|
||||
/** Whether enabled by default */
|
||||
defaultEnabled: boolean;
|
||||
/** Whether this port is locked (always enabled, cannot be toggled off) */
|
||||
locked?: boolean;
|
||||
/** Admonition type for the warning */
|
||||
warningType?: "warning" | "danger";
|
||||
/** Warning content (markdown) */
|
||||
warningContent?: string;
|
||||
/** When to show the warning: when the port is checked or unchecked */
|
||||
warningWhen?: "checked" | "unchecked";
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
import type {
|
||||
DeviceConfig,
|
||||
DeviceMapping,
|
||||
VolumeMapping,
|
||||
} from "../config/types";
|
||||
import { hardwareMap } from "../config";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input type
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface GeneratorInput {
|
||||
device: DeviceConfig;
|
||||
selectedHardware: string[];
|
||||
enabledPorts: string[];
|
||||
configPath: string;
|
||||
mediaPath: string;
|
||||
rtspPassword?: string;
|
||||
timezone: string;
|
||||
shmSize: string;
|
||||
nvidiaGpuCount?: string;
|
||||
nvidiaGpuDeviceId?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function deviceLine(dm: DeviceMapping): string {
|
||||
const host = dm.host;
|
||||
const container = dm.container ?? dm.host;
|
||||
const mapping = host === container ? host : `${host}:${container}`;
|
||||
const comment = dm.comment ? ` # ${dm.comment}` : "";
|
||||
return ` - ${mapping}${comment}`;
|
||||
}
|
||||
|
||||
function volumeLine(vm: VolumeMapping): string {
|
||||
const ro = vm.readOnly ? ":ro" : "";
|
||||
const comment = vm.comment ? ` # ${vm.comment}` : "";
|
||||
return ` - ${vm.host}:${vm.container}${ro}${comment}`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// YAML builder — each section returns an array of lines
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function buildImage(device: DeviceConfig): string[] {
|
||||
const tag = device.imageTagSuffix
|
||||
? `${device.imageTag}${device.imageTagSuffix}`
|
||||
: device.imageTag;
|
||||
return [` image: ghcr.io/blakeblackshear/frigate:${tag}`];
|
||||
}
|
||||
|
||||
function buildDevices(
|
||||
device: DeviceConfig,
|
||||
hwDevices: DeviceMapping[]
|
||||
): string[] {
|
||||
const all: DeviceMapping[] = [
|
||||
...(device.devices ?? []),
|
||||
...hwDevices,
|
||||
];
|
||||
if (all.length === 0) return [];
|
||||
return [
|
||||
" devices:",
|
||||
...all.map(deviceLine),
|
||||
];
|
||||
}
|
||||
|
||||
function buildVolumes(
|
||||
device: DeviceConfig,
|
||||
hwVolumes: VolumeMapping[],
|
||||
configPath: string,
|
||||
mediaPath: string
|
||||
): string[] {
|
||||
const all: VolumeMapping[] = [
|
||||
...(device.volumes ?? []),
|
||||
...hwVolumes,
|
||||
];
|
||||
return [
|
||||
" volumes:",
|
||||
" - /etc/localtime:/etc/localtime:ro # Sync host time",
|
||||
` - ${configPath}:/config # Config file directory`,
|
||||
` - ${mediaPath}:/media/frigate # Recording storage directory`,
|
||||
" - type: tmpfs # 1GB in-memory filesystem for recording segment storage",
|
||||
" target: /tmp/cache",
|
||||
" tmpfs:",
|
||||
" size: 1000000000",
|
||||
...all.map(volumeLine),
|
||||
];
|
||||
}
|
||||
|
||||
function buildPorts(enabledPorts: string[]): string[] {
|
||||
return [
|
||||
" ports:",
|
||||
...enabledPorts,
|
||||
];
|
||||
}
|
||||
|
||||
function buildEnvironment(
|
||||
device: DeviceConfig,
|
||||
hwEnv: Record<string, string>,
|
||||
rtspPassword: string | undefined,
|
||||
timezone: string
|
||||
): string[] {
|
||||
const allEnv: Record<string, string> = {
|
||||
...hwEnv,
|
||||
...(device.env ?? {}),
|
||||
};
|
||||
|
||||
const lines: string[] = [" environment:"];
|
||||
|
||||
if (rtspPassword) {
|
||||
lines.push(
|
||||
` FRIGATE_RTSP_PASSWORD: "${rtspPassword}" # RTSP password — change to your own`
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(` TZ: "${timezone}" # Timezone`);
|
||||
|
||||
for (const [key, value] of Object.entries(allEnv)) {
|
||||
lines.push(` ${key}: "${value}"`);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
function buildDeploy(device: DeviceConfig, input: GeneratorInput): string[] {
|
||||
if (device.id === "stable-tensorrt") {
|
||||
const count = input.nvidiaGpuCount || "all";
|
||||
const isAll = count === "all";
|
||||
const deviceId = input.nvidiaGpuDeviceId?.trim();
|
||||
|
||||
if (isAll) {
|
||||
return [
|
||||
" deploy:",
|
||||
" resources:",
|
||||
" reservations:",
|
||||
" devices:",
|
||||
" - driver: nvidia",
|
||||
" count: all # Use all GPUs",
|
||||
" capabilities: [gpu]",
|
||||
];
|
||||
}
|
||||
|
||||
if (deviceId) {
|
||||
const ids = deviceId
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
.map((s) => `'${s}'`)
|
||||
.join(", ");
|
||||
return [
|
||||
" deploy:",
|
||||
" resources:",
|
||||
" reservations:",
|
||||
" devices:",
|
||||
" - driver: nvidia",
|
||||
` device_ids: [${ids}] # GPU device IDs`,
|
||||
` count: ${count} # GPU count`,
|
||||
" capabilities: [gpu]",
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
" deploy:",
|
||||
" resources:",
|
||||
" reservations:",
|
||||
" devices:",
|
||||
" - driver: nvidia",
|
||||
` count: ${count} # GPU count`,
|
||||
" capabilities: [gpu]",
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function buildRuntime(device: DeviceConfig): string[] {
|
||||
if (device.runtime) {
|
||||
return [` runtime: ${device.runtime}`];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function buildExtraHosts(device: DeviceConfig): string[] {
|
||||
if (!device.extraHosts?.length) return [];
|
||||
return [
|
||||
" extra_hosts:",
|
||||
...device.extraHosts.map(
|
||||
(h, i) =>
|
||||
` - "${h}"${i === 0 ? " # Required to talk to the NPU detector" : ""}`
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function buildSecurityOpt(device: DeviceConfig): string[] {
|
||||
if (!device.securityOpt?.length) return [];
|
||||
return [
|
||||
" security_opt:",
|
||||
...device.securityOpt.map((s) => ` - ${s}`),
|
||||
];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Generate a docker-compose YAML string from the given input.
|
||||
* The output is pure YAML with inline comments (no Shiki annotations).
|
||||
*/
|
||||
export function generateDockerCompose(input: GeneratorInput): string {
|
||||
const { device } = input;
|
||||
|
||||
// Collect hardware-level devices, volumes, and env
|
||||
const hwDevices: DeviceMapping[] = [];
|
||||
const hwVolumes: VolumeMapping[] = [];
|
||||
const hwEnv: Record<string, string> = {};
|
||||
|
||||
for (const hwId of input.selectedHardware) {
|
||||
const hw = hardwareMap.get(hwId);
|
||||
if (!hw) continue;
|
||||
// Skip GPU device mapping for tensorrt images (it uses deploy instead)
|
||||
if (hw.id === "gpu" && device.imageTag === "stable-tensorrt") continue;
|
||||
hwDevices.push(...(hw.devices ?? []));
|
||||
hwVolumes.push(...(hw.volumes ?? []));
|
||||
Object.assign(hwEnv, hw.env ?? {});
|
||||
}
|
||||
|
||||
const lines: string[] = [
|
||||
"services:",
|
||||
" frigate:",
|
||||
" container_name: frigate",
|
||||
" privileged: true # This may not be necessary for all setups",
|
||||
" restart: unless-stopped",
|
||||
" stop_grace_period: 30s # Allow enough time to shut down the various services",
|
||||
...buildImage(device),
|
||||
` shm_size: "${input.shmSize || "512mb"}" # Update for your cameras based on SHM calculation`,
|
||||
...buildRuntime(device),
|
||||
...buildDeploy(device, input),
|
||||
...buildExtraHosts(device),
|
||||
...buildSecurityOpt(device),
|
||||
...buildDevices(device, hwDevices),
|
||||
...buildVolumes(device, hwVolumes, input.configPath, input.mediaPath),
|
||||
...buildPorts(input.enabledPorts),
|
||||
...buildEnvironment(device, hwEnv, input.rtspPassword, input.timezone),
|
||||
];
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import { deviceMap, hardwareMap, portMap } from "../config";
|
||||
import { generateDockerCompose } from "../generator";
|
||||
import type { GeneratorInput } from "../generator";
|
||||
|
||||
/**
|
||||
* Main hook that holds all form state and generates the Docker Compose output.
|
||||
* Configuration is loaded synchronously from build-time generated .ts files.
|
||||
*/
|
||||
export function useConfigGenerator() {
|
||||
const [deviceId, setDeviceId] = useState("stable");
|
||||
|
||||
const [hardwareEnabled, setHardwareEnabled] = useState<Record<string, boolean>>(() => {
|
||||
const defaultDevice = deviceMap.get("stable");
|
||||
const initial: Record<string, boolean> = {};
|
||||
if (defaultDevice) {
|
||||
for (const hwId of defaultDevice.autoHardware) {
|
||||
initial[hwId] = true;
|
||||
}
|
||||
}
|
||||
return initial;
|
||||
});
|
||||
|
||||
const [portEnabled, setPortEnabled] = useState<Record<string, boolean>>(() => {
|
||||
const initial: Record<string, boolean> = {};
|
||||
for (const p of portMap.values()) {
|
||||
initial[p.id] = p.defaultEnabled;
|
||||
}
|
||||
return initial;
|
||||
});
|
||||
|
||||
const [nvidiaGpuCount, setNvidiaGpuCount] = useState("");
|
||||
const [nvidiaGpuDeviceId, setNvidiaGpuDeviceId] = useState("");
|
||||
const [configPath, setConfigPath] = useState("");
|
||||
const [mediaPath, setMediaPath] = useState("");
|
||||
const [rtspPassword, setRtspPassword] = useState("");
|
||||
const [timezone, setTimezone] = useState("");
|
||||
const [shmSize, setShmSize] = useState("512mb");
|
||||
const [shmSizeError, setShmSizeError] = useState(false);
|
||||
const [gpuDeviceIdError, setGpuDeviceIdError] = useState(false);
|
||||
const [configPathError, setConfigPathError] = useState(false);
|
||||
const [mediaPathError, setMediaPathError] = useState(false);
|
||||
|
||||
const device = useMemo(() => deviceMap.get(deviceId)!, [deviceId]);
|
||||
|
||||
const selectDevice = useCallback((id: string) => {
|
||||
const newDevice = deviceMap.get(id);
|
||||
if (!newDevice) return;
|
||||
setDeviceId(id);
|
||||
setHardwareEnabled(() => {
|
||||
const next: Record<string, boolean> = {};
|
||||
for (const hwId of newDevice.autoHardware) {
|
||||
next[hwId] = true;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
setNvidiaGpuCount("");
|
||||
setNvidiaGpuDeviceId("");
|
||||
setGpuDeviceIdError(false);
|
||||
}, []);
|
||||
|
||||
const toggleHardware = useCallback((hwId: string) => {
|
||||
setHardwareEnabled((prev) => ({ ...prev, [hwId]: !prev[hwId] }));
|
||||
}, []);
|
||||
|
||||
const togglePort = useCallback((portId: string) => {
|
||||
const port = portMap.get(portId);
|
||||
if (port?.locked) return;
|
||||
setPortEnabled((prev) => ({ ...prev, [portId]: !prev[portId] }));
|
||||
}, []);
|
||||
|
||||
const isHardwareDisabled = useCallback(
|
||||
(hwId: string): boolean => {
|
||||
const hw = hardwareMap.get(hwId);
|
||||
if (!hw) return false;
|
||||
return hw.disabledWhen?.includes(deviceId) ?? false;
|
||||
},
|
||||
[deviceId]
|
||||
);
|
||||
|
||||
const validateShmSize = useCallback((value: string): boolean => {
|
||||
if (!value) return true;
|
||||
return /^\d+(\.\d+)?[bkmgBKMG]{1,2}$/.test(value);
|
||||
}, []);
|
||||
|
||||
const validatePath = useCallback((value: string): boolean => {
|
||||
if (!value) return true;
|
||||
return /^[a-zA-Z0-9_\-/./]+$/.test(value);
|
||||
}, []);
|
||||
|
||||
const handleShmSizeChange = useCallback(
|
||||
(value: string) => {
|
||||
const filtered = value.replace(/[^0-9.bkmgBKMG]/g, "");
|
||||
const valid = validateShmSize(filtered);
|
||||
setShmSize(filtered);
|
||||
setShmSizeError(!valid && filtered !== "");
|
||||
},
|
||||
[validateShmSize]
|
||||
);
|
||||
|
||||
const handleConfigPathChange = useCallback(
|
||||
(value: string) => {
|
||||
const filtered = value.replace(/[^a-zA-Z0-9_\-/./]/g, "");
|
||||
const valid = validatePath(filtered);
|
||||
setConfigPath(filtered);
|
||||
setConfigPathError(!valid && filtered !== "");
|
||||
},
|
||||
[validatePath]
|
||||
);
|
||||
|
||||
const handleMediaPathChange = useCallback(
|
||||
(value: string) => {
|
||||
const filtered = value.replace(/[^a-zA-Z0-9_\-/./]/g, "");
|
||||
const valid = validatePath(filtered);
|
||||
setMediaPath(filtered);
|
||||
setMediaPathError(!valid && filtered !== "");
|
||||
},
|
||||
[validatePath]
|
||||
);
|
||||
|
||||
const handleNvidiaGpuCountChange = useCallback((value: string) => {
|
||||
// Only allow digits
|
||||
setNvidiaGpuCount(value);
|
||||
if (value === "") {
|
||||
setNvidiaGpuDeviceId("");
|
||||
setGpuDeviceIdError(false);
|
||||
} else {
|
||||
setGpuDeviceIdError(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleNvidiaGpuDeviceIdChange = useCallback((value: string) => {
|
||||
setNvidiaGpuDeviceId(value.trim());
|
||||
setGpuDeviceIdError(false);
|
||||
}, []);
|
||||
|
||||
const enabledPortLines = useMemo(() => {
|
||||
const lines: string[] = [];
|
||||
for (const [id, enabled] of Object.entries(portEnabled)) {
|
||||
if (!enabled) continue;
|
||||
const p = portMap.get(id);
|
||||
if (!p) continue;
|
||||
const proto = p.protocol && p.protocol !== "tcp" ? `/${p.protocol}` : "";
|
||||
const comment = p.description ? ` # ${p.description}` : "";
|
||||
lines.push(` - "${p.host}:${p.container}${proto}"${comment}`);
|
||||
}
|
||||
return lines;
|
||||
}, [portEnabled]);
|
||||
|
||||
const selectedHardwareIds = useMemo(() => {
|
||||
return Object.entries(hardwareEnabled)
|
||||
.filter(([id, enabled]) => {
|
||||
if (!enabled) return false;
|
||||
const hw = hardwareMap.get(id);
|
||||
if (!hw) return false;
|
||||
if (hw.disabledWhen?.includes(deviceId)) return false;
|
||||
return true;
|
||||
})
|
||||
.map(([id]) => id);
|
||||
}, [hardwareEnabled, deviceId]);
|
||||
|
||||
const generatedYaml = useMemo(() => {
|
||||
const input: GeneratorInput = {
|
||||
device,
|
||||
selectedHardware: selectedHardwareIds,
|
||||
enabledPorts: enabledPortLines,
|
||||
configPath: configPath || "/path/to/your/config",
|
||||
mediaPath: mediaPath || "/path/to/your/storage",
|
||||
rtspPassword,
|
||||
timezone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone || "Etc/UTC",
|
||||
shmSize: shmSize || "512mb",
|
||||
nvidiaGpuCount,
|
||||
nvidiaGpuDeviceId,
|
||||
};
|
||||
return generateDockerCompose(input);
|
||||
}, [
|
||||
device, selectedHardwareIds, enabledPortLines,
|
||||
configPath, mediaPath, rtspPassword, timezone, shmSize,
|
||||
nvidiaGpuCount, nvidiaGpuDeviceId,
|
||||
]);
|
||||
|
||||
const hasAnyHardware = selectedHardwareIds.length > 0 || !!device?.devices?.length;
|
||||
|
||||
return {
|
||||
deviceId, device, hardwareEnabled, portEnabled,
|
||||
nvidiaGpuCount, nvidiaGpuDeviceId,
|
||||
configPath, mediaPath, rtspPassword, timezone, shmSize,
|
||||
shmSizeError, gpuDeviceIdError, configPathError, mediaPathError,
|
||||
hasAnyHardware, generatedYaml,
|
||||
selectDevice, toggleHardware, togglePort,
|
||||
handleShmSizeChange, handleConfigPathChange, handleMediaPathChange,
|
||||
handleNvidiaGpuCountChange, handleNvidiaGpuDeviceIdChange,
|
||||
setRtspPassword, setTimezone, isHardwareDisabled,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./DockerComposeGenerator";
|
||||
@@ -0,0 +1,381 @@
|
||||
/* ===================================================================
|
||||
Docker Compose Generator — styles
|
||||
Uses Docusaurus / Infima CSS variables for theme compatibility.
|
||||
=================================================================== */
|
||||
|
||||
.generator {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--ifm-background-surface-color);
|
||||
border: 1px solid var(--ifm-color-emphasis-400);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: var(--ifm-global-shadow-lw);
|
||||
}
|
||||
|
||||
[data-theme="light"] .card {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
border: 1px solid var(--ifm-color-emphasis-300);
|
||||
}
|
||||
|
||||
/* --- Form sections --- */
|
||||
|
||||
.formSection {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-400);
|
||||
}
|
||||
|
||||
.formSection:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.formSection h4 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: var(--ifm-font-color-base);
|
||||
font-size: 1.1rem;
|
||||
font-weight: var(--ifm-font-weight-semibold);
|
||||
}
|
||||
|
||||
/* --- Form controls --- */
|
||||
|
||||
.formGroup {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.formGroup:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
color: var(--ifm-font-color-base);
|
||||
font-weight: var(--ifm-font-weight-semibold);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--ifm-color-emphasis-400);
|
||||
border-radius: 6px;
|
||||
background: var(--ifm-background-color);
|
||||
color: var(--ifm-font-color-base);
|
||||
font-size: 0.95rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
[data-theme="light"] .input {
|
||||
background: #fff;
|
||||
border: 1px solid #d0d7de;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--ifm-color-primary);
|
||||
box-shadow: 0 0 0 3px var(--ifm-color-primary-lightest);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .input {
|
||||
border-color: var(--ifm-color-emphasis-300);
|
||||
}
|
||||
|
||||
.inputError {
|
||||
border-color: #e74c3c;
|
||||
animation: shake 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Select dropdown --- */
|
||||
|
||||
.select {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: var(--ifm-background-color)
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E")
|
||||
no-repeat right 0.75rem center / 12px 12px;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
[data-theme="light"] .select {
|
||||
background: #fff
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23555' d='M6 8L1 3h10z'/%3E%3C/svg%3E")
|
||||
no-repeat right 0.75rem center / 12px 12px;
|
||||
}
|
||||
|
||||
.helpText {
|
||||
margin: 0.5rem 0 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--ifm-font-color-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.helpText a {
|
||||
color: var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
/* --- Device grid --- */
|
||||
|
||||
.deviceGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.deviceCard {
|
||||
padding: 0.75rem;
|
||||
border: 2px solid var(--ifm-color-emphasis-400);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
text-align: center;
|
||||
background: var(--ifm-background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-theme="light"] .deviceCard {
|
||||
border: 2px solid #d0d7de;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.deviceCard:hover {
|
||||
border-color: var(--ifm-color-primary);
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.deviceCardActive {
|
||||
border-color: var(--ifm-color-primary);
|
||||
background: var(--ifm-color-primary-lightest);
|
||||
box-shadow: 0 0 0 1px var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .deviceCardActive {
|
||||
background: color-mix(in srgb, var(--ifm-color-primary) 12%, #fff);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .deviceCardActive {
|
||||
background: color-mix(in srgb, var(--ifm-color-primary) 25%, #1b1b1b);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .deviceCardActive .deviceName {
|
||||
color: var(--ifm-color-primary-light);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .deviceCardActive .deviceDesc {
|
||||
color: var(--ifm-color-primary-light);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.deviceIcon {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.25rem;
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deviceIconSvg {
|
||||
margin-bottom: 0.25rem;
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: visible;
|
||||
/* Allow iconStyle width/height to override */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.deviceIconSvg svg {
|
||||
width: var(--svg-width, 100%);
|
||||
height: var(--svg-height, 100%);
|
||||
fill: var(--svg-fill, currentColor);
|
||||
transform: var(--svg-transform, none);
|
||||
}
|
||||
|
||||
.deviceIconImage {
|
||||
margin-bottom: 0.25rem;
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deviceIconImage img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.deviceName {
|
||||
font-weight: var(--ifm-font-weight-semibold);
|
||||
color: var(--ifm-font-color-base);
|
||||
margin-bottom: 0.15rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.deviceDesc {
|
||||
font-size: 0.75rem;
|
||||
color: var(--ifm-font-color-secondary);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* --- Checkbox grid --- */
|
||||
|
||||
.checkboxGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.checkboxGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.hardwareItem {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.hardwareDescription {
|
||||
margin: 0.15rem 0 0.4rem 1.6rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--ifm-font-color-secondary);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hardwareDescription a {
|
||||
color: var(--ifm-color-primary);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.checkboxLabel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.checkboxLabel:hover {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
|
||||
.checkboxLabel input[type="checkbox"] {
|
||||
width: 1.1rem;
|
||||
height: 1.1rem;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.checkboxLabel span {
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
|
||||
.checkboxDisabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.checkboxDisabled:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.checkboxDisabled input[type="checkbox"] {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* --- Form grid (side-by-side) --- */
|
||||
|
||||
.formGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.formGrid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.formGrid .formGroup {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* --- Port section --- */
|
||||
|
||||
.portSection {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.warningBadge {
|
||||
margin-left: auto;
|
||||
color: #e67e22;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* --- NVIDIA config --- */
|
||||
|
||||
.nvidiaConfig {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: var(--ifm-background-color);
|
||||
border-radius: 8px;
|
||||
border-left: 3px solid var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
[data-theme="light"] .nvidiaConfig {
|
||||
background: #f6f8fa;
|
||||
border-left: 3px solid var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
/* --- Result section --- */
|
||||
|
||||
.resultSection {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.resultHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.resultHeader h4 {
|
||||
margin: 0;
|
||||
color: var(--ifm-font-color-base);
|
||||
}
|
||||
Vendored
+2693
-1403
File diff suppressed because it is too large
Load Diff
+274
-20
@@ -34,15 +34,18 @@ from frigate.api.auth import (
|
||||
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
||||
from frigate.api.defs.request.app_body import (
|
||||
AppConfigSetBody,
|
||||
GenAIProbeBody,
|
||||
MediaSyncBody,
|
||||
)
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.config import FrigateConfig, GenAIConfig, GenAIProviderEnum
|
||||
from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateEnum,
|
||||
CameraConfigUpdateTopic,
|
||||
)
|
||||
from frigate.const import REDACTED_CREDENTIAL_SENTINEL
|
||||
from frigate.ffmpeg_presets import FFMPEG_HWACCEL_VAAPI, _gpu_selector
|
||||
from frigate.genai import PROVIDERS, load_providers
|
||||
from frigate.jobs.media_sync import (
|
||||
get_current_media_sync_job,
|
||||
get_media_sync_job_by_id,
|
||||
@@ -59,7 +62,11 @@ from frigate.util.builtin import (
|
||||
process_config_query_string,
|
||||
update_yaml_file_bulk,
|
||||
)
|
||||
from frigate.util.config import apply_section_update, find_config_file
|
||||
from frigate.util.config import (
|
||||
apply_section_update,
|
||||
find_config_file,
|
||||
redact_credential,
|
||||
)
|
||||
from frigate.util.schema import get_config_schema
|
||||
from frigate.util.services import (
|
||||
get_nvidia_driver_info,
|
||||
@@ -75,6 +82,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(tags=[Tags.app])
|
||||
|
||||
# Short timeout for the /genai/probe path. The probe is interactive — fail
|
||||
# fast on hung providers rather than holding an API worker thread.
|
||||
_PROBE_TIMEOUT_SECONDS = 10
|
||||
# Outer cap that returns control to the caller even if the underlying sync
|
||||
# HTTP call ignores its timeout. The sync work continues in the background
|
||||
# thread; only the response is bounded.
|
||||
_PROBE_OUTER_TIMEOUT_SECONDS = 15
|
||||
|
||||
|
||||
@router.get(
|
||||
"/", response_class=PlainTextResponse, dependencies=[Depends(allow_public())]
|
||||
@@ -96,11 +111,46 @@ def version():
|
||||
|
||||
|
||||
@router.get("/stats", dependencies=[Depends(allow_any_authenticated())])
|
||||
def stats(request: Request):
|
||||
return JSONResponse(content=request.app.stats_emitter.get_latest_stats())
|
||||
def stats(
|
||||
request: Request,
|
||||
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||
):
|
||||
stats_data = request.app.stats_emitter.get_latest_stats()
|
||||
|
||||
# Admins see the full snapshot
|
||||
if request.headers.get("remote-role") == "admin":
|
||||
return JSONResponse(content=stats_data)
|
||||
|
||||
allowed_set = set(allowed_cameras)
|
||||
|
||||
# Shallow-copy so we don't mutate the cached stats history entry.
|
||||
filtered = {**stats_data}
|
||||
|
||||
cameras = stats_data.get("cameras")
|
||||
if cameras is not None:
|
||||
filtered["cameras"] = {
|
||||
name: data for name, data in cameras.items() if name in allowed_set
|
||||
}
|
||||
|
||||
bandwidth = stats_data.get("bandwidth_usages")
|
||||
if bandwidth is not None:
|
||||
filtered["bandwidth_usages"] = {
|
||||
name: data for name, data in bandwidth.items() if name in allowed_set
|
||||
}
|
||||
|
||||
# cmdline can leak camera URLs/paths; strip but keep cpu/mem so
|
||||
# client-side problem heuristics still work.
|
||||
cpu_usages = stats_data.get("cpu_usages")
|
||||
if cpu_usages is not None:
|
||||
filtered["cpu_usages"] = {
|
||||
pid: {k: v for k, v in usage.items() if k != "cmdline"}
|
||||
for pid, usage in cpu_usages.items()
|
||||
}
|
||||
|
||||
return JSONResponse(content=filtered)
|
||||
|
||||
|
||||
@router.get("/stats/history", dependencies=[Depends(allow_any_authenticated())])
|
||||
@router.get("/stats/history", dependencies=[Depends(require_role(["admin"]))])
|
||||
def stats_history(request: Request, keys: str = None):
|
||||
if keys:
|
||||
keys = keys.split(",")
|
||||
@@ -135,6 +185,95 @@ def genai_models(request: Request):
|
||||
return JSONResponse(content=request.app.genai_manager.list_models())
|
||||
|
||||
|
||||
@router.post(
|
||||
"/genai/probe",
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
summary="Probe a GenAI provider without saving config",
|
||||
description=(
|
||||
"Builds a transient client from the request body and returns its "
|
||||
"available models. Used to validate provider credentials in the UI "
|
||||
"before saving the configuration."
|
||||
),
|
||||
)
|
||||
async def genai_probe(body: GenAIProbeBody):
|
||||
load_providers()
|
||||
|
||||
provider_cls = PROVIDERS.get(body.provider)
|
||||
if not provider_cls:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Unknown provider"},
|
||||
)
|
||||
|
||||
# The OpenAI-compatible SDKs accept "timeout" as a constructor kwarg via
|
||||
# provider_options; other plugins use GenAIClient.timeout passed below.
|
||||
# Don't inject timeout for Gemini — its HttpOptions interprets the value
|
||||
# in milliseconds and would clash with the plugin's own default.
|
||||
probe_provider_options: dict[str, Any] = dict(body.provider_options or {})
|
||||
if body.provider in (GenAIProviderEnum.openai, GenAIProviderEnum.azure_openai):
|
||||
probe_provider_options.setdefault("timeout", _PROBE_TIMEOUT_SECONDS)
|
||||
|
||||
try:
|
||||
transient_cfg = GenAIConfig(
|
||||
provider=body.provider,
|
||||
api_key=body.api_key,
|
||||
base_url=body.base_url,
|
||||
provider_options=probe_provider_options,
|
||||
# model is required by the schema but irrelevant for listing.
|
||||
model="probe",
|
||||
roles=[],
|
||||
)
|
||||
except ValidationError:
|
||||
logger.exception("GenAI probe: invalid configuration")
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
content={"success": False, "message": "Invalid provider configuration"},
|
||||
)
|
||||
|
||||
try:
|
||||
client = provider_cls(
|
||||
transient_cfg,
|
||||
timeout=_PROBE_TIMEOUT_SECONDS,
|
||||
validate_model=False,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("GenAI probe: failed to construct client")
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Failed to connect to provider",
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
models = await asyncio.wait_for(
|
||||
asyncio.to_thread(client.list_models),
|
||||
timeout=_PROBE_OUTER_TIMEOUT_SECONDS,
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Probe timed out"},
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("GenAI probe: list_models failed")
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Provider returned no models"},
|
||||
)
|
||||
|
||||
if not models:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": (
|
||||
"No models returned. Check the API key, base URL, and "
|
||||
"that the provider is reachable."
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
return JSONResponse(content={"success": True, "models": models})
|
||||
|
||||
|
||||
@router.get("/config", dependencies=[Depends(allow_any_authenticated())])
|
||||
def config(request: Request):
|
||||
config_obj: FrigateConfig = request.app.frigate_config
|
||||
@@ -146,25 +285,28 @@ def config(request: Request):
|
||||
for name, detector in config_obj.detectors.items()
|
||||
}
|
||||
|
||||
# remove the mqtt password
|
||||
config["mqtt"].pop("password", None)
|
||||
# remove environment_vars for non-admin users
|
||||
if request.headers.get("remote-role") != "admin":
|
||||
config.pop("environment_vars", None)
|
||||
|
||||
# remove the proxy secret
|
||||
config["proxy"].pop("auth_secret", None)
|
||||
# redact mqtt credentials
|
||||
redact_credential(config["mqtt"], "password")
|
||||
|
||||
# remove genai api keys
|
||||
for genai_name, genai_cfg in config.get("genai", {}).items():
|
||||
# redact proxy secret
|
||||
redact_credential(config["proxy"], "auth_secret")
|
||||
|
||||
# redact genai api keys
|
||||
for _genai_name, genai_cfg in config.get("genai", {}).items():
|
||||
if isinstance(genai_cfg, dict):
|
||||
genai_cfg.pop("api_key", None)
|
||||
redact_credential(genai_cfg, "api_key")
|
||||
|
||||
for camera_name, camera in request.app.frigate_config.cameras.items():
|
||||
camera_dict = config["cameras"][camera_name]
|
||||
|
||||
# remove onvif credentials
|
||||
# redact onvif credentials
|
||||
onvif_dict = camera_dict.get("onvif", {})
|
||||
if onvif_dict:
|
||||
onvif_dict.pop("user", None)
|
||||
onvif_dict.pop("password", None)
|
||||
redact_credential(onvif_dict, "password")
|
||||
|
||||
# clean paths
|
||||
for input in camera_dict.get("ffmpeg", {}).get("inputs", []):
|
||||
@@ -494,6 +636,40 @@ def config_save(save_option: str, body: Any = Body(media_type="text/plain")):
|
||||
)
|
||||
|
||||
|
||||
def _restore_masked_camera_paths(config_data: dict, config: FrigateConfig) -> None:
|
||||
"""Substitute incoming `*:*` masked credentials with the in-memory ones.
|
||||
|
||||
The /config response masks ffmpeg input credentials, so the settings UI
|
||||
sends the masked path back when sibling fields (e.g. hwaccel_args) are
|
||||
edited. Without this we'd write `rtsp://*:*@host` into YAML and lose
|
||||
the real credentials. Mutates `config_data` in place.
|
||||
"""
|
||||
cameras = config_data.get("cameras")
|
||||
if not isinstance(cameras, dict):
|
||||
return
|
||||
|
||||
for camera_name, camera_data in cameras.items():
|
||||
if not isinstance(camera_data, dict):
|
||||
continue
|
||||
inputs = camera_data.get("ffmpeg", {}).get("inputs")
|
||||
if not isinstance(inputs, list):
|
||||
continue
|
||||
existing = config.cameras.get(camera_name)
|
||||
if existing is None:
|
||||
continue
|
||||
existing_paths = [inp.path for inp in existing.ffmpeg.inputs]
|
||||
for index, input_obj in enumerate(inputs):
|
||||
if not isinstance(input_obj, dict):
|
||||
continue
|
||||
path = input_obj.get("path")
|
||||
if not isinstance(path, str):
|
||||
continue
|
||||
if ("://*:*@" in path or "user=*&password=*" in path) and index < len(
|
||||
existing_paths
|
||||
):
|
||||
input_obj["path"] = existing_paths[index]
|
||||
|
||||
|
||||
def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONResponse:
|
||||
"""Apply config changes in-memory only, without writing to YAML.
|
||||
|
||||
@@ -504,8 +680,13 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
|
||||
try:
|
||||
updates = {}
|
||||
if body.config_data:
|
||||
_restore_masked_camera_paths(body.config_data, request.app.frigate_config)
|
||||
updates = flatten_config_data(body.config_data)
|
||||
updates = {k: ("" if v is None else v) for k, v in updates.items()}
|
||||
# Drop any field whose value is still the redaction sentinel
|
||||
updates = {
|
||||
k: v for k, v in updates.items() if v != REDACTED_CREDENTIAL_SENTINEL
|
||||
}
|
||||
|
||||
if not updates:
|
||||
return JSONResponse(
|
||||
@@ -569,6 +750,40 @@ def _config_set_in_memory(request: Request, body: AppConfigSetBody) -> JSONRespo
|
||||
settings,
|
||||
)
|
||||
|
||||
# detect resize also republishes motion + objects so other
|
||||
# processes pick up the rebuilt masks, and fires refresh so
|
||||
# the camera maintainer recycles the camera process to pick
|
||||
# up the new ffmpeg cmd / SHM sizing
|
||||
if field == "detect":
|
||||
cam_cfg = config.cameras.get(camera)
|
||||
if cam_cfg is not None:
|
||||
if cam_cfg.motion is not None:
|
||||
request.app.config_publisher.publish_update(
|
||||
CameraConfigUpdateTopic(
|
||||
CameraConfigUpdateEnum.motion, camera
|
||||
),
|
||||
cam_cfg.motion,
|
||||
)
|
||||
request.app.config_publisher.publish_update(
|
||||
CameraConfigUpdateTopic(
|
||||
CameraConfigUpdateEnum.objects, camera
|
||||
),
|
||||
cam_cfg.objects,
|
||||
)
|
||||
if cam_cfg.zones:
|
||||
request.app.config_publisher.publish_update(
|
||||
CameraConfigUpdateTopic(
|
||||
CameraConfigUpdateEnum.zones, camera
|
||||
),
|
||||
cam_cfg.zones,
|
||||
)
|
||||
request.app.config_publisher.publish_update(
|
||||
CameraConfigUpdateTopic(
|
||||
CameraConfigUpdateEnum.refresh, camera
|
||||
),
|
||||
cam_cfg,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content={"success": True, "message": "Config applied in-memory"},
|
||||
status_code=200,
|
||||
@@ -610,9 +825,19 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
if query_string:
|
||||
updates = process_config_query_string(query_string)
|
||||
elif body.config_data:
|
||||
_restore_masked_camera_paths(
|
||||
body.config_data, request.app.frigate_config
|
||||
)
|
||||
updates = flatten_config_data(body.config_data)
|
||||
# Convert None values to empty strings for deletion (e.g., when deleting masks)
|
||||
updates = {k: ("" if v is None else v) for k, v in updates.items()}
|
||||
# Drop sentinel-valued fields so untouched credential
|
||||
# placeholders don't clobber the saved YAML value.
|
||||
updates = {
|
||||
k: v
|
||||
for k, v in updates.items()
|
||||
if v != REDACTED_CREDENTIAL_SENTINEL
|
||||
}
|
||||
|
||||
if not updates:
|
||||
return JSONResponse(
|
||||
@@ -683,6 +908,11 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
status_code=500,
|
||||
)
|
||||
|
||||
# drop runtime overrides for any fields the user just rewrote in
|
||||
# yaml so a stale override doesn't silently win after restart
|
||||
if request.app.dispatcher is not None:
|
||||
request.app.dispatcher.clear_runtime_state_for_yaml_keys(updates.keys())
|
||||
|
||||
if body.requires_restart == 0 or body.update_topic:
|
||||
old_config: FrigateConfig = request.app.frigate_config
|
||||
request.app.frigate_config = config
|
||||
@@ -696,6 +926,8 @@ def config_set(request: Request, body: AppConfigSetBody):
|
||||
|
||||
if request.app.dispatcher is not None:
|
||||
request.app.dispatcher.config = config
|
||||
for comm in request.app.dispatcher.comms:
|
||||
comm.config = config
|
||||
|
||||
if body.update_topic:
|
||||
if body.update_topic.startswith("config/cameras/"):
|
||||
@@ -792,7 +1024,7 @@ def nvinfo():
|
||||
@router.get(
|
||||
"/logs/{service}",
|
||||
tags=[Tags.logs],
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
)
|
||||
async def logs(
|
||||
service: str = Path(enum=["frigate", "nginx", "go2rtc"]),
|
||||
@@ -997,12 +1229,27 @@ def get_media_sync_status(job_id: str):
|
||||
|
||||
|
||||
@router.get("/labels", dependencies=[Depends(allow_any_authenticated())])
|
||||
def get_labels(camera: str = ""):
|
||||
def get_labels(
|
||||
camera: str = "",
|
||||
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||
):
|
||||
try:
|
||||
if camera:
|
||||
if camera not in allowed_cameras:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": f"Access denied to camera '{camera}'",
|
||||
},
|
||||
status_code=403,
|
||||
)
|
||||
events = Event.select(Event.label).where(Event.camera == camera).distinct()
|
||||
else:
|
||||
events = Event.select(Event.label).distinct()
|
||||
events = (
|
||||
Event.select(Event.label)
|
||||
.where(Event.camera << allowed_cameras)
|
||||
.distinct()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return JSONResponse(
|
||||
@@ -1015,9 +1262,16 @@ def get_labels(camera: str = ""):
|
||||
|
||||
|
||||
@router.get("/sub_labels", dependencies=[Depends(allow_any_authenticated())])
|
||||
def get_sub_labels(split_joined: Optional[int] = None):
|
||||
def get_sub_labels(
|
||||
split_joined: Optional[int] = None,
|
||||
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||
):
|
||||
try:
|
||||
events = Event.select(Event.sub_label).distinct()
|
||||
events = (
|
||||
Event.select(Event.sub_label)
|
||||
.where(Event.camera << allowed_cameras)
|
||||
.distinct()
|
||||
)
|
||||
except Exception:
|
||||
return JSONResponse(
|
||||
content=({"success": False, "message": "Failed to get sub_labels"}),
|
||||
|
||||
+29
-10
@@ -26,6 +26,7 @@ from frigate.api.defs.request.app_body import (
|
||||
AppPutRoleBody,
|
||||
)
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.api.media_auth import check_camera_access, deny_response_for_media_uri
|
||||
from frigate.config import AuthConfig, NetworkingConfig, ProxyConfig
|
||||
from frigate.const import CONFIG_DIR, JWT_SECRET_ENV_VAR, PASSWORD_HASH_ALGORITHM
|
||||
from frigate.models import User
|
||||
@@ -633,6 +634,9 @@ def auth(request: Request):
|
||||
logger.debug("X-Proxy-Secret header does not match configured secret value")
|
||||
return fail_response
|
||||
|
||||
original_url = request.headers.get("x-original-url")
|
||||
frigate_config = request.app.frigate_config
|
||||
|
||||
# if auth is disabled, just apply the proxy header map and return success
|
||||
if not auth_config.enabled:
|
||||
# pass the user header value from the upstream proxy if a mapping is specified
|
||||
@@ -649,6 +653,11 @@ def auth(request: Request):
|
||||
role = resolve_role(request.headers, proxy_config, config_roles_set)
|
||||
|
||||
success_response.headers["remote-role"] = role
|
||||
|
||||
deny_status = deny_response_for_media_uri(original_url, role, frigate_config)
|
||||
if deny_status is not None:
|
||||
return Response("", status_code=deny_status)
|
||||
|
||||
return success_response
|
||||
|
||||
# now apply authentication
|
||||
@@ -743,6 +752,11 @@ def auth(request: Request):
|
||||
|
||||
success_response.headers["remote-user"] = user
|
||||
success_response.headers["remote-role"] = role
|
||||
|
||||
deny_status = deny_response_for_media_uri(original_url, role, frigate_config)
|
||||
if deny_status is not None:
|
||||
return Response("", status_code=deny_status)
|
||||
|
||||
return success_response
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing jwt: {e}")
|
||||
@@ -812,6 +826,11 @@ limiter = Limiter(key_func=get_remote_addr)
|
||||
)
|
||||
@limiter.limit(limit_value=rateLimiter.get_limit)
|
||||
def login(request: Request, body: AppPostLoginBody):
|
||||
if not request.app.frigate_config.auth.enabled:
|
||||
return JSONResponse(
|
||||
content={"message": "Authentication is disabled"}, status_code=404
|
||||
)
|
||||
|
||||
JWT_COOKIE_NAME = request.app.frigate_config.auth.cookie_name
|
||||
JWT_COOKIE_SECURE = request.app.frigate_config.auth.cookie_secure
|
||||
JWT_SESSION_LENGTH = request.app.frigate_config.auth.session_length
|
||||
@@ -1064,19 +1083,19 @@ async def require_camera_access(
|
||||
raise HTTPException(status_code=current_user.status_code, detail=detail)
|
||||
|
||||
role = current_user["role"]
|
||||
all_camera_names = set(request.app.frigate_config.cameras.keys())
|
||||
roles_dict = request.app.frigate_config.auth.roles
|
||||
allowed_cameras = User.get_allowed_cameras(role, roles_dict, all_camera_names)
|
||||
frigate_config = request.app.frigate_config
|
||||
|
||||
# Admin or full access bypasses
|
||||
if role == "admin" or not roles_dict.get(role):
|
||||
if check_camera_access(role, camera_name, frigate_config):
|
||||
return
|
||||
|
||||
if camera_name not in allowed_cameras:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Access denied to camera '{camera_name}'. Allowed: {allowed_cameras}",
|
||||
)
|
||||
all_camera_names = set(frigate_config.cameras.keys())
|
||||
allowed_cameras = User.get_allowed_cameras(
|
||||
role, frigate_config.auth.roles, all_camera_names
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Access denied to camera '{camera_name}'. Allowed: {allowed_cameras}",
|
||||
)
|
||||
|
||||
|
||||
def _get_stream_owner_cameras(request: Request, stream_name: str) -> set[str]:
|
||||
|
||||
+163
-48
@@ -19,7 +19,9 @@ from zeep.exceptions import Fault, TransportError
|
||||
from zeep.transports import AsyncTransport
|
||||
|
||||
from frigate.api.auth import (
|
||||
_get_stream_owner_cameras,
|
||||
allow_any_authenticated,
|
||||
get_current_user,
|
||||
require_go2rtc_stream_access,
|
||||
require_role,
|
||||
)
|
||||
@@ -31,11 +33,16 @@ from frigate.config.camera.updater import (
|
||||
CameraConfigUpdateTopic,
|
||||
)
|
||||
from frigate.config.env import substitute_frigate_vars
|
||||
from frigate.util.builtin import clean_camera_user_pass
|
||||
from frigate.models import User
|
||||
from frigate.util.builtin import clean_camera_user_pass, get_record_segment_time
|
||||
from frigate.util.camera_cleanup import cleanup_camera_db, cleanup_camera_files
|
||||
from frigate.util.config import find_config_file
|
||||
from frigate.util.image import run_ffmpeg_snapshot
|
||||
from frigate.util.services import ffprobe_stream
|
||||
from frigate.util.services import (
|
||||
analyze_record_keyframes,
|
||||
ffprobe_stream,
|
||||
is_restricted_go2rtc_source,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -66,7 +73,7 @@ def _is_valid_host(host: str) -> bool:
|
||||
|
||||
|
||||
@router.get("/go2rtc/streams", dependencies=[Depends(allow_any_authenticated())])
|
||||
def go2rtc_streams():
|
||||
async def go2rtc_streams(request: Request):
|
||||
r = requests.get("http://127.0.0.1:1984/api/streams")
|
||||
if not r.ok:
|
||||
logger.error("Failed to fetch streams from go2rtc")
|
||||
@@ -75,6 +82,24 @@ def go2rtc_streams():
|
||||
status_code=500,
|
||||
)
|
||||
stream_data = r.json()
|
||||
|
||||
# Roles with an explicit camera list see only streams owned by an allowed
|
||||
# camera. Admin and full-access roles (no list / empty list) see all streams.
|
||||
current_user = await get_current_user(request)
|
||||
if not isinstance(current_user, JSONResponse):
|
||||
role = current_user["role"]
|
||||
roles_dict = request.app.frigate_config.auth.roles
|
||||
if role != "admin" and roles_dict.get(role):
|
||||
all_camera_names = set(request.app.frigate_config.cameras.keys())
|
||||
allowed_cameras = set(
|
||||
User.get_allowed_cameras(role, roles_dict, all_camera_names)
|
||||
)
|
||||
stream_data = {
|
||||
name: data
|
||||
for name, data in stream_data.items()
|
||||
if _get_stream_owner_cameras(request, name) & allowed_cameras
|
||||
}
|
||||
|
||||
for data in stream_data.values():
|
||||
for producer in data.get("producers") or []:
|
||||
producer["url"] = clean_camera_user_pass(producer.get("url", ""))
|
||||
@@ -126,9 +151,24 @@ def go2rtc_add_stream(request: Request, stream_name: str, src: str = ""):
|
||||
params = {"name": stream_name}
|
||||
if src:
|
||||
try:
|
||||
params["src"] = substitute_frigate_vars(src)
|
||||
resolved_src = substitute_frigate_vars(src)
|
||||
except KeyError:
|
||||
params["src"] = src
|
||||
resolved_src = src
|
||||
|
||||
if is_restricted_go2rtc_source(resolved_src):
|
||||
logger.warning(
|
||||
"Rejected go2rtc stream '%s' with restricted source type (echo/expr/exec)",
|
||||
stream_name,
|
||||
)
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Restricted stream source type",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
params["src"] = resolved_src
|
||||
|
||||
r = requests.put(
|
||||
"http://127.0.0.1:1984/api/streams",
|
||||
@@ -326,6 +366,48 @@ def ffprobe(request: Request, paths: str = "", detailed: bool = False):
|
||||
return JSONResponse(content=output)
|
||||
|
||||
|
||||
@router.get("/keyframe_analysis", dependencies=[Depends(require_role(["admin"]))])
|
||||
async def keyframe_analysis(request: Request, camera: str = ""):
|
||||
"""Probe a camera's record stream and classify its keyframe spacing.
|
||||
|
||||
Detects smart/+ codecs and long/variable GOPs that degrade recording.
|
||||
"""
|
||||
config: FrigateConfig = request.app.frigate_config
|
||||
|
||||
if camera not in config.cameras:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": f"{camera} is not a valid camera."},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
camera_config = config.cameras[camera]
|
||||
|
||||
if not camera_config.enabled:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": f"{camera} is not enabled."},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
# keyframe spacing only matters when this camera is recording
|
||||
if not camera_config.record.enabled:
|
||||
return JSONResponse(content={"severity": "record_disabled"})
|
||||
|
||||
# recording guarantees an input carries the record role; its index matches
|
||||
# the "Stream N" numbering the ffprobe endpoint surfaces (same input order)
|
||||
record_index, record_input = next(
|
||||
(idx, i)
|
||||
for idx, i in enumerate(camera_config.ffmpeg.inputs)
|
||||
if "record" in i.roles
|
||||
)
|
||||
|
||||
segment_time = get_record_segment_time(camera_config)
|
||||
result = await analyze_record_keyframes(
|
||||
config.ffmpeg, record_input.path, segment_time
|
||||
)
|
||||
result["stream_index"] = record_index
|
||||
return JSONResponse(content=result)
|
||||
|
||||
|
||||
@router.get("/ffprobe/snapshot", dependencies=[Depends(require_role(["admin"]))])
|
||||
def ffprobe_snapshot(request: Request, url: str = "", timeout: int = 10):
|
||||
"""Get a snapshot from a stream URL using ffmpeg."""
|
||||
@@ -493,6 +575,68 @@ def _extract_fps(r_frame_rate: str) -> float | None:
|
||||
return None
|
||||
|
||||
|
||||
def _build_digest_transport(username: str, password: str) -> AsyncTransport:
|
||||
"""Build a zeep transport backed by an httpx client using HTTP digest auth."""
|
||||
auth = httpx.DigestAuth(username, password)
|
||||
client = httpx.AsyncClient(auth=auth, timeout=10.0)
|
||||
return AsyncTransport(client=client)
|
||||
|
||||
|
||||
async def _connect_onvif_camera(
|
||||
host: str,
|
||||
port: int,
|
||||
username: str,
|
||||
password: str,
|
||||
wsdl_base: str | None,
|
||||
auth_type: str,
|
||||
) -> ONVIFCamera:
|
||||
"""Connect to an ONVIF device, trying both WS-Security password encodings.
|
||||
|
||||
Cameras disagree on whether the WS-Security UsernameToken should carry a
|
||||
hashed PasswordDigest or a plaintext PasswordText. The wizard can't know
|
||||
which a given camera expects, so we try PasswordDigest first (the common
|
||||
case) and fall back to PasswordText when the device rejects the token. This
|
||||
is independent of auth_type, which controls HTTP transport-level auth.
|
||||
"""
|
||||
first_error: Fault | None = None
|
||||
|
||||
# encrypt=True -> PasswordDigest, encrypt=False -> PasswordText
|
||||
for encrypt in (True, False):
|
||||
onvif_camera = ONVIFCamera(
|
||||
host,
|
||||
port,
|
||||
username or "",
|
||||
password or "",
|
||||
wsdl_dir=wsdl_base,
|
||||
encrypt=encrypt,
|
||||
)
|
||||
|
||||
try:
|
||||
await onvif_camera.update_xaddrs()
|
||||
except Fault as e:
|
||||
# A SOAP fault here is how a camera signals the wrong password
|
||||
# encoding, so retry with the other encoding before giving up.
|
||||
logger.debug(
|
||||
"ONVIF connect with %s rejected, trying alternate encoding",
|
||||
"PasswordDigest" if encrypt else "PasswordText",
|
||||
)
|
||||
if first_error is None:
|
||||
first_error = e
|
||||
continue
|
||||
|
||||
if auth_type == "digest" and username and password:
|
||||
transport = _build_digest_transport(username, password)
|
||||
for service in ("devicemgmt", "media", "ptz"):
|
||||
if hasattr(onvif_camera, service):
|
||||
getattr(onvif_camera, service).zeep_client.transport = transport
|
||||
logger.debug("Configured digest authentication")
|
||||
|
||||
return onvif_camera
|
||||
|
||||
# Both encodings failed authentication; surface the original fault.
|
||||
raise first_error
|
||||
|
||||
|
||||
@router.get(
|
||||
"/onvif/probe",
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
@@ -569,34 +713,10 @@ async def onvif_probe(
|
||||
except Exception:
|
||||
wsdl_base = None
|
||||
|
||||
onvif_camera = ONVIFCamera(
|
||||
host, port, username or "", password or "", wsdl_dir=wsdl_base
|
||||
onvif_camera = await _connect_onvif_camera(
|
||||
host, port, username, password, wsdl_base, auth_type
|
||||
)
|
||||
|
||||
# Configure digest authentication if requested
|
||||
if auth_type == "digest" and username and password:
|
||||
# Create httpx client with digest auth
|
||||
auth = httpx.DigestAuth(username, password)
|
||||
client = httpx.AsyncClient(auth=auth, timeout=10.0)
|
||||
|
||||
# Replace the transport in the zeep client
|
||||
transport = AsyncTransport(client=client)
|
||||
|
||||
# Update the xaddr before setting transport
|
||||
await onvif_camera.update_xaddrs()
|
||||
|
||||
# Replace transport in all services
|
||||
if hasattr(onvif_camera, "devicemgmt"):
|
||||
onvif_camera.devicemgmt.zeep_client.transport = transport
|
||||
if hasattr(onvif_camera, "media"):
|
||||
onvif_camera.media.zeep_client.transport = transport
|
||||
if hasattr(onvif_camera, "ptz"):
|
||||
onvif_camera.ptz.zeep_client.transport = transport
|
||||
|
||||
logger.debug("Configured digest authentication")
|
||||
else:
|
||||
await onvif_camera.update_xaddrs()
|
||||
|
||||
# Get device information
|
||||
device_info = {
|
||||
"manufacturer": "Unknown",
|
||||
@@ -608,10 +728,9 @@ async def onvif_probe(
|
||||
|
||||
# Update transport for device service if digest auth
|
||||
if auth_type == "digest" and username and password:
|
||||
auth = httpx.DigestAuth(username, password)
|
||||
client = httpx.AsyncClient(auth=auth, timeout=10.0)
|
||||
transport = AsyncTransport(client=client)
|
||||
device_service.zeep_client.transport = transport
|
||||
device_service.zeep_client.transport = _build_digest_transport(
|
||||
username, password
|
||||
)
|
||||
|
||||
device_info_resp = await device_service.GetDeviceInformation()
|
||||
manufacturer = getattr(device_info_resp, "Manufacturer", None) or (
|
||||
@@ -649,10 +768,9 @@ async def onvif_probe(
|
||||
|
||||
# Update transport for media service if digest auth
|
||||
if auth_type == "digest" and username and password:
|
||||
auth = httpx.DigestAuth(username, password)
|
||||
client = httpx.AsyncClient(auth=auth, timeout=10.0)
|
||||
transport = AsyncTransport(client=client)
|
||||
media_service.zeep_client.transport = transport
|
||||
media_service.zeep_client.transport = _build_digest_transport(
|
||||
username, password
|
||||
)
|
||||
|
||||
profiles = await media_service.GetProfiles()
|
||||
profiles_count = len(profiles) if profiles else 0
|
||||
@@ -684,10 +802,9 @@ async def onvif_probe(
|
||||
|
||||
# Update transport for PTZ service if digest auth
|
||||
if auth_type == "digest" and username and password:
|
||||
auth = httpx.DigestAuth(username, password)
|
||||
client = httpx.AsyncClient(auth=auth, timeout=10.0)
|
||||
transport = AsyncTransport(client=client)
|
||||
ptz_service.zeep_client.transport = transport
|
||||
ptz_service.zeep_client.transport = _build_digest_transport(
|
||||
username, password
|
||||
)
|
||||
|
||||
# Check if PTZ service is available
|
||||
try:
|
||||
@@ -840,10 +957,9 @@ async def onvif_probe(
|
||||
|
||||
# Update transport for media service if digest auth
|
||||
if auth_type == "digest" and username and password:
|
||||
auth = httpx.DigestAuth(username, password)
|
||||
client = httpx.AsyncClient(auth=auth, timeout=10.0)
|
||||
transport = AsyncTransport(client=client)
|
||||
media_service.zeep_client.transport = transport
|
||||
media_service.zeep_client.transport = _build_digest_transport(
|
||||
username, password
|
||||
)
|
||||
|
||||
if profiles_count and media_service:
|
||||
for p in profiles or []:
|
||||
@@ -966,7 +1082,6 @@ async def onvif_probe(
|
||||
probe = ffprobe_stream(
|
||||
request.app.frigate_config.ffmpeg, test_uri, detailed=False
|
||||
)
|
||||
print(probe)
|
||||
ok = probe is not None and getattr(probe, "returncode", 1) == 0
|
||||
tested_candidates.append(
|
||||
{
|
||||
|
||||
+371
-445
File diff suppressed because it is too large
Load Diff
@@ -280,7 +280,7 @@ async def create_face(request: Request, name: str):
|
||||
success response with details about the registration, or an error if face recognition
|
||||
is not enabled or the image cannot be processed.""",
|
||||
)
|
||||
async def register_face(request: Request, name: str, file: UploadFile):
|
||||
def register_face(request: Request, name: str, file: UploadFile):
|
||||
if not request.app.frigate_config.face_recognition.enabled:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
@@ -288,7 +288,7 @@ async def register_face(request: Request, name: str, file: UploadFile):
|
||||
)
|
||||
|
||||
context: EmbeddingsContext = request.app.embeddings
|
||||
result = None if context is None else context.register_face(name, await file.read())
|
||||
result = None if context is None else context.register_face(name, file.file.read())
|
||||
|
||||
if not isinstance(result, dict):
|
||||
return JSONResponse(
|
||||
@@ -313,7 +313,7 @@ async def register_face(request: Request, name: str, file: UploadFile):
|
||||
registered faces in the system. Returns the recognized face name and confidence score,
|
||||
or an error if face recognition is not enabled or the image cannot be processed.""",
|
||||
)
|
||||
async def recognize_face(request: Request, file: UploadFile):
|
||||
def recognize_face(request: Request, file: UploadFile):
|
||||
if not request.app.frigate_config.face_recognition.enabled:
|
||||
return JSONResponse(
|
||||
status_code=400,
|
||||
@@ -321,7 +321,7 @@ async def recognize_face(request: Request, file: UploadFile):
|
||||
)
|
||||
|
||||
context: EmbeddingsContext = request.app.embeddings
|
||||
result = context.recognize_face(await file.read())
|
||||
result = context.recognize_face(file.file.read())
|
||||
|
||||
if not isinstance(result, dict):
|
||||
return JSONResponse(
|
||||
|
||||
+139
-25
@@ -6,10 +6,18 @@ from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from peewee import DoesNotExist
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from frigate.api.auth import require_role
|
||||
from frigate.api.defs.tags import Tags
|
||||
from frigate.jobs.debug_replay import (
|
||||
ExportDebugReplaySource,
|
||||
RecordingDebugReplaySource,
|
||||
start_debug_replay_job,
|
||||
)
|
||||
from frigate.models import Export
|
||||
from frigate.util.services import get_video_properties
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,15 +32,28 @@ class DebugReplayStartBody(BaseModel):
|
||||
end_time: float = Field(title="End timestamp")
|
||||
|
||||
|
||||
class DebugReplayStartFromExportBody(BaseModel):
|
||||
"""Request body for starting a debug replay session from an export."""
|
||||
|
||||
export_id: str = Field(title="Export id")
|
||||
|
||||
|
||||
class DebugReplayStartResponse(BaseModel):
|
||||
"""Response for starting a debug replay session."""
|
||||
|
||||
success: bool
|
||||
replay_camera: str
|
||||
job_id: str
|
||||
|
||||
|
||||
class DebugReplayStatusResponse(BaseModel):
|
||||
"""Response for debug replay status."""
|
||||
"""Response for debug replay status.
|
||||
|
||||
Returns only session-presence fields. Startup progress and error
|
||||
details flow through the job_state WebSocket topic via the
|
||||
debug_replay job (see frigate.jobs.debug_replay); the
|
||||
Replay page subscribes there with useJobStatus("debug_replay").
|
||||
"""
|
||||
|
||||
active: bool
|
||||
replay_camera: str | None = None
|
||||
@@ -51,15 +72,40 @@ class DebugReplayStopResponse(BaseModel):
|
||||
@router.post(
|
||||
"/debug_replay/start",
|
||||
response_model=DebugReplayStartResponse,
|
||||
status_code=202,
|
||||
responses={
|
||||
400: {"description": "Invalid camera, time range, or no recordings"},
|
||||
409: {"description": "A replay session is already active"},
|
||||
},
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
summary="Start debug replay",
|
||||
description="Start a debug replay session from camera recordings.",
|
||||
description="Start a debug replay session from camera recordings. Returns "
|
||||
"immediately while clip generation runs as a background job; subscribe "
|
||||
"to the 'debug_replay' job_state WS topic to track progress.",
|
||||
)
|
||||
async def start_debug_replay(request: Request, body: DebugReplayStartBody):
|
||||
"""Start a debug replay session."""
|
||||
"""Start a debug replay session asynchronously."""
|
||||
replay_manager = request.app.replay_manager
|
||||
internal_port = request.app.frigate_config.networking.listen.internal
|
||||
if type(internal_port) is str:
|
||||
internal_port = int(internal_port.split(":")[-1])
|
||||
|
||||
if replay_manager.active:
|
||||
source = RecordingDebugReplaySource(
|
||||
source_camera=body.camera,
|
||||
start_ts=body.start_time,
|
||||
end_ts=body.end_time,
|
||||
internal_port=internal_port,
|
||||
)
|
||||
|
||||
try:
|
||||
job_id = await asyncio.to_thread(
|
||||
start_debug_replay_job,
|
||||
source=source,
|
||||
frigate_config=request.app.frigate_config,
|
||||
config_publisher=request.app.config_publisher,
|
||||
replay_manager=replay_manager,
|
||||
)
|
||||
except RuntimeError:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
@@ -67,38 +113,102 @@ async def start_debug_replay(request: Request, body: DebugReplayStartBody):
|
||||
},
|
||||
status_code=409,
|
||||
)
|
||||
|
||||
try:
|
||||
replay_camera = await asyncio.to_thread(
|
||||
replay_manager.start,
|
||||
source_camera=body.camera,
|
||||
start_ts=body.start_time,
|
||||
end_ts=body.end_time,
|
||||
frigate_config=request.app.frigate_config,
|
||||
config_publisher=request.app.config_publisher,
|
||||
)
|
||||
except ValueError:
|
||||
logger.exception("Invalid parameters for debug replay start request")
|
||||
logger.exception("Rejected debug replay start request")
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Invalid debug replay request parameters",
|
||||
"message": "Invalid debug replay parameters",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
except RuntimeError:
|
||||
logger.exception("Error while starting debug replay session")
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": True,
|
||||
"replay_camera": replay_manager.replay_camera_name,
|
||||
"job_id": job_id,
|
||||
},
|
||||
status_code=202,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/debug_replay/start_from_export",
|
||||
response_model=DebugReplayStartResponse,
|
||||
status_code=202,
|
||||
responses={
|
||||
400: {"description": "Invalid export, time range, or no recordings"},
|
||||
404: {"description": "Export not found"},
|
||||
409: {"description": "A replay session is already active"},
|
||||
},
|
||||
dependencies=[Depends(require_role(["admin"]))],
|
||||
summary="Start debug replay from an export",
|
||||
description="Start a debug replay session covering an existing export's "
|
||||
"time range. The end time is derived from the export's video duration.",
|
||||
)
|
||||
async def start_debug_replay_from_export(
|
||||
request: Request, body: DebugReplayStartFromExportBody
|
||||
):
|
||||
"""Start a debug replay session from an existing export."""
|
||||
try:
|
||||
export: Export = Export.get(Export.id == body.export_id)
|
||||
except DoesNotExist:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Export not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
properties = await get_video_properties(
|
||||
request.app.frigate_config.ffmpeg, export.video_path, get_duration=True
|
||||
)
|
||||
duration = properties.get("duration", -1)
|
||||
|
||||
if duration is None or duration <= 0:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "An internal error occurred while starting debug replay",
|
||||
"message": "Could not determine export duration",
|
||||
},
|
||||
status_code=500,
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
return DebugReplayStartResponse(
|
||||
success=True,
|
||||
replay_camera=replay_camera,
|
||||
replay_manager = request.app.replay_manager
|
||||
source = ExportDebugReplaySource(export=export, duration=float(duration))
|
||||
|
||||
try:
|
||||
job_id = await asyncio.to_thread(
|
||||
start_debug_replay_job,
|
||||
source=source,
|
||||
frigate_config=request.app.frigate_config,
|
||||
config_publisher=request.app.config_publisher,
|
||||
replay_manager=replay_manager,
|
||||
)
|
||||
except RuntimeError:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "A replay session is already active",
|
||||
},
|
||||
status_code=409,
|
||||
)
|
||||
except ValueError:
|
||||
logger.exception("Rejected debug replay start request")
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Invalid debug replay parameters",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": True,
|
||||
"replay_camera": replay_manager.replay_camera_name,
|
||||
"job_id": job_id,
|
||||
},
|
||||
status_code=202,
|
||||
)
|
||||
|
||||
|
||||
@@ -118,12 +228,16 @@ def get_debug_replay_status(request: Request):
|
||||
|
||||
if replay_manager.active and replay_camera:
|
||||
frame_processor = request.app.detected_frames_processor
|
||||
frame = frame_processor.get_current_frame(replay_camera)
|
||||
frame = (
|
||||
frame_processor.get_current_frame(replay_camera)
|
||||
if frame_processor is not None
|
||||
else None
|
||||
)
|
||||
|
||||
if frame is not None:
|
||||
frame_time = frame_processor.get_current_frame_time(replay_camera)
|
||||
camera_config = request.app.frigate_config.cameras.get(replay_camera)
|
||||
retry_interval = 10
|
||||
retry_interval = 10.0
|
||||
|
||||
if camera_config is not None:
|
||||
retry_interval = float(camera_config.ffmpeg.retry_interval or 10)
|
||||
|
||||
@@ -2,6 +2,8 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from frigate.config import GenAIProviderEnum
|
||||
|
||||
|
||||
class AppConfigSetBody(BaseModel):
|
||||
requires_restart: int = 1
|
||||
@@ -10,6 +12,13 @@ class AppConfigSetBody(BaseModel):
|
||||
skip_save: bool = False
|
||||
|
||||
|
||||
class GenAIProbeBody(BaseModel):
|
||||
provider: GenAIProviderEnum
|
||||
api_key: Optional[str] = None
|
||||
base_url: Optional[str] = None
|
||||
provider_options: Dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class AppPutPasswordBody(BaseModel):
|
||||
password: str
|
||||
old_password: Optional[str] = None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Chat API request models."""
|
||||
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -11,13 +11,29 @@ class ChatMessage(BaseModel):
|
||||
role: str = Field(
|
||||
description="Message role: 'user', 'assistant', 'system', or 'tool'"
|
||||
)
|
||||
content: str = Field(description="Message content")
|
||||
content: Optional[Any] = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Message content. Usually a string, but may be a multimodal content "
|
||||
"list (e.g. text + image_url) or null for assistant turns that only "
|
||||
"request tool calls."
|
||||
),
|
||||
)
|
||||
tool_call_id: Optional[str] = Field(
|
||||
default=None, description="For tool messages, the ID of the tool call"
|
||||
)
|
||||
name: Optional[str] = Field(
|
||||
default=None, description="For tool messages, the tool name"
|
||||
)
|
||||
tool_calls: Optional[list[dict[str, Any]]] = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"For assistant messages replayed from prior turns, the OpenAI-format "
|
||||
"tool calls the model previously requested. Replaying these verbatim "
|
||||
"keeps the conversation prefix byte-for-byte identical so the model "
|
||||
"server's prompt cache hits on follow-up turns."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ChatCompletionRequest(BaseModel):
|
||||
@@ -36,3 +52,10 @@ class ChatCompletionRequest(BaseModel):
|
||||
default=False,
|
||||
description="If true, stream the final assistant response in the body as newline-delimited JSON.",
|
||||
)
|
||||
enable_thinking: Optional[bool] = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Per-request thinking toggle. None means use the provider default. "
|
||||
"Ignored by providers that do not expose a per-request thinking switch."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -20,6 +20,10 @@ class ChatMessageResponse(BaseModel):
|
||||
content: Optional[str] = Field(
|
||||
default=None, description="Message content (None if tool calls present)"
|
||||
)
|
||||
reasoning: Optional[str] = Field(
|
||||
default=None,
|
||||
description="Separated reasoning/thinking trace if the model emitted one",
|
||||
)
|
||||
tool_calls: Optional[list[ToolCallInvocation]] = Field(
|
||||
default=None, description="Tool calls if LLM wants to call tools"
|
||||
)
|
||||
@@ -52,3 +56,12 @@ class ChatCompletionResponse(BaseModel):
|
||||
default_factory=list,
|
||||
description="List of tool calls that were executed during this completion",
|
||||
)
|
||||
messages: list[dict[str, Any]] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"The full conversation chain, including the system message. Persist "
|
||||
"and replay this verbatim on the next request so the prompt prefix "
|
||||
"stays byte-identical and the model server's prompt cache keeps "
|
||||
"hitting."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -754,6 +754,15 @@ def events_search(
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
if search_event.camera not in allowed_cameras:
|
||||
return JSONResponse(
|
||||
content={
|
||||
"success": False,
|
||||
"message": "Event not found",
|
||||
},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
thumb_result = context.search_thumbnail(search_event)
|
||||
thumb_ids = {result[0]: result[1] for result in thumb_result}
|
||||
search_results = {
|
||||
|
||||
+135
-3
@@ -5,13 +5,15 @@ import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
import zipfile
|
||||
from collections import deque
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from typing import Iterator, List, Optional
|
||||
|
||||
import psutil
|
||||
from fastapi import APIRouter, Depends, Query, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from pathvalidate import sanitize_filepath
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
from pathvalidate import sanitize_filename, sanitize_filepath
|
||||
from peewee import DoesNotExist
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
|
||||
@@ -361,6 +363,136 @@ def get_export_case(case_id: str):
|
||||
)
|
||||
|
||||
|
||||
_ZIP_STREAM_CHUNK_SIZE = 1024 * 1024 # 1 MiB
|
||||
|
||||
|
||||
class _StreamingZipBuffer:
|
||||
"""File-like sink for ZipFile that exposes written bytes via drain().
|
||||
|
||||
ZipFile writes synchronously into this buffer; the generator drains the
|
||||
queue between writes so StreamingResponse can yield bytes without
|
||||
materializing the whole archive in memory.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._queue: deque[bytes] = deque()
|
||||
self._offset = 0
|
||||
|
||||
def write(self, data: bytes) -> int:
|
||||
if data:
|
||||
self._queue.append(bytes(data))
|
||||
self._offset += len(data)
|
||||
return len(data)
|
||||
|
||||
def tell(self) -> int:
|
||||
return self._offset
|
||||
|
||||
def flush(self) -> None:
|
||||
pass
|
||||
|
||||
def drain(self) -> Iterator[bytes]:
|
||||
while self._queue:
|
||||
yield self._queue.popleft()
|
||||
|
||||
|
||||
def _unique_archive_name(export: Export, used: set[str]) -> str:
|
||||
base = sanitize_filename(export.name) if export.name else None
|
||||
if not base:
|
||||
base = f"{export.camera}_{int(export.date)}"
|
||||
|
||||
candidate = f"{base}.mp4"
|
||||
counter = 1
|
||||
while candidate in used:
|
||||
candidate = f"{base}_{counter}.mp4"
|
||||
counter += 1
|
||||
|
||||
used.add(candidate)
|
||||
return candidate
|
||||
|
||||
|
||||
def _stream_case_archive(exports: List[Export]) -> Iterator[bytes]:
|
||||
"""Yield bytes of a zip archive built from the given exports' mp4 files."""
|
||||
buffer = _StreamingZipBuffer()
|
||||
used_names: set[str] = set()
|
||||
|
||||
# ZIP_STORED: mp4 is already compressed, recompressing wastes CPU for ~0% size win.
|
||||
with zipfile.ZipFile(
|
||||
buffer,
|
||||
mode="w",
|
||||
compression=zipfile.ZIP_STORED,
|
||||
allowZip64=True,
|
||||
) as archive:
|
||||
for export in exports:
|
||||
source = Path(export.video_path)
|
||||
if not source.exists():
|
||||
continue
|
||||
|
||||
arcname = _unique_archive_name(export, used_names)
|
||||
|
||||
with (
|
||||
archive.open(arcname, mode="w", force_zip64=True) as entry,
|
||||
source.open("rb") as src,
|
||||
):
|
||||
while True:
|
||||
chunk = src.read(_ZIP_STREAM_CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
entry.write(chunk)
|
||||
yield from buffer.drain()
|
||||
|
||||
yield from buffer.drain()
|
||||
|
||||
yield from buffer.drain()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/cases/{case_id}/download",
|
||||
dependencies=[Depends(allow_any_authenticated())],
|
||||
summary="Download export case as zip",
|
||||
description="Streams a zip archive containing every completed export's mp4 for the given case.",
|
||||
)
|
||||
def download_export_case(
|
||||
case_id: str,
|
||||
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||
):
|
||||
try:
|
||||
case = ExportCase.get(ExportCase.id == case_id)
|
||||
except DoesNotExist:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "Export case not found"},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
exports = list(
|
||||
Export.select()
|
||||
.where(
|
||||
Export.export_case == case_id,
|
||||
~Export.in_progress,
|
||||
Export.camera << allowed_cameras,
|
||||
)
|
||||
.order_by(Export.date.asc())
|
||||
)
|
||||
|
||||
if not exports:
|
||||
return JSONResponse(
|
||||
content={"success": False, "message": "No exports available to download."},
|
||||
status_code=404,
|
||||
)
|
||||
|
||||
archive_base = sanitize_filename(case.name) if case.name else ""
|
||||
if not archive_base:
|
||||
archive_base = case_id
|
||||
|
||||
return StreamingResponse(
|
||||
_stream_case_archive(exports),
|
||||
media_type="application/zip",
|
||||
headers={
|
||||
"Content-Disposition": f'attachment; filename="{archive_base}.zip"',
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.patch(
|
||||
"/cases/{case_id}",
|
||||
response_model=GenericResponse,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional
|
||||
@@ -36,7 +37,7 @@ from frigate.comms.event_metadata_updater import (
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.config.camera.updater import CameraConfigUpdatePublisher
|
||||
from frigate.config.profile_manager import ProfileManager
|
||||
from frigate.debug_replay import DebugReplayManager
|
||||
from frigate.debug_replay import DebugReplayManager, debug_replay_auto_stop_watchdog
|
||||
from frigate.embeddings import EmbeddingsContext
|
||||
from frigate.genai import GenAIClientManager
|
||||
from frigate.ptz.onvif import OnvifController
|
||||
@@ -116,6 +117,11 @@ def create_fastapi_app(
|
||||
@app.on_event("startup")
|
||||
async def startup():
|
||||
logger.info("FastAPI started")
|
||||
asyncio.create_task(
|
||||
debug_replay_auto_stop_watchdog(
|
||||
replay_manager, frigate_config, config_publisher
|
||||
)
|
||||
)
|
||||
|
||||
# Rate limiter (used for login endpoint)
|
||||
if frigate_config.auth.failed_login_rate_limit is None:
|
||||
|
||||
+21
-13
@@ -174,12 +174,10 @@ async def latest_frame(
|
||||
}
|
||||
quality_params = get_image_quality_params(extension.value, params.quality)
|
||||
|
||||
if camera_name in request.app.frigate_config.cameras:
|
||||
camera_config = request.app.frigate_config.cameras.get(camera_name)
|
||||
if camera_config is not None:
|
||||
frame = frame_processor.get_current_frame(camera_name, draw_options)
|
||||
retry_interval = float(
|
||||
request.app.frigate_config.cameras.get(camera_name).ffmpeg.retry_interval
|
||||
or 10
|
||||
)
|
||||
retry_interval = float(camera_config.ffmpeg.retry_interval or 10)
|
||||
|
||||
is_offline = False
|
||||
if frame is None or datetime.now().timestamp() > (
|
||||
@@ -1368,12 +1366,17 @@ def preview_gif(
|
||||
file_start = f"preview_{camera_name}-"
|
||||
start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
|
||||
end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
|
||||
|
||||
camera_files = [
|
||||
entry.name
|
||||
for entry in os.scandir(preview_dir)
|
||||
if entry.name.startswith(file_start)
|
||||
]
|
||||
camera_files.sort()
|
||||
|
||||
selected_previews = []
|
||||
|
||||
for file in sorted(os.listdir(preview_dir)):
|
||||
if not file.startswith(file_start):
|
||||
continue
|
||||
|
||||
for file in camera_files:
|
||||
if file < start_file:
|
||||
continue
|
||||
|
||||
@@ -1550,12 +1553,17 @@ def preview_mp4(
|
||||
file_start = f"preview_{camera_name}-"
|
||||
start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
|
||||
end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
|
||||
|
||||
camera_files = [
|
||||
entry.name
|
||||
for entry in os.scandir(preview_dir)
|
||||
if entry.name.startswith(file_start)
|
||||
]
|
||||
camera_files.sort()
|
||||
|
||||
selected_previews = []
|
||||
|
||||
for file in sorted(os.listdir(preview_dir)):
|
||||
if not file.startswith(file_start):
|
||||
continue
|
||||
|
||||
for file in camera_files:
|
||||
if file < start_file:
|
||||
continue
|
||||
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
"""URI-aware authorization for nginx-served static media.
|
||||
|
||||
The `/auth` endpoint (used as nginx `auth_request` target) calls into this
|
||||
module to classify the requested URI from the `X-Original-URL` header and, for
|
||||
camera-scoped resources, decide whether the current role may access them.
|
||||
|
||||
Without this, `auth_request` only verifies the JWT — every authenticated user
|
||||
could read clips, recordings, and exports for *any* camera, bypassing the
|
||||
per-camera authorization the regular API enforces via `require_camera_access`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
from peewee import DoesNotExist
|
||||
|
||||
from frigate.config import FrigateConfig
|
||||
from frigate.const import EXPORT_DIR
|
||||
from frigate.models import Export, User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MediaAuthResolution(str, Enum):
|
||||
"""Classification of an `X-Original-URL` path for media-auth purposes."""
|
||||
|
||||
CAMERA = "camera"
|
||||
ADMIN_ONLY = "admin_only"
|
||||
LISTING_MULTI_CAMERA = "listing_multi_camera"
|
||||
LISTING_NEUTRAL = "listing_neutral"
|
||||
# Under a recognized media root (/clips, /recordings, /exports) but
|
||||
# unclassifiable (unknown subtree, no matching DB row, DB error).
|
||||
# Restricted users are denied; admins/full-access roles are allowed
|
||||
# (nginx will likely return 404 if the file genuinely doesn't exist).
|
||||
UNRESOLVED_MEDIA = "unresolved_media"
|
||||
# Not a media URI at all (e.g. /api/events, /login).
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
def extract_path(original_url: Optional[str]) -> Optional[str]:
|
||||
"""Return the decoded path component of nginx's `X-Original-URL` header.
|
||||
|
||||
nginx forwards the *raw* request URI (with `..` segments intact) via
|
||||
`$request_uri`. nginx normalizes the path before serving the file, so a
|
||||
request like `/recordings/.../allowed_cam/../forbidden_cam/file.mp4`
|
||||
would (1) parse as the allowed camera in our auth check, (2) be served
|
||||
as the forbidden camera by nginx. To close the bypass we reject any URI
|
||||
whose path contains `.` or `..` segments outright.
|
||||
"""
|
||||
if not original_url:
|
||||
return None
|
||||
|
||||
parsed = urlparse(original_url)
|
||||
raw_path = parsed.path or original_url
|
||||
decoded = unquote(raw_path)
|
||||
if not decoded:
|
||||
return None
|
||||
|
||||
if not decoded.startswith("/"):
|
||||
decoded = "/" + decoded
|
||||
|
||||
segments = decoded.split("/")
|
||||
if ".." in segments or "." in segments:
|
||||
return None
|
||||
|
||||
return decoded
|
||||
|
||||
|
||||
def resolve_media_uri(
|
||||
uri: str, frigate_config: Optional[FrigateConfig] = None
|
||||
) -> tuple[MediaAuthResolution, Optional[str]]:
|
||||
"""Classify a URI and return the owning camera if applicable.
|
||||
|
||||
`frigate_config` is used to disambiguate clip/review filenames whose
|
||||
camera name contains hyphens by matching against the longest configured
|
||||
camera-name prefix.
|
||||
"""
|
||||
if not uri:
|
||||
return MediaAuthResolution.UNKNOWN, None
|
||||
|
||||
parts = [p for p in uri.split("/") if p]
|
||||
if not parts:
|
||||
return MediaAuthResolution.UNKNOWN, None
|
||||
|
||||
root = parts[0]
|
||||
if root == "recordings":
|
||||
return _resolve_recording(parts)
|
||||
if root == "clips":
|
||||
return _resolve_clip(parts, frigate_config)
|
||||
if root == "exports":
|
||||
return _resolve_export(parts)
|
||||
|
||||
return MediaAuthResolution.UNKNOWN, None
|
||||
|
||||
|
||||
def _resolve_recording(
|
||||
parts: list[str],
|
||||
) -> tuple[MediaAuthResolution, Optional[str]]:
|
||||
# /recordings → neutral
|
||||
# /recordings/{date} → neutral
|
||||
# /recordings/{date}/{hour} → multi-camera listing
|
||||
# /recordings/{date}/{hour}/{cam}/... → camera
|
||||
if len(parts) <= 2:
|
||||
return MediaAuthResolution.LISTING_NEUTRAL, None
|
||||
if len(parts) == 3:
|
||||
return MediaAuthResolution.LISTING_MULTI_CAMERA, None
|
||||
return MediaAuthResolution.CAMERA, parts[3]
|
||||
|
||||
|
||||
def _resolve_clip(
|
||||
parts: list[str], frigate_config: Optional[FrigateConfig]
|
||||
) -> tuple[MediaAuthResolution, Optional[str]]:
|
||||
# /clips → multi-camera listing
|
||||
# /clips/thumbs/{cam}/... → camera
|
||||
# /clips/previews/{cam}/... → camera
|
||||
# /clips/review/thumb-{cam}-{review_id}.webp → camera (parsed)
|
||||
# /clips/faces/... → admin-only
|
||||
# /clips/genai-requests/... → admin-only
|
||||
# /clips/preview_restart_cache/... → admin-only
|
||||
# /clips/{model}/train|dataset/... → admin-only
|
||||
# /clips/{cam}-{event_id}[-clean].{ext} → camera (parsed)
|
||||
# other /clips/{subdir}/... → unresolved (deny restricted)
|
||||
if len(parts) == 1:
|
||||
return MediaAuthResolution.LISTING_MULTI_CAMERA, None
|
||||
|
||||
second = parts[1]
|
||||
|
||||
if second in ("thumbs", "previews"):
|
||||
if len(parts) == 2:
|
||||
return MediaAuthResolution.LISTING_MULTI_CAMERA, None
|
||||
return MediaAuthResolution.CAMERA, parts[2]
|
||||
|
||||
if second == "review":
|
||||
if len(parts) == 2:
|
||||
return MediaAuthResolution.LISTING_MULTI_CAMERA, None
|
||||
camera = _camera_from_thumb_filename(parts[2], frigate_config)
|
||||
if camera:
|
||||
return MediaAuthResolution.CAMERA, camera
|
||||
return MediaAuthResolution.UNRESOLVED_MEDIA, None
|
||||
|
||||
if second in ("faces", "genai-requests", "preview_restart_cache"):
|
||||
return MediaAuthResolution.ADMIN_ONLY, None
|
||||
|
||||
if len(parts) >= 3 and parts[2] in ("train", "dataset"):
|
||||
return MediaAuthResolution.ADMIN_ONLY, None
|
||||
|
||||
if len(parts) == 2:
|
||||
camera = _camera_from_clip_filename(second, frigate_config)
|
||||
if camera:
|
||||
return MediaAuthResolution.CAMERA, camera
|
||||
return MediaAuthResolution.UNRESOLVED_MEDIA, None
|
||||
|
||||
return MediaAuthResolution.UNRESOLVED_MEDIA, None
|
||||
|
||||
|
||||
def _longest_prefix_camera(
|
||||
stem: str, frigate_config: Optional[FrigateConfig]
|
||||
) -> Optional[str]:
|
||||
if frigate_config is None:
|
||||
return None
|
||||
for cam in sorted(frigate_config.cameras.keys(), key=len, reverse=True):
|
||||
if stem.startswith(cam + "-"):
|
||||
return cam
|
||||
return None
|
||||
|
||||
|
||||
def _camera_from_clip_filename(
|
||||
filename: str, frigate_config: Optional[FrigateConfig]
|
||||
) -> Optional[str]:
|
||||
"""Match a flat clip filename `{camera}-{event_id}[-clean].{ext}` against
|
||||
configured camera names. Longest-prefix wins so camera names containing
|
||||
hyphens (e.g. `front-door`) resolve correctly.
|
||||
"""
|
||||
dot = filename.rfind(".")
|
||||
stem = filename[:dot] if dot > 0 else filename
|
||||
return _longest_prefix_camera(stem, frigate_config)
|
||||
|
||||
|
||||
def _camera_from_thumb_filename(
|
||||
filename: str, frigate_config: Optional[FrigateConfig]
|
||||
) -> Optional[str]:
|
||||
"""Match a review thumbnail filename `thumb-{camera}-{review_id}.webp`."""
|
||||
if not filename.startswith("thumb-"):
|
||||
return None
|
||||
dot = filename.rfind(".")
|
||||
stem = filename[len("thumb-") : dot] if dot > 0 else filename[len("thumb-") :]
|
||||
return _longest_prefix_camera(stem, frigate_config)
|
||||
|
||||
|
||||
def _resolve_export(
|
||||
parts: list[str],
|
||||
) -> tuple[MediaAuthResolution, Optional[str]]:
|
||||
# /exports → multi-camera listing
|
||||
# /exports/{filename}.mp4 → camera (DB lookup by exact path)
|
||||
if len(parts) == 1:
|
||||
return MediaAuthResolution.LISTING_MULTI_CAMERA, None
|
||||
if len(parts) != 2:
|
||||
return MediaAuthResolution.UNRESOLVED_MEDIA, None
|
||||
|
||||
filename = parts[1]
|
||||
full_path = os.path.join(EXPORT_DIR, filename)
|
||||
try:
|
||||
export = Export.get(Export.video_path == full_path)
|
||||
return MediaAuthResolution.CAMERA, export.camera
|
||||
except DoesNotExist:
|
||||
return MediaAuthResolution.UNRESOLVED_MEDIA, None
|
||||
except Exception as e:
|
||||
logger.warning("Export DB lookup failed for %s: %s", filename, e)
|
||||
return MediaAuthResolution.UNRESOLVED_MEDIA, None
|
||||
|
||||
|
||||
def check_camera_access(role: str, camera: str, frigate_config: FrigateConfig) -> bool:
|
||||
"""Return True iff `role` may access `camera`.
|
||||
|
||||
Mirrors the gating logic in `require_camera_access`: admin and any role
|
||||
without a non-empty allow-list bypass the check.
|
||||
"""
|
||||
if role == "admin":
|
||||
return True
|
||||
|
||||
roles_dict = frigate_config.auth.roles
|
||||
if not roles_dict.get(role):
|
||||
return True
|
||||
|
||||
all_camera_names = set(frigate_config.cameras.keys())
|
||||
allowed = User.get_allowed_cameras(role, roles_dict, all_camera_names)
|
||||
return camera in allowed
|
||||
|
||||
|
||||
def is_role_restricted(role: str, frigate_config: FrigateConfig) -> bool:
|
||||
"""True if `role` has a non-empty allow-list (i.e. not full-access)."""
|
||||
if role == "admin":
|
||||
return False
|
||||
return bool(frigate_config.auth.roles.get(role))
|
||||
|
||||
|
||||
def deny_response_for_media_uri(
|
||||
original_url: Optional[str], role: Optional[str], frigate_config: FrigateConfig
|
||||
) -> Optional[int]:
|
||||
"""Decide whether the current role should be blocked from `original_url`.
|
||||
|
||||
Returns an HTTP status code (403) when access should be denied, or `None`
|
||||
when the request is allowed.
|
||||
"""
|
||||
if not original_url:
|
||||
return None
|
||||
|
||||
path = extract_path(original_url)
|
||||
|
||||
# `extract_path` returns None for URIs containing `.` or `..` segments.
|
||||
# For media-root URIs that's a traversal attempt — deny outright. For
|
||||
# non-media URIs, pass through (nginx / the backend handle them).
|
||||
if path is None:
|
||||
raw = urlparse(original_url).path or original_url
|
||||
decoded = unquote(raw)
|
||||
first = decoded.lstrip("/").split("/", 1)[0] if decoded else ""
|
||||
if first in ("clips", "recordings", "exports"):
|
||||
return 403
|
||||
return None
|
||||
|
||||
resolution, camera = resolve_media_uri(path, frigate_config)
|
||||
if resolution == MediaAuthResolution.UNKNOWN:
|
||||
return None
|
||||
|
||||
if not role or role == "admin":
|
||||
return None
|
||||
|
||||
if not is_role_restricted(role, frigate_config):
|
||||
return None
|
||||
|
||||
if resolution == MediaAuthResolution.LISTING_NEUTRAL:
|
||||
return None
|
||||
|
||||
if resolution in (
|
||||
MediaAuthResolution.LISTING_MULTI_CAMERA,
|
||||
MediaAuthResolution.ADMIN_ONLY,
|
||||
MediaAuthResolution.UNRESOLVED_MEDIA,
|
||||
):
|
||||
return 403
|
||||
|
||||
if resolution == MediaAuthResolution.CAMERA:
|
||||
if camera and check_camera_access(role, camera, frigate_config):
|
||||
return None
|
||||
return 403
|
||||
|
||||
return 403
|
||||
@@ -41,12 +41,6 @@ class MotionSearchRequest(BaseModel):
|
||||
le=100.0,
|
||||
description="Minimum change area as a percentage of the ROI",
|
||||
)
|
||||
frame_skip: int = Field(
|
||||
default=5,
|
||||
ge=1,
|
||||
le=30,
|
||||
description="Process every Nth frame (1=all frames, 5=every 5th frame)",
|
||||
)
|
||||
parallel: bool = Field(
|
||||
default=False,
|
||||
description="Enable parallel scanning across segments",
|
||||
@@ -97,6 +91,8 @@ class MotionSearchStatusResponse(BaseModel):
|
||||
total_frames_processed: Optional[int] = None
|
||||
error_message: Optional[str] = None
|
||||
metrics: Optional[MotionSearchMetricsResponse] = None
|
||||
scanning_timestamp: Optional[float] = None
|
||||
progress: Optional[float] = None
|
||||
|
||||
|
||||
@router.post(
|
||||
@@ -151,7 +147,6 @@ async def start_motion_search(
|
||||
polygon_points=body.polygon_points,
|
||||
threshold=body.threshold,
|
||||
min_area=body.min_area,
|
||||
frame_skip=body.frame_skip,
|
||||
parallel=body.parallel,
|
||||
max_results=body.max_results,
|
||||
)
|
||||
@@ -231,6 +226,9 @@ async def get_motion_search_status_endpoint(
|
||||
if job.metrics:
|
||||
response_content["metrics"] = job.metrics.to_dict()
|
||||
|
||||
response_content["scanning_timestamp"] = job.scanning_timestamp
|
||||
response_content["progress"] = job.progress
|
||||
|
||||
return JSONResponse(content=response_content)
|
||||
|
||||
|
||||
|
||||
@@ -148,12 +148,17 @@ def get_preview_frames_from_cache(camera_name: str, start_ts: float, end_ts: flo
|
||||
file_start = f"preview_{camera_name}-"
|
||||
start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
|
||||
end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
|
||||
|
||||
camera_files = [
|
||||
entry.name
|
||||
for entry in os.scandir(preview_dir)
|
||||
if entry.name.startswith(file_start)
|
||||
]
|
||||
camera_files.sort()
|
||||
|
||||
selected_previews = []
|
||||
|
||||
for file in sorted(os.listdir(preview_dir)):
|
||||
if not file.startswith(file_start):
|
||||
continue
|
||||
|
||||
for file in camera_files:
|
||||
if file < start_file:
|
||||
continue
|
||||
|
||||
|
||||
+21
-7
@@ -35,7 +35,7 @@ logger = logging.getLogger(__name__)
|
||||
router = APIRouter(tags=[Tags.recordings])
|
||||
|
||||
|
||||
@router.get("/recordings/storage", dependencies=[Depends(allow_any_authenticated())])
|
||||
@router.get("/recordings/storage", dependencies=[Depends(require_role(["admin"]))])
|
||||
def get_recordings_storage_usage(request: Request):
|
||||
recording_stats = request.app.stats_emitter.get_latest_stats()["service"][
|
||||
"storage"
|
||||
@@ -299,22 +299,36 @@ async def no_recordings(
|
||||
.iterator()
|
||||
)
|
||||
|
||||
# Convert recordings to list of (start, end) tuples
|
||||
# Convert recordings to list of (start, end) tuples, ordered by start_time
|
||||
recordings = [(r["start_time"], r["end_time"]) for r in data]
|
||||
|
||||
# Merge overlapping/adjacent recordings into covered intervals. The query
|
||||
# orders by start_time, so a single pass merges them
|
||||
covered: list[tuple[float, float]] = []
|
||||
for rec_start, rec_end in recordings:
|
||||
if covered and rec_start <= covered[-1][1]:
|
||||
covered[-1] = (covered[-1][0], max(covered[-1][1], rec_end))
|
||||
else:
|
||||
covered.append((rec_start, rec_end))
|
||||
|
||||
# Iterate through time segments and check if each has any recording
|
||||
no_recording_segments = []
|
||||
current = after
|
||||
current_gap_start = None
|
||||
idx = 0
|
||||
covered_count = len(covered)
|
||||
|
||||
while current < before:
|
||||
segment_end = min(current + scale, before)
|
||||
|
||||
# Check if this segment overlaps with any recording
|
||||
has_recording = any(
|
||||
rec_start < segment_end and rec_end > current
|
||||
for rec_start, rec_end in recordings
|
||||
)
|
||||
# Advance past covered intervals that end before this segment begins;
|
||||
# they cannot overlap this or any later segment.
|
||||
while idx < covered_count and covered[idx][1] <= current:
|
||||
idx += 1
|
||||
|
||||
# A covered interval overlaps the segment when it starts before the
|
||||
# segment ends (its end is already known to be > current).
|
||||
has_recording = idx < covered_count and covered[idx][0] < segment_end
|
||||
|
||||
if not has_recording:
|
||||
# This segment has no recordings
|
||||
|
||||
+14
-10
@@ -605,9 +605,10 @@ def motion_activity(
|
||||
if not filtered:
|
||||
return JSONResponse(content=[])
|
||||
camera_list = list(filtered)
|
||||
clauses.append((Recordings.camera << camera_list))
|
||||
else:
|
||||
clauses.append((Recordings.camera << allowed_cameras))
|
||||
camera_list = list(allowed_cameras)
|
||||
|
||||
clauses.append((Recordings.camera << camera_list))
|
||||
|
||||
data: list[Recordings] = (
|
||||
Recordings.select(
|
||||
@@ -635,14 +636,12 @@ def motion_activity(
|
||||
df.set_index(["start_time"], inplace=True)
|
||||
|
||||
# normalize data
|
||||
motion = (
|
||||
df["motion"]
|
||||
.resample(f"{scale}s")
|
||||
.apply(lambda x: max(x, key=abs, default=0.0))
|
||||
.fillna(0.0)
|
||||
.to_frame()
|
||||
)
|
||||
cameras = df["camera"].resample(f"{scale}s").agg(lambda x: ",".join(set(x)))
|
||||
motion = df["motion"].resample(f"{scale}s").max().fillna(0.0).to_frame()
|
||||
|
||||
if len(camera_list) == 1:
|
||||
cameras = df["camera"].resample(f"{scale}s").first().fillna("")
|
||||
else:
|
||||
cameras = df["camera"].resample(f"{scale}s").agg(lambda x: ",".join(set(x)))
|
||||
df = motion.join(cameras)
|
||||
|
||||
length = df.shape[0]
|
||||
@@ -658,6 +657,11 @@ def motion_activity(
|
||||
else:
|
||||
df.iloc[i : i + chunk, 0] = 0.0
|
||||
|
||||
# Drop resample gap-fill buckets. The resample above emits a row for every
|
||||
# {scale}s bucket spanning the range, and buckets with no recording get a
|
||||
# motion of 0 (from fillna) and an empty camera (from joining an empty set).
|
||||
df = df[df["camera"] != ""]
|
||||
|
||||
# change types for output
|
||||
df.index = df.index.astype(int) // (10**9)
|
||||
normalized = df.reset_index().to_dict("records")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user