homesec 1.2.2__py3-none-any.whl → 1.2.3__py3-none-any.whl
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/app.py +5 -14
- homesec/cli.py +5 -4
- homesec/config/__init__.py +8 -1
- homesec/config/loader.py +17 -2
- homesec/config/validation.py +99 -6
- homesec/interfaces.py +2 -2
- homesec/maintenance/cleanup_clips.py +17 -4
- homesec/models/__init__.py +3 -25
- homesec/models/clip.py +1 -1
- homesec/models/config.py +10 -261
- homesec/models/enums.py +8 -0
- homesec/models/events.py +1 -1
- homesec/models/filter.py +3 -21
- homesec/models/vlm.py +11 -20
- homesec/pipeline/__init__.py +1 -2
- homesec/pipeline/core.py +9 -10
- homesec/plugins/alert_policies/__init__.py +5 -5
- homesec/plugins/alert_policies/default.py +21 -2
- homesec/plugins/analyzers/__init__.py +1 -3
- homesec/plugins/analyzers/openai.py +20 -13
- homesec/plugins/filters/__init__.py +1 -2
- homesec/plugins/filters/yolo.py +25 -5
- homesec/plugins/notifiers/__init__.py +1 -6
- homesec/plugins/notifiers/mqtt.py +21 -1
- homesec/plugins/notifiers/sendgrid_email.py +52 -1
- homesec/plugins/registry.py +27 -0
- homesec/plugins/sources/__init__.py +4 -4
- homesec/plugins/sources/ftp.py +1 -1
- homesec/plugins/sources/local_folder.py +1 -1
- homesec/plugins/sources/rtsp.py +1 -1
- homesec/plugins/storage/__init__.py +1 -9
- homesec/plugins/storage/dropbox.py +13 -1
- homesec/plugins/storage/local.py +8 -1
- homesec/repository/clip_repository.py +1 -1
- homesec/sources/__init__.py +3 -6
- homesec/sources/ftp.py +95 -2
- homesec/sources/local_folder.py +27 -2
- homesec/sources/rtsp/core.py +162 -2
- {homesec-1.2.2.dist-info → homesec-1.2.3.dist-info}/METADATA +7 -12
- homesec-1.2.3.dist-info/RECORD +73 -0
- homesec/models/source/__init__.py +0 -3
- homesec/models/source/ftp.py +0 -97
- homesec/models/source/local_folder.py +0 -30
- homesec/models/source/rtsp.py +0 -165
- homesec/pipeline/alert_policy.py +0 -5
- homesec-1.2.2.dist-info/RECORD +0 -78
- /homesec/{plugins/notifiers → notifiers}/multiplex.py +0 -0
- {homesec-1.2.2.dist-info → homesec-1.2.3.dist-info}/WHEEL +0 -0
- {homesec-1.2.2.dist-info → homesec-1.2.3.dist-info}/entry_points.txt +0 -0
- {homesec-1.2.2.dist-info → homesec-1.2.3.dist-info}/licenses/LICENSE +0 -0
homesec/sources/rtsp/core.py
CHANGED
|
@@ -17,9 +17,9 @@ from pathlib import Path
|
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
import numpy.typing as npt
|
|
20
|
+
from pydantic import BaseModel, Field, model_validator
|
|
20
21
|
|
|
21
22
|
from homesec.models.clip import Clip
|
|
22
|
-
from homesec.models.source.rtsp import RTSPSourceConfig
|
|
23
23
|
from homesec.sources.base import ThreadedClipSource
|
|
24
24
|
from homesec.sources.rtsp.clock import Clock, SystemClock
|
|
25
25
|
from homesec.sources.rtsp.frame_pipeline import FfmpegFramePipeline, FramePipeline
|
|
@@ -36,6 +36,166 @@ from homesec.sources.rtsp.utils import (
|
|
|
36
36
|
logger = logging.getLogger(__name__)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
class RTSPMotionConfig(BaseModel):
|
|
40
|
+
"""Motion detection configuration."""
|
|
41
|
+
|
|
42
|
+
model_config = {"extra": "forbid"}
|
|
43
|
+
|
|
44
|
+
pixel_threshold: int = Field(
|
|
45
|
+
default=45,
|
|
46
|
+
ge=0,
|
|
47
|
+
description="Pixel intensity delta required to count a pixel as changed.",
|
|
48
|
+
)
|
|
49
|
+
min_changed_pct: float = Field(
|
|
50
|
+
default=1.0,
|
|
51
|
+
ge=0.0,
|
|
52
|
+
description="Percent of pixels that must change to trigger motion (idle state).",
|
|
53
|
+
)
|
|
54
|
+
recording_sensitivity_factor: float = Field(
|
|
55
|
+
default=2.0,
|
|
56
|
+
ge=1.0,
|
|
57
|
+
description="Factor to reduce the threshold while recording (>=1.0).",
|
|
58
|
+
)
|
|
59
|
+
blur_kernel: int = Field(
|
|
60
|
+
default=5,
|
|
61
|
+
ge=0,
|
|
62
|
+
description="Gaussian blur kernel size (odd or zero; even values are normalized).",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class RTSPRecordingConfig(BaseModel):
|
|
67
|
+
"""Recording lifecycle configuration."""
|
|
68
|
+
|
|
69
|
+
model_config = {"extra": "forbid"}
|
|
70
|
+
|
|
71
|
+
stop_delay: float = Field(
|
|
72
|
+
default=10.0,
|
|
73
|
+
ge=0.0,
|
|
74
|
+
description="Seconds to keep recording after motion stops.",
|
|
75
|
+
)
|
|
76
|
+
max_recording_s: float = Field(
|
|
77
|
+
default=60.0,
|
|
78
|
+
gt=0.0,
|
|
79
|
+
description="Maximum seconds per recording before rotating.",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class RTSPStreamConfig(BaseModel):
|
|
84
|
+
"""RTSP/ffmpeg transport configuration."""
|
|
85
|
+
|
|
86
|
+
model_config = {"extra": "forbid"}
|
|
87
|
+
|
|
88
|
+
connect_timeout_s: float = Field(
|
|
89
|
+
default=2.0,
|
|
90
|
+
ge=0.0,
|
|
91
|
+
description="RTSP connect timeout (seconds) passed to ffmpeg/ffprobe when supported.",
|
|
92
|
+
)
|
|
93
|
+
io_timeout_s: float = Field(
|
|
94
|
+
default=2.0,
|
|
95
|
+
ge=0.0,
|
|
96
|
+
description="RTSP I/O timeout (seconds) passed to ffmpeg/ffprobe when supported.",
|
|
97
|
+
)
|
|
98
|
+
ffmpeg_flags: list[str] = Field(
|
|
99
|
+
default_factory=list,
|
|
100
|
+
description="Additional ffmpeg flags appended to the command.",
|
|
101
|
+
)
|
|
102
|
+
disable_hwaccel: bool = Field(
|
|
103
|
+
default=False,
|
|
104
|
+
description="Disable hardware-accelerated decoding.",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RTSPReconnectConfig(BaseModel):
|
|
109
|
+
"""Reconnect and fallback policy."""
|
|
110
|
+
|
|
111
|
+
model_config = {"extra": "forbid"}
|
|
112
|
+
|
|
113
|
+
max_attempts: int = Field(
|
|
114
|
+
default=0,
|
|
115
|
+
ge=0,
|
|
116
|
+
description="Max reconnect attempts (0 = retry forever).",
|
|
117
|
+
)
|
|
118
|
+
backoff_s: float = Field(
|
|
119
|
+
default=1.0,
|
|
120
|
+
ge=0.0,
|
|
121
|
+
description="Base backoff (seconds) between reconnect attempts.",
|
|
122
|
+
)
|
|
123
|
+
detect_fallback_attempts: int = Field(
|
|
124
|
+
default=3,
|
|
125
|
+
ge=0,
|
|
126
|
+
description="Failures before falling back from detect stream to main stream.",
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class RTSPRuntimeConfig(BaseModel):
|
|
131
|
+
"""Runtime loop configuration."""
|
|
132
|
+
|
|
133
|
+
model_config = {"extra": "forbid"}
|
|
134
|
+
|
|
135
|
+
frame_timeout_s: float = Field(
|
|
136
|
+
default=2.0,
|
|
137
|
+
ge=0.0,
|
|
138
|
+
description="Seconds without frames before considering the pipeline stalled.",
|
|
139
|
+
)
|
|
140
|
+
frame_queue_size: int = Field(
|
|
141
|
+
default=20,
|
|
142
|
+
ge=1,
|
|
143
|
+
description="Frame queue size used by the frame reader thread.",
|
|
144
|
+
)
|
|
145
|
+
heartbeat_s: float = Field(
|
|
146
|
+
default=30.0,
|
|
147
|
+
ge=0.0,
|
|
148
|
+
description="Seconds between heartbeat logs.",
|
|
149
|
+
)
|
|
150
|
+
debug_motion: bool = Field(
|
|
151
|
+
default=False,
|
|
152
|
+
description="Enable verbose motion detection logging.",
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class RTSPSourceConfig(BaseModel):
|
|
157
|
+
"""RTSP source configuration."""
|
|
158
|
+
|
|
159
|
+
model_config = {"extra": "forbid"}
|
|
160
|
+
|
|
161
|
+
camera_name: str | None = Field(
|
|
162
|
+
default=None,
|
|
163
|
+
description="Optional human-friendly camera name.",
|
|
164
|
+
)
|
|
165
|
+
rtsp_url_env: str | None = Field(
|
|
166
|
+
default=None,
|
|
167
|
+
description="Environment variable containing the RTSP URL.",
|
|
168
|
+
)
|
|
169
|
+
rtsp_url: str | None = Field(
|
|
170
|
+
default=None,
|
|
171
|
+
description="RTSP URL for the main stream.",
|
|
172
|
+
)
|
|
173
|
+
detect_rtsp_url_env: str | None = Field(
|
|
174
|
+
default=None,
|
|
175
|
+
description="Environment variable containing the detect stream RTSP URL.",
|
|
176
|
+
)
|
|
177
|
+
detect_rtsp_url: str | None = Field(
|
|
178
|
+
default=None,
|
|
179
|
+
description="RTSP URL for the detect stream.",
|
|
180
|
+
)
|
|
181
|
+
output_dir: str = Field(
|
|
182
|
+
default="./recordings",
|
|
183
|
+
description="Directory to store recordings and logs.",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
motion: RTSPMotionConfig = Field(default_factory=RTSPMotionConfig)
|
|
187
|
+
recording: RTSPRecordingConfig = Field(default_factory=RTSPRecordingConfig)
|
|
188
|
+
stream: RTSPStreamConfig = Field(default_factory=RTSPStreamConfig)
|
|
189
|
+
reconnect: RTSPReconnectConfig = Field(default_factory=RTSPReconnectConfig)
|
|
190
|
+
runtime: RTSPRuntimeConfig = Field(default_factory=RTSPRuntimeConfig)
|
|
191
|
+
|
|
192
|
+
@model_validator(mode="after")
|
|
193
|
+
def _require_rtsp_url(self) -> RTSPSourceConfig:
|
|
194
|
+
if not (self.rtsp_url or self.rtsp_url_env):
|
|
195
|
+
raise ValueError("rtsp_url_env or rtsp_url required for RTSP source")
|
|
196
|
+
return self
|
|
197
|
+
|
|
198
|
+
|
|
39
199
|
class RTSPRunState(str, Enum):
|
|
40
200
|
IDLE = "idle"
|
|
41
201
|
RECORDING = "recording"
|
|
@@ -970,7 +1130,7 @@ class RTSPSource(ThreadedClipSource):
|
|
|
970
1130
|
start_ts=start_ts,
|
|
971
1131
|
end_ts=end_ts,
|
|
972
1132
|
duration_s=duration_s,
|
|
973
|
-
|
|
1133
|
+
source_backend="rtsp",
|
|
974
1134
|
)
|
|
975
1135
|
|
|
976
1136
|
self._emit_clip(clip)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: homesec
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.3
|
|
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
|
|
@@ -326,15 +326,10 @@ graph TD
|
|
|
326
326
|
|
|
327
327
|
## Quickstart
|
|
328
328
|
|
|
329
|
-
###
|
|
330
|
-
|
|
329
|
+
### Docker
|
|
330
|
+
Use the included [docker-compose.yml](docker-compose.yml) (HomeSec + Postgres, pulls `leva/homesec:latest`).
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
git clone https://github.com/lan17/homesec.git
|
|
334
|
-
cd homesec
|
|
335
|
-
make up
|
|
336
|
-
```
|
|
337
|
-
*Modify `config/config.yaml` to add your real cameras, then restart.*
|
|
332
|
+
Configure your own config.yaml and .env files as described in Manual Setup.
|
|
338
333
|
|
|
339
334
|
### Manual Setup
|
|
340
335
|
For standard production usage without Docker Compose:
|
|
@@ -400,7 +395,7 @@ Best for real-world setups with flaky cameras.
|
|
|
400
395
|
cameras:
|
|
401
396
|
- name: driveway
|
|
402
397
|
source:
|
|
403
|
-
|
|
398
|
+
backend: rtsp
|
|
404
399
|
config:
|
|
405
400
|
rtsp_url_env: DRIVEWAY_RTSP_URL
|
|
406
401
|
output_dir: "./recordings"
|
|
@@ -411,7 +406,7 @@ cameras:
|
|
|
411
406
|
backoff_s: 5
|
|
412
407
|
|
|
413
408
|
filter:
|
|
414
|
-
|
|
409
|
+
backend: yolo
|
|
415
410
|
config:
|
|
416
411
|
classes: ["person", "car"]
|
|
417
412
|
min_confidence: 0.6
|
|
@@ -428,7 +423,7 @@ Uploads to Cloud but keeps analysis local.
|
|
|
428
423
|
```yaml
|
|
429
424
|
storage:
|
|
430
425
|
backend: dropbox
|
|
431
|
-
|
|
426
|
+
config:
|
|
432
427
|
token_env: DROPBOX_TOKEN
|
|
433
428
|
root: "/SecurityCam"
|
|
434
429
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
homesec/__init__.py,sha256=i4wRlg0CV3bxbxAWL_uSqW39r_F2sTRBNbelmoYOPuA,452
|
|
2
|
+
homesec/app.py,sha256=QkO_cs10_ARXS4H7fVe1scTfMDWjJPXU2hLVuCcRheA,11860
|
|
3
|
+
homesec/cli.py,sha256=8I8odsgjsZpzpV2AKZNqktx7xcw7n6i_Vzr6mlpNwAM,5685
|
|
4
|
+
homesec/errors.py,sha256=fBW_OdnYgqtb6u6t0YZh4tHgsiv0Pb1DiF9G6b4Vcbk,2105
|
|
5
|
+
homesec/interfaces.py,sha256=O059JmSVcRVWnUspd257ReEjX1zshjoiz86lYa52nsk,9873
|
|
6
|
+
homesec/logging_setup.py,sha256=Z12nzCPK04cqFHfIQgasioGfXb4TLvXJT9stzD8Dh4c,5546
|
|
7
|
+
homesec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
homesec/storage_paths.py,sha256=MGQNT_7mJS6wuTWUHZNl0hvRmSVRvNksLu_imfZOEpo,1696
|
|
9
|
+
homesec/config/__init__.py,sha256=luKM0A_xT-Ky5-xmwPNA3f1EAYn9oBGCra2Inf6ATFU,538
|
|
10
|
+
homesec/config/loader.py,sha256=lUpry5Ha9Kbd9Rx8SkGMSQ2xt1_-yEfBHldvstuU310,3465
|
|
11
|
+
homesec/config/validation.py,sha256=z_8AN0ytFNme3cApUDwpLZYTKbmOXMsOH5y9Ca-dSJA,7056
|
|
12
|
+
homesec/health/__init__.py,sha256=fbndfsLOR9aA7d_5I1mEZN0oM5IYMmcJNjOt0iaXKZc,107
|
|
13
|
+
homesec/health/server.py,sha256=VP-4XmZ0K3ooFyd000AFOZohZ5R7QcnZC4n7oj0RMqI,7014
|
|
14
|
+
homesec/maintenance/__init__.py,sha256=6a5W2x8oUgnoWaK374-Wq_nrOD5UDAUqUtSANaEck2M,60
|
|
15
|
+
homesec/maintenance/cleanup_clips.py,sha256=H-12mY9yJcmJjh7es0vZLpif0ttj1uTNQ7aWTwEYr4I,24047
|
|
16
|
+
homesec/models/__init__.py,sha256=AZZxyqdNyLqWz8Lg-1IwwEIBsFO1WNsEXNGQinMFRys,1372
|
|
17
|
+
homesec/models/alert.py,sha256=cMFr4NUGygq0-m2ep0jhjzVrFKM7kN1wJON1J0XwGY4,1010
|
|
18
|
+
homesec/models/clip.py,sha256=FNun9yH8gN-Z66zbrFM4FA2gbAwp8iLyOifWLL41qDY,2228
|
|
19
|
+
homesec/models/config.py,sha256=jGsi65mNgfBg1qqh2Urrrt5C9bBmEL0hPX24mqwx1pU,4319
|
|
20
|
+
homesec/models/enums.py,sha256=puyfL8_JQNy3RbML5vkO2vTFMjXTeRgGp7GQhok1SVQ,3446
|
|
21
|
+
homesec/models/events.py,sha256=m3jQM6GrgUCiXj4pXgtaM-EBtL56qE5nqYaXrz3mMuA,4945
|
|
22
|
+
homesec/models/filter.py,sha256=Jqd0nk6hygf-1rB_yjQcabcvVsavk5TY6v3K_v_Xjac,1647
|
|
23
|
+
homesec/models/storage.py,sha256=63wyHdDt3QrfdsP0SmhrxtOeWRllZ1O2GPrA4jI7XmU,235
|
|
24
|
+
homesec/models/vlm.py,sha256=QpwItxAm4k7DM-4GWm-L_ObdBRq4j9hOxoUJI2cTTY0,2966
|
|
25
|
+
homesec/notifiers/multiplex.py,sha256=LlnwozjkMDQwz7__v7mT4AohZbiWZK39CZunamRp7FM,3676
|
|
26
|
+
homesec/pipeline/__init__.py,sha256=7iil1gPFagQIM79hTtmY4gtr8Q-g4HOBI_3BA2CRVHo,131
|
|
27
|
+
homesec/pipeline/core.py,sha256=IhyFNjBL-OMfpj100xv9ddH12-TQxKvi6r3E8nSE3yw,23596
|
|
28
|
+
homesec/plugins/__init__.py,sha256=ex1AY_pJa9PZkr6yaneX3KgImZ-GkenTwExH2cMcF3A,1269
|
|
29
|
+
homesec/plugins/registry.py,sha256=crLQ2Fidni1GNNwuaGGSg66K-e1-nvjiY-KdLTrfjlA,6564
|
|
30
|
+
homesec/plugins/utils.py,sha256=a87kRYBZPnaUuvn6kRdf6N38b8aAbcPW9r-30T2rlBI,1980
|
|
31
|
+
homesec/plugins/alert_policies/__init__.py,sha256=WllHQAOLF85H6DdeiJAzgNJNceaWnR5gw7ZUnZH_aDA,1405
|
|
32
|
+
homesec/plugins/alert_policies/default.py,sha256=Rhm__YBYLFq-Q6whpuJMWl0ccl2mw8NMEBZ9J4DwGsA,3914
|
|
33
|
+
homesec/plugins/alert_policies/noop.py,sha256=xcdyZAlQf6X_AqQWUPkRMHLK8J6d7PTXWoPbwpnSlQE,1262
|
|
34
|
+
homesec/plugins/analyzers/__init__.py,sha256=Lg1DcOx4zGCDmZ4p09oDH1ZX-rxTUL_k0DLU0vvRW08,822
|
|
35
|
+
homesec/plugins/analyzers/openai.py,sha256=LBXAm2MULwSxgto-y5qhVi1sEXLtFusUNLUcXz7cy5w,16280
|
|
36
|
+
homesec/plugins/filters/__init__.py,sha256=IO7kSKKXPgBYNa3_a9w0wQv41orOIuUhfQ8dxOdIIBM,823
|
|
37
|
+
homesec/plugins/filters/yolo.py,sha256=H1m-ZHd796EJUjwofw0WH6MBu0r_S5VprZwjulsAdW0,11705
|
|
38
|
+
homesec/plugins/notifiers/__init__.py,sha256=gr9TFzVayun5DFIr92S3Iy5DtmKqVvefSKfH2-Nj-MM,932
|
|
39
|
+
homesec/plugins/notifiers/mqtt.py,sha256=nMlaynpgdLYmhjg8bo2c3lw0pJCCgdBQykFbnctJ7vc,6377
|
|
40
|
+
homesec/plugins/notifiers/sendgrid_email.py,sha256=lMOg7BCThbVNnlXkT8yqqw-Xb-lWbiMHT8oiww2c83Y,10614
|
|
41
|
+
homesec/plugins/sources/__init__.py,sha256=DrseH7lAcRFl70sKUG9GQXWWBbPVuoE9cAsz61fZV9o,1037
|
|
42
|
+
homesec/plugins/sources/ftp.py,sha256=PTz9IdjPUjRnqqYqkjYF3SRFJPkFR2lJlzMHray-u5g,847
|
|
43
|
+
homesec/plugins/sources/local_folder.py,sha256=5CalSDRCijGKC_1Lk6EaZt9uZ3QuQiukrLQeupEnzlU,1221
|
|
44
|
+
homesec/plugins/sources/rtsp.py,sha256=-JdbDrYo924tKetU423HWQRlHelVHXs9EPRkx94sa18,823
|
|
45
|
+
homesec/plugins/storage/__init__.py,sha256=_mjmAEX7-0uDRY5v50thv6NmXyG-jpqo_Av5bCw959A,887
|
|
46
|
+
homesec/plugins/storage/dropbox.py,sha256=HLkblKL4_qod8m4q7ZUiwEMYJ1reaEgZ2cw5gPOhsz8,10301
|
|
47
|
+
homesec/plugins/storage/local.py,sha256=QJgd0lRX3KUSzJFAtOII7SiaIVgGKE59QSo6e7YYXG4,3317
|
|
48
|
+
homesec/repository/__init__.py,sha256=6cye2uQIA2v6jeLk5D2S9y3rlkfzJH5GceqdOroF3hU,160
|
|
49
|
+
homesec/repository/clip_repository.py,sha256=NrJXa4-HedO9xMnlZsckH7mi6EqeOpUb53LlE0qb2TA,17096
|
|
50
|
+
homesec/sources/__init__.py,sha256=GEhd68w1BNhF09SK2rfH93RImOoyYzsPG7OW1Lrk2yE,477
|
|
51
|
+
homesec/sources/base.py,sha256=dKTxJxcDwJtykWDN3WYzkW5mtkRqlOJxJLWcLy82_Zo,7582
|
|
52
|
+
homesec/sources/ftp.py,sha256=PRg3oFqlUrxSbwyoayF1Fp076zJILNylLdJVUZAU_Eg,10058
|
|
53
|
+
homesec/sources/local_folder.py,sha256=C6g0FGZqp-O31gEhLphLmfCtLhOlJsymY0lgMMc8dh4,9125
|
|
54
|
+
homesec/sources/rtsp/__init__.py,sha256=wtBzdwzL7Cg0HyIGIpS3lBagekCAi_EYJOyCZFbT7K0,103
|
|
55
|
+
homesec/sources/rtsp/clock.py,sha256=Gf-CLBfgUmxfajmZim89vmWRG14hnc7iUUBVNNiUz6w,338
|
|
56
|
+
homesec/sources/rtsp/core.py,sha256=bzO5RBqQQx6rEAxLwesdZY5-Tm05MM_n9Gd-HA-cLDQ,52957
|
|
57
|
+
homesec/sources/rtsp/frame_pipeline.py,sha256=cIGrH5z4WE8swoAThTl7Wu3SViNq-00YyCcKMLBrCso,11620
|
|
58
|
+
homesec/sources/rtsp/hardware.py,sha256=sKCJhoVdmkDfCk2s5RL0lDPX_CuvcwlgVUESgRJK3us,4892
|
|
59
|
+
homesec/sources/rtsp/motion.py,sha256=FKDa1hvD_H3uxgnQ-Z0c7MhVd6u_35cO6XKZRVrXpaY,2870
|
|
60
|
+
homesec/sources/rtsp/recorder.py,sha256=fqL-zr-vtnFrDQc87zZFYeM-6JSUfMwQsc0zfR-mgy4,7009
|
|
61
|
+
homesec/sources/rtsp/utils.py,sha256=aufPAP6oc39kyFAiN0HTDhdXf3bQqWSVeVWNIHx5MmI,1021
|
|
62
|
+
homesec/state/__init__.py,sha256=Evt1jqTebmpJD1NUzNh3vwt5pbjDlLjQ0DgMCSAZOuM,255
|
|
63
|
+
homesec/state/postgres.py,sha256=I-cXqW5cgz-hpaHc0JIv3DnIBTmGxE28P8ZxBAGabSw,17765
|
|
64
|
+
homesec/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
+
homesec/telemetry/db_log_handler.py,sha256=KM8g4kcOyPzFJbpGxpSzecx_hrEWY0YfpoIKygETy5k,7539
|
|
66
|
+
homesec/telemetry/postgres_settings.py,sha256=EVD2_oi_KReFJvQmXxW026aurl_YD-KexT7rkbGQPHc,1198
|
|
67
|
+
homesec/telemetry/db/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
68
|
+
homesec/telemetry/db/log_table.py,sha256=wcZLwRht7FMa0z2gf37f_RxdVTNIdDiK4i_N3c_ibwg,473
|
|
69
|
+
homesec-1.2.3.dist-info/METADATA,sha256=j8zvaP62Dws7sbTz6eUB3TCYcOAX-tlHjiz0q2lWpQk,25081
|
|
70
|
+
homesec-1.2.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
71
|
+
homesec-1.2.3.dist-info/entry_points.txt,sha256=8ocCj_fP1qxIuL-DVDAUiaUbEdTMX_kg_BzVrJsbQYg,45
|
|
72
|
+
homesec-1.2.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
73
|
+
homesec-1.2.3.dist-info/RECORD,,
|
homesec/models/source/ftp.py
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
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
|
|
@@ -1,30 +0,0 @@
|
|
|
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
|
-
)
|
homesec/models/source/rtsp.py
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
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
|