python-aidot-cameras 0.5.13__tar.gz → 0.5.15__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.13/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.5.15}/PKG-INFO +1 -1
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/pyproject.toml +1 -1
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/device_client.py +55 -32
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/LICENSE +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/README.md +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/setup.cfg +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/__init__.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/aes_utils.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/client.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/const.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/credentials.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/discover.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/exceptions.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/g711.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/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.15
|
|
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.15"
|
|
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"
|
|
@@ -3799,21 +3799,20 @@ class DeviceClient(object):
|
|
|
3799
3799
|
"""
|
|
3800
3800
|
import aiohttp
|
|
3801
3801
|
|
|
3802
|
-
|
|
3802
|
+
_body = {
|
|
3803
|
+
"deviceIds": [self.device_id],
|
|
3804
|
+
"pageNum": page,
|
|
3805
|
+
"pageSize": page_size,
|
|
3806
|
+
"recordSta": start_ts,
|
|
3807
|
+
"recordEnd": end_ts,
|
|
3808
|
+
}
|
|
3809
|
+
_LOGGER.debug("eventRecordingList request for %s: %s", self.device_id, _body)
|
|
3803
3810
|
async def _fetch():
|
|
3804
3811
|
async with aiohttp.ClientSession() as session:
|
|
3805
3812
|
async with session.post(
|
|
3806
|
-
f"{self.
|
|
3807
|
-
json=
|
|
3808
|
-
|
|
3809
|
-
"eventCodes": [],
|
|
3810
|
-
"areaIds": [],
|
|
3811
|
-
"pageNum": page,
|
|
3812
|
-
"pageSize": page_size,
|
|
3813
|
-
"recordSta": start_ts,
|
|
3814
|
-
"recordEnd": end_ts,
|
|
3815
|
-
},
|
|
3816
|
-
headers=self._leedarson_headers(),
|
|
3813
|
+
f"{self._aidot_v32_base}/playback/eventRecordingList",
|
|
3814
|
+
json=_body,
|
|
3815
|
+
headers=self._aidot_headers(),
|
|
3817
3816
|
timeout=aiohttp.ClientTimeout(total=30),
|
|
3818
3817
|
) as resp:
|
|
3819
3818
|
return await resp.json(content_type=None)
|
|
@@ -3845,33 +3844,40 @@ class DeviceClient(object):
|
|
|
3845
3844
|
)
|
|
3846
3845
|
return []
|
|
3847
3846
|
|
|
3848
|
-
async def
|
|
3849
|
-
|
|
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.
|
|
3850
3851
|
|
|
3851
|
-
|
|
3852
|
-
|
|
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.
|
|
3853
3855
|
|
|
3854
3856
|
From CloudPlaybackFragment.java / EventListRepos.java:
|
|
3855
3857
|
POST /api/ipc/playback/getEventVideoUrl
|
|
3856
3858
|
body: {deviceId, eventList: [{eventUuid}]}
|
|
3857
|
-
response: data.eventUrlList[0].videoUrlList[
|
|
3858
|
-
type 1
|
|
3859
|
+
response: data.eventUrlList[0].videoUrlList[]
|
|
3860
|
+
each item: {url, type} (type 1=MP4, type 2=M3U8)
|
|
3859
3861
|
"""
|
|
3860
3862
|
import aiohttp
|
|
3861
3863
|
|
|
3864
|
+
_MIME = {1: "video/mp4", 2: "application/x-mpegURL"}
|
|
3865
|
+
|
|
3862
3866
|
try:
|
|
3863
3867
|
async with aiohttp.ClientSession() as session:
|
|
3864
3868
|
async with session.post(
|
|
3865
|
-
f"{self.
|
|
3869
|
+
f"{self._aidot_v32_base}/playback/getEventVideoUrl",
|
|
3866
3870
|
json={
|
|
3867
3871
|
"deviceId": self.device_id,
|
|
3868
3872
|
"eventList": [{"eventUuid": event_uuid}],
|
|
3869
3873
|
},
|
|
3870
|
-
headers=self.
|
|
3874
|
+
headers=self._aidot_headers(),
|
|
3871
3875
|
timeout=aiohttp.ClientTimeout(total=15),
|
|
3872
3876
|
) as resp:
|
|
3873
3877
|
body = await resp.json(content_type=None)
|
|
3874
3878
|
|
|
3879
|
+
_LOGGER.debug("getEventVideoUrl raw for %s uuid=%s: %s", self.device_id, event_uuid, body)
|
|
3880
|
+
|
|
3875
3881
|
if body.get("code") != 200:
|
|
3876
3882
|
_LOGGER.warning(
|
|
3877
3883
|
"getEventVideoUrl code=%s for %s uuid=%s: %s",
|
|
@@ -3885,15 +3891,35 @@ class DeviceClient(object):
|
|
|
3885
3891
|
video_url_list = (event_url_list[0] or {}).get("videoUrlList") or []
|
|
3886
3892
|
if not video_url_list:
|
|
3887
3893
|
return None
|
|
3888
|
-
|
|
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
|
|
3889
3907
|
|
|
3890
3908
|
except Exception as exc:
|
|
3891
3909
|
_LOGGER.error(
|
|
3892
|
-
"
|
|
3910
|
+
"async_get_event_video_media failed for %s uuid=%s: %s",
|
|
3893
3911
|
self.device_id, event_uuid, exc,
|
|
3894
3912
|
)
|
|
3895
3913
|
return None
|
|
3896
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
|
+
|
|
3897
3923
|
async def async_get_latest_thumbnail(self) -> Optional[str]:
|
|
3898
3924
|
"""Return the URL of the most recent event/motion thumbnail for this camera.
|
|
3899
3925
|
|
|
@@ -3905,24 +3931,21 @@ class DeviceClient(object):
|
|
|
3905
3931
|
"""
|
|
3906
3932
|
import aiohttp, time
|
|
3907
3933
|
|
|
3908
|
-
await self._async_get_smarthome_auth()
|
|
3909
3934
|
end_ts = int(time.time() * 1000)
|
|
3910
3935
|
start_ts = end_ts - 30 * 86_400_000 # look back 30 days
|
|
3911
3936
|
|
|
3912
3937
|
try:
|
|
3913
3938
|
async with aiohttp.ClientSession() as session:
|
|
3914
3939
|
async with session.post(
|
|
3915
|
-
f"{self.
|
|
3940
|
+
f"{self._aidot_v32_base}/playback/eventRecordingList",
|
|
3916
3941
|
json={
|
|
3917
|
-
"deviceIds":
|
|
3918
|
-
"
|
|
3919
|
-
"
|
|
3920
|
-
"
|
|
3921
|
-
"
|
|
3922
|
-
"recordSta": start_ts,
|
|
3923
|
-
"recordEnd": end_ts,
|
|
3942
|
+
"deviceIds": [self.device_id],
|
|
3943
|
+
"pageNum": 1,
|
|
3944
|
+
"pageSize": 1,
|
|
3945
|
+
"recordSta": start_ts,
|
|
3946
|
+
"recordEnd": end_ts,
|
|
3924
3947
|
},
|
|
3925
|
-
headers=self.
|
|
3948
|
+
headers=self._aidot_headers(),
|
|
3926
3949
|
timeout=aiohttp.ClientTimeout(total=15),
|
|
3927
3950
|
) as resp:
|
|
3928
3951
|
body = await resp.json(content_type=None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.15
|
|
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.13 → python_aidot_cameras-0.5.15}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/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
|