python-aidot-cameras 0.15.2__tar.gz → 0.15.3__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 (174) hide show
  1. {python_aidot_cameras-0.15.2/python_aidot_cameras.egg-info → python_aidot_cameras-0.15.3}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/sdes.py +19 -1
  3. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/sdes_open.py +43 -0
  4. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/pyproject.toml +1 -1
  5. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  6. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
  7. python_aidot_cameras-0.15.3/tests/test_sdes_serve_stderr_drain.py +161 -0
  8. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/LICENSE +0 -0
  9. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/MANIFEST.in +0 -0
  10. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/README.md +0 -0
  11. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/__init__.py +0 -0
  12. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/__main__.py +0 -0
  13. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_upstream.py +0 -0
  14. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/__init__.py +0 -0
  15. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  16. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  17. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  18. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  19. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  20. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  21. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  22. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  23. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  24. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  25. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  26. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  27. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  28. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  29. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  30. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  31. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  32. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  33. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  34. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  35. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  36. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  37. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  38. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  39. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  40. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  41. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  42. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  43. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  44. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  45. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  46. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  47. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/ble_gateway.py +0 -0
  48. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/__init__.py +0 -0
  49. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/client.py +0 -0
  50. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/constants.py +0 -0
  51. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/controls.py +0 -0
  52. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/go2rtc.py +0 -0
  53. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/hwaccel.py +0 -0
  54. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/lan_control.py +0 -0
  55. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/models.py +0 -0
  56. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/playback.py +0 -0
  57. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/protocol.py +0 -0
  58. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/tutk.py +0 -0
  59. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/webrtc.py +0 -0
  60. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/camera/webrtc_open.py +0 -0
  61. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/client.py +0 -0
  62. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/const.py +0 -0
  63. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/credentials.py +0 -0
  64. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/crypto.py +0 -0
  65. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/device_client.py +0 -0
  66. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/discover.py +0 -0
  67. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/exceptions.py +0 -0
  68. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/g711.py +0 -0
  69. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/aidot_cameras/py.typed +0 -0
  70. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  71. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  72. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/python_aidot_cameras.egg-info/requires.txt +0 -0
  73. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  74. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/setup.cfg +0 -0
  75. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_aioice_compat.py +0 -0
  76. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_alarm_event.py +0 -0
  77. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_answer_sdp_video_pt.py +0 -0
  78. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_audio_mux_offline.py +0 -0
  79. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_backoff.py +0 -0
  80. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_battery_detection.py +0 -0
  81. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_battery_evidence_corroboration.py +0 -0
  82. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_battery_skip_turn.py +0 -0
  83. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_ble_gateway.py +0 -0
  84. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_constants_imports_resolve.py +0 -0
  85. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_deferred_hardening.py +0 -0
  86. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_device_login_guard.py +0 -0
  87. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_device_user_info_cache.py +0 -0
  88. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dormant_camera_cleanup.py +0 -0
  89. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_mux_terminal_write.py +0 -0
  90. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_not_ready_burst.py +0 -0
  91. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_pinning.py +0 -0
  92. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_serve_decode_skip.py +0 -0
  93. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_serve_open_timeout.py +0 -0
  94. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_skip_signaling_wait.py +0 -0
  95. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_dtls_slow_probe.py +0 -0
  96. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_egress_guard.py +0 -0
  97. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_env_seams.py +0 -0
  98. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_go2rtc.py +0 -0
  99. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_go2rtc_cli.py +0 -0
  100. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_h264_decode_rate_limit.py +0 -0
  101. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_highport_nomination.py +0 -0
  102. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_hwaccel_probe.py +0 -0
  103. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_ice_config_cache.py +0 -0
  104. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_ice_peer_reflexive.py +0 -0
  105. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_idle_release_both_loops.py +0 -0
  106. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_keepalive_renew.py +0 -0
  107. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_key_restart_sdp.py +0 -0
  108. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_keyframe_prompter.py +0 -0
  109. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_lan_control.py +0 -0
  110. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_light_active_color_mode.py +0 -0
  111. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_live_stream_param.py +0 -0
  112. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_live_validate_policy.py +0 -0
  113. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_logging_caps.py +0 -0
  114. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_media_wait_floor.py +0 -0
  115. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_motion_poll.py +0 -0
  116. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_mqtt_credential_refetch.py +0 -0
  117. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_mqtt_credential_self_heal.py +0 -0
  118. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_mqtt_password_not_persisted.py +0 -0
  119. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_mqtt_session_reconnect.py +0 -0
  120. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_narrow_pc_ice.py +0 -0
  121. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_no_self_referential_go2rtc.py +0 -0
  122. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_no_undefined_names.py +0 -0
  123. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_offline_keepalive_pause.py +0 -0
  124. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_open_fail_logger.py +0 -0
  125. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_open_gate_delay.py +0 -0
  126. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_peerid_reuse.py +0 -0
  127. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_persistent_mqtt.py +0 -0
  128. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_persistent_mqtt_robustness.py +0 -0
  129. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_playback_tls.py +0 -0
  130. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_post_merge_hardening.py +0 -0
  131. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_resolution_persists.py +0 -0
  132. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_restart_cycle.py +0 -0
  133. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_retry_policy.py +0 -0
  134. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_scripts_importable.py +0 -0
  135. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_adaptive.py +0 -0
  136. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_bridge_break_gate.py +0 -0
  137. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_echo_wait_timeout.py +0 -0
  138. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_fast_liveplay.py +0 -0
  139. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  140. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_idle_release.py +0 -0
  141. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_open_cleanup.py +0 -0
  142. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_serve_audio.py +0 -0
  143. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_serve_cmd.py +0 -0
  144. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_sprop.py +0 -0
  145. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_talk.py +0 -0
  146. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_teardown_exit_log.py +0 -0
  147. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_terminal_ack.py +0 -0
  148. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_terminal_ack_latency.py +0 -0
  149. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdes_watchdog.py +0 -0
  150. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdp_narrowing_properties.py +0 -0
  151. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sdp_payload_type_narrowing.py +0 -0
  152. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_security_hardening.py +0 -0
  153. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_serializable_login_info.py +0 -0
  154. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_serve_relay.py +0 -0
  155. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_session_stats.py +0 -0
  156. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_shared_house_seam.py +0 -0
  157. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_speak.py +0 -0
  158. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_sprop_staleness.py +0 -0
  159. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_stream_cap.py +0 -0
  160. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_stream_drain_ownership.py +0 -0
  161. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_stream_idle.py +0 -0
  162. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_stream_slot_cap.py +0 -0
  163. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_stream_teardown.py +0 -0
  164. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_talk.py +0 -0
  165. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_terminal_ack.py +0 -0
  166. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_token_file_robustness.py +0 -0
  167. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_token_refresh.py +0 -0
  168. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_turn_create_permission.py +0 -0
  169. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_upstream_compat.py +0 -0
  170. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_viewer_check_throttle.py +0 -0
  171. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_viewer_presence.py +0 -0
  172. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_viewer_present_push_mode.py +0 -0
  173. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/tests/test_wait_or_event.py +0 -0
  174. {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.3}/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.2
3
+ Version: 0.15.3
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
@@ -177,6 +177,24 @@ class SdesSession:
177
177
  "audio_pt": self._first_audio_pt[0],
178
178
  }
179
179
 
180
+ def _drained_stderr_tail(self) -> bytes:
181
+ """The stderr the drain thread already consumed, for the teardown log.
182
+
183
+ The serve's stderr is drained continuously on a daemon thread (see
184
+ ``_start_serve_stderr_drain`` in sdes_open) so an un-drained pipe cannot
185
+ fill and stall ffmpeg. A consequence is that by teardown the pipe is at
186
+ EOF and stop()'s ``stderr.read()`` returns nothing, which would leave
187
+ ``_log_ffmpeg_stderr`` with an empty buffer and silently drop the
188
+ teardown diagnostic - the mid-stream ffmpeg error it exists to surface
189
+ included. Fall back to the bounded tail the drainer kept. Returns
190
+ ``b""`` when there is no drainer or it captured nothing, which
191
+ ``_log_ffmpeg_stderr`` already treats as "nothing to say".
192
+ """
193
+ tail = getattr(self._proc, "_aidot_stderr_tail", None)
194
+ if not tail:
195
+ return b""
196
+ return "\n".join(tail).encode("utf-8", "replace")
197
+
180
198
  def _log_ffmpeg_stderr(self, stderr_bytes: bytes) -> None:
181
199
  """Log ffmpeg's captured stderr at a level that matches what happened.
182
200
 
@@ -285,7 +303,7 @@ class SdesSession:
285
303
  self._proc.stderr.close()
286
304
  except Exception:
287
305
  _LOGGER.debug("swallowed exception in %s", 'stop', exc_info=True)
288
- self._log_ffmpeg_stderr(stderr_bytes)
306
+ self._log_ffmpeg_stderr(stderr_bytes or self._drained_stderr_tail())
289
307
  import os
290
308
  try:
291
309
  os.unlink(self._sdp_path)
@@ -38,6 +38,38 @@ from .protocol import (
38
38
 
39
39
  _LOGGER = logging.getLogger(__name__)
40
40
 
41
+
42
+ def _start_serve_stderr_drain(proc, *, maxlines: int = 40) -> None:
43
+ """Drain a serve ffmpeg's stderr continuously into a bounded tail on the proc.
44
+
45
+ The serve is spawned with ``stderr=PIPE`` but the bridge loop only polls the
46
+ process and reads its RTP sockets - it never reads stderr. An un-drained
47
+ ``PIPE`` fills its ~64KB kernel buffer and then blocks ffmpeg on its next
48
+ stderr write, stalling the serve; and when ffmpeg exits non-zero the reason is
49
+ otherwise lost (the RTSP-push ANNOUNCE error in particular). Read it on a
50
+ daemon thread that ends at EOF when the process exits, keeping the last
51
+ ``maxlines`` lines on ``proc._aidot_stderr_tail`` for the exit logger.
52
+ """
53
+ import collections
54
+ import threading
55
+
56
+ tail = collections.deque(maxlen=maxlines)
57
+ proc._aidot_stderr_tail = tail
58
+ if proc.stderr is None:
59
+ return
60
+
61
+ def _drain() -> None:
62
+ try:
63
+ for _line in iter(proc.stderr.readline, b""):
64
+ tail.append(_line.decode("utf-8", "replace").rstrip())
65
+ except Exception:
66
+ pass
67
+
68
+ threading.Thread(
69
+ target=_drain, daemon=True, name="aidot-sdes-serve-stderr"
70
+ ).start()
71
+
72
+
41
73
  # Strong references to detached per-session helper tasks, so the event loop
42
74
  # cannot garbage-collect one mid-flight. Entries remove themselves on done.
43
75
  _SDES_BACKGROUND_TASKS: set = set()
@@ -2803,6 +2835,15 @@ class _SdesOpenMixin:
2803
2835
  _log_br.getLogger(__name__).log(
2804
2836
  _br_level, _br_msg, _br_rc
2805
2837
  )
2838
+ if _br_level >= _log_br.WARNING:
2839
+ _serr_tail = getattr(
2840
+ _br_proc, "_aidot_stderr_tail", None)
2841
+ if _serr_tail:
2842
+ _log_br.getLogger(__name__).warning(
2843
+ "SDES serve ffmpeg stderr (last %d"
2844
+ " lines):\n%s", len(_serr_tail),
2845
+ "\n".join(_serr_tail),
2846
+ )
2806
2847
  if _bridge_should_break(
2807
2848
  _br_rc, _br_teardown_requested
2808
2849
  ):
@@ -4462,6 +4503,7 @@ class _SdesOpenMixin:
4462
4503
  stdout=subprocess.DEVNULL,
4463
4504
  stderr=subprocess.PIPE,
4464
4505
  )
4506
+ _start_serve_stderr_drain(proc)
4465
4507
  _proc_holder[0] = proc
4466
4508
  _cl(_reap, proc) # kill ffmpeg if the open is cancelled before hand-off
4467
4509
  except FileNotFoundError:
@@ -4745,6 +4787,7 @@ class _SdesOpenMixin:
4745
4787
  stdout=subprocess.DEVNULL,
4746
4788
  stderr=subprocess.PIPE,
4747
4789
  )
4790
+ _start_serve_stderr_drain(proc)
4748
4791
  # Point the shared holder at the live proc immediately.
4749
4792
  # The bridge thread polls _proc_holder[0]; if it still
4750
4793
  # sees the terminated old proc it logs "stream ended",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.15.2"
7
+ version = "0.15.3"
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.2
3
+ Version: 0.15.3
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
@@ -138,6 +138,7 @@ tests/test_sdes_idle_release.py
138
138
  tests/test_sdes_open_cleanup.py
139
139
  tests/test_sdes_serve_audio.py
140
140
  tests/test_sdes_serve_cmd.py
141
+ tests/test_sdes_serve_stderr_drain.py
141
142
  tests/test_sdes_sprop.py
142
143
  tests/test_sdes_talk.py
143
144
  tests/test_sdes_teardown_exit_log.py
@@ -0,0 +1,161 @@
1
+ """Tests for the SDES serve ffmpeg stderr drain.
2
+
3
+ The serve is spawned with ``stderr=PIPE`` but the bridge loop never reads it, so an
4
+ un-drained pipe can fill and stall ffmpeg and a non-zero exit loses its reason.
5
+ ``_start_serve_stderr_drain`` reads the pipe on a daemon thread and keeps a bounded
6
+ tail on ``proc._aidot_stderr_tail`` for the exit logger.
7
+ """
8
+
9
+ import asyncio
10
+ import io
11
+ import logging
12
+ import time
13
+
14
+ from aidot_cameras.camera.sdes import SdesSession
15
+ from aidot_cameras.camera.sdes_open import _start_serve_stderr_drain
16
+
17
+
18
+ class _FakeProc:
19
+ """Minimal stand-in exposing a readable ``stderr``, like ``subprocess.Popen``."""
20
+
21
+ def __init__(self, data: bytes) -> None:
22
+ self.stderr = io.BytesIO(data)
23
+
24
+
25
+ def _tail(proc) -> list:
26
+ """The captured stderr tail (attribute is set dynamically by the drainer)."""
27
+ return list(getattr(proc, "_aidot_stderr_tail"))
28
+
29
+
30
+ def _wait_until(pred, timeout: float = 2.0) -> None:
31
+ deadline = time.monotonic() + timeout
32
+ while not pred():
33
+ if time.monotonic() > deadline:
34
+ break
35
+ time.sleep(0.01)
36
+
37
+
38
+ def test_stderr_drain_captures_lines() -> None:
39
+ proc = _FakeProc(b"line one\nline two\nline three\n")
40
+ _start_serve_stderr_drain(proc)
41
+ _wait_until(lambda: _tail(proc)[-1:] == ["line three"])
42
+ assert _tail(proc) == ["line one", "line two", "line three"]
43
+
44
+
45
+ def test_stderr_drain_is_bounded_to_the_tail() -> None:
46
+ data = b"".join(f"l{i}\n".encode() for i in range(100))
47
+ proc = _FakeProc(data)
48
+ _start_serve_stderr_drain(proc, maxlines=10)
49
+ _wait_until(lambda: _tail(proc)[-1:] == ["l99"])
50
+ assert _tail(proc) == [f"l{i}" for i in range(90, 100)]
51
+
52
+
53
+ def test_stderr_none_is_safe() -> None:
54
+ class _NoStderr:
55
+ stderr = None
56
+
57
+ proc = _NoStderr()
58
+ _start_serve_stderr_drain(proc) # must not raise
59
+ assert _tail(proc) == []
60
+
61
+
62
+ class _TornDownProc:
63
+ """A serve proc whose stderr pipe the drainer already emptied.
64
+
65
+ ``stop()`` reads ``stderr`` after the process is gone; with the drain thread
66
+ running that read returns nothing, so the teardown diagnostic has to come
67
+ from the tail the drainer kept.
68
+ """
69
+
70
+ def __init__(self, tail: list) -> None:
71
+ self._alive = True
72
+ self.returncode = 0
73
+ self.stderr = self
74
+ self._aidot_stderr_tail = tail
75
+
76
+ def poll(self):
77
+ return None if self._alive else self.returncode
78
+
79
+ def terminate(self):
80
+ self._alive = False
81
+
82
+ def wait(self, timeout=None):
83
+ self._alive = False
84
+ return self.returncode
85
+
86
+ def kill(self):
87
+ self._alive = False
88
+
89
+ def read(self): # stderr.read() - the drainer got there first
90
+ return b""
91
+
92
+
93
+ class _FakeSock:
94
+ def close(self) -> None:
95
+ pass
96
+
97
+
98
+ class _FakeQ:
99
+ def put_nowait(self, _x) -> None:
100
+ pass
101
+
102
+
103
+ def _stopped_session(tail: list) -> None:
104
+ """Run the real ``SdesSession.stop()`` against a drained serve proc."""
105
+ session = SdesSession(
106
+ proc=_TornDownProc(tail),
107
+ sdp_path="/tmp/aidot_test_does_not_exist.sdp",
108
+ outgoing_q=_FakeQ(),
109
+ mqtt_fut=None,
110
+ audio_sock=_FakeSock(),
111
+ video_sock=_FakeSock(),
112
+ cmd_chan=[None],
113
+ talk_state=None,
114
+ )
115
+ asyncio.run(session.stop())
116
+
117
+
118
+ def test_teardown_logs_the_drained_tail_when_the_pipe_is_empty(caplog) -> None:
119
+ # Without the fallback this diagnostic is lost entirely: the drain thread
120
+ # consumed the pipe, so stop()'s stderr.read() has nothing left to log.
121
+ caplog.set_level(logging.DEBUG, logger="aidot_cameras.camera.sdes")
122
+ _stopped_session(["frame= 120 fps=20", "Error writing trailer: Broken pipe"])
123
+ warnings = [
124
+ r for r in caplog.records
125
+ if r.levelno == logging.WARNING and "ffmpeg SDES stderr" in r.getMessage()
126
+ ]
127
+ assert warnings, "a mid-stream ffmpeg error must still reach the log"
128
+ assert "Error writing trailer: Broken pipe" in warnings[0].getMessage()
129
+
130
+
131
+ def test_teardown_tail_still_demotes_the_expected_no_media_shape(caplog) -> None:
132
+ # The tail feeds the SAME classifier, so a camera that never sent media does
133
+ # not start warning once per retry just because the stderr route changed.
134
+ caplog.set_level(logging.DEBUG, logger="aidot_cameras.camera.sdes")
135
+ _stopped_session(["Output file is empty, nothing was encoded"])
136
+ assert not [
137
+ r for r in caplog.records
138
+ if r.levelno == logging.WARNING and "ffmpeg SDES stderr" in r.getMessage()
139
+ ]
140
+ assert [
141
+ r for r in caplog.records
142
+ if r.levelno == logging.DEBUG and "ffmpeg SDES stderr" in r.getMessage()
143
+ ]
144
+
145
+
146
+ def test_teardown_without_a_tail_logs_nothing(caplog) -> None:
147
+ caplog.set_level(logging.DEBUG, logger="aidot_cameras.camera.sdes")
148
+ _stopped_session([])
149
+ assert not [
150
+ r for r in caplog.records if "ffmpeg SDES stderr" in r.getMessage()
151
+ ]
152
+
153
+
154
+ def test_undecodable_bytes_do_not_crash_the_drain() -> None:
155
+ proc = _FakeProc(b"good\n\xff\xfe bad bytes\ndone\n")
156
+ _start_serve_stderr_drain(proc)
157
+ _wait_until(lambda: _tail(proc)[-1:] == ["done"])
158
+ tail = _tail(proc)
159
+ assert tail[0] == "good"
160
+ assert tail[-1] == "done"
161
+ assert len(tail) == 3