python-aidot-cameras 0.11.2__tar.gz → 0.11.3__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.2/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.3}/PKG-INFO +2 -2
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/README.md +1 -1
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/__main__.py +31 -8
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/client.py +22 -10
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/lan_control.py +4 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/playback.py +19 -1
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/sdes_open.py +15 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/webrtc_open.py +3 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/client.py +8 -1
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/pyproject.toml +1 -1
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3/python_aidot_cameras.egg-info}/PKG-INFO +2 -2
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_lan_control.py +20 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_motion_poll.py +1 -1
- python_aidot_cameras-0.11.3/tests/test_token_file_robustness.py +43 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/LICENSE +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/aes_utils.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/constants.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/controls.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/models.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/protocol.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/sdes.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/tutk.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/const.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/credentials.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/device_client.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/discover.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/exceptions.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/g711.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/login_const.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/models/__init__.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/models/device_client_model.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/models/device_model.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/models/discover_model.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/py.typed +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/setup.cfg +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_wait_or_event.py +0 -0
{python_aidot_cameras-0.11.2/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.3}/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.3
|
|
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
|
|
@@ -167,7 +167,7 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
167
167
|
| `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
|
|
168
168
|
| `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
169
169
|
| `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
170
|
-
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live
|
|
170
|
+
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
171
171
|
| `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
|
|
172
172
|
| `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
|
|
173
173
|
|
|
@@ -137,7 +137,7 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
137
137
|
| `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
|
|
138
138
|
| `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
139
139
|
| `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
140
|
-
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live
|
|
140
|
+
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
141
141
|
| `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
|
|
142
142
|
| `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
|
|
143
143
|
|
|
@@ -59,6 +59,7 @@ import logging
|
|
|
59
59
|
import os
|
|
60
60
|
import signal
|
|
61
61
|
import sys
|
|
62
|
+
import tempfile
|
|
62
63
|
|
|
63
64
|
import aiohttp
|
|
64
65
|
|
|
@@ -85,10 +86,25 @@ def _read_token_file(path: str) -> dict:
|
|
|
85
86
|
|
|
86
87
|
|
|
87
88
|
def _write_token_file(path: str, data: dict) -> None:
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
os.chmod
|
|
89
|
+
"""Atomic, 0600-secure write of login_info to ``path``.
|
|
90
|
+
|
|
91
|
+
Serialize into a temp file in the same directory (mkstemp creates it 0600,
|
|
92
|
+
closing the world-readable window os.chmod-after-write leaves) and only then
|
|
93
|
+
os.replace it over ``path`` - so a serialization failure never truncates the
|
|
94
|
+
previously-valid token. On any error the temp file is removed and re-raised.
|
|
95
|
+
"""
|
|
96
|
+
directory = os.path.dirname(path) or "."
|
|
97
|
+
fd, tmp = tempfile.mkstemp(dir=directory, prefix=".token-", suffix=".tmp")
|
|
98
|
+
try:
|
|
99
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
100
|
+
json.dump(data, fh)
|
|
101
|
+
os.replace(tmp, path)
|
|
102
|
+
except BaseException:
|
|
103
|
+
try:
|
|
104
|
+
os.unlink(tmp)
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
raise
|
|
92
108
|
|
|
93
109
|
|
|
94
110
|
def _install_token_cache(client: AidotClient, path: str) -> None:
|
|
@@ -133,10 +149,17 @@ async def _make_client(session: aiohttp.ClientSession) -> AidotClient:
|
|
|
133
149
|
password = os.environ.get("AIDOT_PASSWORD")
|
|
134
150
|
|
|
135
151
|
if token_file and os.path.exists(token_file):
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
152
|
+
try:
|
|
153
|
+
token = await loop.run_in_executor(None, _read_token_file, token_file)
|
|
154
|
+
except (OSError, ValueError) as exc:
|
|
155
|
+
# ValueError covers json.JSONDecodeError (empty/partial/corrupt
|
|
156
|
+
# cache). Fall through to the username/password path rather than
|
|
157
|
+
# crash CLI startup.
|
|
158
|
+
_LOGGER.warning("ignoring unreadable token cache %s: %s", token_file, exc)
|
|
159
|
+
else:
|
|
160
|
+
client = AidotClient(session=session, token=token)
|
|
161
|
+
_install_token_cache(client, token_file)
|
|
162
|
+
return client
|
|
140
163
|
|
|
141
164
|
if not username or not password:
|
|
142
165
|
sys.exit(
|
|
@@ -695,7 +695,9 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
695
695
|
self._motion_active: bool = False
|
|
696
696
|
self._motion_cb: Optional[Callable] = None
|
|
697
697
|
self._motion_task: Optional["asyncio.Task[None]"] = None
|
|
698
|
-
|
|
698
|
+
# Insertion-ordered set (dict keys) so the memory-bound trim can evict
|
|
699
|
+
# the OLDEST uids deterministically rather than arbitrary ones.
|
|
700
|
+
self._motion_seen: dict = {}
|
|
699
701
|
self._motion_interval: float = 30.0
|
|
700
702
|
|
|
701
703
|
# -- Pre-0.6 core API, kept for existing consumers ----------------------- #
|
|
@@ -1020,9 +1022,12 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
1020
1022
|
if not mqtt_host:
|
|
1021
1023
|
# Fall back to the regional MQTT broker URL known to work from
|
|
1022
1024
|
# the AiDot web client: wss://{region}-mqtt.arnoo.com:8443/mqtt
|
|
1025
|
+
# Redacted: `data`/`body` carries mqttPassword; log only the
|
|
1026
|
+
# non-sensitive key names present, mirroring the hasPwd-style
|
|
1027
|
+
# redaction of the debug log below.
|
|
1023
1028
|
_LOGGER.warning(
|
|
1024
1029
|
"getServerUrlConfig returned no mqttServerUrl; "
|
|
1025
|
-
"using regional fallback.
|
|
1030
|
+
"using regional fallback. keys=%s", sorted(data)
|
|
1026
1031
|
)
|
|
1027
1032
|
mqtt_host = f"wss://{self._region}-mqtt.arnoo.com:8443/mqtt"
|
|
1028
1033
|
|
|
@@ -2116,11 +2121,16 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2116
2121
|
return
|
|
2117
2122
|
frame_event.set()
|
|
2118
2123
|
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
+
try:
|
|
2125
|
+
session = await self.async_open_webrtc_stream(
|
|
2126
|
+
on_frame=_on_frame,
|
|
2127
|
+
timeout=timeout,
|
|
2128
|
+
status_callback=status_callback,
|
|
2129
|
+
)
|
|
2130
|
+
except Exception as _snap_exc:
|
|
2131
|
+
_LOGGER.error(
|
|
2132
|
+
"async_snapshot DTLS failed for %s: %s", self.device_id, _snap_exc)
|
|
2133
|
+
return False
|
|
2124
2134
|
try:
|
|
2125
2135
|
try:
|
|
2126
2136
|
await _asyncio.wait_for(frame_event.wait(), timeout=timeout)
|
|
@@ -2243,7 +2253,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2243
2253
|
if self._motion_task is not None and not self._motion_task.done():
|
|
2244
2254
|
return
|
|
2245
2255
|
self._motion_active = True
|
|
2246
|
-
self._motion_seen =
|
|
2256
|
+
self._motion_seen = {}
|
|
2247
2257
|
self._motion_task = asyncio.ensure_future(self._motion_poll_loop(lookback_s))
|
|
2248
2258
|
|
|
2249
2259
|
async def async_stop_motion_polling(self) -> None:
|
|
@@ -2273,7 +2283,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2273
2283
|
uid = it.get("eventUuid") or it.get("eventId") or it.get("recordId")
|
|
2274
2284
|
if not uid or uid in self._motion_seen:
|
|
2275
2285
|
continue
|
|
2276
|
-
self._motion_seen
|
|
2286
|
+
self._motion_seen[uid] = None
|
|
2277
2287
|
fresh.append(it)
|
|
2278
2288
|
if primed and fresh and self._motion_cb is not None:
|
|
2279
2289
|
for it in sorted(fresh, key=lambda x: x.get("eventTime") or 0):
|
|
@@ -2285,7 +2295,9 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
2285
2295
|
_LOGGER.debug("motion callback raised", exc_info=True)
|
|
2286
2296
|
primed = True
|
|
2287
2297
|
if len(self._motion_seen) > 1000: # bound memory
|
|
2288
|
-
|
|
2298
|
+
# Keep the 400 most-recent uids (insertion order), evicting
|
|
2299
|
+
# the oldest so still-in-window events stay deduplicated.
|
|
2300
|
+
self._motion_seen = dict.fromkeys(list(self._motion_seen)[-400:])
|
|
2289
2301
|
except asyncio.CancelledError:
|
|
2290
2302
|
return
|
|
2291
2303
|
except Exception as exc:
|
|
@@ -309,6 +309,10 @@ class CameraLanClient:
|
|
|
309
309
|
"attr": [], "channel": "tcp", "ascNumber": asc},
|
|
310
310
|
"tst": int(time.time() * 1000), "deviceId": self.device_id})]
|
|
311
311
|
replies = await self._session(build)
|
|
312
|
+
if not replies:
|
|
313
|
+
raise CameraLanError(
|
|
314
|
+
f"{self.device_id}: no reply to attribute query"
|
|
315
|
+
)
|
|
312
316
|
return (replies[0].get("payload") or {}).get("attr") or {}
|
|
313
317
|
|
|
314
318
|
async def async_set_attributes(self, attr: dict) -> bool:
|
|
@@ -193,15 +193,18 @@ class CloudPlaybackSession:
|
|
|
193
193
|
)
|
|
194
194
|
except TimeoutError:
|
|
195
195
|
_LOGGER.error("Cloud playback: login response timed out")
|
|
196
|
+
await self._cleanup()
|
|
196
197
|
return False
|
|
197
198
|
except Exception as exc:
|
|
198
199
|
_LOGGER.error("Cloud playback: login read error: %s", exc)
|
|
200
|
+
await self._cleanup()
|
|
199
201
|
return False
|
|
200
202
|
|
|
201
203
|
if hdr["cmd"] != _CMD_LOGIN_RES:
|
|
202
204
|
_LOGGER.error(
|
|
203
205
|
"Cloud playback: unexpected login response cmd=0x%04x", hdr["cmd"]
|
|
204
206
|
)
|
|
207
|
+
await self._cleanup()
|
|
205
208
|
return False
|
|
206
209
|
|
|
207
210
|
try:
|
|
@@ -211,6 +214,7 @@ class CloudPlaybackSession:
|
|
|
211
214
|
"Cloud playback: login rejected code=%s body=%s",
|
|
212
215
|
body_obj.get("code"), body_obj,
|
|
213
216
|
)
|
|
217
|
+
await self._cleanup()
|
|
214
218
|
return False
|
|
215
219
|
except (json.JSONDecodeError, ValueError):
|
|
216
220
|
pass # some firmware sends no JSON body - treat as success
|
|
@@ -292,6 +296,13 @@ class CloudPlaybackSession:
|
|
|
292
296
|
else:
|
|
293
297
|
_LOGGER.warning("Cloud playback: unexpected stream result=%d", result)
|
|
294
298
|
|
|
299
|
+
# Loop exited on end-of-stream or a receive error/timeout (not via an
|
|
300
|
+
# external stop(), which cancels this task and owns its own teardown).
|
|
301
|
+
# Tear down here so the heartbeat loop wakes to a closed writer and
|
|
302
|
+
# exits, and the socket is not left open until stop() is called.
|
|
303
|
+
self._running = False
|
|
304
|
+
await self._cleanup()
|
|
305
|
+
|
|
295
306
|
async def start(self) -> bool:
|
|
296
307
|
self._running = True
|
|
297
308
|
if not await self._connect_and_login():
|
|
@@ -326,12 +337,19 @@ class CloudPlaybackSession:
|
|
|
326
337
|
pass
|
|
327
338
|
self._hb_task = None
|
|
328
339
|
self._rx_task = None
|
|
340
|
+
await self._cleanup()
|
|
341
|
+
|
|
342
|
+
async def _cleanup(self) -> None:
|
|
343
|
+
# Close the TCP writer and drop the reader/writer refs. Nulling the
|
|
344
|
+
# writer makes a subsequent stop() (or a second call) a safe no-op, so
|
|
345
|
+
# this is the single teardown path shared by the login-failure returns,
|
|
346
|
+
# the receive-loop exit, and stop().
|
|
329
347
|
if self._writer is not None:
|
|
330
348
|
try:
|
|
331
349
|
self._writer.close()
|
|
332
350
|
await self._writer.wait_closed()
|
|
333
351
|
except Exception:
|
|
334
|
-
_LOGGER.debug("swallowed exception in %s", '
|
|
352
|
+
_LOGGER.debug("swallowed exception in %s", '_cleanup', exc_info=True)
|
|
335
353
|
self._writer = None
|
|
336
354
|
self._reader = None
|
|
337
355
|
|
|
@@ -672,6 +672,15 @@ class _SdesOpenMixin:
|
|
|
672
672
|
# proceed rather than abort on a code we can't classify as terminal
|
|
673
673
|
# (genuine terminal rejects are still caught on the webrtcResp ack).
|
|
674
674
|
if _lp_on_sdes == 0:
|
|
675
|
+
# Match the sibling raises: close the reserved sockets and
|
|
676
|
+
# signal the MQTT thread before propagating. (No sdp_path
|
|
677
|
+
# unlink here - it is not created until later, below.)
|
|
678
|
+
for _rsock in (_audio_sock, _video_sock):
|
|
679
|
+
try:
|
|
680
|
+
_rsock.close()
|
|
681
|
+
except Exception:
|
|
682
|
+
_LOGGER.debug("camera %s: swallowed exception in %s", getattr(self, "device_id", "?"), '_bridge_fn', exc_info=True)
|
|
683
|
+
outgoing_q.put_nowait(None) # stop MQTT thread
|
|
675
684
|
raise RuntimeError(
|
|
676
685
|
f"livePlay refused by camera (livePlay=0, code={_lp_code_sdes})")
|
|
677
686
|
elif _lp_code_sdes not in (0, 200):
|
|
@@ -3604,6 +3613,12 @@ class _SdesOpenMixin:
|
|
|
3604
3613
|
stdout=subprocess.DEVNULL,
|
|
3605
3614
|
stderr=subprocess.PIPE,
|
|
3606
3615
|
)
|
|
3616
|
+
# Point the shared holder at the live proc immediately.
|
|
3617
|
+
# The bridge thread polls _proc_holder[0]; if it still
|
|
3618
|
+
# sees the terminated old proc it logs "stream ended",
|
|
3619
|
+
# breaks, and closes the loopback sockets - starving the
|
|
3620
|
+
# restarted ffmpeg (0-frame stream).
|
|
3621
|
+
_proc_holder[0] = proc
|
|
3607
3622
|
_status("ffmpeg restarted with camera's SRTP keys")
|
|
3608
3623
|
except TimeoutError:
|
|
3609
3624
|
if _cam_echo_received:
|
|
@@ -1156,6 +1156,7 @@ class _WebRTCOpenMixin:
|
|
|
1156
1156
|
# transient on battery cameras and recover via ICE; abort
|
|
1157
1157
|
# there would spuriously kill otherwise-good streams.
|
|
1158
1158
|
if _lp_on == 0:
|
|
1159
|
+
outgoing_q.put_nowait(None) # stop MQTT thread (avoid orphan)
|
|
1159
1160
|
raise RuntimeError(
|
|
1160
1161
|
f"livePlay refused by camera (livePlay=0, code={_lp_code})"
|
|
1161
1162
|
)
|
|
@@ -1207,6 +1208,7 @@ class _WebRTCOpenMixin:
|
|
|
1207
1208
|
_lp_code2 = int(_lp_resp2.get("code", 200))
|
|
1208
1209
|
_lp_on2 = int(_lp_resp2.get("livePlay", 1))
|
|
1209
1210
|
if _lp_on2 == 0:
|
|
1211
|
+
outgoing_q.put_nowait(None) # stop MQTT thread (avoid orphan)
|
|
1210
1212
|
raise RuntimeError(
|
|
1211
1213
|
f"livePlay refused by camera (livePlay=0, code={_lp_code2})"
|
|
1212
1214
|
)
|
|
@@ -3210,6 +3212,7 @@ class _WebRTCOpenMixin:
|
|
|
3210
3212
|
# encoder is still cold. Tear down and raise so the serve loop
|
|
3211
3213
|
# fast-retries in a bounded burst instead of letting DTLS
|
|
3212
3214
|
# co-fail into the full 15s-gated generic retry.
|
|
3215
|
+
outgoing_q.put_nowait(None) # stop MQTT thread (avoid orphan)
|
|
3213
3216
|
try:
|
|
3214
3217
|
await pc.close()
|
|
3215
3218
|
except Exception:
|
|
@@ -38,6 +38,8 @@ from .const import (
|
|
|
38
38
|
SUPPORTED_COUNTRYS,
|
|
39
39
|
DEFAULT_COUNTRY_CODE,
|
|
40
40
|
CONF_IS_OWNER,
|
|
41
|
+
LOGIN_INFO_PERSISTENT_MQTT_KEY,
|
|
42
|
+
LOGIN_INFO_PERSISTENT_MQTT_LOCK_KEY,
|
|
41
43
|
RUNTIME_ONLY_LOGIN_INFO_KEYS,
|
|
42
44
|
ServerErrorCode,
|
|
43
45
|
)
|
|
@@ -567,7 +569,12 @@ class AidotClient:
|
|
|
567
569
|
# Close the account-shared persistent MQTT connection, if one was opened
|
|
568
570
|
# (AIDOT_PERSISTENT_MQTT). Stored on the shared login_info by the camera
|
|
569
571
|
# command/attr paths; closing here stops its background paho loop.
|
|
570
|
-
pm =
|
|
572
|
+
pm = None
|
|
573
|
+
if isinstance(self.login_info, dict):
|
|
574
|
+
pm = self.login_info.pop(LOGIN_INFO_PERSISTENT_MQTT_KEY, None)
|
|
575
|
+
# Also drop the lock so the asyncio.Lock does not linger on the
|
|
576
|
+
# account-shared login_info after close.
|
|
577
|
+
self.login_info.pop(LOGIN_INFO_PERSISTENT_MQTT_LOCK_KEY, None)
|
|
571
578
|
if pm is not None:
|
|
572
579
|
try:
|
|
573
580
|
pm.close()
|
|
@@ -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.3"
|
|
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.2 → python_aidot_cameras-0.11.3/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.3
|
|
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
|
|
@@ -167,7 +167,7 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
167
167
|
| `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. | unset (off) |
|
|
168
168
|
| `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
169
169
|
| `AIDOT_DTLS_FAST_LIVEPLAY` | The DTLS (A000088) analogue: skip the `livePlayReq`-echo and `livePlayResp` waits (the dominant LAN cold-start cost) while keeping the full ICE/TURN/DTLS handshake, so remote/relay viewing is unaffected. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
170
|
-
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live
|
|
170
|
+
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
171
171
|
| `AIDOT_SERVE_RELAY` | Hold the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
|
|
172
172
|
| `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
|
|
173
173
|
|
|
@@ -80,3 +80,23 @@ def test_network_helpers_are_callable():
|
|
|
80
80
|
assert inspect.iscoroutinefunction(discover_subnet)
|
|
81
81
|
sig = inspect.signature(discover_subnet)
|
|
82
82
|
assert list(sig.parameters) == ["cidr24", "timeout", "concurrency"]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_get_attributes_empty_replies_raises_lanerror():
|
|
86
|
+
# If the camera accepts login but never answers getDevAttrReq (read timeout),
|
|
87
|
+
# _session returns [] - async_get_attributes must raise CameraLanError (the
|
|
88
|
+
# contract callers rely on for cloud fallback), not an unguarded IndexError.
|
|
89
|
+
import asyncio
|
|
90
|
+
|
|
91
|
+
c = CameraLanClient(DEVICE, USER, ip="192.0.2.10")
|
|
92
|
+
|
|
93
|
+
async def _empty(_build):
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
c._session = _empty
|
|
97
|
+
|
|
98
|
+
async def _go():
|
|
99
|
+
with pytest.raises(CameraLanError):
|
|
100
|
+
await c.async_get_attributes()
|
|
101
|
+
|
|
102
|
+
asyncio.run(_go())
|
|
@@ -24,7 +24,7 @@ class _FakeDC:
|
|
|
24
24
|
self._call = 0
|
|
25
25
|
self._motion_active = True
|
|
26
26
|
self._motion_cb = None
|
|
27
|
-
self._motion_seen = set()
|
|
27
|
+
self._motion_seen: dict = {} # insertion-ordered set (dict keys); see CameraMixin
|
|
28
28
|
self._motion_interval = 0.01 # fast for the test
|
|
29
29
|
|
|
30
30
|
async def async_get_cloud_recordings(self, *a, **k):
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Tests for the standalone CLI's token-cache read/write robustness.
|
|
2
|
+
|
|
3
|
+
Covers the 0.11.3 fixes: the token file is written atomically at 0600 (a
|
|
4
|
+
serialization failure must not truncate a previously-valid token), and a
|
|
5
|
+
corrupt/partial cache is a caught ValueError (so _make_client can fall back
|
|
6
|
+
to username/password login instead of crashing).
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import stat
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from aidot.__main__ import _read_token_file, _write_token_file
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_write_token_file_round_trips_and_is_0600(tmp_path):
|
|
17
|
+
p = tmp_path / "token.json"
|
|
18
|
+
_write_token_file(str(p), {"accessToken": "abc", "id": "u1"})
|
|
19
|
+
assert _read_token_file(str(p)) == {"accessToken": "abc", "id": "u1"}
|
|
20
|
+
assert stat.S_IMODE(os.stat(p).st_mode) == 0o600
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_write_token_file_preserves_previous_on_serialization_failure(tmp_path):
|
|
24
|
+
p = tmp_path / "token.json"
|
|
25
|
+
_write_token_file(str(p), {"accessToken": "good"}) # seed a valid token
|
|
26
|
+
|
|
27
|
+
# A set is not JSON-serializable, so json.dump raises mid-write. The atomic
|
|
28
|
+
# temp-then-replace write must leave the previously-valid token untouched.
|
|
29
|
+
with pytest.raises(TypeError):
|
|
30
|
+
_write_token_file(str(p), {"broken": {1, 2, 3}})
|
|
31
|
+
|
|
32
|
+
assert _read_token_file(str(p)) == {"accessToken": "good"}
|
|
33
|
+
# And it must not leave a stray temp file behind.
|
|
34
|
+
assert [f for f in os.listdir(tmp_path) if f.startswith(".token-")] == []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_read_token_file_raises_valueerror_on_corrupt(tmp_path):
|
|
38
|
+
p = tmp_path / "token.json"
|
|
39
|
+
p.write_text("{ not valid json", encoding="utf-8")
|
|
40
|
+
# json.JSONDecodeError is a subclass of ValueError - the exact type
|
|
41
|
+
# _make_client catches to fall back to password login rather than crash.
|
|
42
|
+
with pytest.raises(ValueError):
|
|
43
|
+
_read_token_file(str(p))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/base.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/g711.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/g722.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/h264.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/opus.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/codecs/vpx.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/contrib/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/contrib/media.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/exceptions.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/jitterbuffer.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/mediastreams.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcconfiguration.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcdatachannel.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcdtlstransport.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcicetransport.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtpparameters.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtpreceiver.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcrtpsender.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/aidot/_vendor/aiortc/rtcsctptransport.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
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/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.2 → python_aidot_cameras-0.11.3}/tests/test_deferred_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_device_login_guard.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_device_user_info_cache.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_dtls_not_ready_burst.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/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.2 → python_aidot_cameras-0.11.3}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_no_undefined_names.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_offline_keepalive_pause.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_persistent_mqtt_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_post_merge_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_echo_wait_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_sdes_fast_liveplay.py
RENAMED
|
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.2 → python_aidot_cameras-0.11.3}/tests/test_security_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.11.2 → python_aidot_cameras-0.11.3}/tests/test_serializable_login_info.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
|