python-aidot-cameras 0.15.2__tar.gz → 0.15.4__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.
- {python_aidot_cameras-0.15.2/python_aidot_cameras.egg-info → python_aidot_cameras-0.15.4}/PKG-INFO +1 -1
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/sdes.py +26 -2
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/sdes_open.py +57 -5
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/pyproject.toml +1 -1
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_ffmpeg_stderr_log.py +16 -2
- python_aidot_cameras-0.15.4/tests/test_sdes_serve_stderr_drain.py +190 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_teardown_exit_log.py +21 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/LICENSE +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/README.md +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/ble_gateway.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/client.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/protocol.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/setup.cfg +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_battery_skip_turn.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_ble_gateway.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_wake_readiness_retry.py +0 -0
{python_aidot_cameras-0.15.2/python_aidot_cameras.egg-info → python_aidot_cameras-0.15.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.4
|
|
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
|
|
@@ -38,7 +38,12 @@ class SdesSession:
|
|
|
38
38
|
teardown_requested=None,
|
|
39
39
|
first_video_pt=None,
|
|
40
40
|
first_audio_pt=None,
|
|
41
|
+
device_id=None,
|
|
41
42
|
) -> None:
|
|
43
|
+
# Which camera this session belongs to, for logging only. Optional so
|
|
44
|
+
# an existing caller that does not pass it still works; the logs then
|
|
45
|
+
# read "?" exactly as they did before.
|
|
46
|
+
self._device_id = device_id or "?"
|
|
42
47
|
self._proc = proc
|
|
43
48
|
self._sdp_path = sdp_path
|
|
44
49
|
self._outgoing_q = outgoing_q
|
|
@@ -177,6 +182,24 @@ class SdesSession:
|
|
|
177
182
|
"audio_pt": self._first_audio_pt[0],
|
|
178
183
|
}
|
|
179
184
|
|
|
185
|
+
def _drained_stderr_tail(self) -> bytes:
|
|
186
|
+
"""The stderr the drain thread already consumed, for the teardown log.
|
|
187
|
+
|
|
188
|
+
The serve's stderr is drained continuously on a daemon thread (see
|
|
189
|
+
``_start_serve_stderr_drain`` in sdes_open) so an un-drained pipe cannot
|
|
190
|
+
fill and stall ffmpeg. A consequence is that by teardown the pipe is at
|
|
191
|
+
EOF and stop()'s ``stderr.read()`` returns nothing, which would leave
|
|
192
|
+
``_log_ffmpeg_stderr`` with an empty buffer and silently drop the
|
|
193
|
+
teardown diagnostic - the mid-stream ffmpeg error it exists to surface
|
|
194
|
+
included. Fall back to the bounded tail the drainer kept. Returns
|
|
195
|
+
``b""`` when there is no drainer or it captured nothing, which
|
|
196
|
+
``_log_ffmpeg_stderr`` already treats as "nothing to say".
|
|
197
|
+
"""
|
|
198
|
+
tail = getattr(self._proc, "_aidot_stderr_tail", None)
|
|
199
|
+
if not tail:
|
|
200
|
+
return b""
|
|
201
|
+
return "\n".join(tail).encode("utf-8", "replace")
|
|
202
|
+
|
|
180
203
|
def _log_ffmpeg_stderr(self, stderr_bytes: bytes) -> None:
|
|
181
204
|
"""Log ffmpeg's captured stderr at a level that matches what happened.
|
|
182
205
|
|
|
@@ -197,7 +220,8 @@ class SdesSession:
|
|
|
197
220
|
or "Could not find codec parameters" in text
|
|
198
221
|
)
|
|
199
222
|
(_LOGGER.debug if expected_no_media else _LOGGER.warning)(
|
|
200
|
-
"ffmpeg SDES stderr:\n%s",
|
|
223
|
+
"camera %s: ffmpeg SDES stderr:\n%s",
|
|
224
|
+
getattr(self, "_device_id", "?"), text
|
|
201
225
|
)
|
|
202
226
|
|
|
203
227
|
@staticmethod
|
|
@@ -285,7 +309,7 @@ class SdesSession:
|
|
|
285
309
|
self._proc.stderr.close()
|
|
286
310
|
except Exception:
|
|
287
311
|
_LOGGER.debug("swallowed exception in %s", 'stop', exc_info=True)
|
|
288
|
-
self._log_ffmpeg_stderr(stderr_bytes)
|
|
312
|
+
self._log_ffmpeg_stderr(stderr_bytes or self._drained_stderr_tail())
|
|
289
313
|
import os
|
|
290
314
|
try:
|
|
291
315
|
os.unlink(self._sdp_path)
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/sdes_open.py
RENAMED
|
@@ -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()
|
|
@@ -2793,16 +2825,33 @@ class _SdesOpenMixin:
|
|
|
2793
2825
|
_br_level = _classify_ffmpeg_exit(
|
|
2794
2826
|
_br_rc, _br_teardown_requested
|
|
2795
2827
|
)
|
|
2828
|
+
# Name the camera. These lines used to carry no
|
|
2829
|
+
# device id at all, and this account runs several
|
|
2830
|
+
# SDES cameras at once, so a burst of exits could
|
|
2831
|
+
# not be attributed to one of them without extra
|
|
2832
|
+
# tooling - which sent a live investigation down
|
|
2833
|
+
# the wrong path once already.
|
|
2834
|
+
_br_dev = getattr(self, "device_id", "?")
|
|
2796
2835
|
_br_msg = (
|
|
2797
|
-
"SDES bridge: ffmpeg exited with
|
|
2798
|
-
" - stopped by teardown"
|
|
2836
|
+
"camera %s: SDES bridge: ffmpeg exited with"
|
|
2837
|
+
" code %d - stopped by teardown"
|
|
2799
2838
|
if _br_level < _log_br.WARNING else
|
|
2800
|
-
"SDES bridge: ffmpeg exited with
|
|
2801
|
-
" - stream ended"
|
|
2839
|
+
"camera %s: SDES bridge: ffmpeg exited with"
|
|
2840
|
+
" code %d - stream ended"
|
|
2802
2841
|
)
|
|
2803
2842
|
_log_br.getLogger(__name__).log(
|
|
2804
|
-
_br_level, _br_msg, _br_rc
|
|
2843
|
+
_br_level, _br_msg, _br_dev, _br_rc
|
|
2805
2844
|
)
|
|
2845
|
+
if _br_level >= _log_br.WARNING:
|
|
2846
|
+
_serr_tail = getattr(
|
|
2847
|
+
_br_proc, "_aidot_stderr_tail", None)
|
|
2848
|
+
if _serr_tail:
|
|
2849
|
+
_log_br.getLogger(__name__).warning(
|
|
2850
|
+
"camera %s: SDES serve ffmpeg stderr"
|
|
2851
|
+
" (last %d lines):\n%s", _br_dev,
|
|
2852
|
+
len(_serr_tail),
|
|
2853
|
+
"\n".join(_serr_tail),
|
|
2854
|
+
)
|
|
2806
2855
|
if _bridge_should_break(
|
|
2807
2856
|
_br_rc, _br_teardown_requested
|
|
2808
2857
|
):
|
|
@@ -4462,6 +4511,7 @@ class _SdesOpenMixin:
|
|
|
4462
4511
|
stdout=subprocess.DEVNULL,
|
|
4463
4512
|
stderr=subprocess.PIPE,
|
|
4464
4513
|
)
|
|
4514
|
+
_start_serve_stderr_drain(proc)
|
|
4465
4515
|
_proc_holder[0] = proc
|
|
4466
4516
|
_cl(_reap, proc) # kill ffmpeg if the open is cancelled before hand-off
|
|
4467
4517
|
except FileNotFoundError:
|
|
@@ -4745,6 +4795,7 @@ class _SdesOpenMixin:
|
|
|
4745
4795
|
stdout=subprocess.DEVNULL,
|
|
4746
4796
|
stderr=subprocess.PIPE,
|
|
4747
4797
|
)
|
|
4798
|
+
_start_serve_stderr_drain(proc)
|
|
4748
4799
|
# Point the shared holder at the live proc immediately.
|
|
4749
4800
|
# The bridge thread polls _proc_holder[0]; if it still
|
|
4750
4801
|
# sees the terminated old proc it logs "stream ended",
|
|
@@ -4866,4 +4917,5 @@ class _SdesOpenMixin:
|
|
|
4866
4917
|
teardown_requested=_teardown_holder,
|
|
4867
4918
|
first_video_pt=_first_video_pt,
|
|
4868
4919
|
first_audio_pt=_first_audio_pt,
|
|
4920
|
+
device_id=getattr(self, "device_id", None),
|
|
4869
4921
|
)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "0.15.
|
|
7
|
+
version = "0.15.4"
|
|
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"
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4/python_aidot_cameras.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.4
|
|
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
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_ffmpeg_stderr_log.py
RENAMED
|
@@ -12,8 +12,10 @@ import types
|
|
|
12
12
|
from aidot_cameras.camera.sdes import SdesSession
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def _log_fn(last_media):
|
|
16
|
-
obj = types.SimpleNamespace(
|
|
15
|
+
def _log_fn(last_media, device_id="dev-abc"):
|
|
16
|
+
obj = types.SimpleNamespace(
|
|
17
|
+
last_media_monotonic=last_media, _device_id=device_id
|
|
18
|
+
)
|
|
17
19
|
return SdesSession._log_ffmpeg_stderr.__get__(obj)
|
|
18
20
|
|
|
19
21
|
|
|
@@ -31,6 +33,18 @@ def test_no_media_expected_shape_logs_debug(caplog):
|
|
|
31
33
|
assert recs and recs[0].levelno == logging.DEBUG, msg
|
|
32
34
|
|
|
33
35
|
|
|
36
|
+
def test_the_log_names_the_camera(caplog):
|
|
37
|
+
"""Several SDES cameras stream at once; an unattributed line is near-useless.
|
|
38
|
+
|
|
39
|
+
A burst of these lines with no device id could not be pinned to a camera
|
|
40
|
+
during a live investigation, which cost real time and one wrong conclusion.
|
|
41
|
+
"""
|
|
42
|
+
with caplog.at_level(logging.DEBUG, logger="aidot_cameras.camera.sdes"):
|
|
43
|
+
_log_fn(1.0, device_id="12b144cb12da")(b"Error writing trailer")
|
|
44
|
+
recs = _stderr_records(caplog)
|
|
45
|
+
assert recs and "12b144cb12da" in recs[0].getMessage()
|
|
46
|
+
|
|
47
|
+
|
|
34
48
|
def test_no_media_unexpected_error_still_warns(caplog):
|
|
35
49
|
# No media, but NOT the expected empty-output shape -> a real ffmpeg failure.
|
|
36
50
|
with caplog.at_level(logging.DEBUG, logger="aidot_cameras.camera.sdes"):
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
def test_every_piped_spawn_installs_the_drain() -> None:
|
|
63
|
+
"""A serve spawned with an un-drained ``stderr=PIPE`` is the outage itself.
|
|
64
|
+
|
|
65
|
+
There are two serve-spawn sites (cold open and key-restart), and the bug
|
|
66
|
+
shipped because a pipe was opened at one of them with nothing reading it.
|
|
67
|
+
A third site, or a drain call deleted from an existing one, reintroduces it
|
|
68
|
+
exactly - and no behavioural test can see the difference until ffmpeg has
|
|
69
|
+
written ~64KB, which is far past where a unit test looks. Pin the source.
|
|
70
|
+
"""
|
|
71
|
+
import inspect
|
|
72
|
+
|
|
73
|
+
from aidot_cameras.camera import sdes_open
|
|
74
|
+
|
|
75
|
+
src = inspect.getsource(sdes_open).splitlines()
|
|
76
|
+
spawns = [i for i, line in enumerate(src) if "stderr=subprocess.PIPE" in line]
|
|
77
|
+
assert spawns, (
|
|
78
|
+
"no piped spawn found in sdes_open - if the serve stopped using a pipe "
|
|
79
|
+
"this guard is obsolete, but do not delete it without checking why"
|
|
80
|
+
)
|
|
81
|
+
for line_no in spawns:
|
|
82
|
+
window = "\n".join(src[line_no:line_no + 12])
|
|
83
|
+
assert "_start_serve_stderr_drain(" in window, (
|
|
84
|
+
f"{sdes_open.__name__}:{line_no + 1} spawns with stderr=PIPE and "
|
|
85
|
+
"nothing drains it within the next few lines. An un-drained pipe "
|
|
86
|
+
"fills at ~64KB and blocks ffmpeg on its next write, which kills "
|
|
87
|
+
"the RTSP publisher and 404s every viewer"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class _TornDownProc:
|
|
92
|
+
"""A serve proc whose stderr pipe the drainer already emptied.
|
|
93
|
+
|
|
94
|
+
``stop()`` reads ``stderr`` after the process is gone; with the drain thread
|
|
95
|
+
running that read returns nothing, so the teardown diagnostic has to come
|
|
96
|
+
from the tail the drainer kept.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, tail: list) -> None:
|
|
100
|
+
self._alive = True
|
|
101
|
+
self.returncode = 0
|
|
102
|
+
self.stderr = self
|
|
103
|
+
self._aidot_stderr_tail = tail
|
|
104
|
+
|
|
105
|
+
def poll(self):
|
|
106
|
+
return None if self._alive else self.returncode
|
|
107
|
+
|
|
108
|
+
def terminate(self):
|
|
109
|
+
self._alive = False
|
|
110
|
+
|
|
111
|
+
def wait(self, timeout=None):
|
|
112
|
+
self._alive = False
|
|
113
|
+
return self.returncode
|
|
114
|
+
|
|
115
|
+
def kill(self):
|
|
116
|
+
self._alive = False
|
|
117
|
+
|
|
118
|
+
def read(self): # stderr.read() - the drainer got there first
|
|
119
|
+
return b""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class _FakeSock:
|
|
123
|
+
def close(self) -> None:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class _FakeQ:
|
|
128
|
+
def put_nowait(self, _x) -> None:
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _stopped_session(tail: list) -> None:
|
|
133
|
+
"""Run the real ``SdesSession.stop()`` against a drained serve proc."""
|
|
134
|
+
session = SdesSession(
|
|
135
|
+
proc=_TornDownProc(tail),
|
|
136
|
+
sdp_path="/tmp/aidot_test_does_not_exist.sdp",
|
|
137
|
+
outgoing_q=_FakeQ(),
|
|
138
|
+
mqtt_fut=None,
|
|
139
|
+
audio_sock=_FakeSock(),
|
|
140
|
+
video_sock=_FakeSock(),
|
|
141
|
+
cmd_chan=[None],
|
|
142
|
+
talk_state=None,
|
|
143
|
+
)
|
|
144
|
+
asyncio.run(session.stop())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def test_teardown_logs_the_drained_tail_when_the_pipe_is_empty(caplog) -> None:
|
|
148
|
+
# Without the fallback this diagnostic is lost entirely: the drain thread
|
|
149
|
+
# consumed the pipe, so stop()'s stderr.read() has nothing left to log.
|
|
150
|
+
caplog.set_level(logging.DEBUG, logger="aidot_cameras.camera.sdes")
|
|
151
|
+
_stopped_session(["frame= 120 fps=20", "Error writing trailer: Broken pipe"])
|
|
152
|
+
warnings = [
|
|
153
|
+
r for r in caplog.records
|
|
154
|
+
if r.levelno == logging.WARNING and "ffmpeg SDES stderr" in r.getMessage()
|
|
155
|
+
]
|
|
156
|
+
assert warnings, "a mid-stream ffmpeg error must still reach the log"
|
|
157
|
+
assert "Error writing trailer: Broken pipe" in warnings[0].getMessage()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_teardown_tail_still_demotes_the_expected_no_media_shape(caplog) -> None:
|
|
161
|
+
# The tail feeds the SAME classifier, so a camera that never sent media does
|
|
162
|
+
# not start warning once per retry just because the stderr route changed.
|
|
163
|
+
caplog.set_level(logging.DEBUG, logger="aidot_cameras.camera.sdes")
|
|
164
|
+
_stopped_session(["Output file is empty, nothing was encoded"])
|
|
165
|
+
assert not [
|
|
166
|
+
r for r in caplog.records
|
|
167
|
+
if r.levelno == logging.WARNING and "ffmpeg SDES stderr" in r.getMessage()
|
|
168
|
+
]
|
|
169
|
+
assert [
|
|
170
|
+
r for r in caplog.records
|
|
171
|
+
if r.levelno == logging.DEBUG and "ffmpeg SDES stderr" in r.getMessage()
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_teardown_without_a_tail_logs_nothing(caplog) -> None:
|
|
176
|
+
caplog.set_level(logging.DEBUG, logger="aidot_cameras.camera.sdes")
|
|
177
|
+
_stopped_session([])
|
|
178
|
+
assert not [
|
|
179
|
+
r for r in caplog.records if "ffmpeg SDES stderr" in r.getMessage()
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_undecodable_bytes_do_not_crash_the_drain() -> None:
|
|
184
|
+
proc = _FakeProc(b"good\n\xff\xfe bad bytes\ndone\n")
|
|
185
|
+
_start_serve_stderr_drain(proc)
|
|
186
|
+
_wait_until(lambda: _tail(proc)[-1:] == ["done"])
|
|
187
|
+
tail = _tail(proc)
|
|
188
|
+
assert tail[0] == "good"
|
|
189
|
+
assert tail[-1] == "done"
|
|
190
|
+
assert len(tail) == 3
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_teardown_exit_log.py
RENAMED
|
@@ -61,6 +61,27 @@ def test_reap_sets_teardown_flag_before_kill():
|
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def test_the_bridge_exit_log_names_the_camera():
|
|
65
|
+
"""The exit line is emitted from a closure the tests cannot call directly.
|
|
66
|
+
|
|
67
|
+
It is still worth pinning: this account streams several SDES cameras at
|
|
68
|
+
once, and while these lines carried no device id a burst of exits could not
|
|
69
|
+
be attributed to a camera at all. Assert the format string takes one, and
|
|
70
|
+
that the session is handed the id it needs for its own stderr log.
|
|
71
|
+
"""
|
|
72
|
+
src = inspect.getsource(sdes_open)
|
|
73
|
+
assert "camera %s: SDES bridge: ffmpeg exited" in src, (
|
|
74
|
+
"the bridge exit log lost its device id - a burst of these is "
|
|
75
|
+
"unattributable without it"
|
|
76
|
+
)
|
|
77
|
+
assert "camera %s: SDES serve ffmpeg stderr" in src
|
|
78
|
+
start = src.index("return SdesSession(")
|
|
79
|
+
assert "device_id=" in src[start:start + 800], (
|
|
80
|
+
"SdesSession is constructed without a device_id, so its own stderr "
|
|
81
|
+
"log cannot name the camera"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
64
85
|
class _FakeProc:
|
|
65
86
|
def __init__(self):
|
|
66
87
|
self._alive = True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/clock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/events.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/rtp.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/sdp.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/stats.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/_vendor/aiortc/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/constants.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/controls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/lan_control.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/playback.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/aidot_cameras/camera/webrtc_open.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_answer_sdp_video_pt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_constants_imports_resolve.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_deferred_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_device_login_guard.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_device_user_info_cache.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dormant_camera_cleanup.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_mux_terminal_write.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_not_ready_burst.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_serve_decode_skip.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_serve_open_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_dtls_skip_signaling_wait.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_h264_decode_rate_limit.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_ice_peer_reflexive.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_idle_release_both_loops.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_light_active_color_mode.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_live_validate_policy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_credential_refetch.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_credential_self_heal.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_mqtt_session_reconnect.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_no_self_referential_go2rtc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_no_undefined_names.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_offline_keepalive_pause.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_persistent_mqtt_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_post_merge_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_resolution_persists.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_scripts_importable.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_bridge_break_gate.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_echo_wait_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_fast_liveplay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdes_terminal_ack_latency.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdp_narrowing_properties.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_sdp_payload_type_narrowing.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_security_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_serializable_login_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_stream_drain_ownership.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_token_file_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_turn_create_permission.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_viewer_check_throttle.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_viewer_present_push_mode.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.15.2 → python_aidot_cameras-0.15.4}/tests/test_wake_readiness_retry.py
RENAMED
|
File without changes
|