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.
Files changed (32) hide show
  1. {python_aidot_cameras-0.5.13/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.5.15}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/pyproject.toml +1 -1
  3. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/device_client.py +55 -32
  4. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  5. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/LICENSE +0 -0
  6. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/README.md +0 -0
  7. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/setup.cfg +0 -0
  8. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/__init__.py +0 -0
  9. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/aes_utils.py +0 -0
  10. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/client.py +0 -0
  11. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/const.py +0 -0
  12. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/credentials.py +0 -0
  13. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/discover.py +0 -0
  14. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/exceptions.py +0 -0
  15. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/aidot/g711.py +0 -0
  16. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  17. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  18. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  19. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  20. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_alarm_event.py +0 -0
  21. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_highport_nomination.py +0 -0
  22. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_motion_poll.py +0 -0
  23. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_no_undefined_names.py +0 -0
  24. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_idle_release.py +0 -0
  25. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_sprop.py +0 -0
  26. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_talk.py +0 -0
  27. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_sdes_watchdog.py +0 -0
  28. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_speak.py +0 -0
  29. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_stream_cap.py +0 -0
  30. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_talk.py +0 -0
  31. {python_aidot_cameras-0.5.13 → python_aidot_cameras-0.5.15}/tests/test_terminal_ack.py +0 -0
  32. {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.13
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.13"
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
- await self._async_get_smarthome_auth()
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._smarthome_base}/api/ipc/playback/eventRecordingList",
3807
- json={
3808
- "deviceIds": [self.device_id],
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 async_get_event_video_url(self, event_uuid: str) -> Optional[str]:
3849
- """Return a direct playback URL (MP4 or M3U8) for a specific recording.
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
- event_uuid: the eventUuid field from an async_get_cloud_recordings() item.
3852
- Returns the first video URL from getEventVideoUrl, or None on error.
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[0].url
3858
- type 1 = MP4, type 2 = M3U8
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._smarthome_base}/api/ipc/playback/getEventVideoUrl",
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._leedarson_headers(),
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
- return video_url_list[0].get("url") or None
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
- "async_get_event_video_url failed for %s uuid=%s: %s",
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._smarthome_base}/api/ipc/playback/eventRecordingList",
3940
+ f"{self._aidot_v32_base}/playback/eventRecordingList",
3916
3941
  json={
3917
- "deviceIds": [self.device_id],
3918
- "eventCodes": [],
3919
- "areaIds": [],
3920
- "pageNum": 1,
3921
- "pageSize": 1,
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._leedarson_headers(),
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.13
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