python-aidot-cameras 0.15.0b5__tar.gz → 0.15.2__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 (173) hide show
  1. {python_aidot_cameras-0.15.0b5/python_aidot_cameras.egg-info → python_aidot_cameras-0.15.2}/PKG-INFO +3 -1
  2. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/README.md +2 -0
  3. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/client.py +5 -0
  4. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/controls.py +32 -2
  5. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/hwaccel.py +14 -4
  6. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/pyproject.toml +1 -1
  7. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2/python_aidot_cameras.egg-info}/PKG-INFO +3 -1
  8. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
  9. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_hwaccel_probe.py +24 -5
  10. python_aidot_cameras-0.15.2/tests/test_resolution_persists.py +95 -0
  11. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/LICENSE +0 -0
  12. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/MANIFEST.in +0 -0
  13. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/__init__.py +0 -0
  14. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/__main__.py +0 -0
  15. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_upstream.py +0 -0
  16. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/__init__.py +0 -0
  17. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  18. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  19. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  20. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  21. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  22. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  23. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  24. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  25. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  26. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  27. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  28. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  29. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  30. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  31. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  32. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  33. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  34. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  35. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  36. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  37. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  38. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  39. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  40. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  41. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  42. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  43. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  44. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  45. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  46. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  47. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  48. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  49. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/ble_gateway.py +0 -0
  50. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/__init__.py +0 -0
  51. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/constants.py +0 -0
  52. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/go2rtc.py +0 -0
  53. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/lan_control.py +0 -0
  54. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/models.py +0 -0
  55. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/playback.py +0 -0
  56. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/protocol.py +0 -0
  57. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/sdes.py +0 -0
  58. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/sdes_open.py +0 -0
  59. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/tutk.py +0 -0
  60. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/webrtc.py +0 -0
  61. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/camera/webrtc_open.py +0 -0
  62. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/client.py +0 -0
  63. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/const.py +0 -0
  64. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/credentials.py +0 -0
  65. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/crypto.py +0 -0
  66. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/device_client.py +0 -0
  67. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/discover.py +0 -0
  68. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/exceptions.py +0 -0
  69. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/g711.py +0 -0
  70. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/aidot_cameras/py.typed +0 -0
  71. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  72. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  73. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/python_aidot_cameras.egg-info/requires.txt +0 -0
  74. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  75. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/setup.cfg +0 -0
  76. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_aioice_compat.py +0 -0
  77. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_alarm_event.py +0 -0
  78. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_answer_sdp_video_pt.py +0 -0
  79. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_audio_mux_offline.py +0 -0
  80. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_backoff.py +0 -0
  81. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_battery_detection.py +0 -0
  82. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_battery_evidence_corroboration.py +0 -0
  83. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_battery_skip_turn.py +0 -0
  84. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_ble_gateway.py +0 -0
  85. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_constants_imports_resolve.py +0 -0
  86. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_deferred_hardening.py +0 -0
  87. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_device_login_guard.py +0 -0
  88. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_device_user_info_cache.py +0 -0
  89. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dormant_camera_cleanup.py +0 -0
  90. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_mux_terminal_write.py +0 -0
  91. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_not_ready_burst.py +0 -0
  92. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_pinning.py +0 -0
  93. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_serve_decode_skip.py +0 -0
  94. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_serve_open_timeout.py +0 -0
  95. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_skip_signaling_wait.py +0 -0
  96. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_dtls_slow_probe.py +0 -0
  97. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_egress_guard.py +0 -0
  98. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_env_seams.py +0 -0
  99. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_go2rtc.py +0 -0
  100. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_go2rtc_cli.py +0 -0
  101. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_h264_decode_rate_limit.py +0 -0
  102. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_highport_nomination.py +0 -0
  103. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_ice_config_cache.py +0 -0
  104. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_ice_peer_reflexive.py +0 -0
  105. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_idle_release_both_loops.py +0 -0
  106. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_keepalive_renew.py +0 -0
  107. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_key_restart_sdp.py +0 -0
  108. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_keyframe_prompter.py +0 -0
  109. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_lan_control.py +0 -0
  110. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_light_active_color_mode.py +0 -0
  111. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_live_stream_param.py +0 -0
  112. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_live_validate_policy.py +0 -0
  113. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_logging_caps.py +0 -0
  114. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_media_wait_floor.py +0 -0
  115. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_motion_poll.py +0 -0
  116. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_mqtt_credential_refetch.py +0 -0
  117. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_mqtt_credential_self_heal.py +0 -0
  118. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_mqtt_password_not_persisted.py +0 -0
  119. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_mqtt_session_reconnect.py +0 -0
  120. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_narrow_pc_ice.py +0 -0
  121. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_no_self_referential_go2rtc.py +0 -0
  122. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_no_undefined_names.py +0 -0
  123. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_offline_keepalive_pause.py +0 -0
  124. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_open_fail_logger.py +0 -0
  125. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_open_gate_delay.py +0 -0
  126. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_peerid_reuse.py +0 -0
  127. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_persistent_mqtt.py +0 -0
  128. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_persistent_mqtt_robustness.py +0 -0
  129. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_playback_tls.py +0 -0
  130. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_post_merge_hardening.py +0 -0
  131. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_restart_cycle.py +0 -0
  132. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_retry_policy.py +0 -0
  133. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_scripts_importable.py +0 -0
  134. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_adaptive.py +0 -0
  135. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_bridge_break_gate.py +0 -0
  136. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_echo_wait_timeout.py +0 -0
  137. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_fast_liveplay.py +0 -0
  138. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  139. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_idle_release.py +0 -0
  140. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_open_cleanup.py +0 -0
  141. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_serve_audio.py +0 -0
  142. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_serve_cmd.py +0 -0
  143. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_sprop.py +0 -0
  144. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_talk.py +0 -0
  145. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_teardown_exit_log.py +0 -0
  146. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_terminal_ack.py +0 -0
  147. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_terminal_ack_latency.py +0 -0
  148. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdes_watchdog.py +0 -0
  149. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdp_narrowing_properties.py +0 -0
  150. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sdp_payload_type_narrowing.py +0 -0
  151. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_security_hardening.py +0 -0
  152. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_serializable_login_info.py +0 -0
  153. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_serve_relay.py +0 -0
  154. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_session_stats.py +0 -0
  155. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_shared_house_seam.py +0 -0
  156. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_speak.py +0 -0
  157. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_sprop_staleness.py +0 -0
  158. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_stream_cap.py +0 -0
  159. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_stream_drain_ownership.py +0 -0
  160. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_stream_idle.py +0 -0
  161. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_stream_slot_cap.py +0 -0
  162. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_stream_teardown.py +0 -0
  163. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_talk.py +0 -0
  164. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_terminal_ack.py +0 -0
  165. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_token_file_robustness.py +0 -0
  166. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_token_refresh.py +0 -0
  167. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_turn_create_permission.py +0 -0
  168. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_upstream_compat.py +0 -0
  169. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_viewer_check_throttle.py +0 -0
  170. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_viewer_presence.py +0 -0
  171. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_viewer_present_push_mode.py +0 -0
  172. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_wait_or_event.py +0 -0
  173. {python_aidot_cameras-0.15.0b5 → python_aidot_cameras-0.15.2}/tests/test_wake_readiness_retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.15.0b5
3
+ Version: 0.15.2
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
@@ -167,6 +167,8 @@ audio, idle release, the sprop cache path) are documented in
167
167
 
168
168
  | Variable | Purpose | Default |
169
169
  | --- | --- | --- |
170
+ | `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
171
+ | `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
170
172
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently across all cameras. | `2` |
171
173
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
172
174
  | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
@@ -136,6 +136,8 @@ audio, idle release, the sprop cache path) are documented in
136
136
 
137
137
  | Variable | Purpose | Default |
138
138
  | --- | --- | --- |
139
+ | `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
140
+ | `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
139
141
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently across all cameras. | `2` |
140
142
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
141
143
  | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
@@ -867,6 +867,8 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
867
867
  self.latest_jpeg: Optional[bytes] = None
868
868
  self._streaming_active: bool = False
869
869
  self._stream_session: Optional[Any] = None
870
+ # Last resolution asked for, re-sent when a session starts.
871
+ self._desired_quality: Optional[str] = None
870
872
  self._stream_task: Optional["asyncio.Task[None]"] = None
871
873
  self._stream_mqtt_drain: Optional["asyncio.Future"] = None
872
874
  self._last_frame_time: float = 0.0
@@ -3354,6 +3356,8 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3354
3356
 
3355
3357
  self._fast_attempt_override = None
3356
3358
  self._stream_session = session
3359
+ # A remembered resolution is only deliverable once a session exists.
3360
+ self._apply_pending_resolution()
3357
3361
  # Don't block solely on wait_done(): the SDES ffmpeg reads RTP over a
3358
3362
  # UDP socket with no input timeout, so when a battery camera tears the
3359
3363
  # session down (~49-72s) ffmpeg keeps waiting on dead sockets forever
@@ -3564,6 +3568,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3564
3568
  continue
3565
3569
 
3566
3570
  self._stream_session = session
3571
+ self._apply_pending_resolution()
3567
3572
  try:
3568
3573
  while self._streaming_active:
3569
3574
  await asyncio.sleep(5.0)
@@ -144,6 +144,13 @@ class _CameraControlsMixin:
144
144
  mirroring the official app's HD/SD toggle. Rides the active stream
145
145
  session (SETSTREAMCTRL=800), so the camera must be streaming.
146
146
 
147
+ The choice is REMEMBERED and re-sent whenever a session next starts.
148
+ Without that it was simply dropped when nothing was streaming - and
149
+ since a camera is idle far more often than not, the usual outcome of
150
+ changing this was nothing at all, while the caller was told the setting
151
+ had been applied. Nothing else re-sent it, so "it will apply on the next
152
+ session" was not true of any code path.
153
+
147
154
  Source: f0.java g3() -> X2(800, SetStreamCtrlReq.parseContent(0, quality)).
148
155
  Payload <IB3x> = channel(0) + quality byte + 3 reserved.
149
156
  """
@@ -151,14 +158,37 @@ class _CameraControlsMixin:
151
158
  if q is None:
152
159
  _LOGGER.error("async_set_resolution: unknown quality %r", quality)
153
160
  return False
161
+ self._desired_quality = quality.lower()
154
162
  payload = struct.pack("<IB3x", 0, q)
155
163
  session = self._stream_session
156
164
  if session is None:
157
- _LOGGER.warning("async_set_resolution: no active stream session")
158
- return False
165
+ _LOGGER.info(
166
+ "resolution %s remembered; the camera is not streaming, so it "
167
+ "will be applied when a session next starts", quality)
168
+ return True
159
169
  ok = session._avio_cmd(SETSTREAMCTRL_CMD, payload)
160
170
  _LOGGER.debug(
161
171
  "set resolution %s (quality=%d) -> %s",
162
172
  quality, q, "sent" if ok else "no channel yet",
163
173
  )
164
174
  return ok
175
+
176
+ def _apply_pending_resolution(self) -> None:
177
+ """Re-send the remembered resolution now that a session exists.
178
+
179
+ Called when a stream session becomes active. Best-effort and silent on
180
+ failure: a stream that is up matters more than a quality preference, so
181
+ this must never be able to disturb it.
182
+ """
183
+ quality = getattr(self, "_desired_quality", None)
184
+ if not quality:
185
+ return
186
+ q = _STREAM_QUALITY.get(quality)
187
+ session = self._stream_session
188
+ if q is None or session is None:
189
+ return
190
+ try:
191
+ if session._avio_cmd(SETSTREAMCTRL_CMD, struct.pack("<IB3x", 0, q)):
192
+ _LOGGER.debug("re-applied remembered resolution %s", quality)
193
+ except Exception:
194
+ _LOGGER.debug("could not re-apply resolution %s", quality, exc_info=True)
@@ -393,6 +393,14 @@ def warm_decoder_cache(codecs: tuple = ("h264",)) -> "threading.Thread":
393
393
  return _warm_thread
394
394
 
395
395
  def _run() -> None:
396
+ # Prime the in-memory cache from disk HERE, on this thread, so
397
+ # cached_decoder() never has to read a file on the event loop.
398
+ try:
399
+ for k, v in _load_cache().items():
400
+ if isinstance(v, list):
401
+ _cache_mem.setdefault(k, list(v))
402
+ except Exception:
403
+ _LOGGER.debug("decoder cache preload failed", exc_info=True)
396
404
  for codec in codecs:
397
405
  try:
398
406
  probe_decoder(codec)
@@ -432,13 +440,15 @@ def cached_decoder(codec: str) -> Optional[List[str]]:
432
440
  if os.environ.get(_ENV_DISABLE, "").strip().lower() in ("1", "true", "yes", "on"):
433
441
  return []
434
442
 
443
+ # MEMORY ONLY. This runs on the event loop, so it must not touch the disk:
444
+ # Home Assistant detects a blocking open() here and reports it as a
445
+ # stability problem, correctly. The disk cache is read by
446
+ # warm_decoder_cache() on its own thread, which populates _cache_mem; a miss
447
+ # here simply means "not known yet", and the caller lets ffmpeg choose,
448
+ # which is the behaviour that existed before any of this.
435
449
  key = f"v{_CACHE_SCHEMA}:{codec}:{_ffmpeg_identity()}"
436
450
  if key in _cache_mem:
437
451
  return list(_cache_mem[key])
438
- val = _load_cache().get(key)
439
- if isinstance(val, list):
440
- _cache_mem[key] = list(val)
441
- return list(val)
442
452
  return None
443
453
 
444
454
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.15.0b5"
7
+ version = "0.15.2"
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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.15.0b5
3
+ Version: 0.15.2
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
@@ -167,6 +167,8 @@ audio, idle release, the sprop cache path) are documented in
167
167
 
168
168
  | Variable | Purpose | Default |
169
169
  | --- | --- | --- |
170
+ | `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
171
+ | `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
170
172
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently across all cameras. | `2` |
171
173
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
172
174
  | `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
@@ -125,6 +125,7 @@ tests/test_persistent_mqtt.py
125
125
  tests/test_persistent_mqtt_robustness.py
126
126
  tests/test_playback_tls.py
127
127
  tests/test_post_merge_hardening.py
128
+ tests/test_resolution_persists.py
128
129
  tests/test_restart_cycle.py
129
130
  tests/test_retry_policy.py
130
131
  tests/test_scripts_importable.py
@@ -158,6 +158,18 @@ def test_no_sample_falls_back_to_software(monkeypatch):
158
158
  assert hwaccel.probe_decoder("h264", force=True) == []
159
159
 
160
160
 
161
+ def test_warm_cache_primes_memory_from_disk(monkeypatch):
162
+ """Since cached_decoder() is memory-only, something has to load the disk
163
+ cache - and it must be the background thread, not the event loop."""
164
+ monkeypatch.setattr(hwaccel, "_ffmpeg_identity", lambda: "b")
165
+ key = f"v{hwaccel._CACHE_SCHEMA}:h264:b"
166
+ hwaccel._save_cache({key: ["-c:v", "h264_v4l2m2m"]})
167
+ assert hwaccel.cached_decoder("h264") is None # not yet in memory
168
+ monkeypatch.setattr(hwaccel, "probe_decoder", lambda c: [])
169
+ hwaccel.warm_decoder_cache(("h264",)).join(timeout=30)
170
+ assert hwaccel.cached_decoder("h264") == ["-c:v", "h264_v4l2m2m"]
171
+
172
+
161
173
  def test_software_verdict_is_not_confused_with_unprobed(monkeypatch):
162
174
  """[] means "software, proven"; None means "not probed yet". Collapsing the
163
175
  two would re-probe forever on any host with no hardware decoding."""
@@ -168,7 +180,7 @@ def test_software_verdict_is_not_confused_with_unprobed(monkeypatch):
168
180
  lambda cand, sample: 1.0 if not cand else None)
169
181
  assert hwaccel.cached_decoder("h264") is None # nothing probed yet
170
182
  assert hwaccel.probe_decoder("h264") == [] # proven software
171
- assert hwaccel.cached_decoder("h264") == [] # remembered, not None
183
+ assert hwaccel.cached_decoder("h264") == [] # memoised, not None
172
184
 
173
185
 
174
186
  def test_a_stale_schema_entry_is_ignored(monkeypatch):
@@ -176,6 +188,8 @@ def test_a_stale_schema_entry_is_ignored(monkeypatch):
176
188
  Reading that back as a verdict would put a string into the ffmpeg argv."""
177
189
  monkeypatch.setattr(hwaccel, "_ffmpeg_identity", lambda: "b")
178
190
  hwaccel._save_cache({"h264:b": "h264_v4l2m2m"}) # v1-shaped entry
191
+ monkeypatch.setattr(hwaccel, "probe_decoder", lambda c: [])
192
+ hwaccel.warm_decoder_cache(("h264",)).join(timeout=30)
179
193
  assert hwaccel.cached_decoder("h264") is None
180
194
 
181
195
 
@@ -231,13 +245,18 @@ def test_no_verdict_leaves_ffmpeg_to_choose():
231
245
  sdp_path="/tmp/x.sdp", video_decoder=None)
232
246
 
233
247
 
234
- def test_cached_decoder_never_probes(monkeypatch):
235
- """It runs on the event loop, so it must not shell out."""
248
+ def test_cached_decoder_touches_neither_disk_nor_subprocess(monkeypatch):
249
+ """It runs on the event loop, so it must not shell out AND must not read a
250
+ file. Home Assistant detects a blocking open() on the loop and reports it as
251
+ a stability problem - it did, on a shipped release. The disk cache is read
252
+ by warm_decoder_cache() on its own thread instead."""
253
+ monkeypatch.setattr(hwaccel, "_load_cache",
254
+ lambda: pytest.fail("must not read the cache file"))
236
255
  monkeypatch.setattr(hwaccel, "_try_decoder",
237
256
  lambda cand, sample: pytest.fail("must not probe"))
238
257
  monkeypatch.setattr(hwaccel, "_make_sample",
239
258
  lambda codec, path: pytest.fail("must not probe"))
240
- assert hwaccel.cached_decoder("h264") is None # empty cache, no probing
259
+ assert hwaccel.cached_decoder("h264") is None # nothing memoised, no I/O
241
260
 
242
261
 
243
262
  # --------------------------------------------------------------------------- #
@@ -266,7 +285,7 @@ def test_software_is_not_timed_when_no_hardware_qualifies(monkeypatch):
266
285
 
267
286
  def _try(cand, sample):
268
287
  tried.append(tuple(cand))
269
- return None # nothing decodes
288
+ return # nothing decodes here
270
289
 
271
290
  monkeypatch.setattr(hwaccel, "_try_decoder", _try)
272
291
  assert hwaccel.probe_decoder("h264") == []
@@ -0,0 +1,95 @@
1
+ """A resolution choice must survive being made while nothing is streaming.
2
+
3
+ The setter rides the live session (SETSTREAMCTRL): with no session there is
4
+ nothing to send it over. It used to return False and drop the choice entirely,
5
+ and nothing re-sent it - so the usual outcome of changing resolution was
6
+ nothing at all, because a camera is idle far more often than it is streaming.
7
+ The caller was told the setting had been applied, and the comment in the
8
+ integration's select claimed it would "take effect on the next session", which
9
+ no code path did.
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ from unittest.mock import MagicMock
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
17
+
18
+ import pytest
19
+
20
+ from aidot_cameras.camera.constants import _STREAM_QUALITY, SETSTREAMCTRL_CMD
21
+ from aidot_cameras.camera.controls import _CameraControlsMixin
22
+
23
+
24
+ class _Cam(_CameraControlsMixin):
25
+ def __init__(self, session=None):
26
+ self._stream_session = session
27
+ self._desired_quality = None
28
+
29
+
30
+ def _session():
31
+ s = MagicMock()
32
+ s._avio_cmd = MagicMock(return_value=True)
33
+ return s
34
+
35
+
36
+ @pytest.mark.asyncio
37
+ async def test_choice_made_while_idle_is_remembered_not_dropped():
38
+ cam = _Cam(session=None)
39
+
40
+ assert await cam.async_set_resolution("sd") is True
41
+ assert cam._desired_quality == "sd"
42
+
43
+
44
+ @pytest.mark.asyncio
45
+ async def test_remembered_choice_is_sent_when_a_session_starts():
46
+ cam = _Cam(session=None)
47
+ await cam.async_set_resolution("sd")
48
+
49
+ cam._stream_session = _session()
50
+ cam._apply_pending_resolution()
51
+
52
+ cam._stream_session._avio_cmd.assert_called_once()
53
+ cmd, payload = cam._stream_session._avio_cmd.call_args[0]
54
+ assert cmd == SETSTREAMCTRL_CMD
55
+ assert payload[4] == _STREAM_QUALITY["sd"] # AVIOCTRL_QUALITY_MIDDLE
56
+
57
+
58
+ @pytest.mark.asyncio
59
+ async def test_hd_round_trips_too():
60
+ cam = _Cam(session=None)
61
+ await cam.async_set_resolution("hd")
62
+ cam._stream_session = _session()
63
+ cam._apply_pending_resolution()
64
+ assert cam._stream_session._avio_cmd.call_args[0][1][4] == _STREAM_QUALITY["hd"]
65
+
66
+
67
+ @pytest.mark.asyncio
68
+ async def test_a_live_session_still_gets_it_immediately():
69
+ """Unchanged behaviour when the camera is already streaming."""
70
+ cam = _Cam(session=_session())
71
+
72
+ assert await cam.async_set_resolution("sd") is True
73
+ cam._stream_session._avio_cmd.assert_called_once()
74
+
75
+
76
+ def test_nothing_is_sent_when_no_choice_was_ever_made():
77
+ cam = _Cam(session=_session())
78
+ cam._apply_pending_resolution()
79
+ cam._stream_session._avio_cmd.assert_not_called()
80
+
81
+
82
+ @pytest.mark.asyncio
83
+ async def test_an_unknown_quality_is_still_rejected():
84
+ cam = _Cam(session=_session())
85
+ assert await cam.async_set_resolution("ultra") is False
86
+ assert cam._desired_quality is None
87
+
88
+
89
+ def test_reapply_never_disturbs_a_working_stream():
90
+ """A quality preference must never be able to break a stream that is up."""
91
+ cam = _Cam(session=_session())
92
+ cam._desired_quality = "sd"
93
+ cam._stream_session._avio_cmd.side_effect = RuntimeError("channel gone")
94
+
95
+ cam._apply_pending_resolution() # must not raise