python-aidot-cameras 0.11.8__tar.gz → 0.11.10__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.
- {python_aidot_cameras-0.11.8/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.10}/PKG-INFO +1 -1
- python_aidot_cameras-0.11.10/aidot/__init__.py +54 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/sdes.py +24 -2
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/pyproject.toml +1 -1
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
- python_aidot_cameras-0.11.10/tests/test_logging_caps.py +31 -0
- python_aidot_cameras-0.11.10/tests/test_sdes_ffmpeg_stderr_log.py +52 -0
- python_aidot_cameras-0.11.8/aidot/__init__.py +0 -46
- python_aidot_cameras-0.11.8/tests/test_logging_caps.py +0 -22
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/LICENSE +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/README.md +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/__main__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/aes_utils.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/__init__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/client.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/constants.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/controls.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/lan_control.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/models.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/playback.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/protocol.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/sdes_open.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/tutk.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/client.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/const.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/credentials.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/device_client.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/discover.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/exceptions.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/g711.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/login_const.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/models/__init__.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/models/device_client_model.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/models/device_model.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/models/discover_model.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/py.typed +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/setup.cfg +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_wait_or_event.py +0 -0
{python_aidot_cameras-0.11.8/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.10}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.10
|
|
4
4
|
Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
|
|
5
5
|
Author-email: cbrightly <chris.brightly@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""AiDot camera and device library."""
|
|
2
|
+
|
|
3
|
+
import logging as _logging
|
|
4
|
+
|
|
5
|
+
from .client import AidotClient
|
|
6
|
+
from .device_client import DeviceClient
|
|
7
|
+
from .discover import Discover
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
AidotAuthFailed,
|
|
10
|
+
AidotAuthTokenExpired,
|
|
11
|
+
AidotCameraBusy,
|
|
12
|
+
AidotError,
|
|
13
|
+
AidotNotLogin,
|
|
14
|
+
AidotOSError,
|
|
15
|
+
AidotUserOrPassIncorrect,
|
|
16
|
+
HTTPError,
|
|
17
|
+
InvalidHost,
|
|
18
|
+
InvalidURL,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Cap the per-packet loggers that flood the log at DEBUG. When a user enables
|
|
22
|
+
# DEBUG on the parent ``aidot`` logger to diagnose the integration, these emit
|
|
23
|
+
# thousands of lines per second (in one capture, 99% of all lines) - and on a
|
|
24
|
+
# microSD Home Assistant host that log I/O can starve the recorder. Cap them at
|
|
25
|
+
# INFO so enabling ``aidot`` DEBUG stays useful; the diagnostically valuable
|
|
26
|
+
# DEBUG (DTLS, SCTP/DCEP, ICE connection state) still flows. This covers both the
|
|
27
|
+
# vendored aiortc RTP receiver/sender AND the external ``aioice`` package's
|
|
28
|
+
# per-STUN/TURN-packet loggers (aioice is a real dependency, not vendored, so it
|
|
29
|
+
# is not under ``aidot._vendor``). Respect an explicit level if the user set one.
|
|
30
|
+
for _chatty in (
|
|
31
|
+
"aidot._vendor.aiortc.rtcrtpreceiver",
|
|
32
|
+
"aidot._vendor.aiortc.rtcrtpsender",
|
|
33
|
+
"aioice.ice",
|
|
34
|
+
"aioice.turn",
|
|
35
|
+
):
|
|
36
|
+
_lg = _logging.getLogger(_chatty)
|
|
37
|
+
if _lg.level == _logging.NOTSET:
|
|
38
|
+
_lg.setLevel(_logging.INFO)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AidotAuthFailed",
|
|
42
|
+
"AidotAuthTokenExpired",
|
|
43
|
+
"AidotCameraBusy",
|
|
44
|
+
"AidotClient",
|
|
45
|
+
"AidotError",
|
|
46
|
+
"AidotNotLogin",
|
|
47
|
+
"AidotOSError",
|
|
48
|
+
"AidotUserOrPassIncorrect",
|
|
49
|
+
"DeviceClient",
|
|
50
|
+
"Discover",
|
|
51
|
+
"HTTPError",
|
|
52
|
+
"InvalidHost",
|
|
53
|
+
"InvalidURL",
|
|
54
|
+
]
|
|
@@ -132,6 +132,29 @@ class SdesSession:
|
|
|
132
132
|
"""time.monotonic() of the last media packet forwarded, 0.0 if none yet."""
|
|
133
133
|
return self._media_progress[0]
|
|
134
134
|
|
|
135
|
+
def _log_ffmpeg_stderr(self, stderr_bytes: bytes) -> None:
|
|
136
|
+
"""Log ffmpeg's captured stderr at a level that matches what happened.
|
|
137
|
+
|
|
138
|
+
The common NO_MEDIA case - the camera's SRTP never arrives, so ffmpeg
|
|
139
|
+
finds no keyframe and writes an empty file - would otherwise spam a
|
|
140
|
+
WARNING on every serve retry for a camera that never delivers media.
|
|
141
|
+
Demote to debug ONLY when no media ever arrived AND the stderr is that
|
|
142
|
+
expected shape. Anything else - media WAS flowing (a genuine mid-stream
|
|
143
|
+
error), or an unexpected ffmpeg failure with no media (malformed SDP, bad
|
|
144
|
+
SRTP key, protocol reject) - still logs at WARNING, so real problems are
|
|
145
|
+
not hidden. Unknown ffmpeg wording degrades safely to WARNING.
|
|
146
|
+
"""
|
|
147
|
+
if not stderr_bytes:
|
|
148
|
+
return
|
|
149
|
+
text = stderr_bytes.decode(errors="replace")
|
|
150
|
+
expected_no_media = self.last_media_monotonic == 0.0 and (
|
|
151
|
+
"Output file is empty" in text
|
|
152
|
+
or "Could not find codec parameters" in text
|
|
153
|
+
)
|
|
154
|
+
(_LOGGER.debug if expected_no_media else _LOGGER.warning)(
|
|
155
|
+
"ffmpeg SDES stderr:\n%s", text
|
|
156
|
+
)
|
|
157
|
+
|
|
135
158
|
@staticmethod
|
|
136
159
|
def is_stalled(
|
|
137
160
|
last_media: float,
|
|
@@ -213,8 +236,7 @@ class SdesSession:
|
|
|
213
236
|
self._proc.stderr.close()
|
|
214
237
|
except Exception:
|
|
215
238
|
_LOGGER.debug("swallowed exception in %s", 'stop', exc_info=True)
|
|
216
|
-
|
|
217
|
-
_LOGGER.warning("ffmpeg SDES stderr:\n%s", stderr_bytes.decode(errors="replace"))
|
|
239
|
+
self._log_ffmpeg_stderr(stderr_bytes)
|
|
218
240
|
import os
|
|
219
241
|
try:
|
|
220
242
|
os.unlink(self._sdp_path)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.10"
|
|
8
8
|
description = "Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10/python_aidot_cameras.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.10
|
|
4
4
|
Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
|
|
5
5
|
Author-email: cbrightly <chris.brightly@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -105,6 +105,7 @@ tests/test_retry_policy.py
|
|
|
105
105
|
tests/test_sdes_adaptive.py
|
|
106
106
|
tests/test_sdes_echo_wait_timeout.py
|
|
107
107
|
tests/test_sdes_fast_liveplay.py
|
|
108
|
+
tests/test_sdes_ffmpeg_stderr_log.py
|
|
108
109
|
tests/test_sdes_idle_release.py
|
|
109
110
|
tests/test_sdes_open_cleanup.py
|
|
110
111
|
tests/test_sdes_serve_audio.py
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Importing the library caps the chatty per-packet loggers.
|
|
2
|
+
|
|
3
|
+
aiortc's RTP receiver/sender log every media packet at DEBUG, and the external
|
|
4
|
+
aioice package logs every STUN/TURN packet. Enabling DEBUG on the parent ``aidot``
|
|
5
|
+
logger (the normal way to diagnose the integration) would otherwise unleash
|
|
6
|
+
thousands of lines per second - which on a microSD host can starve the recorder.
|
|
7
|
+
The package init caps those loggers at INFO so DEBUG on ``aidot`` stays useful.
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
import aidot # noqa: F401 - import triggers the logger caps
|
|
12
|
+
|
|
13
|
+
CAPPED = (
|
|
14
|
+
"aidot._vendor.aiortc.rtcrtpreceiver",
|
|
15
|
+
"aidot._vendor.aiortc.rtcrtpsender",
|
|
16
|
+
"aioice.ice",
|
|
17
|
+
"aioice.turn",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_packet_loggers_capped_to_info():
|
|
22
|
+
for name in CAPPED:
|
|
23
|
+
lg = logging.getLogger(name)
|
|
24
|
+
assert lg.level == logging.INFO, f"{name} not capped (level={lg.level})"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_aidot_debug_does_not_unleash_the_packet_firehose():
|
|
28
|
+
# Even with the parent 'aidot' logger at DEBUG, the per-packet DEBUG is off.
|
|
29
|
+
logging.getLogger("aidot").setLevel(logging.DEBUG)
|
|
30
|
+
for name in CAPPED:
|
|
31
|
+
assert not logging.getLogger(name).isEnabledFor(logging.DEBUG), f"{name} still emits DEBUG"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""ffmpeg SDES stderr log level: quiet the expected NO_MEDIA case, keep real errors.
|
|
2
|
+
|
|
3
|
+
A camera whose SRTP never reaches us (NO_MEDIA - e.g. media not traversing the
|
|
4
|
+
relay) produces the expected "could not find codec parameters / empty output"
|
|
5
|
+
ffmpeg stderr on every serve retry. That must log at DEBUG (not spam WARNING).
|
|
6
|
+
But a genuine ffmpeg error - whether media was flowing, or an unexpected failure
|
|
7
|
+
with no media - still logs at WARNING so real problems are not hidden.
|
|
8
|
+
"""
|
|
9
|
+
import logging
|
|
10
|
+
import types
|
|
11
|
+
|
|
12
|
+
from aidot.camera.sdes import SdesSession
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _log_fn(last_media):
|
|
16
|
+
obj = types.SimpleNamespace(last_media_monotonic=last_media)
|
|
17
|
+
return SdesSession._log_ffmpeg_stderr.__get__(obj)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _stderr_records(caplog):
|
|
21
|
+
return [r for r in caplog.records if "ffmpeg SDES stderr" in r.getMessage()]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_no_media_expected_shape_logs_debug(caplog):
|
|
25
|
+
for msg in (b"...Output file is empty, nothing was encoded",
|
|
26
|
+
b"Could not find codec parameters for stream 1 (Video: h264, none)"):
|
|
27
|
+
caplog.clear()
|
|
28
|
+
with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
|
|
29
|
+
_log_fn(0.0)(msg)
|
|
30
|
+
recs = _stderr_records(caplog)
|
|
31
|
+
assert recs and recs[0].levelno == logging.DEBUG, msg
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_no_media_unexpected_error_still_warns(caplog):
|
|
35
|
+
# No media, but NOT the expected empty-output shape -> a real ffmpeg failure.
|
|
36
|
+
with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
|
|
37
|
+
_log_fn(0.0)(b"/tmp/x.sdp: Permission denied")
|
|
38
|
+
recs = _stderr_records(caplog)
|
|
39
|
+
assert recs and recs[0].levelno == logging.WARNING
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_media_flowed_logs_warning(caplog):
|
|
43
|
+
with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
|
|
44
|
+
_log_fn(1234.5)(b"Could not find codec parameters for stream 1")
|
|
45
|
+
recs = _stderr_records(caplog)
|
|
46
|
+
assert recs and recs[0].levelno == logging.WARNING
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_empty_stderr_logs_nothing(caplog):
|
|
50
|
+
with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
|
|
51
|
+
_log_fn(0.0)(b"")
|
|
52
|
+
assert _stderr_records(caplog) == []
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""AiDot camera and device library."""
|
|
2
|
-
|
|
3
|
-
import logging as _logging
|
|
4
|
-
|
|
5
|
-
from .client import AidotClient
|
|
6
|
-
from .device_client import DeviceClient
|
|
7
|
-
from .discover import Discover
|
|
8
|
-
from .exceptions import (
|
|
9
|
-
AidotAuthFailed,
|
|
10
|
-
AidotAuthTokenExpired,
|
|
11
|
-
AidotCameraBusy,
|
|
12
|
-
AidotError,
|
|
13
|
-
AidotNotLogin,
|
|
14
|
-
AidotOSError,
|
|
15
|
-
AidotUserOrPassIncorrect,
|
|
16
|
-
HTTPError,
|
|
17
|
-
InvalidHost,
|
|
18
|
-
InvalidURL,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
# The vendored aiortc RTP receiver/sender log EVERY media packet at DEBUG. When a
|
|
22
|
-
# user enables DEBUG on the parent ``aidot`` logger to diagnose the integration,
|
|
23
|
-
# that becomes thousands of lines per second and floods the logs (in one capture,
|
|
24
|
-
# 99% of all lines). Cap just those two per-packet loggers at INFO so enabling
|
|
25
|
-
# ``aidot`` DEBUG stays useful - the diagnostically valuable aiortc DEBUG (DTLS,
|
|
26
|
-
# ICE, SCTP/DCEP) still flows. Respect an explicit level if the user set one.
|
|
27
|
-
for _chatty in ("aidot._vendor.aiortc.rtcrtpreceiver", "aidot._vendor.aiortc.rtcrtpsender"):
|
|
28
|
-
_lg = _logging.getLogger(_chatty)
|
|
29
|
-
if _lg.level == _logging.NOTSET:
|
|
30
|
-
_lg.setLevel(_logging.INFO)
|
|
31
|
-
|
|
32
|
-
__all__ = [
|
|
33
|
-
"AidotAuthFailed",
|
|
34
|
-
"AidotAuthTokenExpired",
|
|
35
|
-
"AidotCameraBusy",
|
|
36
|
-
"AidotClient",
|
|
37
|
-
"AidotError",
|
|
38
|
-
"AidotNotLogin",
|
|
39
|
-
"AidotOSError",
|
|
40
|
-
"AidotUserOrPassIncorrect",
|
|
41
|
-
"DeviceClient",
|
|
42
|
-
"Discover",
|
|
43
|
-
"HTTPError",
|
|
44
|
-
"InvalidHost",
|
|
45
|
-
"InvalidURL",
|
|
46
|
-
]
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""Importing the library caps the vendored aiortc per-packet loggers.
|
|
2
|
-
|
|
3
|
-
aiortc's RTP receiver/sender log every media packet at DEBUG. Enabling DEBUG on
|
|
4
|
-
the parent ``aidot`` logger (the normal way to diagnose the integration) would
|
|
5
|
-
otherwise unleash thousands of lines per second. The package init caps just those
|
|
6
|
-
two loggers at INFO so DEBUG on ``aidot`` stays useful without the firehose.
|
|
7
|
-
"""
|
|
8
|
-
import logging
|
|
9
|
-
|
|
10
|
-
import aidot # noqa: F401 - import triggers the logger caps
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_vendored_aiortc_packet_loggers_capped_to_info():
|
|
14
|
-
for name in (
|
|
15
|
-
"aidot._vendor.aiortc.rtcrtpreceiver",
|
|
16
|
-
"aidot._vendor.aiortc.rtcrtpsender",
|
|
17
|
-
):
|
|
18
|
-
lg = logging.getLogger(name)
|
|
19
|
-
assert lg.level == logging.INFO, f"{name} not capped (level={lg.level})"
|
|
20
|
-
# Even with the parent 'aidot' logger at DEBUG, the per-packet DEBUG is off.
|
|
21
|
-
logging.getLogger("aidot").setLevel(logging.DEBUG)
|
|
22
|
-
assert not lg.isEnabledFor(logging.DEBUG), f"{name} still emits DEBUG"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/base.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/g711.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/g722.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/h264.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/opus.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/codecs/vpx.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/contrib/media.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/exceptions.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/jitterbuffer.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/mediastreams.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcdatachannel.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcicetransport.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcrtpreceiver.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/_vendor/aiortc/rtcrtpsender.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/aidot/models/device_client_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_deferred_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_device_login_guard.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_device_user_info_cache.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_dtls_not_ready_burst.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_dtls_skip_signaling_wait.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_keyframe_prompter.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_live_stream_param.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_mqtt_session_reconnect.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_no_undefined_names.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_offline_keepalive_pause.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_post_merge_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_echo_wait_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_fast_liveplay.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_idle_release.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_sdes_open_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_security_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_serializable_login_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_stream_drain_ownership.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.8 → python_aidot_cameras-0.11.10}/tests/test_token_file_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|