python-aidot-cameras 1.0.0rc62__tar.gz → 1.0.0rc63__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 (364) hide show
  1. {python_aidot_cameras-1.0.0rc62/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc63}/PKG-INFO +26 -10
  2. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/README.md +25 -9
  3. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/__init__.py +2 -0
  4. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +12 -1
  5. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/client.py +51 -13
  6. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/protocol.py +94 -15
  7. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/recording.py +13 -2
  8. python_aidot_cameras-1.0.0rc63/aidot_cameras/camera/sd_playback.py +574 -0
  9. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/sdes.py +13 -3
  10. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/sdes_open.py +307 -33
  11. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/webrtc.py +13 -2
  12. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/webrtc_open.py +217 -12
  13. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/exceptions.py +8 -0
  14. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/pyproject.toml +1 -1
  15. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63/python_aidot_cameras.egg-info}/PKG-INFO +26 -10
  16. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/python_aidot_cameras.egg-info/SOURCES.txt +8 -0
  17. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_avio_ctrl_uses_the_session_dseq.py +41 -0
  18. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_retry_short_sessions.py +33 -4
  19. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_open_errors_are_aidot_errors.py +2 -0
  20. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_peer_id_client_type.py +26 -0
  21. python_aidot_cameras-1.0.0rc63/tests/test_sd_playback.py +98 -0
  22. python_aidot_cameras-1.0.0rc63/tests/test_sd_playback_controls.py +165 -0
  23. python_aidot_cameras-1.0.0rc63/tests/test_sd_playback_dtls.py +417 -0
  24. python_aidot_cameras-1.0.0rc63/tests/test_sd_playback_entry.py +112 -0
  25. python_aidot_cameras-1.0.0rc63/tests/test_sd_playback_media_hold.py +140 -0
  26. python_aidot_cameras-1.0.0rc63/tests/test_sd_playback_open.py +306 -0
  27. python_aidot_cameras-1.0.0rc63/tests/test_session_mode_seq.py +82 -0
  28. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/LICENSE +0 -0
  29. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/MANIFEST.in +0 -0
  30. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/__main__.py +0 -0
  31. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_log_redaction.py +0 -0
  32. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_upstream.py +0 -0
  33. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/__init__.py +0 -0
  34. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
  35. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  36. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  37. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  38. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  39. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  40. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  41. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  42. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  43. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  44. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  45. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  46. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  47. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  48. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  49. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  50. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  51. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  52. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  53. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  54. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  55. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  56. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  57. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  58. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  59. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  60. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  61. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  62. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  63. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  64. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  65. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  66. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/__init__.py +0 -0
  67. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/aac_track.py +0 -0
  68. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/agc.py +0 -0
  69. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/constants.py +0 -0
  70. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/controls.py +0 -0
  71. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/go2rtc.py +0 -0
  72. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/h264_sps.py +0 -0
  73. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/health.py +0 -0
  74. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/hls_ts.py +0 -0
  75. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/hwaccel.py +0 -0
  76. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/lan_control.py +0 -0
  77. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/models.py +0 -0
  78. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/rtp_h264.py +0 -0
  79. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/rtsp_publish.py +0 -0
  80. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/sd_events.py +0 -0
  81. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/sd_listing.py +0 -0
  82. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/ts_fanout.py +0 -0
  83. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/camera/ts_tee.py +0 -0
  84. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/client.py +0 -0
  85. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/cloud_auth.py +0 -0
  86. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/const.py +0 -0
  87. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/credentials.py +0 -0
  88. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/crypto.py +0 -0
  89. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/device_client.py +0 -0
  90. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/discover.py +0 -0
  91. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/g711.py +0 -0
  92. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/lan_retry.py +0 -0
  93. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/py.typed +0 -0
  94. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/aidot_cameras/settings.py +0 -0
  95. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  96. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  97. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/python_aidot_cameras.egg-info/requires.txt +0 -0
  98. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  99. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/setup.cfg +0 -0
  100. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_aac_track.py +0 -0
  101. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_abandoned_attempt_skips_the_doomed_serve.py +0 -0
  102. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_abandoned_wait_still_maps_audio.py +0 -0
  103. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_aioice_compat.py +0 -0
  104. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_alarm_event.py +0 -0
  105. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_answer_pt_kinds.py +0 -0
  106. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_answer_sdp_video_pt.py +0 -0
  107. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_answer_section_selection.py +0 -0
  108. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_arrival_timestamps.py +0 -0
  109. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_audio_mux_offline.py +0 -0
  110. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_auth_error_routing.py +0 -0
  111. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_auto_alarm.py +0 -0
  112. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_auto_light_settings.py +0 -0
  113. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_avio_header_single_layout.py +0 -0
  114. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_avio_request.py +0 -0
  115. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_avio_response_parse.py +0 -0
  116. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_avio_response_router.py +0 -0
  117. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_backoff.py +0 -0
  118. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_battery_cap_spans_sessions.py +0 -0
  119. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_battery_detection.py +0 -0
  120. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_battery_evidence_corroboration.py +0 -0
  121. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_battery_stale_offer_is_abandoned_early.py +0 -0
  122. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_battery_wake_settles_before_the_command.py +0 -0
  123. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ble_gateway_is_gone.py +0 -0
  124. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_bridge_state_sentinels.py +0 -0
  125. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_busy_backoff.py +0 -0
  126. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_camera_properties.py +0 -0
  127. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_canary_log_names_the_camera.py +0 -0
  128. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_close_mid_ice_leaves_nothing_pending.py +0 -0
  129. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_cloud_count.py +0 -0
  130. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_cloud_error_log_names_the_exception.py +0 -0
  131. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_cloud_error_severity.py +0 -0
  132. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_cloud_plan.py +0 -0
  133. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_constants_imports_resolve.py +0 -0
  134. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_control_verdicts.py +0 -0
  135. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_declared_properties.py +0 -0
  136. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_deferred_hardening.py +0 -0
  137. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_detection_types.py +0 -0
  138. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_device_cmd_seq_match.py +0 -0
  139. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_device_login_guard.py +0 -0
  140. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_device_user_info_cache.py +0 -0
  141. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_direct_publish_attaches_negotiated_audio.py +0 -0
  142. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_direct_ts_keyframe_signal.py +0 -0
  143. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_direct_ts_serve.py +0 -0
  144. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_direct_ts_serve_only_for_http.py +0 -0
  145. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dormant_camera_cleanup.py +0 -0
  146. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_drain_is_cancellable.py +0 -0
  147. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_mux_terminal_write.py +0 -0
  148. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_not_ready_burst.py +0 -0
  149. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_open_abort_covers_every_exit.py +0 -0
  150. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_pinning.py +0 -0
  151. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_recorder_wiring.py +0 -0
  152. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_refusal_after_the_echo.py +0 -0
  153. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_resolution_ack.py +0 -0
  154. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_serve_decode_skip.py +0 -0
  155. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_serve_ice_wait.py +0 -0
  156. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_serve_open_timeout.py +0 -0
  157. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_skip_signaling_wait.py +0 -0
  158. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_slow_probe.py +0 -0
  159. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_stdout_producer.py +0 -0
  160. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_tap_states_no_reorder.py +0 -0
  161. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_ts_copy_recording.py +0 -0
  162. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_dtls_video_presence.py +0 -0
  163. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_egress_guard.py +0 -0
  164. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_env_seams.py +0 -0
  165. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_every_exception_is_exported.py +0 -0
  166. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_feature_probe_needs_a_live_session.py +0 -0
  167. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ffmpeg_rx_sdp_single_template.py +0 -0
  168. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
  169. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_first_media_abandon_rules.py +0 -0
  170. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_futile_keepalive.py +0 -0
  171. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_gate_requires_a_decoded_frame.py +0 -0
  172. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_go2rtc.py +0 -0
  173. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_go2rtc_cli.py +0 -0
  174. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_h264_decode_rate_limit.py +0 -0
  175. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_h264_sps.py +0 -0
  176. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_health_line.py +0 -0
  177. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_health_line_counts_pictures.py +0 -0
  178. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_highport_nomination.py +0 -0
  179. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_hls_ts_end_to_end.py +0 -0
  180. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_hls_ts_wiring.py +0 -0
  181. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_hwaccel_probe.py +0 -0
  182. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ice_config_cache.py +0 -0
  183. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ice_credentials_match_the_app.py +0 -0
  184. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ice_peer_reflexive.py +0 -0
  185. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_idle_release_both_loops.py +0 -0
  186. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ipc_envelope_single_builder.py +0 -0
  187. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_keepalive_renew.py +0 -0
  188. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_key_restart_sdp.py +0 -0
  189. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_keyframe_prompter.py +0 -0
  190. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_control.py +0 -0
  191. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_login_accessors.py +0 -0
  192. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
  193. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_retry_silences_upstream_over_ceiling.py +0 -0
  194. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_retry_stops_on_close.py +0 -0
  195. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_lan_sweep_only_with_lights.py +0 -0
  196. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_late_repeats_are_dropped.py +0 -0
  197. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_light_active_color_mode.py +0 -0
  198. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_live_session_carries_talk.py +0 -0
  199. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_live_stream_param.py +0 -0
  200. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_live_validate_policy.py +0 -0
  201. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_live_validate_token_refresh.py +0 -0
  202. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_live_video_canary.py +0 -0
  203. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_liveplay_echo_timeout.py +0 -0
  204. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_local_only_controls.py +0 -0
  205. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_log_hygiene.py +0 -0
  206. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_logging_caps.py +0 -0
  207. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_login_connect_deadline.py +0 -0
  208. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_login_reconnect_backoff.py +0 -0
  209. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_loop_break_ends_the_loop.py +0 -0
  210. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_media_counted_only_when_plaintext.py +0 -0
  211. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_media_path_is_reported.py +0 -0
  212. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_media_socket_rcvbuf.py +0 -0
  213. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_media_wait_floor.py +0 -0
  214. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_motion_poll.py +0 -0
  215. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mqtt_client_id_is_exact.py +0 -0
  216. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mqtt_credential_refetch.py +0 -0
  217. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mqtt_credential_self_heal.py +0 -0
  218. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mqtt_password_not_persisted.py +0 -0
  219. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mqtt_session_reconnect.py +0 -0
  220. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mqtt_thread_not_orphaned.py +0 -0
  221. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_mypy_new_findings.py +0 -0
  222. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_nack.py +0 -0
  223. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
  224. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_narrow_pc_ice.py +0 -0
  225. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_no_key_material_in_logs.py +0 -0
  226. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_no_private_notes_on_the_public_surface.py +0 -0
  227. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_no_self_referential_go2rtc.py +0 -0
  228. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_no_unbounded_hex_dumps.py +0 -0
  229. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_no_undefined_names.py +0 -0
  230. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_nominates_as_soon_as_the_answer_lands.py +0 -0
  231. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_off_subnet_candidate.py +0 -0
  232. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
  233. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_offline_keepalive_pause.py +0 -0
  234. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_on_frame_is_an_av_frame.py +0 -0
  235. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_open_fail_logger.py +0 -0
  236. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_open_gate_delay.py +0 -0
  237. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_peerid_reuse.py +0 -0
  238. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_persistent_mqtt.py +0 -0
  239. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_persistent_mqtt_robustness.py +0 -0
  240. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_phase_returns_reach_the_caller.py +0 -0
  241. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_phase_state_is_complete.py +0 -0
  242. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_plaintext_mqtt_refused.py +0 -0
  243. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_post_merge_hardening.py +0 -0
  244. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
  245. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_public_signature_matches_impl.py +0 -0
  246. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_publish_target_readiness.py +0 -0
  247. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_quality_campaign_in_session.py +0 -0
  248. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_reboot_action.py +0 -0
  249. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_recent_recordings.py +0 -0
  250. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_region_allowlist.py +0 -0
  251. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_remb.py +0 -0
  252. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
  253. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_removed_keepalive_arguments_are_tolerated.py +0 -0
  254. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_resent_frames_are_dropped.py +0 -0
  255. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_resolution_persists.py +0 -0
  256. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_restart_cycle.py +0 -0
  257. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_retry_policy.py +0 -0
  258. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_review_lan_retry_and_graces.py +0 -0
  259. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_rtp_h264.py +0 -0
  260. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_rtsp_publish.py +0 -0
  261. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_rtsp_publish_go2rtc.py +0 -0
  262. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_scripts_importable.py +0 -0
  263. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sctp_init_ack_tsn.py +0 -0
  264. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sctp_reassembly.py +0 -0
  265. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sctp_sack.py +0 -0
  266. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sd_card_presence.py +0 -0
  267. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sd_event_decode.py +0 -0
  268. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sd_event_probe_payloads.py +0 -0
  269. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sd_listing.py +0 -0
  270. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_avio_dispatch.py +0 -0
  271. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_bridge_break_gate.py +0 -0
  272. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_connection_mode.py +0 -0
  273. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_echo_wait_is_not_dead_time.py +0 -0
  274. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_fast_liveplay.py +0 -0
  275. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  276. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_first_media_stall_report.py +0 -0
  277. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_ice_teardown.py +0 -0
  278. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_idle_release.py +0 -0
  279. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_nack_bridge.py +0 -0
  280. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_offer_video_pin.py +0 -0
  281. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_open_cleanup.py +0 -0
  282. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_open_yields_the_loop.py +0 -0
  283. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_pinned_codec_retry.py +0 -0
  284. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_sctp_abort.py +0 -0
  285. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_serve_audio.py +0 -0
  286. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_serve_cmd.py +0 -0
  287. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_serve_stderr_drain.py +0 -0
  288. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_sprop.py +0 -0
  289. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_talk.py +0 -0
  290. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_teardown_exit_log.py +0 -0
  291. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_terminal_ack.py +0 -0
  292. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_terminal_ack_latency.py +0 -0
  293. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdes_watchdog.py +0 -0
  294. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdp_narrowing_properties.py +0 -0
  295. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sdp_payload_type_narrowing.py +0 -0
  296. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_security_hardening.py +0 -0
  297. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
  298. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serializable_login_info.py +0 -0
  299. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_bound_survives_arrival_stamping.py +0 -0
  300. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_input_timeout.py +0 -0
  301. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_loop_releases_on_error.py +0 -0
  302. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_loss_tolerance.py +0 -0
  303. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_pt_respects_the_pin.py +0 -0
  304. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_relay.py +0 -0
  305. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
  306. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_session_stats.py +0 -0
  307. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_session_stop_twice.py +0 -0
  308. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_settings_registry.py +0 -0
  309. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_shared_house_seam.py +0 -0
  310. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_signalling_is_attributable_to_a_camera.py +0 -0
  311. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_skip_guard.py +0 -0
  312. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_snapshot_cancellation.py +0 -0
  313. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_speak.py +0 -0
  314. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sprop_capture_gate.py +0 -0
  315. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sprop_staleness.py +0 -0
  316. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sprop_unstable_public.py +0 -0
  317. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
  318. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_srtp_rx_rekey.py +0 -0
  319. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_srtp_tx_key_note.py +0 -0
  320. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stable_terminal_id.py +0 -0
  321. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stall_nudge_policy.py +0 -0
  322. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
  323. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
  324. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
  325. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
  326. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stall_reports_reach_the_artifact.py +0 -0
  327. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_status_and_trace_channels.py +0 -0
  328. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stream_cap.py +0 -0
  329. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stream_drain_ownership.py +0 -0
  330. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stream_idle.py +0 -0
  331. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stream_slot_cap.py +0 -0
  332. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stream_teardown.py +0 -0
  333. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_stun_attr_single_builder.py +0 -0
  334. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_swallowed_exception_names.py +0 -0
  335. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_talk.py +0 -0
  336. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
  337. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_tap_to_mux_end_to_end.py +0 -0
  338. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_terminal_ack.py +0 -0
  339. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_tmmbr.py +0 -0
  340. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_token_file_robustness.py +0 -0
  341. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_token_refresh.py +0 -0
  342. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_transport_failure_reporting.py +0 -0
  343. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ts_fanout.py +0 -0
  344. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_ts_tee.py +0 -0
  345. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_turn_create_permission.py +0 -0
  346. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_turn_target_shared.py +0 -0
  347. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_unbuildable_summary_repeats_at_debug.py +0 -0
  348. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_unreachable_nominee_is_abandoned_early.py +0 -0
  349. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_upstream_compat.py +0 -0
  350. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_upstream_seam_adapter.py +0 -0
  351. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_upstream_secrets_redacted.py +0 -0
  352. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_validator_shares_one_token.py +0 -0
  353. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_vendored_aiortc_integrity.py +0 -0
  354. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_video_presence_needs_a_keyframe.py +0 -0
  355. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_video_profile_log.py +0 -0
  356. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_video_pts_dts.py +0 -0
  357. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_video_ts_unwrap.py +0 -0
  358. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_viewer_check_throttle.py +0 -0
  359. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_viewer_presence.py +0 -0
  360. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_viewer_present_push_mode.py +0 -0
  361. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_wait_or_event.py +0 -0
  362. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_wake_gate_camera_evidence.py +0 -0
  363. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_wake_readiness_retry.py +0 -0
  364. {python_aidot_cameras-1.0.0rc62 → python_aidot_cameras-1.0.0rc63}/tests/test_webrtc_answer_race.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 1.0.0rc62
3
+ Version: 1.0.0rc63
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
@@ -129,15 +129,15 @@ the authoritative table and per-model notes.
129
129
  Two behaviours that are measured, understood as far as the evidence allows, and
130
130
  not going to change in this release. Neither is a fault to report.
131
131
 
132
- **Recorded video is retrievable only from cloud storage.** Whether a camera
133
- records to the cloud or to its own SD card is per-camera, not per-model, and the
134
- device reports it as `IsSupportPlayback`. This library lists cloud recordings
135
- (`async_get_cloud_recordings`) and resolves a playable HLS URL for one
136
- (`async_get_event_video_media`); for a camera storing to a card it lists the
137
- on-device recordings (`async_get_sd_recordings`) but cannot play one back, so
138
- such a camera records normally and nothing here can show you the video. On the
139
- reference fleet that is four of seven cameras. Playback from the card is closed
140
- as not feasible; see `docs/CAMERAS.md`, "On-device (SD card) recordings".
132
+ **Recorded video comes from the cloud or from the camera's own SD card.**
133
+ Whether a camera records to the cloud or to its card is per-camera, not
134
+ per-model, and the device reports it as `IsSupportPlayback`. This library lists
135
+ cloud recordings (`async_get_cloud_recordings`) and resolves a playable HLS URL
136
+ for one (`async_get_event_video_media`); for a camera storing to a card it lists
137
+ the on-device recordings (`async_get_sd_recordings`) and plays one
138
+ (`async_open_sd_playback`), on all three models: an SDES camera (A001064,
139
+ A001513) serves it like a live stream, a DTLS camera (A000088) records it to a
140
+ `.ts` file. See `docs/CAMERAS.md`, "On-device (SD card) recordings".
141
141
 
142
142
  **The A001064 (PTZ) streams at roughly 2.2 Mbps, about 24% above the vendor
143
143
  app.** Measured from a capture of the app on the same camera: it takes about
@@ -514,6 +514,22 @@ battery camera asleep, or a model without the feature -- returns `None`, and
514
514
  treating that as a set of disabled detectors would report a state the camera
515
515
  never claimed. A write refuses outright when there is no current list to echo.
516
516
 
517
+ ### Playing a recording off the SD card
518
+
519
+ ```python
520
+ listing = await device_client.async_get_sd_recordings(session=live_session)
521
+ await device_client.async_stop_streaming() # the camera has one media slot
522
+ session = await device_client.async_open_sd_playback(
523
+ listing.records[-1], output_path="/tmp/clip.ts"
524
+ )
525
+ await session.async_sd_seek(20) # also async_sd_pause() / async_sd_resume()
526
+ # ... session.stop() when done
527
+ ```
528
+
529
+ The camera sends the clip in real time, from a few seconds before the
530
+ record's listed time. On a DTLS camera (A000088) it can only be recorded to a
531
+ `.ts` file. See `docs/CAMERAS.md`, "Playing a recording".
532
+
517
533
  ### WiFi and SD card
518
534
 
519
535
  ```python
@@ -79,15 +79,15 @@ the authoritative table and per-model notes.
79
79
  Two behaviours that are measured, understood as far as the evidence allows, and
80
80
  not going to change in this release. Neither is a fault to report.
81
81
 
82
- **Recorded video is retrievable only from cloud storage.** Whether a camera
83
- records to the cloud or to its own SD card is per-camera, not per-model, and the
84
- device reports it as `IsSupportPlayback`. This library lists cloud recordings
85
- (`async_get_cloud_recordings`) and resolves a playable HLS URL for one
86
- (`async_get_event_video_media`); for a camera storing to a card it lists the
87
- on-device recordings (`async_get_sd_recordings`) but cannot play one back, so
88
- such a camera records normally and nothing here can show you the video. On the
89
- reference fleet that is four of seven cameras. Playback from the card is closed
90
- as not feasible; see `docs/CAMERAS.md`, "On-device (SD card) recordings".
82
+ **Recorded video comes from the cloud or from the camera's own SD card.**
83
+ Whether a camera records to the cloud or to its card is per-camera, not
84
+ per-model, and the device reports it as `IsSupportPlayback`. This library lists
85
+ cloud recordings (`async_get_cloud_recordings`) and resolves a playable HLS URL
86
+ for one (`async_get_event_video_media`); for a camera storing to a card it lists
87
+ the on-device recordings (`async_get_sd_recordings`) and plays one
88
+ (`async_open_sd_playback`), on all three models: an SDES camera (A001064,
89
+ A001513) serves it like a live stream, a DTLS camera (A000088) records it to a
90
+ `.ts` file. See `docs/CAMERAS.md`, "On-device (SD card) recordings".
91
91
 
92
92
  **The A001064 (PTZ) streams at roughly 2.2 Mbps, about 24% above the vendor
93
93
  app.** Measured from a capture of the app on the same camera: it takes about
@@ -464,6 +464,22 @@ battery camera asleep, or a model without the feature -- returns `None`, and
464
464
  treating that as a set of disabled detectors would report a state the camera
465
465
  never claimed. A write refuses outright when there is no current list to echo.
466
466
 
467
+ ### Playing a recording off the SD card
468
+
469
+ ```python
470
+ listing = await device_client.async_get_sd_recordings(session=live_session)
471
+ await device_client.async_stop_streaming() # the camera has one media slot
472
+ session = await device_client.async_open_sd_playback(
473
+ listing.records[-1], output_path="/tmp/clip.ts"
474
+ )
475
+ await session.async_sd_seek(20) # also async_sd_pause() / async_sd_resume()
476
+ # ... session.stop() when done
477
+ ```
478
+
479
+ The camera sends the clip in real time, from a few seconds before the
480
+ record's listed time. On a DTLS camera (A000088) it can only be recorded to a
481
+ `.ts` file. See `docs/CAMERAS.md`, "Playing a recording".
482
+
467
483
  ### WiFi and SD card
468
484
 
469
485
  ```python
@@ -37,6 +37,7 @@ from .exceptions import (
37
37
  AidotNoWebrtcAnswer,
38
38
  AidotOSError,
39
39
  AidotPlaintextMqttRefused,
40
+ AidotSdPlaybackUnavailable,
40
41
  AidotStreamOpenError,
41
42
  AidotUnknownRegion,
42
43
  AidotUserOrPassIncorrect,
@@ -177,6 +178,7 @@ __all__ = [
177
178
  "AidotNotLogin",
178
179
  "AidotOSError",
179
180
  "AidotPlaintextMqttRefused",
181
+ "AidotSdPlaybackUnavailable",
180
182
  "AidotStreamOpenError",
181
183
  "AidotUnknownRegion",
182
184
  "AidotUserOrPassIncorrect",
@@ -6,7 +6,7 @@ import logging
6
6
  import os
7
7
  import traceback
8
8
  from dataclasses import dataclass, field
9
- from typing import Optional, Protocol, Type, TypeVar, Union
9
+ from typing import Callable, Optional, Protocol, Type, TypeVar, Union # aidot: Callable
10
10
 
11
11
  import pylibsrtp
12
12
  from cryptography import x509
@@ -253,6 +253,9 @@ class RtpRouter:
253
253
  self.mid_table: dict[str, RtpReceiver] = {}
254
254
  self.ssrc_table: dict[int, RtpReceiver] = {}
255
255
  self.payload_type_table: dict[int, set[RtpReceiver]] = {}
256
+ # aidot: optional callable shown every RTP packet before routing,
257
+ # routed or not - SD playback takes H265 this way (see VENDOR.md).
258
+ self.raw_tap: Optional[Callable[[RtpPacket], None]] = None
256
259
 
257
260
  def register_receiver(
258
261
  self,
@@ -616,6 +619,14 @@ class RTCDtlsTransport(AsyncIOEventEmitter):
616
619
  self.__log_debug("x RTP parsing failed: %s", exc)
617
620
  return
618
621
 
622
+ # aidot: the raw tap sees the packet whether or not it routes.
623
+ tap = self._rtp_router.raw_tap
624
+ if tap is not None:
625
+ try:
626
+ tap(packet)
627
+ except Exception as exc:
628
+ self.__log_debug("x RTP raw tap failed: %s", exc)
629
+
619
630
  # route RTP packet
620
631
  receiver = self._rtp_router.route_rtp(packet)
621
632
  if receiver is not None:
@@ -6,6 +6,7 @@ import logging
6
6
  from urllib.parse import urlparse
7
7
  import os
8
8
  import time
9
+ import threading
9
10
  import asyncio
10
11
  import dataclasses
11
12
  from typing import TYPE_CHECKING, Any, Callable, List, NamedTuple, Optional, Union
@@ -61,6 +62,7 @@ from .sdes import (
61
62
  ) # re-exported (split into sdes.py); also the SDES return type
62
63
  from .controls import _CameraControlsMixin
63
64
  from .sd_listing import _CameraSdMixin
65
+ from .sd_playback import _CameraSdPlaybackMixin
64
66
  from .webrtc_open import _WebRTCOpenMixin
65
67
  from .sdes_open import _SdesOpenMixin
66
68
  from .protocol import ( # noqa: F401 - used here and/or by the webrtc_open mixin; kept re-exported for back-compat
@@ -120,6 +122,9 @@ from .health import HealthReporter, dtls_snapshot as _dtls_snapshot
120
122
 
121
123
  _LOGGER = logging.getLogger(__name__)
122
124
 
125
+ #: Guards every client's ``_live_dseq``; see ``_next_dseq``.
126
+ _DSEQ_LOCK = threading.Lock()
127
+
123
128
 
124
129
  def _is_transient_cloud_error(exc: BaseException) -> bool:
125
130
  """True for the network failures a retry is expected to clear.
@@ -860,6 +865,7 @@ def _build_sdes_serve_cmd(
860
865
  push_video_only: bool = False,
861
866
  video_decoder: Optional[list] = None,
862
867
  input_timeout_s: Optional[int] = None,
868
+ keep_probe_packets: bool = False,
863
869
  ) -> list:
864
870
  """Build the ffmpeg argv for the SDES bridge serve.
865
871
 
@@ -1040,8 +1046,15 @@ def _build_sdes_serve_cmd(
1040
1046
  if _SERVE_ARRIVAL_TS and not max_seconds
1041
1047
  else []
1042
1048
  ),
1049
+ # ``nobuffer`` drops what ffmpeg reads while probing the input (up to
1050
+ # -analyzeduration / -probesize) - latency a live view does not want.
1051
+ # A recording's opening seconds are not latency, they are the clip:
1052
+ # at 2560x1440 H265 the probe took its first ~3 s (A001064,
1053
+ # 2026-10-27), so SD playback keeps them (``keep_probe_packets``).
1043
1054
  "-fflags",
1044
- "+nobuffer+genpts+discardcorrupt",
1055
+ "+genpts+discardcorrupt"
1056
+ if keep_probe_packets
1057
+ else "+nobuffer+genpts+discardcorrupt",
1045
1058
  "-analyzeduration",
1046
1059
  "2000000",
1047
1060
  "-probesize",
@@ -2428,7 +2441,11 @@ async def _sdes_keepalive_after_session(
2428
2441
 
2429
2442
 
2430
2443
  class CameraMixin(
2431
- _CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesOpenMixin
2444
+ _CameraControlsMixin,
2445
+ _CameraSdMixin,
2446
+ _CameraSdPlaybackMixin,
2447
+ _WebRTCOpenMixin,
2448
+ _SdesOpenMixin,
2432
2449
  ):
2433
2450
  """All camera/streaming methods, mixed into DeviceClient via inheritance."""
2434
2451
 
@@ -2770,9 +2787,15 @@ class CameraMixin(
2770
2787
  return getattr(self, "_last_live_play_code", None) == _LIVE_PLAY_NOT_READY
2771
2788
 
2772
2789
  def _next_dseq(self) -> int:
2773
- """Next livePlayReq dseq (app parity: Q0() starts at 100, increments)."""
2774
- d = self._live_dseq
2775
- self._live_dseq += 1
2790
+ """Next livePlayReq dseq (app parity: Q0() starts at 100, increments).
2791
+
2792
+ Taken from the SDES bridge thread (SESSION_MODE, control commands) as
2793
+ well as the event loop (livePlayReq), so read-and-increment is one
2794
+ locked step: a repeated seq is a SESSION_MODE the camera ignores.
2795
+ """
2796
+ with _DSEQ_LOCK:
2797
+ d = self._live_dseq
2798
+ self._live_dseq += 1
2776
2799
  return d
2777
2800
 
2778
2801
  def update_status_from_device(self, device: dict) -> "DeviceStatusData":
@@ -7109,6 +7132,8 @@ class CameraMixin(
7109
7132
  *,
7110
7133
  sdes: bool = False,
7111
7134
  device_id: "Optional[str]" = None,
7135
+ client_class: int = 2,
7136
+ stream: "Optional[int]" = None,
7112
7137
  ) -> str:
7113
7138
  """Generate a peerId for a WebRTC connection.
7114
7139
 
@@ -7191,14 +7216,27 @@ class CameraMixin(
7191
7216
  # smart-home classes (3, 4) that skip the camera's keepalive watchdog,
7192
7217
  # so this changes what we claim to be and nothing about how the session
7193
7218
  # is policed. Screen any future change on EACH model with a patched
7194
- # build - the A000088's firmware has never been disassembled.
7195
- # Field 2 = client class + 5 random. '2' is EN_WEBRTC_CLIENT_TYPE_WEB,
7196
- # what the vendor web app announces. See the note above for why this
7197
- # is '2' and never '0'. The tail stays random: pinning all six would
7198
- # make every peer id identical across opens, which is the cross-session
7199
- # reuse the camera dedups on.
7200
- rand6 = "2" + os.urandom(3).hex()[1:]
7201
- version = 1 if sdes else 2
7219
+ # build.
7220
+ #
7221
+ # The class-0 result is explained now (firmware read 2026-10-27, the
7222
+ # same on the A000088, A001064 and A001513): the per-viewer codec
7223
+ # decision reads field2[0] itself and gives H265 only to class 0 or 1,
7224
+ # and only then adds a second, H265 video transceiver. An A000088 that
7225
+ # decides H265 sends its live video there, which the aiortc path
7226
+ # cannot receive - the session is up and empty. SD playback needs that
7227
+ # transceiver on a camera that records H265, so its open passes
7228
+ # ``client_class=0``; live views keep 2.
7229
+ #
7230
+ # ``stream`` is field 5, which the firmware logs as StreamID (0 = main
7231
+ # stream). The transport default it replaces is what every live open
7232
+ # has always sent.
7233
+ # Field 2 = client class + 5 random. The tail stays random: pinning all
7234
+ # six would make every peer id identical across opens, which is the
7235
+ # cross-session reuse the camera dedups on.
7236
+ if not 0 <= client_class <= 9:
7237
+ raise ValueError(f"client_class must be one digit, got {client_class}")
7238
+ rand6 = str(client_class) + os.urandom(3).hex()[1:]
7239
+ version = (1 if sdes else 2) if stream is None else stream
7202
7240
  return f"{session}_{rand6}_{live_type}_{stream_id}_{version}"
7203
7241
 
7204
7242
  async def async_open_webrtc_stream(
@@ -1788,7 +1788,15 @@ def video_pts_dts(state: dict, pts: int, slack: int) -> tuple:
1788
1788
 
1789
1789
 
1790
1790
  def _dtls_av_mux_run(
1791
- vq, aq, out_fileobj, progress, stop_flag, *, align_audio_start=False, state=None
1791
+ vq,
1792
+ aq,
1793
+ out_fileobj,
1794
+ progress,
1795
+ stop_flag,
1796
+ *,
1797
+ align_audio_start=False,
1798
+ state=None,
1799
+ video_codec="h264",
1792
1800
  ) -> None:
1793
1801
  """Mux tapped video (H.264 copy) + audio (PCMA->AAC) to ``out_fileobj`` as
1794
1802
  RTP-timestamped MPEG-TS. Runs in a worker thread; the serve's ffmpeg reads
@@ -1810,7 +1818,14 @@ def _dtls_av_mux_run(
1810
1818
  the serve's timing is unchanged.
1811
1819
  - ``state``, if given, gets ``state["failed"] = True`` when a write failed
1812
1820
  and the mux ended early.
1821
+ - ``video_codec`` is the video stream's codec. None leaves it to the first
1822
+ keyframe: a queue item may carry its codec as a fourth element
1823
+ (``"hevc"`` from the SD playback H265 tap), and a three-element item is
1824
+ H.264. Only with ``align_audio_start``, which writes nothing before the
1825
+ first keyframe, so the stream can still be added then.
1813
1826
  """
1827
+ if video_codec is None and not align_audio_start:
1828
+ raise ValueError("video_codec=None needs align_audio_start")
1814
1829
  import time as _t
1815
1830
  import queue as _q
1816
1831
  from fractions import Fraction
@@ -1880,8 +1895,10 @@ def _dtls_av_mux_run(
1880
1895
  except Exception as exc:
1881
1896
  _LOGGER.warning("DTLS A/V mux: av.open failed: %s", exc)
1882
1897
  return
1883
- vs = out.add_stream("h264")
1884
- vs.time_base = Fraction(1, 90000)
1898
+ vs = None
1899
+ if video_codec is not None:
1900
+ vs = out.add_stream(video_codec)
1901
+ vs.time_base = Fraction(1, 90000)
1885
1902
  try:
1886
1903
  adec = av.CodecContext.create("pcm_alaw", "r")
1887
1904
  adec.sample_rate = 8000
@@ -1922,16 +1939,42 @@ def _dtls_av_mux_run(
1922
1939
  # ffmpeg at its input probe so it never bound its -listen port. One dead
1923
1940
  # write -> exit the thread and leave the queues alone.
1924
1941
  mux_dead = [False]
1942
+ vs_box = [vs]
1943
+ vcodec_box = [video_codec]
1925
1944
 
1926
1945
  def _flush_video():
1927
1946
  while True:
1928
1947
  try:
1929
- data, ts, kf = vq.get_nowait()
1948
+ item = vq.get_nowait()
1930
1949
  except _q.Empty:
1931
1950
  break
1951
+ data, ts, kf = item[:3]
1952
+ _codec = item[3] if len(item) > 3 else "h264"
1953
+ if vs_box[0] is not None and video_codec is None:
1954
+ # Lazy mode: a camera sending both tracks must not put the
1955
+ # other codec into this stream. Compared by the name it was
1956
+ # added under: the stream reports its encoder (libx265).
1957
+ if vcodec_box[0] != _codec:
1958
+ continue
1932
1959
  if not vstarted[0]:
1933
1960
  if not kf:
1934
1961
  continue # begin on a keyframe so the GOP is decodable
1962
+ if vs_box[0] is None:
1963
+ # The first keyframe says what the recording is.
1964
+ # The stream is only muxed into, but PyAV opens an
1965
+ # encoder for it, and x265 prints its banner straight to
1966
+ # stderr - into Home Assistant's log on every play.
1967
+ _vs = out.add_stream(
1968
+ _codec,
1969
+ options=(
1970
+ {"x265-params": "log-level=none"}
1971
+ if _codec == "hevc"
1972
+ else None
1973
+ ),
1974
+ )
1975
+ _vs.time_base = Fraction(1, 90000)
1976
+ vs_box[0] = _vs
1977
+ vcodec_box[0] = _codec
1935
1978
  vstarted[0] = True
1936
1979
  v0[0] = ts
1937
1980
  _rel = ts - v0[0]
@@ -1941,7 +1984,7 @@ def _dtls_av_mux_run(
1941
1984
  # is_resent_video_frame. Dropping costs no media time.
1942
1985
  continue
1943
1986
  pkt = av.Packet(data)
1944
- pkt.stream = vs
1987
+ pkt.stream = vs_box[0]
1945
1988
  pkt.pts, pkt.dts = video_pts_dts(_ts_state, _rel, _slack)
1946
1989
  pkt.time_base = Fraction(1, 90000)
1947
1990
  if kf:
@@ -3166,22 +3209,50 @@ class _AvioWaiter:
3166
3209
  look silent.
3167
3210
  """
3168
3211
 
3169
- def __init__(self, router: "AvioResponseRouter", command: int) -> None:
3212
+ def __init__(self, router: "AvioResponseRouter", command: int, match=None) -> None:
3170
3213
  self._router = router
3171
3214
  self._command = command
3215
+ # Optional predicate on the reply: a command whose replies all share
3216
+ # one AVIO id (RECORD_PLAYCONTROL echoes the playback command in its
3217
+ # payload) can then have several questions out at once.
3218
+ self._match = match
3172
3219
  self._future = _cfutures.Future()
3173
3220
 
3221
+ def _accepts(self, resp) -> bool:
3222
+ if self._match is None:
3223
+ return True
3224
+ try:
3225
+ return bool(self._match(resp))
3226
+ except Exception:
3227
+ return False
3228
+
3174
3229
  def cancel(self) -> None:
3175
3230
  """Withdraw the question - used when the command could not be sent."""
3176
3231
  self._router._forget(self)
3177
3232
 
3178
- async def result(self, timeout: float) -> "Optional[AvioResponse]":
3233
+ async def result(
3234
+ self, timeout: float, *, keep: bool = False
3235
+ ) -> "Optional[AvioResponse]":
3179
3236
  """The camera's reply, or None if it did not answer within ``timeout``.
3180
3237
 
3181
3238
  None is a real answer, not an error: this firmware may not implement a
3182
3239
  response for every command we send, and a control call must never block
3183
3240
  a view while we find out.
3241
+
3242
+ ``keep`` leaves the question open when the wait times out, so a later
3243
+ call still gets a reply that arrives late. For a one-off question held
3244
+ for a session's life (the PLAY_START reply), not for a command sent
3245
+ repeatedly, whose registrations would pile up.
3184
3246
  """
3247
+ if keep and not self._future.done():
3248
+ try:
3249
+ # Shielded: a timed-out wait must not cancel the future the
3250
+ # bridge thread will still set.
3251
+ return await asyncio.wait_for(
3252
+ asyncio.shield(asyncio.wrap_future(self._future)), timeout
3253
+ )
3254
+ except TimeoutError:
3255
+ return None
3185
3256
  if self._future.done():
3186
3257
  # A wait that timed out cancels its own wrapper, and that
3187
3258
  # cancellation can propagate down to this future - asking again
@@ -3223,13 +3294,16 @@ class AvioResponseRouter:
3223
3294
  self._lock = threading.Lock()
3224
3295
  self._waiting: "dict[int, list[_AvioWaiter]]" = {}
3225
3296
 
3226
- def expect(self, command: int) -> _AvioWaiter:
3297
+ def expect(self, command: int, match=None) -> _AvioWaiter:
3227
3298
  """Register interest in the next response carrying ``command``.
3228
3299
 
3300
+ ``match``, when given, narrows that to a response it returns True for;
3301
+ a response no waiter accepts is not answered to anyone.
3302
+
3229
3303
  Call this before sending the request: the camera can answer before the
3230
3304
  sender returns, and a registration made afterwards would miss the reply.
3231
3305
  """
3232
- waiter = _AvioWaiter(self, command)
3306
+ waiter = _AvioWaiter(self, command, match)
3233
3307
  with self._lock:
3234
3308
  self._waiting.setdefault(command, []).append(waiter)
3235
3309
  return waiter
@@ -3248,11 +3322,15 @@ class AvioResponseRouter:
3248
3322
  with self._lock:
3249
3323
  queue = self._waiting.get(resp.command)
3250
3324
  waiter = None
3251
- while queue:
3252
- candidate = queue.pop(0)
3253
- if not candidate._future.done():
3254
- waiter = candidate
3255
- break
3325
+ if queue:
3326
+ # Oldest first, skipping questions already settled and
3327
+ # any whose match turns this reply down.
3328
+ queue[:] = [w for w in queue if not w._future.done()]
3329
+ for candidate in queue:
3330
+ if candidate._accepts(resp):
3331
+ waiter = candidate
3332
+ queue.remove(candidate)
3333
+ break
3256
3334
  if queue is not None and not queue:
3257
3335
  self._waiting.pop(resp.command, None)
3258
3336
  if waiter is None:
@@ -3748,6 +3826,7 @@ class AvioRequestMixin:
3748
3826
  *,
3749
3827
  response_cmd: int,
3750
3828
  timeout: float = AVIO_RESPONSE_TIMEOUT_S,
3829
+ match=None,
3751
3830
  ) -> "Optional[AvioResponse]":
3752
3831
  """Send ``cmd`` and return the camera's ``response_cmd`` reply, or None.
3753
3832
 
@@ -3755,7 +3834,7 @@ class AvioRequestMixin:
3755
3834
  not a failure: this firmware may implement a request without a response,
3756
3835
  and no reply at all points at the channel rather than the firmware.
3757
3836
  """
3758
- waiter = self._avio_responses.expect(response_cmd)
3837
+ waiter = self._avio_responses.expect(response_cmd, match)
3759
3838
  # Registered BEFORE the send: a camera on the LAN can answer before the
3760
3839
  # sending call returns, and a reply that arrives with nobody listening
3761
3840
  # would be reported as silence.
@@ -50,8 +50,15 @@ class TsCopyRecorder:
50
50
  video to :attr:`vq` and audio to :attr:`aq`.
51
51
  """
52
52
 
53
- def __init__(self, path: str, device_id: Optional[str] = None) -> None:
53
+ def __init__(
54
+ self,
55
+ path: str,
56
+ device_id: Optional[str] = None,
57
+ video_codec: Optional[str] = "h264",
58
+ ) -> None:
54
59
  self._path = path
60
+ # None: the first keyframe names it (SD playback, H264 or H265).
61
+ self._video_codec = video_codec
55
62
  self._device_id = device_id or "?"
56
63
  self.vq: "queue.Queue" = queue.Queue(maxsize=_QUEUE_MAX)
57
64
  self.aq: "queue.Queue" = queue.Queue(maxsize=_QUEUE_MAX)
@@ -71,7 +78,11 @@ class TsCopyRecorder:
71
78
  self._thread = threading.Thread(
72
79
  target=_dtls_av_mux_run,
73
80
  args=(self.vq, self.aq, self._file, self._progress, self._stop_flag),
74
- kwargs={"align_audio_start": True, "state": self._state},
81
+ kwargs={
82
+ "align_audio_start": True,
83
+ "state": self._state,
84
+ "video_codec": self._video_codec,
85
+ },
75
86
  name="aidot-dtls-record",
76
87
  daemon=True,
77
88
  )