python-aidot-cameras 0.5.14__tar.gz → 0.5.16__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.5.14/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.5.16}/PKG-INFO +1 -1
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/pyproject.toml +1 -1
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/device_client.py +47 -11
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/LICENSE +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/README.md +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/setup.cfg +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/__init__.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/aes_utils.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/client.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/const.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/credentials.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/discover.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/exceptions.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/aidot/g711.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_token_refresh.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.16
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.16"
|
|
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"
|
|
@@ -3844,20 +3844,25 @@ class DeviceClient(object):
|
|
|
3844
3844
|
)
|
|
3845
3845
|
return []
|
|
3846
3846
|
|
|
3847
|
-
async def
|
|
3848
|
-
|
|
3847
|
+
async def async_get_event_video_media(
|
|
3848
|
+
self, event_uuid: str
|
|
3849
|
+
) -> Optional[tuple[str, str]]:
|
|
3850
|
+
"""Return (url, mime_type) for a specific cloud recording.
|
|
3849
3851
|
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
+
Prefers MP4 (type=1) over M3U8 (type=2) because HA's media player and
|
|
3853
|
+
most browsers handle direct MP4 more reliably than HLS with signed CDN
|
|
3854
|
+
segments. Falls back to whatever is available.
|
|
3852
3855
|
|
|
3853
3856
|
From CloudPlaybackFragment.java / EventListRepos.java:
|
|
3854
3857
|
POST /api/ipc/playback/getEventVideoUrl
|
|
3855
3858
|
body: {deviceId, eventList: [{eventUuid}]}
|
|
3856
|
-
response: data.eventUrlList[0].videoUrlList[
|
|
3857
|
-
type 1
|
|
3859
|
+
response: data.eventUrlList[0].videoUrlList[]
|
|
3860
|
+
each item: {url, type} (type 1=MP4, type 2=M3U8)
|
|
3858
3861
|
"""
|
|
3859
3862
|
import aiohttp
|
|
3860
3863
|
|
|
3864
|
+
_MIME = {1: "video/mp4", 2: "application/x-mpegURL"}
|
|
3865
|
+
|
|
3861
3866
|
try:
|
|
3862
3867
|
async with aiohttp.ClientSession() as session:
|
|
3863
3868
|
async with session.post(
|
|
@@ -3871,6 +3876,8 @@ class DeviceClient(object):
|
|
|
3871
3876
|
) as resp:
|
|
3872
3877
|
body = await resp.json(content_type=None)
|
|
3873
3878
|
|
|
3879
|
+
_LOGGER.debug("getEventVideoUrl raw for %s uuid=%s: %s", self.device_id, event_uuid, body)
|
|
3880
|
+
|
|
3874
3881
|
if body.get("code") != 200:
|
|
3875
3882
|
_LOGGER.warning(
|
|
3876
3883
|
"getEventVideoUrl code=%s for %s uuid=%s: %s",
|
|
@@ -3884,15 +3891,35 @@ class DeviceClient(object):
|
|
|
3884
3891
|
video_url_list = (event_url_list[0] or {}).get("videoUrlList") or []
|
|
3885
3892
|
if not video_url_list:
|
|
3886
3893
|
return None
|
|
3887
|
-
|
|
3894
|
+
|
|
3895
|
+
# Prefer MP4 (type=1), fall back to first available entry
|
|
3896
|
+
mp4 = next((v for v in video_url_list if v.get("type") == 1), None)
|
|
3897
|
+
entry = mp4 or video_url_list[0]
|
|
3898
|
+
url = entry.get("url")
|
|
3899
|
+
if not url:
|
|
3900
|
+
return None
|
|
3901
|
+
mime = _MIME.get(entry.get("type"), "video/mp4")
|
|
3902
|
+
_LOGGER.debug(
|
|
3903
|
+
"getEventVideoUrl resolved for %s: type=%s mime=%s url=%s",
|
|
3904
|
+
self.device_id, entry.get("type"), mime, url,
|
|
3905
|
+
)
|
|
3906
|
+
return url, mime
|
|
3888
3907
|
|
|
3889
3908
|
except Exception as exc:
|
|
3890
3909
|
_LOGGER.error(
|
|
3891
|
-
"
|
|
3910
|
+
"async_get_event_video_media failed for %s uuid=%s: %s",
|
|
3892
3911
|
self.device_id, event_uuid, exc,
|
|
3893
3912
|
)
|
|
3894
3913
|
return None
|
|
3895
3914
|
|
|
3915
|
+
async def async_get_event_video_url(self, event_uuid: str) -> Optional[str]:
|
|
3916
|
+
"""Return the video URL for a cloud recording (URL only, no MIME type).
|
|
3917
|
+
|
|
3918
|
+
Prefer async_get_event_video_media() when you also need the MIME type.
|
|
3919
|
+
"""
|
|
3920
|
+
result = await self.async_get_event_video_media(event_uuid)
|
|
3921
|
+
return result[0] if result else None
|
|
3922
|
+
|
|
3896
3923
|
async def async_get_latest_thumbnail(self) -> Optional[str]:
|
|
3897
3924
|
"""Return the URL of the most recent event/motion thumbnail for this camera.
|
|
3898
3925
|
|
|
@@ -12464,7 +12491,12 @@ class DeviceClient(object):
|
|
|
12464
12491
|
return
|
|
12465
12492
|
data_len = len(data)
|
|
12466
12493
|
if data_len <= 0:
|
|
12467
|
-
|
|
12494
|
+
# Peer closed the control socket (idle cloud socket, device
|
|
12495
|
+
# reboot, or a brief Wi-Fi blip). reset() reconnects, so this is
|
|
12496
|
+
# a recoverable hiccup, not an error-level condition.
|
|
12497
|
+
_LOGGER.debug(
|
|
12498
|
+
"%s control socket closed by peer; reconnecting", self.device_id
|
|
12499
|
+
)
|
|
12468
12500
|
await self.reset()
|
|
12469
12501
|
self.status.online = False
|
|
12470
12502
|
return
|
|
@@ -12580,8 +12612,12 @@ class DeviceClient(object):
|
|
|
12580
12612
|
}
|
|
12581
12613
|
try:
|
|
12582
12614
|
if self.ping_count >= 2:
|
|
12583
|
-
|
|
12584
|
-
|
|
12615
|
+
# Two pings (~20s) went unanswered: the device is briefly
|
|
12616
|
+
# unreachable (Wi-Fi congestion, device busy). reset() reconnects
|
|
12617
|
+
# automatically, so this is a recoverable hiccup, not an error.
|
|
12618
|
+
_LOGGER.warning(
|
|
12619
|
+
"No ping response within 20s from %s; resetting connection "
|
|
12620
|
+
"(will reconnect)", self.device_id,
|
|
12585
12621
|
)
|
|
12586
12622
|
await self.reset()
|
|
12587
12623
|
return -1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.16
|
|
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
|
|
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.5.14 → python_aidot_cameras-0.5.16}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.16}/tests/test_no_undefined_names.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
|