python-aidot-cameras 1.0.0rc67__tar.gz → 1.0.0rc68__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-1.0.0rc67/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc68}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/protocol.py +62 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/sd_listing.py +141 -29
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/pyproject.toml +1 -1
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_avio_request.py +81 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_listing.py +121 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/LICENSE +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/MANIFEST.in +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/README.md +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_log_redaction.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/aac_track.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/agc.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/client.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/h264_sps.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/health.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/hls_ts.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/recording.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/rtp_h264.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/rtsp_publish.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/sd_events.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/sdes.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/sdes_open.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/ts_fanout.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/ts_tee.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/cloud_auth.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/lan_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/settings.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/setup.cfg +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_aac_track.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_abandoned_attempt_skips_the_doomed_serve.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_abandoned_wait_still_maps_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_answer_pt_kinds.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_answer_section_selection.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_arrival_timestamps.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_auth_error_routing.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_auto_alarm.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_auto_light_settings.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_avio_header_single_layout.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_battery_cap_spans_sessions.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_battery_stale_offer_is_abandoned_early.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_battery_wake_settles_before_the_command.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ble_gateway_is_gone.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_camera_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_canary_log_names_the_camera.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_close_mid_ice_leaves_nothing_pending.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_cloud_count.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_cloud_error_log_names_the_exception.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_cloud_error_severity.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_cloud_plan.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_declared_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_detection_types.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_direct_publish_attaches_negotiated_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_direct_ts_keyframe_signal.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_direct_ts_serve.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_direct_ts_serve_only_for_http.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_drain_is_cancellable.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_open_abort_covers_every_exit.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_recorder_wiring.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_refusal_after_the_echo.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_resolution_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_serve_ice_wait.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_tap_states_no_reorder.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_ts_copy_recording.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_dtls_video_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_every_exception_is_exported.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_feature_probe_needs_a_live_session.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ffmpeg_rx_sdp_single_template.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_first_media_abandon_rules.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_futile_keepalive.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_gate_requires_a_decoded_frame.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_h264_sps.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_health_line.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_health_line_counts_pictures.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_hls_ts_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_hls_ts_wiring.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ice_credentials_match_the_app.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ipc_envelope_single_builder.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_login_accessors.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_retry_short_sessions.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_retry_silences_upstream_over_ceiling.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_retry_stops_on_close.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_lan_sweep_only_with_lights.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_late_repeats_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_live_session_carries_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_live_validate_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_live_video_canary.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_liveplay_echo_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_local_only_controls.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_log_hygiene.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_login_connect_deadline.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_login_reconnect_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_loop_break_ends_the_loop.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_media_counted_only_when_plaintext.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_media_path_is_reported.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mqtt_client_id_is_exact.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mqtt_thread_not_orphaned.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_mypy_new_findings.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_nack.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_no_key_material_in_logs.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_no_private_notes_on_the_public_surface.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_no_unbounded_hex_dumps.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_nominates_as_soon_as_the_answer_lands.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_off_subnet_candidate.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_on_frame_is_an_av_frame.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_open_errors_are_aidot_errors.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_peer_id_client_type.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_phase_returns_reach_the_caller.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_phase_state_is_complete.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_plaintext_mqtt_refused.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_public_signature_matches_impl.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_publish_target_readiness.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_quality_campaign_in_session.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_reboot_action.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_recent_recordings.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_region_allowlist.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_remb.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_removed_keepalive_arguments_are_tolerated.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_resent_frames_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_review_lan_retry_and_graces.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_rtp_h264.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_rtsp_publish.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_rtsp_publish_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sctp_init_ack_tsn.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sctp_reassembly.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sctp_sack.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_card_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_event_decode.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_event_probe_payloads.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback_clock.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback_controls.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback_dtls.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback_entry.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback_media_hold.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sd_playback_open.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_connection_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_echo_wait_is_not_dead_time.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_first_media_stall_report.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_ice_teardown.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_nack_bridge.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_offer_video_pin.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_open_yields_the_loop.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_pinned_codec_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_bound_survives_arrival_stamping.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_input_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_loop_releases_on_error.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_pt_respects_the_pin.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_session_mode_seq.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_session_stop_twice.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_settings_registry.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_signalling_is_attributable_to_a_camera.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_skip_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_snapshot_cancellation.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_speak.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sprop_unstable_public.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_srtp_rx_rekey.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_srtp_tx_key_note.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stable_terminal_id.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stall_nudge_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stall_reports_reach_the_artifact.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_status_and_trace_channels.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_stun_attr_single_builder.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_swallowed_exception_names.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_tap_to_mux_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_tmmbr.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_transport_failure_reporting.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ts_fanout.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_ts_tee.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_turn_target_shared.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_unbuildable_summary_repeats_at_debug.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_unreachable_nominee_is_abandoned_early.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_upstream_seam_adapter.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_upstream_secrets_redacted.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_validator_shares_one_token.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_vendored_aiortc_integrity.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_video_presence_needs_a_keyframe.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_video_pts_dts.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_video_ts_unwrap.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_wake_readiness_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_webrtc_answer_race.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0rc68
|
|
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
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/protocol.py
RENAMED
|
@@ -3847,6 +3847,68 @@ class AvioRequestMixin:
|
|
|
3847
3847
|
return None
|
|
3848
3848
|
return await waiter.result(timeout)
|
|
3849
3849
|
|
|
3850
|
+
async def async_avio_request_all(
|
|
3851
|
+
self,
|
|
3852
|
+
cmd: int,
|
|
3853
|
+
payload: bytes = b"",
|
|
3854
|
+
*,
|
|
3855
|
+
response_cmd: int,
|
|
3856
|
+
timeout: float = AVIO_RESPONSE_TIMEOUT_S,
|
|
3857
|
+
more_timeout: float = AVIO_RESPONSE_TIMEOUT_S,
|
|
3858
|
+
max_replies: int = 64,
|
|
3859
|
+
budget: "Optional[float]" = None,
|
|
3860
|
+
done=None,
|
|
3861
|
+
) -> "list[AvioResponse]":
|
|
3862
|
+
"""Send ``cmd`` once and return every ``response_cmd`` reply it draws.
|
|
3863
|
+
|
|
3864
|
+
For a command the camera answers in several packets with no further
|
|
3865
|
+
request: the SD listing sends a 7-day window as one 255-record page
|
|
3866
|
+
after another, all unprompted. ``async_avio_request`` keeps the first
|
|
3867
|
+
and the rest arrive with nobody waiting, so a caller saw the oldest
|
|
3868
|
+
255 recordings and never today's.
|
|
3869
|
+
|
|
3870
|
+
Every waiter is registered before the send, for the same reason as the
|
|
3871
|
+
single request: the pages follow each other faster than a waiter
|
|
3872
|
+
registered after the first reply could catch the second. Collection
|
|
3873
|
+
stops at the first wait that times out (``timeout`` for the first
|
|
3874
|
+
reply, ``more_timeout`` after it), at ``max_replies``, once ``budget``
|
|
3875
|
+
seconds have passed since the send, or as soon as ``done(replies)`` is
|
|
3876
|
+
true. An empty list means nothing came back.
|
|
3877
|
+
|
|
3878
|
+
Replies are matched on ``response_cmd`` alone, so two of these for the
|
|
3879
|
+
same command at once on one session can take each other's replies.
|
|
3880
|
+
"""
|
|
3881
|
+
router = self._avio_responses
|
|
3882
|
+
waiters = [router.expect(response_cmd) for _ in range(max(1, max_replies))]
|
|
3883
|
+
replies: "list[AvioResponse]" = []
|
|
3884
|
+
try:
|
|
3885
|
+
try:
|
|
3886
|
+
sent = self._avio_cmd(cmd, payload)
|
|
3887
|
+
except Exception:
|
|
3888
|
+
sent = False
|
|
3889
|
+
if not sent:
|
|
3890
|
+
return replies
|
|
3891
|
+
loop = asyncio.get_running_loop()
|
|
3892
|
+
give_up = None if budget is None else loop.time() + budget
|
|
3893
|
+
for waiter in waiters:
|
|
3894
|
+
wait = more_timeout if replies else timeout
|
|
3895
|
+
if give_up is not None:
|
|
3896
|
+
wait = min(wait, give_up - loop.time())
|
|
3897
|
+
if wait <= 0:
|
|
3898
|
+
break
|
|
3899
|
+
resp = await waiter.result(wait)
|
|
3900
|
+
if resp is None:
|
|
3901
|
+
break
|
|
3902
|
+
replies.append(resp)
|
|
3903
|
+
if done is not None and done(replies):
|
|
3904
|
+
break
|
|
3905
|
+
finally:
|
|
3906
|
+
# The ones never reached are still registered; a stray later reply
|
|
3907
|
+
# must go to nobody rather than to a question long since answered.
|
|
3908
|
+
for waiter in waiters:
|
|
3909
|
+
waiter.cancel()
|
|
3910
|
+
return replies
|
|
3911
|
+
|
|
3850
3912
|
|
|
3851
3913
|
_SDP_SECRET_ATTRS = ("a=ice-pwd:", "a=psk:")
|
|
3852
3914
|
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/sd_listing.py
RENAMED
|
@@ -14,7 +14,7 @@ list rather than as a traceback.
|
|
|
14
14
|
|
|
15
15
|
import logging
|
|
16
16
|
import time
|
|
17
|
-
from typing import Any, Optional
|
|
17
|
+
from typing import Any, List, Optional
|
|
18
18
|
|
|
19
19
|
from .sd_events import (
|
|
20
20
|
HASLISTEVENT_REQ_CMD,
|
|
@@ -34,6 +34,94 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
34
34
|
#: requests cannot hold a session for more than a viewer would tolerate.
|
|
35
35
|
_SD_LIST_TIMEOUT_S = 8.0
|
|
36
36
|
|
|
37
|
+
#: The pages of one listing follow each other back to back; measured on a PTZ,
|
|
38
|
+
#: four pages of a 7-day window arrived together. A gap this long after a page
|
|
39
|
+
#: means no more are coming.
|
|
40
|
+
_SD_MORE_PAGES_TIMEOUT_S = 3.0
|
|
41
|
+
|
|
42
|
+
#: 255 records a page, so 64 pages is over 16,000 recordings - far more than a
|
|
43
|
+
#: card holds in the 7 days a caller asks about.
|
|
44
|
+
_SD_MAX_PAGES = 64
|
|
45
|
+
|
|
46
|
+
#: However the pages trickle in, a listing gives up collecting them after this
|
|
47
|
+
#: long and reports what it has as incomplete. Without it each page would
|
|
48
|
+
#: restart the gap timer and a slow camera could hold the session for minutes.
|
|
49
|
+
_SD_LIST_PAGES_BUDGET_S = 30.0
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class _ListPages:
|
|
53
|
+
"""The pages of one listing reply, each decoded once, as it arrives."""
|
|
54
|
+
|
|
55
|
+
def __init__(self) -> None:
|
|
56
|
+
self.pages: dict = {}
|
|
57
|
+
self.undecodable = 0
|
|
58
|
+
self._total: Optional[int] = None
|
|
59
|
+
self._seen = 0
|
|
60
|
+
|
|
61
|
+
def take(self, replies: List[Any]) -> None:
|
|
62
|
+
"""Decode whatever in ``replies`` has not been decoded yet."""
|
|
63
|
+
for reply in replies[self._seen :]:
|
|
64
|
+
self._add(reply)
|
|
65
|
+
self._seen = len(replies)
|
|
66
|
+
|
|
67
|
+
def done(self, replies: List[Any]) -> bool:
|
|
68
|
+
"""The ``done`` hook: stop collecting once the reply is whole."""
|
|
69
|
+
self.take(replies)
|
|
70
|
+
return self.covered()
|
|
71
|
+
|
|
72
|
+
def _add(self, reply: Any) -> None:
|
|
73
|
+
page = decode_list_event_response(
|
|
74
|
+
bytes(reply.payload or b""), command=LISTEVENT_RESP_CMD
|
|
75
|
+
)
|
|
76
|
+
if page is None:
|
|
77
|
+
# The decode is measured from a handful of live replies and a
|
|
78
|
+
# reply with a different shape is entirely possible. It is an
|
|
79
|
+
# empty list with a logged reason, never an exception - but not
|
|
80
|
+
# a COMPLETE empty list: something came back and could not be
|
|
81
|
+
# read, so there may well be recordings behind it.
|
|
82
|
+
self.undecodable += 1
|
|
83
|
+
_LOGGER.debug(
|
|
84
|
+
"SD list reply did not decode (%d bytes)",
|
|
85
|
+
len(bytes(reply.payload or b"")),
|
|
86
|
+
)
|
|
87
|
+
return
|
|
88
|
+
if self._total is None:
|
|
89
|
+
self._total = page.total
|
|
90
|
+
elif page.total != self._total:
|
|
91
|
+
# Every page of one reply declares the same page count. One that
|
|
92
|
+
# does not is a late page of some earlier listing; mixing it in
|
|
93
|
+
# would file another window's records under this one.
|
|
94
|
+
_LOGGER.debug(
|
|
95
|
+
"SD list page declares %d pages, this reply %d: not ours",
|
|
96
|
+
page.total,
|
|
97
|
+
self._total,
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
# A page the camera sent twice is one page.
|
|
101
|
+
self.pages.setdefault(page.page_index, page)
|
|
102
|
+
|
|
103
|
+
def covered(self) -> bool:
|
|
104
|
+
"""The end flag has arrived and no page before it is missing.
|
|
105
|
+
|
|
106
|
+
``total`` is the page count the camera declared. A camera that declares
|
|
107
|
+
none (0) is held to the pages up to its end-flag page instead.
|
|
108
|
+
"""
|
|
109
|
+
ends = [i for i, p in self.pages.items() if p.end_flag == 1]
|
|
110
|
+
if not ends:
|
|
111
|
+
return False
|
|
112
|
+
want = range(self._total) if self._total else range(max(ends) + 1)
|
|
113
|
+
return set(self.pages) >= set(want)
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def records(self) -> list:
|
|
117
|
+
return [e for i in sorted(self.pages) for e in self.pages[i].events]
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def complete(self) -> bool:
|
|
121
|
+
if self.undecodable:
|
|
122
|
+
return False
|
|
123
|
+
return not self.pages or self.covered()
|
|
124
|
+
|
|
37
125
|
|
|
38
126
|
def _session_is_live(session: Any) -> bool:
|
|
39
127
|
"""Is this session object one that could still carry a request?
|
|
@@ -88,6 +176,48 @@ class _CameraSdMixin:
|
|
|
88
176
|
)
|
|
89
177
|
return None
|
|
90
178
|
|
|
179
|
+
async def _sd_ask_pages(
|
|
180
|
+
self, session: Any, cmd: int, payload: bytes, response_cmd: int, timeout: float
|
|
181
|
+
) -> "tuple[_ListPages, bool]":
|
|
182
|
+
"""Every reply page to one request, and whether any reply came back.
|
|
183
|
+
|
|
184
|
+
A session without ``async_avio_request_all`` (an older or a stand-in
|
|
185
|
+
session) is asked the single-reply way, which is the first page only.
|
|
186
|
+
|
|
187
|
+
Not safe to run two at once on one session: replies carry no id this
|
|
188
|
+
layer can match on SDES, so concurrent listings could take each other's
|
|
189
|
+
pages. Callers serialise listings per camera.
|
|
190
|
+
"""
|
|
191
|
+
pages = _ListPages()
|
|
192
|
+
ask_all = getattr(session, "async_avio_request_all", None)
|
|
193
|
+
if ask_all is None:
|
|
194
|
+
reply = await self._sd_ask(session, cmd, payload, response_cmd, timeout)
|
|
195
|
+
replies = [] if reply is None else [reply]
|
|
196
|
+
else:
|
|
197
|
+
try:
|
|
198
|
+
replies = list(
|
|
199
|
+
await ask_all(
|
|
200
|
+
cmd,
|
|
201
|
+
payload,
|
|
202
|
+
response_cmd=response_cmd,
|
|
203
|
+
timeout=timeout,
|
|
204
|
+
more_timeout=_SD_MORE_PAGES_TIMEOUT_S,
|
|
205
|
+
max_replies=_SD_MAX_PAGES,
|
|
206
|
+
budget=_SD_LIST_PAGES_BUDGET_S,
|
|
207
|
+
done=pages.done,
|
|
208
|
+
)
|
|
209
|
+
)
|
|
210
|
+
except Exception as exc:
|
|
211
|
+
_LOGGER.debug(
|
|
212
|
+
"SD request %#x failed for %s: %s",
|
|
213
|
+
cmd,
|
|
214
|
+
getattr(self, "device_id", "?"),
|
|
215
|
+
exc,
|
|
216
|
+
)
|
|
217
|
+
replies = []
|
|
218
|
+
pages.take(replies)
|
|
219
|
+
return pages, bool(replies)
|
|
220
|
+
|
|
91
221
|
async def async_get_sd_recordings(
|
|
92
222
|
self,
|
|
93
223
|
*,
|
|
@@ -115,11 +245,13 @@ class _CameraSdMixin:
|
|
|
115
245
|
card-bearing A000088 that returns an EMPTY page while 0 returns the
|
|
116
246
|
records - measured 2026-08-11, same session, same window.
|
|
117
247
|
|
|
118
|
-
One request each for the list and the occupancy map. The reply is
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
248
|
+
One request each for the list and the occupancy map. The list reply is
|
|
249
|
+
paged, 255 records a page, oldest first, and the camera sends every page
|
|
250
|
+
unprompted: there is no continuation request and none is needed. All of
|
|
251
|
+
them are collected (measured 2026-11-01: a PTZ's 7-day window was four
|
|
252
|
+
pages, 824 records, and reading only the first stopped six days short
|
|
253
|
+
of today). ``complete=False`` when the end flag never arrived or a page
|
|
254
|
+
before it is missing.
|
|
123
255
|
"""
|
|
124
256
|
session = (
|
|
125
257
|
session if session is not None else getattr(self, "_stream_session", None)
|
|
@@ -134,34 +266,14 @@ class _CameraSdMixin:
|
|
|
134
266
|
end_ts = time.time()
|
|
135
267
|
start_ts = end_ts - days * 86400
|
|
136
268
|
|
|
137
|
-
|
|
138
|
-
complete = True
|
|
139
|
-
list_reply = await self._sd_ask(
|
|
269
|
+
pages, list_answered = await self._sd_ask_pages(
|
|
140
270
|
session,
|
|
141
271
|
LISTEVENT_REQ_CMD,
|
|
142
272
|
listevent_payload(start_ts, end_ts, channel=channel),
|
|
143
273
|
LISTEVENT_RESP_CMD,
|
|
144
274
|
timeout,
|
|
145
275
|
)
|
|
146
|
-
|
|
147
|
-
page = decode_list_event_response(
|
|
148
|
-
bytes(list_reply.payload or b""), command=LISTEVENT_RESP_CMD
|
|
149
|
-
)
|
|
150
|
-
if page is None:
|
|
151
|
-
# The decode is measured from a handful of live replies and a
|
|
152
|
-
# reply with a different shape is entirely possible. It is an
|
|
153
|
-
# empty list with a logged reason, never an exception - but not
|
|
154
|
-
# a COMPLETE empty list: something came back and could not be
|
|
155
|
-
# read, so there may well be recordings behind it.
|
|
156
|
-
complete = False
|
|
157
|
-
_LOGGER.debug(
|
|
158
|
-
"SD list reply for %s did not decode (%d bytes)",
|
|
159
|
-
getattr(self, "device_id", "?"),
|
|
160
|
-
len(bytes(list_reply.payload or b"")),
|
|
161
|
-
)
|
|
162
|
-
else:
|
|
163
|
-
records = page.events
|
|
164
|
-
complete = page.end_flag == 1
|
|
276
|
+
records, complete = pages.records, pages.complete
|
|
165
277
|
|
|
166
278
|
hours = None
|
|
167
279
|
map_reply = await self._sd_ask(
|
|
@@ -181,7 +293,7 @@ class _CameraSdMixin:
|
|
|
181
293
|
hours=hours,
|
|
182
294
|
# Either reply is enough to prove the camera is listening. Both
|
|
183
295
|
# silent is the case that must never read as an empty card.
|
|
184
|
-
answered=
|
|
296
|
+
answered=list_answered or map_reply is not None,
|
|
185
297
|
complete=complete,
|
|
186
298
|
start_ts=start_ts,
|
|
187
299
|
end_ts=end_ts,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0rc68"
|
|
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: 1.0.
|
|
3
|
+
Version: 1.0.0rc68
|
|
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
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/tests/test_avio_request.py
RENAMED
|
@@ -218,3 +218,84 @@ async def test_dispatching_a_frame_nobody_asked_for_is_harmless():
|
|
|
218
218
|
session = _dtls_session(SimpleNamespace(send=lambda data: None))
|
|
219
219
|
assert session.dispatch_avio_frame(_reply()) is False
|
|
220
220
|
assert session.dispatch_avio_frame(b"junk") is False
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
async def test_a_request_answered_in_several_replies_returns_them_all():
|
|
224
|
+
"""The SD listing sends its pages back to back with no further request.
|
|
225
|
+
|
|
226
|
+
The second reply follows the first immediately; a waiter registered only
|
|
227
|
+
after the first reply came back would miss it.
|
|
228
|
+
"""
|
|
229
|
+
session = None
|
|
230
|
+
|
|
231
|
+
def _send_and_answer(data):
|
|
232
|
+
for body in (b"\x00\x01", b"\x00\x02", b"\x00\x03"):
|
|
233
|
+
session.dispatch_avio_frame(_reply(payload=body))
|
|
234
|
+
|
|
235
|
+
session = _dtls_session(SimpleNamespace(send=_send_and_answer))
|
|
236
|
+
|
|
237
|
+
got = await session.async_avio_request_all(
|
|
238
|
+
SETSTREAMCTRL,
|
|
239
|
+
response_cmd=SETSTREAMCTRL_RESP,
|
|
240
|
+
timeout=2.0,
|
|
241
|
+
more_timeout=0.05,
|
|
242
|
+
)
|
|
243
|
+
assert [r.payload for r in got] == [b"\x00\x01", b"\x00\x02", b"\x00\x03"]
|
|
244
|
+
# Nothing left registered: a reply that turns up later belongs to nobody.
|
|
245
|
+
assert session.dispatch_avio_frame(_reply()) is False
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
async def test_collecting_replies_stops_when_the_caller_has_enough():
|
|
249
|
+
session = None
|
|
250
|
+
|
|
251
|
+
def _send_and_answer(data):
|
|
252
|
+
for body in (b"\x00\x01", b"\x00\x02", b"\x00\x03"):
|
|
253
|
+
session.dispatch_avio_frame(_reply(payload=body))
|
|
254
|
+
|
|
255
|
+
session = _dtls_session(SimpleNamespace(send=_send_and_answer))
|
|
256
|
+
|
|
257
|
+
got = await session.async_avio_request_all(
|
|
258
|
+
SETSTREAMCTRL,
|
|
259
|
+
response_cmd=SETSTREAMCTRL_RESP,
|
|
260
|
+
timeout=2.0,
|
|
261
|
+
done=lambda replies: len(replies) == 2,
|
|
262
|
+
)
|
|
263
|
+
assert [r.payload for r in got] == [b"\x00\x01", b"\x00\x02"]
|
|
264
|
+
assert session.dispatch_avio_frame(_reply()) is False
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
async def test_collecting_replies_to_a_command_that_could_not_be_sent():
|
|
268
|
+
session = _dtls_session(None) # DataChannel not open
|
|
269
|
+
|
|
270
|
+
loop = asyncio.get_running_loop()
|
|
271
|
+
started = loop.time()
|
|
272
|
+
got = await session.async_avio_request_all(
|
|
273
|
+
SETSTREAMCTRL, response_cmd=SETSTREAMCTRL_RESP, timeout=30.0
|
|
274
|
+
)
|
|
275
|
+
assert got == []
|
|
276
|
+
assert loop.time() - started < 1.0
|
|
277
|
+
assert session.dispatch_avio_frame(_reply()) is False
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
async def test_collecting_replies_gives_up_once_its_budget_is_spent():
|
|
281
|
+
"""Each reply restarts the gap timer; the budget bounds the whole wait."""
|
|
282
|
+
session = _dtls_session(SimpleNamespace(send=lambda data: None))
|
|
283
|
+
|
|
284
|
+
async def _trickle():
|
|
285
|
+
for _ in range(20):
|
|
286
|
+
await asyncio.sleep(0.03)
|
|
287
|
+
session.dispatch_avio_frame(_reply())
|
|
288
|
+
|
|
289
|
+
feeder = asyncio.create_task(_trickle())
|
|
290
|
+
loop = asyncio.get_running_loop()
|
|
291
|
+
started = loop.time()
|
|
292
|
+
got = await session.async_avio_request_all(
|
|
293
|
+
SETSTREAMCTRL,
|
|
294
|
+
response_cmd=SETSTREAMCTRL_RESP,
|
|
295
|
+
timeout=1.0,
|
|
296
|
+
more_timeout=1.0,
|
|
297
|
+
budget=0.2,
|
|
298
|
+
)
|
|
299
|
+
feeder.cancel()
|
|
300
|
+
assert 1 <= len(got) < 20
|
|
301
|
+
assert loop.time() - started < 0.5
|
|
@@ -243,3 +243,124 @@ async def test_a_session_that_raises_does_not_reach_the_caller():
|
|
|
243
243
|
|
|
244
244
|
out = await _Camera(_Angry({})).async_get_sd_recordings()
|
|
245
245
|
assert out is not None and out.records == []
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class _PagedSession(_Session):
|
|
249
|
+
"""A session that answers the list with every page, like a real camera."""
|
|
250
|
+
|
|
251
|
+
def __init__(self, pages, answers=None):
|
|
252
|
+
super().__init__(answers or {})
|
|
253
|
+
self._pages = pages
|
|
254
|
+
|
|
255
|
+
async def async_avio_request_all(
|
|
256
|
+
self, cmd, payload, *, response_cmd, max_replies, done, **_timeouts
|
|
257
|
+
):
|
|
258
|
+
self.asked.append((cmd, payload, response_cmd))
|
|
259
|
+
got = []
|
|
260
|
+
for body in self._pages[:max_replies]:
|
|
261
|
+
got.append(_Reply(response_cmd, body))
|
|
262
|
+
if done(got):
|
|
263
|
+
break
|
|
264
|
+
return got
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.asyncio
|
|
268
|
+
async def test_every_page_of_a_long_listing_is_read():
|
|
269
|
+
"""Measured 2026-11-01: four pages for one PTZ week, and only the first was read.
|
|
270
|
+
|
|
271
|
+
The first page holds the OLDEST records, so stopping there hid every
|
|
272
|
+
recording newer than the first 255.
|
|
273
|
+
"""
|
|
274
|
+
session = _PagedSession(
|
|
275
|
+
[
|
|
276
|
+
_page([_record(day=5)], total=3, index=0, end_flag=0),
|
|
277
|
+
_page([_record(day=6)], total=3, index=1, end_flag=0),
|
|
278
|
+
_page([_record(day=7)], total=3, index=2, end_flag=1),
|
|
279
|
+
]
|
|
280
|
+
)
|
|
281
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
282
|
+
assert [r.day for r in out.records] == [5, 6, 7]
|
|
283
|
+
assert out.complete is True
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@pytest.mark.asyncio
|
|
287
|
+
async def test_pages_that_arrive_out_of_order_are_put_back_in_order():
|
|
288
|
+
session = _PagedSession(
|
|
289
|
+
[
|
|
290
|
+
_page([_record(day=6)], total=2, index=1, end_flag=1),
|
|
291
|
+
_page([_record(day=5)], total=2, index=0, end_flag=0),
|
|
292
|
+
]
|
|
293
|
+
)
|
|
294
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
295
|
+
assert [r.day for r in out.records] == [5, 6]
|
|
296
|
+
assert out.complete is True
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@pytest.mark.asyncio
|
|
300
|
+
async def test_a_page_sent_twice_is_counted_once():
|
|
301
|
+
session = _PagedSession(
|
|
302
|
+
[
|
|
303
|
+
_page([_record(day=5)], total=2, index=0, end_flag=0),
|
|
304
|
+
_page([_record(day=5)], total=2, index=0, end_flag=0),
|
|
305
|
+
_page([_record(day=6)], total=2, index=1, end_flag=1),
|
|
306
|
+
]
|
|
307
|
+
)
|
|
308
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
309
|
+
assert [r.day for r in out.records] == [5, 6]
|
|
310
|
+
assert out.complete is True
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@pytest.mark.asyncio
|
|
314
|
+
async def test_a_missing_page_before_the_end_is_reported_incomplete():
|
|
315
|
+
session = _PagedSession(
|
|
316
|
+
[
|
|
317
|
+
_page([_record(day=5)], total=3, index=0, end_flag=0),
|
|
318
|
+
_page([_record(day=7)], total=3, index=2, end_flag=1),
|
|
319
|
+
]
|
|
320
|
+
)
|
|
321
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
322
|
+
assert [r.day for r in out.records] == [5, 7]
|
|
323
|
+
assert out.complete is False
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@pytest.mark.asyncio
|
|
327
|
+
async def test_pages_that_stop_before_the_end_are_reported_incomplete():
|
|
328
|
+
session = _PagedSession(
|
|
329
|
+
[
|
|
330
|
+
_page([_record(day=5)], total=3, index=0, end_flag=0),
|
|
331
|
+
_page([_record(day=6)], total=3, index=1, end_flag=0),
|
|
332
|
+
]
|
|
333
|
+
)
|
|
334
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
335
|
+
assert [r.day for r in out.records] == [5, 6]
|
|
336
|
+
assert out.complete is False
|
|
337
|
+
assert out.answered is True
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@pytest.mark.asyncio
|
|
341
|
+
async def test_a_late_page_of_another_listing_is_not_mixed_in():
|
|
342
|
+
"""Every page of one reply declares the same page count; a stray does not."""
|
|
343
|
+
session = _PagedSession(
|
|
344
|
+
[
|
|
345
|
+
_page([_record(day=5)], total=2, index=0, end_flag=0),
|
|
346
|
+
_page([_record(day=1)], total=4, index=1, end_flag=0),
|
|
347
|
+
_page([_record(day=6)], total=2, index=1, end_flag=1),
|
|
348
|
+
]
|
|
349
|
+
)
|
|
350
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
351
|
+
assert [r.day for r in out.records] == [5, 6]
|
|
352
|
+
assert out.complete is True
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@pytest.mark.asyncio
|
|
356
|
+
async def test_an_end_page_first_with_no_page_count_does_not_end_the_listing():
|
|
357
|
+
"""With no declared count, the pages before the end-flag page are still owed."""
|
|
358
|
+
session = _PagedSession(
|
|
359
|
+
[
|
|
360
|
+
_page([_record(day=6)], total=0, index=1, end_flag=1),
|
|
361
|
+
_page([_record(day=5)], total=0, index=0, end_flag=0),
|
|
362
|
+
]
|
|
363
|
+
)
|
|
364
|
+
out = await _Camera(session).async_get_sd_recordings()
|
|
365
|
+
assert [r.day for r in out.records] == [5, 6]
|
|
366
|
+
assert out.complete is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_log_redaction.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_upstream.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/_vendor/__init__.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
|
|
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-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/aac_track.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/agc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/client.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/constants.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/controls.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/go2rtc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/h264_sps.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/health.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/hls_ts.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc67 → python_aidot_cameras-1.0.0rc68}/aidot_cameras/camera/hwaccel.py
RENAMED
|
File without changes
|