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.
Files changed (142) hide show
  1. {homesec-1.2.1 → homesec-1.2.2}/CHANGELOG.md +106 -0
  2. {homesec-1.2.1 → homesec-1.2.2}/DESIGN.md +8 -1
  3. {homesec-1.2.1 → homesec-1.2.2}/PKG-INFO +7 -5
  4. {homesec-1.2.1 → homesec-1.2.2}/README.md +6 -4
  5. {homesec-1.2.1 → homesec-1.2.2}/config/example.yaml +23 -10
  6. {homesec-1.2.1 → homesec-1.2.2}/pyproject.toml +1 -1
  7. homesec-1.2.2/skills/local/homesec-db-logs/SKILL.md +73 -0
  8. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/__init__.py +3 -1
  9. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/config.py +3 -1
  10. homesec-1.2.2/src/homesec/models/source/__init__.py +3 -0
  11. homesec-1.2.2/src/homesec/models/source/ftp.py +97 -0
  12. homesec-1.2.2/src/homesec/models/source/local_folder.py +30 -0
  13. homesec-1.2.2/src/homesec/models/source/rtsp.py +165 -0
  14. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/ftp.py +1 -1
  15. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/local_folder.py +1 -1
  16. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/rtsp.py +2 -2
  17. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/__init__.py +4 -2
  18. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/ftp.py +1 -1
  19. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/local_folder.py +1 -1
  20. homesec-1.2.2/src/homesec/sources/rtsp/__init__.py +5 -0
  21. homesec-1.2.2/src/homesec/sources/rtsp/clock.py +18 -0
  22. homesec-1.2.2/src/homesec/sources/rtsp/core.py +1264 -0
  23. homesec-1.2.2/src/homesec/sources/rtsp/frame_pipeline.py +325 -0
  24. homesec-1.2.2/src/homesec/sources/rtsp/hardware.py +143 -0
  25. homesec-1.2.2/src/homesec/sources/rtsp/motion.py +94 -0
  26. homesec-1.2.2/src/homesec/sources/rtsp/recorder.py +180 -0
  27. homesec-1.2.2/src/homesec/sources/rtsp/utils.py +35 -0
  28. homesec-1.2.2/tests/homesec/rtsp/test_frame_pipeline.py +120 -0
  29. homesec-1.2.2/tests/homesec/rtsp/test_hardware.py +111 -0
  30. homesec-1.2.1/tests/homesec/test_rtsp_helpers.py → homesec-1.2.2/tests/homesec/rtsp/test_helpers.py +3 -3
  31. homesec-1.2.2/tests/homesec/rtsp/test_runtime.py +962 -0
  32. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_app.py +1 -1
  33. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_ftp_source.py +1 -1
  34. {homesec-1.2.1 → homesec-1.2.2}/uv.lock +1 -1
  35. homesec-1.2.1/src/homesec/models/source.py +0 -81
  36. homesec-1.2.1/src/homesec/sources/rtsp.py +0 -1304
  37. {homesec-1.2.1 → homesec-1.2.2}/.dockerignore +0 -0
  38. {homesec-1.2.1 → homesec-1.2.2}/.env.example +0 -0
  39. {homesec-1.2.1 → homesec-1.2.2}/.github/workflows/ci.yml +0 -0
  40. {homesec-1.2.1 → homesec-1.2.2}/.github/workflows/release.yaml +0 -0
  41. {homesec-1.2.1 → homesec-1.2.2}/.github/workflows/validate-pr-title.yaml +0 -0
  42. {homesec-1.2.1 → homesec-1.2.2}/.gitignore +0 -0
  43. {homesec-1.2.1 → homesec-1.2.2}/AGENTS.md +0 -0
  44. {homesec-1.2.1 → homesec-1.2.2}/Dockerfile +0 -0
  45. {homesec-1.2.1 → homesec-1.2.2}/LICENSE +0 -0
  46. {homesec-1.2.1 → homesec-1.2.2}/Makefile +0 -0
  47. {homesec-1.2.1 → homesec-1.2.2}/PLUGIN_DEVELOPMENT.md +0 -0
  48. {homesec-1.2.1 → homesec-1.2.2}/TESTING.md +0 -0
  49. {homesec-1.2.1 → homesec-1.2.2}/alembic/env.py +0 -0
  50. {homesec-1.2.1 → homesec-1.2.2}/alembic/script.py.mako +0 -0
  51. {homesec-1.2.1 → homesec-1.2.2}/alembic/versions/e6f25df0df90_initial.py +0 -0
  52. {homesec-1.2.1 → homesec-1.2.2}/alembic.ini +0 -0
  53. {homesec-1.2.1 → homesec-1.2.2}/docker-compose.yml +0 -0
  54. {homesec-1.2.1 → homesec-1.2.2}/docker-entrypoint.sh +0 -0
  55. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/__init__.py +0 -0
  56. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/app.py +0 -0
  57. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/cli.py +0 -0
  58. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/config/__init__.py +0 -0
  59. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/config/loader.py +0 -0
  60. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/config/validation.py +0 -0
  61. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/errors.py +0 -0
  62. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/health/__init__.py +0 -0
  63. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/health/server.py +0 -0
  64. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/interfaces.py +0 -0
  65. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/logging_setup.py +0 -0
  66. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/maintenance/__init__.py +0 -0
  67. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/maintenance/cleanup_clips.py +0 -0
  68. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/alert.py +0 -0
  69. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/clip.py +0 -0
  70. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/enums.py +0 -0
  71. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/events.py +0 -0
  72. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/filter.py +0 -0
  73. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/storage.py +0 -0
  74. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/models/vlm.py +0 -0
  75. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/pipeline/__init__.py +0 -0
  76. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/pipeline/alert_policy.py +0 -0
  77. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/pipeline/core.py +0 -0
  78. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/__init__.py +0 -0
  79. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/alert_policies/__init__.py +0 -0
  80. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/alert_policies/default.py +0 -0
  81. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/alert_policies/noop.py +0 -0
  82. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/analyzers/__init__.py +0 -0
  83. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/analyzers/openai.py +0 -0
  84. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/filters/__init__.py +0 -0
  85. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/filters/yolo.py +0 -0
  86. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/__init__.py +0 -0
  87. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/mqtt.py +0 -0
  88. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/multiplex.py +0 -0
  89. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/notifiers/sendgrid_email.py +0 -0
  90. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/registry.py +0 -0
  91. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/sources/__init__.py +0 -0
  92. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/storage/__init__.py +0 -0
  93. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/storage/dropbox.py +0 -0
  94. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/storage/local.py +0 -0
  95. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/plugins/utils.py +0 -0
  96. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/py.typed +0 -0
  97. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/repository/__init__.py +0 -0
  98. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/repository/clip_repository.py +0 -0
  99. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/sources/base.py +0 -0
  100. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/state/__init__.py +0 -0
  101. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/state/postgres.py +0 -0
  102. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/storage_paths.py +0 -0
  103. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/__init__.py +0 -0
  104. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/db/__init__.py +0 -0
  105. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/db/log_table.py +0 -0
  106. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/db_log_handler.py +0 -0
  107. {homesec-1.2.1 → homesec-1.2.2}/src/homesec/telemetry/postgres_settings.py +0 -0
  108. {homesec-1.2.1 → homesec-1.2.2}/tests/__init__.py +0 -0
  109. {homesec-1.2.1 → homesec-1.2.2}/tests/conftest.py +0 -0
  110. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/__init__.py +0 -0
  111. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/conftest.py +0 -0
  112. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/__init__.py +0 -0
  113. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/event_store.py +0 -0
  114. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/filter.py +0 -0
  115. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/notifier.py +0 -0
  116. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/state_store.py +0 -0
  117. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/storage.py +0 -0
  118. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/mocks/vlm.py +0 -0
  119. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_alert_policy.py +0 -0
  120. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_cleanup_clips.py +0 -0
  121. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_cli.py +0 -0
  122. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_clip_repository.py +0 -0
  123. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_clip_sources.py +0 -0
  124. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_config.py +0 -0
  125. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_dropbox_storage.py +0 -0
  126. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_enums.py +0 -0
  127. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_event_store.py +0 -0
  128. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_health.py +0 -0
  129. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_integration.py +0 -0
  130. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_local_storage.py +0 -0
  131. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_logging_setup.py +0 -0
  132. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_mqtt_notifier.py +0 -0
  133. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_notifiers.py +0 -0
  134. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_openai_vlm.py +0 -0
  135. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_pipeline.py +0 -0
  136. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_pipeline_events.py +0 -0
  137. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_plugin_registration.py +0 -0
  138. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_plugin_utils.py +0 -0
  139. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_sendgrid_notifier.py +0 -0
  140. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_source_health.py +0 -0
  141. {homesec-1.2.1 → homesec-1.2.2}/tests/homesec/test_state_store.py +0 -0
  142. {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.1
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
- # Critical for camera compatibility:
408
- ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
409
- reconnect_backoff_s: 5
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
- # Critical for camera compatibility:
165
- ffmpeg_flags: ["-rtsp_transport", "tcp", "-vsync", "0"]
166
- reconnect_backoff_s: 5
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
- # Motion detection settings
31
- pixel_threshold: 45 # Pixel difference threshold (0-255)
32
- min_changed_pct: 1.0 # Min % of frame that must change
33
- blur_kernel: 5 # Blur kernel size (reduces noise)
34
- stop_delay: 10 # Seconds of no motion before stopping
35
- max_recording_s: 60 # Max clip length
36
- frame_timeout_s: 2.0 # Timeout waiting for frames
37
- reconnect_backoff_s: 1.0 # Backoff between reconnects
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "homesec"
3
- version = "1.2.1"
3
+ version = "1.2.2"
4
4
  description = "Pluggable async home security camera pipeline with detection, VLM analysis, and alerts."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -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, LocalFolderSourceConfig, RTSPSourceConfig
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, LocalFolderSourceConfig, RTSPSourceConfig
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,3 @@
1
+ """Source configuration models."""
2
+
3
+ __all__: list[str] = []
@@ -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