homesec 1.2.1__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {homesec-1.2.1 → homesec-1.2.2}/CHANGELOG.md +106 -0
- {homesec-1.2.1 → homesec-1.2.2}/DESIGN.md +8 -1
- {homesec-1.2.1 → homesec-1.2.2}/PKG-INFO +7 -5
- {homesec-1.2.1 → homesec-1.2.2}/README.md +6 -4
- {homesec-1.2.1 → homesec-1.2.2}/config/example.yaml +23 -10
- {homesec-1.2.1 → homesec-1.2.2}/pyproject.toml +1 -1
- homesec-1.2.2/skills/local/homesec-db-logs/SKILL.md +73 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/__init__.py +3 -1
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/config.py +3 -1
- homesec-1.2.2/src/homesec/models/source/__init__.py +3 -0
- homesec-1.2.2/src/homesec/models/source/ftp.py +97 -0
- homesec-1.2.2/src/homesec/models/source/local_folder.py +30 -0
- homesec-1.2.2/src/homesec/models/source/rtsp.py +165 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/ftp.py +1 -1
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/local_folder.py +1 -1
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/rtsp.py +2 -2
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/__init__.py +4 -2
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/ftp.py +1 -1
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/local_folder.py +1 -1
- homesec-1.2.2/src/homesec/sources/rtsp/__init__.py +5 -0
- homesec-1.2.2/src/homesec/sources/rtsp/clock.py +18 -0
- homesec-1.2.2/src/homesec/sources/rtsp/core.py +1264 -0
- homesec-1.2.2/src/homesec/sources/rtsp/frame_pipeline.py +325 -0
- homesec-1.2.2/src/homesec/sources/rtsp/hardware.py +143 -0
- homesec-1.2.2/src/homesec/sources/rtsp/motion.py +94 -0
- homesec-1.2.2/src/homesec/sources/rtsp/recorder.py +180 -0
- homesec-1.2.2/src/homesec/sources/rtsp/utils.py +35 -0
- homesec-1.2.2/tests/homesec/rtsp/test_frame_pipeline.py +120 -0
- homesec-1.2.2/tests/homesec/rtsp/test_hardware.py +111 -0
- homesec-1.2.1/tests/homesec/test_rtsp_helpers.py → homesec-1.2.2/tests/homesec/rtsp/test_helpers.py +3 -3
- homesec-1.2.2/tests/homesec/rtsp/test_runtime.py +962 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_app.py +1 -1
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_ftp_source.py +1 -1
- {homesec-1.2.1 → homesec-1.2.2}/uv.lock +1 -1
- homesec-1.2.1/src/homesec/models/source.py +0 -81
- homesec-1.2.1/src/homesec/sources/rtsp.py +0 -1304
- {homesec-1.2.1 → homesec-1.2.2}/.dockerignore +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/.env.example +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/.github/workflows/ci.yml +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/.github/workflows/release.yaml +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/.github/workflows/validate-pr-title.yaml +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/.gitignore +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/AGENTS.md +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/Dockerfile +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/LICENSE +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/Makefile +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/PLUGIN_DEVELOPMENT.md +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/TESTING.md +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/alembic/env.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/alembic/script.py.mako +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/alembic/versions/e6f25df0df90_initial.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/alembic.ini +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/docker-compose.yml +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/docker-entrypoint.sh +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/app.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/cli.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/config/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/config/loader.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/config/validation.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/errors.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/health/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/health/server.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/interfaces.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/logging_setup.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/maintenance/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/maintenance/cleanup_clips.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/alert.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/clip.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/enums.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/events.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/filter.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/storage.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/vlm.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/pipeline/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/pipeline/alert_policy.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/pipeline/core.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/alert_policies/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/alert_policies/default.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/alert_policies/noop.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/analyzers/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/analyzers/openai.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/filters/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/filters/yolo.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/mqtt.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/multiplex.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/sendgrid_email.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/registry.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/storage/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/storage/dropbox.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/storage/local.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/utils.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/py.typed +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/repository/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/repository/clip_repository.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/base.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/state/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/state/postgres.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/storage_paths.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/db/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/db/log_table.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/db_log_handler.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/postgres_settings.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/conftest.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/conftest.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/__init__.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/event_store.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/filter.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/notifier.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/state_store.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/storage.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/vlm.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_alert_policy.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_cleanup_clips.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_cli.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_clip_repository.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_clip_sources.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_config.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_dropbox_storage.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_enums.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_event_store.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_health.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_integration.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_local_storage.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_logging_setup.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_mqtt_notifier.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_notifiers.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_openai_vlm.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_pipeline.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_pipeline_events.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_plugin_registration.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_plugin_utils.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_sendgrid_notifier.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_source_health.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_state_store.py +0 -0
- {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_yolo_filter.py +0 -0
|
@@ -2,6 +2,112 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v1.2.2 (2026-01-27)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Align recording sensitivity constraints ([#16](https://github.com/lan17/homesec/pull/16),
|
|
10
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
11
|
+
|
|
12
|
+
- Defer rtsp detect fallback while recording ([#16](https://github.com/lan17/homesec/pull/16),
|
|
13
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
14
|
+
|
|
15
|
+
- Harden rtsp recording retries ([#16](https://github.com/lan17/homesec/pull/16),
|
|
16
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
17
|
+
|
|
18
|
+
- Honor exact rtsp reconnect attempts ([#16](https://github.com/lan17/homesec/pull/16),
|
|
19
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
20
|
+
|
|
21
|
+
- Improve rtsp reconnect and fallback ([#16](https://github.com/lan17/homesec/pull/16),
|
|
22
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
23
|
+
|
|
24
|
+
- Make recording motion threshold more sensitive ([#16](https://github.com/lan17/homesec/pull/16),
|
|
25
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
26
|
+
|
|
27
|
+
### Chores
|
|
28
|
+
|
|
29
|
+
- Remove rtsp improvements plan from repo ([#16](https://github.com/lan17/homesec/pull/16),
|
|
30
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
31
|
+
|
|
32
|
+
### Documentation
|
|
33
|
+
|
|
34
|
+
- Add homesec db logs skill ([#16](https://github.com/lan17/homesec/pull/16),
|
|
35
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
36
|
+
|
|
37
|
+
### Refactoring
|
|
38
|
+
|
|
39
|
+
- Centralize rtsp recording state ([#16](https://github.com/lan17/homesec/pull/16),
|
|
40
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
41
|
+
|
|
42
|
+
- Export rtsp public api ([#16](https://github.com/lan17/homesec/pull/16),
|
|
43
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
44
|
+
|
|
45
|
+
- Extract rtsp motion detector ([#16](https://github.com/lan17/homesec/pull/16),
|
|
46
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
47
|
+
|
|
48
|
+
- Make rtsp pipeline lifecycle explicit ([#16](https://github.com/lan17/homesec/pull/16),
|
|
49
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
50
|
+
|
|
51
|
+
- Reorganize source configs ([#16](https://github.com/lan17/homesec/pull/16),
|
|
52
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
53
|
+
|
|
54
|
+
- Restrict rtsp package exports ([#16](https://github.com/lan17/homesec/pull/16),
|
|
55
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
56
|
+
|
|
57
|
+
- Simplify rtsp run loop ([#16](https://github.com/lan17/homesec/pull/16),
|
|
58
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
59
|
+
|
|
60
|
+
- Simplify rtsp run loop and probes ([#16](https://github.com/lan17/homesec/pull/16),
|
|
61
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
62
|
+
|
|
63
|
+
- Split rtsp into package modules ([#16](https://github.com/lan17/homesec/pull/16),
|
|
64
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
65
|
+
|
|
66
|
+
- Tighten rtsp detect fallback and backoff ([#16](https://github.com/lan17/homesec/pull/16),
|
|
67
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
68
|
+
|
|
69
|
+
### Testing
|
|
70
|
+
|
|
71
|
+
- Add rtsp reconnect deferral coverage ([#16](https://github.com/lan17/homesec/pull/16),
|
|
72
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
73
|
+
|
|
74
|
+
- Add rtsp stall and detect recovery coverage ([#16](https://github.com/lan17/homesec/pull/16),
|
|
75
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
76
|
+
|
|
77
|
+
- Cover detect switch fallback ([#16](https://github.com/lan17/homesec/pull/16),
|
|
78
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
79
|
+
|
|
80
|
+
- Cover ffmpeg timeout fallback ([#16](https://github.com/lan17/homesec/pull/16),
|
|
81
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
82
|
+
|
|
83
|
+
- Cover ffprobe error handling ([#16](https://github.com/lan17/homesec/pull/16),
|
|
84
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
85
|
+
|
|
86
|
+
- Cover ffprobe timeout fallback ([#16](https://github.com/lan17/homesec/pull/16),
|
|
87
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
88
|
+
|
|
89
|
+
- Cover recording start backoff ([#16](https://github.com/lan17/homesec/pull/16),
|
|
90
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
91
|
+
|
|
92
|
+
- Cover rtsp frame pipeline edge cases ([#16](https://github.com/lan17/homesec/pull/16),
|
|
93
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
94
|
+
|
|
95
|
+
- Cover rtsp recording timers ([#16](https://github.com/lan17/homesec/pull/16),
|
|
96
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
97
|
+
|
|
98
|
+
- Expand rtsp config coverage ([#16](https://github.com/lan17/homesec/pull/16),
|
|
99
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
100
|
+
|
|
101
|
+
- Expand rtsp coverage and regroup tests ([#16](https://github.com/lan17/homesec/pull/16),
|
|
102
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
103
|
+
|
|
104
|
+
- Harden rtsp config paths ([#16](https://github.com/lan17/homesec/pull/16),
|
|
105
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
106
|
+
|
|
107
|
+
- Harden rtsp hardware detection ([#16](https://github.com/lan17/homesec/pull/16),
|
|
108
|
+
[`8ac42d2`](https://github.com/lan17/homesec/commit/8ac42d2e828cf2c5e9f7c0cc5da32b677ddbbd54))
|
|
109
|
+
|
|
110
|
+
|
|
5
111
|
## v1.2.1 (2026-01-19)
|
|
6
112
|
|
|
7
113
|
### Bug Fixes
|
|
@@ -136,7 +136,14 @@ Build a reliable, pluggable pipeline to:
|
|
|
136
136
|
|
|
137
137
|
## Current Building Blocks (Repo)
|
|
138
138
|
|
|
139
|
-
- Motion + recording: `src/homesec/sources/rtsp.py` (OpenCV motion detection + ffmpeg recording).
|
|
139
|
+
- Motion + recording: `src/homesec/sources/rtsp/core.py` (OpenCV motion detection + ffmpeg recording).
|
|
140
|
+
- RTSP helpers live in `src/homesec/sources/rtsp/`:
|
|
141
|
+
- `frame_pipeline.py` (ffmpeg frame reader + stall/reconnect support)
|
|
142
|
+
- `recorder.py` (ffmpeg recording process)
|
|
143
|
+
- `motion.py` (motion detection logic)
|
|
144
|
+
- `hardware.py` (hwaccel detection)
|
|
145
|
+
- `clock.py` (clock abstraction for tests)
|
|
146
|
+
- `utils.py` (shared helpers)
|
|
140
147
|
- Object detection plugin (reference): `src/homesec/plugins/filters/yolo.py` (YOLOv8 sampling to detect people/animals).
|
|
141
148
|
- VLM plugin (reference): `src/homesec/plugins/analyzers/openai.py` (structured output with `risk_level`, `activity_type`, timeline).
|
|
142
149
|
- Postgres (workflow state when available): Alembic migrations (`alembic/`) + telemetry logging (`src/db_log_handler.py`) + workflow state table `clip_states` (best-effort; DB outages may drop state updates).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: homesec
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Pluggable async home security camera pipeline with detection, VLM analysis, and alerts.
|
|
5
5
|
Project-URL: Homepage, https://github.com/lan17/homesec
|
|
6
6
|
Project-URL: Source, https://github.com/lan17/homesec
|
|
@@ -404,9 +404,11 @@ cameras:
|
|
|
404
404
|
config:
|
|
405
405
|
rtsp_url_env: DRIVEWAY_RTSP_URL
|
|
406
406
|
output_dir: "./recordings"
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
stream:
|
|
408
|
+
# Critical for camera compatibility:
|
|
409
|
+
ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
|
|
410
|
+
reconnect:
|
|
411
|
+
backoff_s: 5
|
|
410
412
|
|
|
411
413
|
filter:
|
|
412
414
|
plugin: yolo
|
|
@@ -488,7 +490,7 @@ HomeSec uses a plugin architecture where every component is discovered at runtim
|
|
|
488
490
|
|
|
489
491
|
| Type | Plugins |
|
|
490
492
|
|------|---------|
|
|
491
|
-
| Sources | [`rtsp`](src/homesec/sources/rtsp.py), [`ftp`](src/homesec/sources/ftp.py), [`local_folder`](src/homesec/sources/local_folder.py) |
|
|
493
|
+
| Sources | [`rtsp`](src/homesec/sources/rtsp/core.py), [`ftp`](src/homesec/sources/ftp.py), [`local_folder`](src/homesec/sources/local_folder.py) |
|
|
492
494
|
| Filters | [`yolo`](src/homesec/plugins/filters/yolo.py) |
|
|
493
495
|
| Storage | [`dropbox`](src/homesec/plugins/storage/dropbox.py), [`local`](src/homesec/plugins/storage/local.py) |
|
|
494
496
|
| VLM analyzers | [`openai`](src/homesec/plugins/analyzers/openai.py) |
|
|
@@ -161,9 +161,11 @@ cameras:
|
|
|
161
161
|
config:
|
|
162
162
|
rtsp_url_env: DRIVEWAY_RTSP_URL
|
|
163
163
|
output_dir: "./recordings"
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
stream:
|
|
165
|
+
# Critical for camera compatibility:
|
|
166
|
+
ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
|
|
167
|
+
reconnect:
|
|
168
|
+
backoff_s: 5
|
|
167
169
|
|
|
168
170
|
filter:
|
|
169
171
|
plugin: yolo
|
|
@@ -245,7 +247,7 @@ HomeSec uses a plugin architecture where every component is discovered at runtim
|
|
|
245
247
|
|
|
246
248
|
| Type | Plugins |
|
|
247
249
|
|------|---------|
|
|
248
|
-
| Sources | [`rtsp`](src/homesec/sources/rtsp.py), [`ftp`](src/homesec/sources/ftp.py), [`local_folder`](src/homesec/sources/local_folder.py) |
|
|
250
|
+
| Sources | [`rtsp`](src/homesec/sources/rtsp/core.py), [`ftp`](src/homesec/sources/ftp.py), [`local_folder`](src/homesec/sources/local_folder.py) |
|
|
249
251
|
| Filters | [`yolo`](src/homesec/plugins/filters/yolo.py) |
|
|
250
252
|
| Storage | [`dropbox`](src/homesec/plugins/storage/dropbox.py), [`local`](src/homesec/plugins/storage/local.py) |
|
|
251
253
|
| VLM analyzers | [`openai`](src/homesec/plugins/analyzers/openai.py) |
|
|
@@ -24,17 +24,30 @@ cameras:
|
|
|
24
24
|
rtsp_url_env: FRONT_DOOR_RTSP_URL
|
|
25
25
|
# Optional: separate low-res stream for motion detection
|
|
26
26
|
# detect_rtsp_url_env: FRONT_DOOR_RTSP_SUB_URL
|
|
27
|
-
# Customize ffmpeg behavior (e.g., for flaky TCP streams)
|
|
28
|
-
ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
|
|
29
27
|
output_dir: "./recordings"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
stream:
|
|
29
|
+
# Customize ffmpeg behavior (e.g., for flaky TCP streams)
|
|
30
|
+
ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
|
|
31
|
+
connect_timeout_s: 2.0 # RTSP connect timeout (seconds)
|
|
32
|
+
io_timeout_s: 2.0 # RTSP I/O timeout (seconds)
|
|
33
|
+
disable_hwaccel: false
|
|
34
|
+
motion:
|
|
35
|
+
pixel_threshold: 45 # Pixel difference threshold (0-255)
|
|
36
|
+
min_changed_pct: 1.0 # Min % of frame that must change (idle)
|
|
37
|
+
recording_sensitivity_factor: 2.0 # Recording keepalive sensitivity (>=1.0)
|
|
38
|
+
blur_kernel: 5 # Blur kernel size (reduces noise)
|
|
39
|
+
recording:
|
|
40
|
+
stop_delay: 10 # Seconds of no motion before stopping
|
|
41
|
+
max_recording_s: 60 # Max clip length before rotation
|
|
42
|
+
runtime:
|
|
43
|
+
frame_timeout_s: 2.0 # Timeout waiting for frames
|
|
44
|
+
frame_queue_size: 20 # Frame queue size
|
|
45
|
+
heartbeat_s: 30.0 # Heartbeat log interval
|
|
46
|
+
debug_motion: false
|
|
47
|
+
reconnect:
|
|
48
|
+
backoff_s: 1.0 # Backoff between reconnects
|
|
49
|
+
max_attempts: 0 # 0 = retry forever
|
|
50
|
+
detect_fallback_attempts: 3 # Switch detect stream to main after N failures
|
|
38
51
|
|
|
39
52
|
# FTP server (receive uploads from cameras that support FTP)
|
|
40
53
|
- name: ftp_camera
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: homesec-db-logs
|
|
3
|
+
description: Query the HomeSec Postgres logs table to inspect recent runtime issues, counts by level/logger, and recurring error messages. Use when asked to check database logs in the telemetry DB, scan the last N hours for errors, or summarize log patterns. Enforce read-only access (SELECT only) and redact secrets.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# HomeSec DB Log Queries
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
1. Confirm the DSN and time window; use SELECT-only queries.
|
|
11
|
+
2. Confirm the logs table schema (from SQLAlchemy models) and time window.
|
|
12
|
+
3. Pull counts by level/logger, then top error/warn messages.
|
|
13
|
+
4. Inspect a few recent payloads to confirm fields.
|
|
14
|
+
5. Summarize findings; avoid dumping large payloads or secrets.
|
|
15
|
+
|
|
16
|
+
## Canonical Queries
|
|
17
|
+
|
|
18
|
+
Use psql with `-Atc` for clean output. Replace the DSN as needed.
|
|
19
|
+
|
|
20
|
+
- List tables:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
psql "$DSN" -Atc "SELECT table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name;"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- Inspect columns:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
psql "$DSN" -Atc "SELECT column_name, data_type FROM information_schema.columns WHERE table_schema='public' AND table_name='logs' ORDER BY ordinal_position;"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
- Severity counts (logs table with JSON payload):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
psql "$DSN" -Atc "SELECT payload->>'level' AS level, count(*) FROM logs WHERE ts >= now() - interval '12 hours' GROUP BY level ORDER BY count DESC;"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- Top error messages (logs table):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
psql "$DSN" -Atc "SELECT payload->>'message' AS message, count(*) FROM logs WHERE ts >= now() - interval '12 hours' AND payload->>'level'='ERROR' GROUP BY message ORDER BY count DESC LIMIT 10;"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- Top warn/error by logger (logs table):
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
psql "$DSN" -Atc "SELECT payload->>'logger' AS logger, count(*) FROM logs WHERE ts >= now() - interval '12 hours' AND payload->>'level' IN ('ERROR','WARNING') GROUP BY logger ORDER BY count DESC;"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- Recent samples (logs table):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
psql "$DSN" -Atc "SELECT payload FROM logs WHERE ts >= now() - interval '12 hours' ORDER BY ts DESC LIMIT 3;"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Table Structure Notes
|
|
57
|
+
|
|
58
|
+
- `logs`: generic JSONB log sink (defined in `src/homesec/telemetry/db/log_table.py`).
|
|
59
|
+
- Columns: `id` (bigint), `ts` (timestamptz), `payload` (jsonb).
|
|
60
|
+
- Common payload keys: `ts`, `kind`, `level`, `message`, `fields`, `logger`, `module`, `pathname`, `event_type`, `camera_name`, `recording_id`.
|
|
61
|
+
- Typical filters use JSONB: `payload->>'level'`, `payload->>'logger'`, `payload->>'message'`.
|
|
62
|
+
|
|
63
|
+
## Guardrails
|
|
64
|
+
|
|
65
|
+
- Use SELECT statements only. Never run INSERT/UPDATE/DELETE/DDL.
|
|
66
|
+
- If output contains secrets (RTSP URLs with credentials, tokens), redact before reporting.
|
|
67
|
+
- Keep results compact: aggregate counts first, then sample rows with LIMIT.
|
|
68
|
+
- State the time window explicitly (use absolute dates when summarizing).
|
|
69
|
+
|
|
70
|
+
## Interpretation Tips
|
|
71
|
+
|
|
72
|
+
- For repeated ffmpeg/ffprobe errors, check for missing flags or unsupported options.
|
|
73
|
+
- If warnings/errors cluster around a single logger, focus analysis there before expanding scope.
|
|
@@ -25,7 +25,9 @@ from homesec.models.config import (
|
|
|
25
25
|
)
|
|
26
26
|
from homesec.models.enums import RiskLevel, RiskLevelField
|
|
27
27
|
from homesec.models.filter import FilterConfig, FilterOverrides, FilterResult, YoloFilterSettings
|
|
28
|
-
from homesec.models.source import FtpSourceConfig
|
|
28
|
+
from homesec.models.source.ftp import FtpSourceConfig
|
|
29
|
+
from homesec.models.source.local_folder import LocalFolderSourceConfig
|
|
30
|
+
from homesec.models.source.rtsp import RTSPSourceConfig
|
|
29
31
|
from homesec.models.vlm import (
|
|
30
32
|
AnalysisResult,
|
|
31
33
|
EntityTimeline,
|
|
@@ -8,7 +8,9 @@ from pydantic import BaseModel, Field, field_validator, model_validator
|
|
|
8
8
|
|
|
9
9
|
from homesec.models.enums import RiskLevel, RiskLevelField
|
|
10
10
|
from homesec.models.filter import FilterConfig
|
|
11
|
-
from homesec.models.source import FtpSourceConfig
|
|
11
|
+
from homesec.models.source.ftp import FtpSourceConfig
|
|
12
|
+
from homesec.models.source.local_folder import LocalFolderSourceConfig
|
|
13
|
+
from homesec.models.source.rtsp import RTSPSourceConfig
|
|
12
14
|
from homesec.models.vlm import VLMConfig
|
|
13
15
|
|
|
14
16
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""FTP source configuration model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FtpSourceConfig(BaseModel):
|
|
9
|
+
"""FTP source configuration."""
|
|
10
|
+
|
|
11
|
+
model_config = {"extra": "forbid"}
|
|
12
|
+
|
|
13
|
+
camera_name: str | None = Field(
|
|
14
|
+
default=None,
|
|
15
|
+
description="Optional human-friendly camera name.",
|
|
16
|
+
)
|
|
17
|
+
host: str = Field(
|
|
18
|
+
default="0.0.0.0",
|
|
19
|
+
description="FTP bind address.",
|
|
20
|
+
)
|
|
21
|
+
port: int = Field(
|
|
22
|
+
default=2121,
|
|
23
|
+
ge=0,
|
|
24
|
+
le=65535,
|
|
25
|
+
description="FTP listen port (0 lets the OS choose an ephemeral port).",
|
|
26
|
+
)
|
|
27
|
+
root_dir: str = Field(
|
|
28
|
+
default="./ftp_incoming",
|
|
29
|
+
description="FTP root directory for uploads.",
|
|
30
|
+
)
|
|
31
|
+
ftp_subdir: str | None = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description="Optional subdirectory under root_dir.",
|
|
34
|
+
)
|
|
35
|
+
anonymous: bool = Field(
|
|
36
|
+
default=True,
|
|
37
|
+
description="Allow anonymous FTP uploads.",
|
|
38
|
+
)
|
|
39
|
+
username_env: str | None = Field(
|
|
40
|
+
default=None,
|
|
41
|
+
description="Environment variable containing FTP username.",
|
|
42
|
+
)
|
|
43
|
+
password_env: str | None = Field(
|
|
44
|
+
default=None,
|
|
45
|
+
description="Environment variable containing FTP password.",
|
|
46
|
+
)
|
|
47
|
+
perms: str = Field(
|
|
48
|
+
default="elw",
|
|
49
|
+
description="pyftpdlib permissions string.",
|
|
50
|
+
)
|
|
51
|
+
passive_ports: str | None = Field(
|
|
52
|
+
default=None,
|
|
53
|
+
description="Passive ports range (e.g., '60000-60100' or '60000,60010').",
|
|
54
|
+
)
|
|
55
|
+
masquerade_address: str | None = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="Optional masquerade address for passive mode.",
|
|
58
|
+
)
|
|
59
|
+
heartbeat_s: float = Field(
|
|
60
|
+
default=30.0,
|
|
61
|
+
ge=0.0,
|
|
62
|
+
description="Seconds between FTP health checks.",
|
|
63
|
+
)
|
|
64
|
+
allowed_extensions: list[str] = Field(
|
|
65
|
+
default_factory=lambda: [".mp4"],
|
|
66
|
+
description="Allowed file extensions for uploaded clips.",
|
|
67
|
+
)
|
|
68
|
+
delete_non_matching: bool = Field(
|
|
69
|
+
default=True,
|
|
70
|
+
description="Delete files with disallowed extensions.",
|
|
71
|
+
)
|
|
72
|
+
delete_incomplete: bool = Field(
|
|
73
|
+
default=True,
|
|
74
|
+
description="Delete incomplete uploads when enabled.",
|
|
75
|
+
)
|
|
76
|
+
default_duration_s: float = Field(
|
|
77
|
+
default=10.0,
|
|
78
|
+
ge=0.0,
|
|
79
|
+
description="Fallback clip duration when timestamps are missing.",
|
|
80
|
+
)
|
|
81
|
+
log_level: str = Field(
|
|
82
|
+
default="INFO",
|
|
83
|
+
description="FTP server log level.",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@field_validator("allowed_extensions")
|
|
87
|
+
@classmethod
|
|
88
|
+
def _normalize_extensions(cls, value: list[str]) -> list[str]:
|
|
89
|
+
cleaned: list[str] = []
|
|
90
|
+
for item in value:
|
|
91
|
+
ext = str(item).strip().lower()
|
|
92
|
+
if not ext:
|
|
93
|
+
continue
|
|
94
|
+
if not ext.startswith("."):
|
|
95
|
+
ext = f".{ext}"
|
|
96
|
+
cleaned.append(ext)
|
|
97
|
+
return cleaned
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Local folder source configuration model."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LocalFolderSourceConfig(BaseModel):
|
|
9
|
+
"""Local folder source configuration."""
|
|
10
|
+
|
|
11
|
+
model_config = {"extra": "forbid"}
|
|
12
|
+
|
|
13
|
+
camera_name: str | None = Field(
|
|
14
|
+
default=None,
|
|
15
|
+
description="Optional human-friendly camera name.",
|
|
16
|
+
)
|
|
17
|
+
watch_dir: str = Field(
|
|
18
|
+
default="recordings",
|
|
19
|
+
description="Directory to watch for new clips.",
|
|
20
|
+
)
|
|
21
|
+
poll_interval: float = Field(
|
|
22
|
+
default=1.0,
|
|
23
|
+
ge=0.0,
|
|
24
|
+
description="Polling interval in seconds.",
|
|
25
|
+
)
|
|
26
|
+
stability_threshold_s: float = Field(
|
|
27
|
+
default=3.0,
|
|
28
|
+
ge=0.0,
|
|
29
|
+
description="Seconds to wait for file size to stabilize before accepting a clip.",
|
|
30
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""RTSP source configuration models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RTSPMotionConfig(BaseModel):
|
|
9
|
+
"""Motion detection configuration."""
|
|
10
|
+
|
|
11
|
+
model_config = {"extra": "forbid"}
|
|
12
|
+
|
|
13
|
+
pixel_threshold: int = Field(
|
|
14
|
+
default=45,
|
|
15
|
+
ge=0,
|
|
16
|
+
description="Pixel intensity delta required to count a pixel as changed.",
|
|
17
|
+
)
|
|
18
|
+
min_changed_pct: float = Field(
|
|
19
|
+
default=1.0,
|
|
20
|
+
ge=0.0,
|
|
21
|
+
description="Percent of pixels that must change to trigger motion (idle state).",
|
|
22
|
+
)
|
|
23
|
+
recording_sensitivity_factor: float = Field(
|
|
24
|
+
default=2.0,
|
|
25
|
+
ge=1.0,
|
|
26
|
+
description="Factor to reduce the threshold while recording (>=1.0).",
|
|
27
|
+
)
|
|
28
|
+
blur_kernel: int = Field(
|
|
29
|
+
default=5,
|
|
30
|
+
ge=0,
|
|
31
|
+
description="Gaussian blur kernel size (odd or zero; even values are normalized).",
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class RTSPRecordingConfig(BaseModel):
|
|
36
|
+
"""Recording lifecycle configuration."""
|
|
37
|
+
|
|
38
|
+
model_config = {"extra": "forbid"}
|
|
39
|
+
|
|
40
|
+
stop_delay: float = Field(
|
|
41
|
+
default=10.0,
|
|
42
|
+
ge=0.0,
|
|
43
|
+
description="Seconds to keep recording after motion stops.",
|
|
44
|
+
)
|
|
45
|
+
max_recording_s: float = Field(
|
|
46
|
+
default=60.0,
|
|
47
|
+
gt=0.0,
|
|
48
|
+
description="Maximum seconds per recording before rotating.",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RTSPStreamConfig(BaseModel):
|
|
53
|
+
"""RTSP/ffmpeg transport configuration."""
|
|
54
|
+
|
|
55
|
+
model_config = {"extra": "forbid"}
|
|
56
|
+
|
|
57
|
+
connect_timeout_s: float = Field(
|
|
58
|
+
default=2.0,
|
|
59
|
+
ge=0.0,
|
|
60
|
+
description="RTSP connect timeout (seconds) passed to ffmpeg/ffprobe when supported.",
|
|
61
|
+
)
|
|
62
|
+
io_timeout_s: float = Field(
|
|
63
|
+
default=2.0,
|
|
64
|
+
ge=0.0,
|
|
65
|
+
description="RTSP I/O timeout (seconds) passed to ffmpeg/ffprobe when supported.",
|
|
66
|
+
)
|
|
67
|
+
ffmpeg_flags: list[str] = Field(
|
|
68
|
+
default_factory=list,
|
|
69
|
+
description="Additional ffmpeg flags appended to the command.",
|
|
70
|
+
)
|
|
71
|
+
disable_hwaccel: bool = Field(
|
|
72
|
+
default=False,
|
|
73
|
+
description="Disable hardware-accelerated decoding.",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class RTSPReconnectConfig(BaseModel):
|
|
78
|
+
"""Reconnect and fallback policy."""
|
|
79
|
+
|
|
80
|
+
model_config = {"extra": "forbid"}
|
|
81
|
+
|
|
82
|
+
max_attempts: int = Field(
|
|
83
|
+
default=0,
|
|
84
|
+
ge=0,
|
|
85
|
+
description="Max reconnect attempts (0 = retry forever).",
|
|
86
|
+
)
|
|
87
|
+
backoff_s: float = Field(
|
|
88
|
+
default=1.0,
|
|
89
|
+
ge=0.0,
|
|
90
|
+
description="Base backoff (seconds) between reconnect attempts.",
|
|
91
|
+
)
|
|
92
|
+
detect_fallback_attempts: int = Field(
|
|
93
|
+
default=3,
|
|
94
|
+
ge=0,
|
|
95
|
+
description="Failures before falling back from detect stream to main stream.",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class RTSPRuntimeConfig(BaseModel):
|
|
100
|
+
"""Runtime loop configuration."""
|
|
101
|
+
|
|
102
|
+
model_config = {"extra": "forbid"}
|
|
103
|
+
|
|
104
|
+
frame_timeout_s: float = Field(
|
|
105
|
+
default=2.0,
|
|
106
|
+
ge=0.0,
|
|
107
|
+
description="Seconds without frames before considering the pipeline stalled.",
|
|
108
|
+
)
|
|
109
|
+
frame_queue_size: int = Field(
|
|
110
|
+
default=20,
|
|
111
|
+
ge=1,
|
|
112
|
+
description="Frame queue size used by the frame reader thread.",
|
|
113
|
+
)
|
|
114
|
+
heartbeat_s: float = Field(
|
|
115
|
+
default=30.0,
|
|
116
|
+
ge=0.0,
|
|
117
|
+
description="Seconds between heartbeat logs.",
|
|
118
|
+
)
|
|
119
|
+
debug_motion: bool = Field(
|
|
120
|
+
default=False,
|
|
121
|
+
description="Enable verbose motion detection logging.",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class RTSPSourceConfig(BaseModel):
|
|
126
|
+
"""RTSP source configuration."""
|
|
127
|
+
|
|
128
|
+
model_config = {"extra": "forbid"}
|
|
129
|
+
|
|
130
|
+
camera_name: str | None = Field(
|
|
131
|
+
default=None,
|
|
132
|
+
description="Optional human-friendly camera name.",
|
|
133
|
+
)
|
|
134
|
+
rtsp_url_env: str | None = Field(
|
|
135
|
+
default=None,
|
|
136
|
+
description="Environment variable containing the RTSP URL.",
|
|
137
|
+
)
|
|
138
|
+
rtsp_url: str | None = Field(
|
|
139
|
+
default=None,
|
|
140
|
+
description="RTSP URL for the main stream.",
|
|
141
|
+
)
|
|
142
|
+
detect_rtsp_url_env: str | None = Field(
|
|
143
|
+
default=None,
|
|
144
|
+
description="Environment variable containing the detect stream RTSP URL.",
|
|
145
|
+
)
|
|
146
|
+
detect_rtsp_url: str | None = Field(
|
|
147
|
+
default=None,
|
|
148
|
+
description="RTSP URL for the detect stream.",
|
|
149
|
+
)
|
|
150
|
+
output_dir: str = Field(
|
|
151
|
+
default="./recordings",
|
|
152
|
+
description="Directory to store recordings and logs.",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
motion: RTSPMotionConfig = Field(default_factory=RTSPMotionConfig)
|
|
156
|
+
recording: RTSPRecordingConfig = Field(default_factory=RTSPRecordingConfig)
|
|
157
|
+
stream: RTSPStreamConfig = Field(default_factory=RTSPStreamConfig)
|
|
158
|
+
reconnect: RTSPReconnectConfig = Field(default_factory=RTSPReconnectConfig)
|
|
159
|
+
runtime: RTSPRuntimeConfig = Field(default_factory=RTSPRuntimeConfig)
|
|
160
|
+
|
|
161
|
+
@model_validator(mode="after")
|
|
162
|
+
def _require_rtsp_url(self) -> RTSPSourceConfig:
|
|
163
|
+
if not (self.rtsp_url or self.rtsp_url_env):
|
|
164
|
+
raise ValueError("rtsp_url_env or rtsp_url required for RTSP source")
|
|
165
|
+
return self
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from homesec.interfaces import ClipSource
|
|
6
|
-
from homesec.models.source import FtpSourceConfig
|
|
6
|
+
from homesec.models.source.ftp import FtpSourceConfig
|
|
7
7
|
from homesec.plugins.registry import PluginType, plugin
|
|
8
8
|
|
|
9
9
|
# Import the actual implementation from sources module
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from homesec.interfaces import ClipSource
|
|
6
|
-
from homesec.models.source import LocalFolderSourceConfig
|
|
6
|
+
from homesec.models.source.local_folder import LocalFolderSourceConfig
|
|
7
7
|
from homesec.plugins.registry import PluginType, plugin
|
|
8
8
|
from homesec.sources.local_folder import LocalFolderSource as LocalFolderSourceImpl
|
|
9
9
|
|
|
@@ -5,9 +5,9 @@ from __future__ import annotations
|
|
|
5
5
|
from typing import TYPE_CHECKING
|
|
6
6
|
|
|
7
7
|
from homesec.interfaces import ClipSource
|
|
8
|
-
from homesec.models.source import RTSPSourceConfig
|
|
8
|
+
from homesec.models.source.rtsp import RTSPSourceConfig
|
|
9
9
|
from homesec.plugins.registry import PluginType, plugin
|
|
10
|
-
from homesec.sources.rtsp import RTSPSource as RTSPSourceImpl
|
|
10
|
+
from homesec.sources.rtsp.core import RTSPSource as RTSPSourceImpl
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
pass
|