python-aidot-cameras 0.5.14__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.14/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.5.15}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/pyproject.toml +1 -1
  3. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/device_client.py +35 -8
  4. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  5. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/LICENSE +0 -0
  6. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/README.md +0 -0
  7. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/setup.cfg +0 -0
  8. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/__init__.py +0 -0
  9. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/aes_utils.py +0 -0
  10. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/client.py +0 -0
  11. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/const.py +0 -0
  12. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/credentials.py +0 -0
  13. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/discover.py +0 -0
  14. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/exceptions.py +0 -0
  15. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/aidot/g711.py +0 -0
  16. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  17. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  18. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  19. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  20. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_alarm_event.py +0 -0
  21. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_highport_nomination.py +0 -0
  22. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_motion_poll.py +0 -0
  23. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_no_undefined_names.py +0 -0
  24. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_sdes_idle_release.py +0 -0
  25. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_sdes_sprop.py +0 -0
  26. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_sdes_talk.py +0 -0
  27. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_sdes_watchdog.py +0 -0
  28. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_speak.py +0 -0
  29. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_stream_cap.py +0 -0
  30. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_talk.py +0 -0
  31. {python_aidot_cameras-0.5.14 → python_aidot_cameras-0.5.15}/tests/test_terminal_ack.py +0 -0
  32. {python_aidot_cameras-0.5.14 → 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.14
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.14"
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"
@@ -3844,20 +3844,25 @@ class DeviceClient(object):
3844
3844
  )
3845
3845
  return []
3846
3846
 
3847
- async def async_get_event_video_url(self, event_uuid: str) -> Optional[str]:
3848
- """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.
3849
3851
 
3850
- event_uuid: the eventUuid field from an async_get_cloud_recordings() item.
3851
- 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.
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[0].url
3857
- type 1 = MP4, type 2 = M3U8
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
- 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
3888
3907
 
3889
3908
  except Exception as exc:
3890
3909
  _LOGGER.error(
3891
- "async_get_event_video_url failed for %s uuid=%s: %s",
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.5.14
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