python-aidot-cameras 0.15.4__tar.gz → 0.15.5__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 (175) hide show
  1. {python_aidot_cameras-0.15.4/python_aidot_cameras.egg-info → python_aidot_cameras-0.15.5}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/__main__.py +29 -5
  3. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/client.py +27 -7
  4. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/pyproject.toml +1 -1
  5. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  6. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
  7. python_aidot_cameras-0.15.5/tests/test_dtls_stdout_producer.py +77 -0
  8. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/LICENSE +0 -0
  9. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/MANIFEST.in +0 -0
  10. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/README.md +0 -0
  11. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/__init__.py +0 -0
  12. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_upstream.py +0 -0
  13. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/__init__.py +0 -0
  14. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  15. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  16. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  17. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  18. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  19. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  20. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  21. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  22. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  23. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  24. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  25. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  26. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  27. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  28. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  29. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  30. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  31. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  32. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  33. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  34. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  35. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  36. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  37. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  38. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  39. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  40. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  41. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  42. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  43. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  44. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  45. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  46. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/ble_gateway.py +0 -0
  47. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/__init__.py +0 -0
  48. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/constants.py +0 -0
  49. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/controls.py +0 -0
  50. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/go2rtc.py +0 -0
  51. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/hwaccel.py +0 -0
  52. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/lan_control.py +0 -0
  53. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/models.py +0 -0
  54. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/playback.py +0 -0
  55. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/protocol.py +0 -0
  56. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/sdes.py +0 -0
  57. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/sdes_open.py +0 -0
  58. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/tutk.py +0 -0
  59. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/webrtc.py +0 -0
  60. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/camera/webrtc_open.py +0 -0
  61. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/client.py +0 -0
  62. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/const.py +0 -0
  63. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/credentials.py +0 -0
  64. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/crypto.py +0 -0
  65. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/device_client.py +0 -0
  66. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/discover.py +0 -0
  67. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/exceptions.py +0 -0
  68. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/g711.py +0 -0
  69. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/aidot_cameras/py.typed +0 -0
  70. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  71. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  72. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/python_aidot_cameras.egg-info/requires.txt +0 -0
  73. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  74. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/setup.cfg +0 -0
  75. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_aioice_compat.py +0 -0
  76. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_alarm_event.py +0 -0
  77. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_answer_sdp_video_pt.py +0 -0
  78. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_audio_mux_offline.py +0 -0
  79. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_backoff.py +0 -0
  80. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_battery_detection.py +0 -0
  81. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_battery_evidence_corroboration.py +0 -0
  82. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_battery_skip_turn.py +0 -0
  83. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_ble_gateway.py +0 -0
  84. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_constants_imports_resolve.py +0 -0
  85. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_deferred_hardening.py +0 -0
  86. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_device_login_guard.py +0 -0
  87. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_device_user_info_cache.py +0 -0
  88. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dormant_camera_cleanup.py +0 -0
  89. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_mux_terminal_write.py +0 -0
  90. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_not_ready_burst.py +0 -0
  91. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_pinning.py +0 -0
  92. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_serve_decode_skip.py +0 -0
  93. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_serve_open_timeout.py +0 -0
  94. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_skip_signaling_wait.py +0 -0
  95. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_dtls_slow_probe.py +0 -0
  96. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_egress_guard.py +0 -0
  97. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_env_seams.py +0 -0
  98. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_go2rtc.py +0 -0
  99. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_go2rtc_cli.py +0 -0
  100. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_h264_decode_rate_limit.py +0 -0
  101. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_highport_nomination.py +0 -0
  102. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_hwaccel_probe.py +0 -0
  103. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_ice_config_cache.py +0 -0
  104. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_ice_peer_reflexive.py +0 -0
  105. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_idle_release_both_loops.py +0 -0
  106. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_keepalive_renew.py +0 -0
  107. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_key_restart_sdp.py +0 -0
  108. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_keyframe_prompter.py +0 -0
  109. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_lan_control.py +0 -0
  110. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_light_active_color_mode.py +0 -0
  111. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_live_stream_param.py +0 -0
  112. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_live_validate_policy.py +0 -0
  113. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_logging_caps.py +0 -0
  114. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_media_wait_floor.py +0 -0
  115. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_motion_poll.py +0 -0
  116. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_mqtt_credential_refetch.py +0 -0
  117. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_mqtt_credential_self_heal.py +0 -0
  118. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_mqtt_password_not_persisted.py +0 -0
  119. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_mqtt_session_reconnect.py +0 -0
  120. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_narrow_pc_ice.py +0 -0
  121. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_no_self_referential_go2rtc.py +0 -0
  122. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_no_undefined_names.py +0 -0
  123. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_offline_keepalive_pause.py +0 -0
  124. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_open_fail_logger.py +0 -0
  125. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_open_gate_delay.py +0 -0
  126. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_peerid_reuse.py +0 -0
  127. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_persistent_mqtt.py +0 -0
  128. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_persistent_mqtt_robustness.py +0 -0
  129. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_playback_tls.py +0 -0
  130. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_post_merge_hardening.py +0 -0
  131. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_resolution_persists.py +0 -0
  132. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_restart_cycle.py +0 -0
  133. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_retry_policy.py +0 -0
  134. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_scripts_importable.py +0 -0
  135. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_adaptive.py +0 -0
  136. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_bridge_break_gate.py +0 -0
  137. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_echo_wait_timeout.py +0 -0
  138. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_fast_liveplay.py +0 -0
  139. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  140. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_idle_release.py +0 -0
  141. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_open_cleanup.py +0 -0
  142. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_serve_audio.py +0 -0
  143. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_serve_cmd.py +0 -0
  144. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_serve_stderr_drain.py +0 -0
  145. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_sprop.py +0 -0
  146. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_talk.py +0 -0
  147. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_teardown_exit_log.py +0 -0
  148. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_terminal_ack.py +0 -0
  149. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_terminal_ack_latency.py +0 -0
  150. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdes_watchdog.py +0 -0
  151. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdp_narrowing_properties.py +0 -0
  152. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sdp_payload_type_narrowing.py +0 -0
  153. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_security_hardening.py +0 -0
  154. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_serializable_login_info.py +0 -0
  155. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_serve_relay.py +0 -0
  156. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_session_stats.py +0 -0
  157. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_shared_house_seam.py +0 -0
  158. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_speak.py +0 -0
  159. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_sprop_staleness.py +0 -0
  160. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_stream_cap.py +0 -0
  161. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_stream_drain_ownership.py +0 -0
  162. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_stream_idle.py +0 -0
  163. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_stream_slot_cap.py +0 -0
  164. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_stream_teardown.py +0 -0
  165. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_talk.py +0 -0
  166. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_terminal_ack.py +0 -0
  167. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_token_file_robustness.py +0 -0
  168. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_token_refresh.py +0 -0
  169. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_turn_create_permission.py +0 -0
  170. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_upstream_compat.py +0 -0
  171. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_viewer_check_throttle.py +0 -0
  172. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_viewer_presence.py +0 -0
  173. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_viewer_present_push_mode.py +0 -0
  174. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/tests/test_wait_or_event.py +0 -0
  175. {python_aidot_cameras-0.15.4 → python_aidot_cameras-0.15.5}/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.4
3
+ Version: 0.15.5
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
@@ -45,9 +45,18 @@ go2rtc.yaml example (SDES camera - native RTSP push, video + audio):
45
45
  streams:
46
46
  frontdoor: exec:aidot-go2rtc 8d2521ea... {output}
47
47
 
48
- For a DTLS camera (A000088) there is no RTSP-push path; pass an http serve URL
49
- (http://127.0.0.1:PORT/name.ts) and add it to go2rtc as an http-mpegts source -
50
- but this process must stay running to keep that serve bound.
48
+ A DTLS camera (A000088) has no RTSP-push path, but a stdout producer works the
49
+ same lazy way. Pass ``-`` as the output and this process writes MPEG-TS (video
50
+ plus AAC audio) to stdout, which go2rtc's exec: source reads directly:
51
+
52
+ streams:
53
+ driveway: exec:aidot-go2rtc 7c89a5c1... -
54
+
55
+ go2rtc then owns the lifecycle - spawn on the first viewer, kill when idle -
56
+ exactly as it does with ``{output}`` for an SDES camera. An
57
+ ``http://host:port/name.ts`` output still works too: the older HTTP-listen
58
+ serve, useful for a persistent consumer that is not go2rtc, but that process
59
+ must stay running to keep the serve bound.
51
60
  """
52
61
 
53
62
  from __future__ import annotations
@@ -225,7 +234,21 @@ async def cmd_stream(dev_id: str, output_url: str) -> int:
225
234
 
226
235
  dc = client.get_device_client(device)
227
236
 
228
- if not output_url.startswith("rtsp") and dc.is_sdes_camera:
237
+ if output_url == "-":
238
+ # Stdout producer: MPEG-TS on this process's stdout, for a go2rtc
239
+ # exec: source. SDES cameras already have {output} (RTSP push) and
240
+ # do not take this path, so say which one to use rather than
241
+ # producing an empty stream.
242
+ if dc.is_sdes_camera:
243
+ sys.exit(
244
+ f"{dev_id} is an SDES camera - '-' (stdout) is the DTLS "
245
+ "producer path. Use {output} (RTSP push) instead."
246
+ )
247
+ _LOGGER.info(
248
+ "DTLS stdout mode: writing MPEG-TS (video + AAC audio) to "
249
+ "stdout for a go2rtc exec: consumer."
250
+ )
251
+ elif not output_url.startswith("rtsp") and dc.is_sdes_camera:
229
252
  _LOGGER.warning(
230
253
  "SDES camera but output %r is not rtsp:// - the library will "
231
254
  "HTTP-serve (video only by default) instead of pushing audio+video. "
@@ -235,7 +258,8 @@ async def cmd_stream(dev_id: str, output_url: str) -> int:
235
258
  if output_url.startswith("rtsp") and not dc.is_sdes_camera:
236
259
  sys.exit(
237
260
  f"{dev_id} is a DTLS camera - the library has no RTSP-push path "
238
- "for it. Use an http://host:port/name.ts serve URL instead."
261
+ "for it. Use '-' (stdout, for a go2rtc exec: source) or an "
262
+ "http://host:port/name.ts serve URL instead."
239
263
  )
240
264
 
241
265
  await dc.async_login()
@@ -4552,16 +4552,36 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
4552
4552
  # AIDOT_DTLS_RETRY_GATE_S and added dead latency after healthy drops.)
4553
4553
 
4554
4554
  async def _spawn_dtls_serve_ffmpeg(self, serve_url: Optional[str], stdin_fd: int):
4555
- """Launch ffmpeg to serve the mux thread's MPEG-TS (read from ``stdin_fd``)
4556
- on an HTTP-listen socket go2rtc pulls. The stream is already cleanly
4557
- timestamped + AAC-muxed (with the gain/limiter applied) by PyAV, so
4558
- ffmpeg just copies and serves - re-encoding here dropped the live audio
4559
- (mpegts PMT-timing on the real-time pipe). Returns the process, or None."""
4555
+ """Launch ffmpeg to serve the mux thread's MPEG-TS (read from ``stdin_fd``).
4556
+
4557
+ The stream is already cleanly timestamped + AAC-muxed (with the
4558
+ gain/limiter applied) by PyAV, so ffmpeg just copies and serves -
4559
+ re-encoding here dropped the live audio (mpegts PMT-timing on the
4560
+ real-time pipe). Returns the process, or None.
4561
+
4562
+ Two destinations:
4563
+
4564
+ - ``serve_url == "-"`` -> STDOUT model: write MPEG-TS to this process's
4565
+ own stdout (``pipe:1``), so a parent that spawned us - go2rtc's
4566
+ ``exec:`` source - consumes the camera directly and owns the process
4567
+ lifecycle (spawn on first viewer, kill on idle). No listen port and
4568
+ no cold-start relay, which is the DTLS analogue of the SDES
4569
+ ``{output}`` RTSP push. Logging already goes to stderr, so stdout
4570
+ carries nothing but media.
4571
+ - anything else -> HTTP-listen model, unchanged: serve on a ``-listen``
4572
+ socket that go2rtc pulls.
4573
+ """
4560
4574
  if not serve_url:
4561
4575
  return None
4562
4576
  if _ffmpeg_path() is None:
4563
4577
  _LOGGER.error("DTLS serve: %s", _FFMPEG_MISSING_MSG)
4564
4578
  return None
4579
+ if serve_url == "-":
4580
+ out_args = ["-f", "mpegts", "pipe:1"]
4581
+ out_target = 1 # this process's real stdout
4582
+ else:
4583
+ out_args = ["-f", "mpegts", "-listen", "1", serve_url]
4584
+ out_target = asyncio.subprocess.DEVNULL
4565
4585
  cmd = [
4566
4586
  "ffmpeg", "-y", "-loglevel", "warning",
4567
4587
  # Suppress input-side buffering: the PyAV mux already writes
@@ -4572,13 +4592,13 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
4572
4592
  # bursty/choppy audio pattern. Matches the SDES serve's approach.
4573
4593
  "-fflags", "+nobuffer",
4574
4594
  "-i", "pipe:0",
4575
- "-c", "copy", "-f", "mpegts", "-listen", "1", serve_url,
4595
+ "-c", "copy", *out_args,
4576
4596
  ]
4577
4597
  try:
4578
4598
  return await asyncio.create_subprocess_exec(
4579
4599
  *cmd,
4580
4600
  stdin=stdin_fd,
4581
- stdout=asyncio.subprocess.DEVNULL,
4601
+ stdout=out_target,
4582
4602
  stderr=asyncio.subprocess.DEVNULL,
4583
4603
  )
4584
4604
  except FileNotFoundError:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.15.4"
7
+ version = "0.15.5"
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.4
3
+ Version: 0.15.5
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
@@ -90,6 +90,7 @@ tests/test_dtls_serve_decode_skip.py
90
90
  tests/test_dtls_serve_open_timeout.py
91
91
  tests/test_dtls_skip_signaling_wait.py
92
92
  tests/test_dtls_slow_probe.py
93
+ tests/test_dtls_stdout_producer.py
93
94
  tests/test_egress_guard.py
94
95
  tests/test_env_seams.py
95
96
  tests/test_go2rtc.py
@@ -0,0 +1,77 @@
1
+ """The DTLS serve can write MPEG-TS to stdout for a go2rtc ``exec:`` source.
2
+
3
+ SDES cameras get a lazily-managed producer for free: go2rtc spawns the process
4
+ per ``{output}``, the library RTSP-pushes into it, and go2rtc kills it when the
5
+ last viewer leaves. DTLS had no equivalent - it could only serve on an
6
+ ``-listen`` socket, which has to be bound before anyone asks for it and stays
7
+ bound afterwards.
8
+
9
+ ``-`` closes that gap: ffmpeg writes to this process's own stdout, so whoever
10
+ spawned us is the consumer and owns the lifecycle. Everything else about the
11
+ serve is untouched, which is what these tests pin - a regression here would
12
+ either break every existing HTTP-listen serve or silently send media to
13
+ /dev/null.
14
+ """
15
+ import asyncio
16
+ import types
17
+
18
+ import aidot_cameras.camera.client as client_mod
19
+ from aidot_cameras.camera.client import CameraMixin
20
+
21
+
22
+ def _spawn(monkeypatch, serve_url):
23
+ """Run the real _spawn_dtls_serve_ffmpeg, capturing what it would exec."""
24
+ seen = {}
25
+
26
+ async def _fake_exec(*cmd, stdin=None, stdout=None, stderr=None):
27
+ seen["cmd"] = list(cmd)
28
+ seen["stdout"] = stdout
29
+ seen["stdin"] = stdin
30
+ return object()
31
+
32
+ monkeypatch.setattr(client_mod, "_ffmpeg_path", lambda: "/usr/bin/ffmpeg")
33
+ monkeypatch.setattr(asyncio, "create_subprocess_exec", _fake_exec)
34
+ obj = types.SimpleNamespace()
35
+ fn = CameraMixin._spawn_dtls_serve_ffmpeg.__get__(obj)
36
+ proc = asyncio.run(fn(serve_url, 7))
37
+ return proc, seen
38
+
39
+
40
+ def test_stdout_mode_writes_mpegts_to_this_process_stdout(monkeypatch):
41
+ proc, seen = _spawn(monkeypatch, "-")
42
+ assert proc is not None
43
+ assert seen["cmd"][-3:] == ["-f", "mpegts", "pipe:1"], seen["cmd"]
44
+ # fd 1, not DEVNULL: the media has to reach the parent that spawned us.
45
+ assert seen["stdout"] == 1, (
46
+ "stdout mode must hand ffmpeg this process's real stdout - anything "
47
+ "else sends the camera to nowhere while looking healthy"
48
+ )
49
+ assert "-listen" not in seen["cmd"]
50
+
51
+
52
+ def test_http_listen_mode_is_unchanged(monkeypatch):
53
+ url = "http://127.0.0.1:18981/cam.ts"
54
+ proc, seen = _spawn(monkeypatch, url)
55
+ assert proc is not None
56
+ assert seen["cmd"][-4:] == ["-f", "mpegts", "-listen", "1", url][-4:]
57
+ assert seen["cmd"][-1] == url
58
+ assert "-listen" in seen["cmd"]
59
+ assert seen["stdout"] == asyncio.subprocess.DEVNULL, (
60
+ "the listen serve must keep stdout discarded; writing media to our own "
61
+ "stdout there would corrupt whatever the parent reads"
62
+ )
63
+
64
+
65
+ def test_both_modes_still_copy_from_the_mux_pipe(monkeypatch):
66
+ for url in ("-", "http://127.0.0.1:1/x.ts"):
67
+ _proc, seen = _spawn(monkeypatch, url)
68
+ cmd = seen["cmd"]
69
+ assert cmd[:2] == ["ffmpeg", "-y"]
70
+ assert "-i" in cmd and cmd[cmd.index("-i") + 1] == "pipe:0"
71
+ assert "-c" in cmd and cmd[cmd.index("-c") + 1] == "copy"
72
+ assert seen["stdin"] == 7
73
+
74
+
75
+ def test_no_serve_url_still_returns_none(monkeypatch):
76
+ proc, seen = _spawn(monkeypatch, "")
77
+ assert proc is None and "cmd" not in seen