python-aidot-cameras 1.0.0rc63__tar.gz → 1.0.0rc64__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.0rc63/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc64}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/client.py +6 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sd_playback.py +35 -15
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sdes_open.py +54 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/webrtc_open.py +15 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/pyproject.toml +1 -1
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_dtls.py +39 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_entry.py +45 -1
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_media_hold.py +66 -1
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/LICENSE +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/MANIFEST.in +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/README.md +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_log_redaction.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/aac_track.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/agc.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/h264_sps.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/health.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/hls_ts.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/protocol.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/recording.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/rtp_h264.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/rtsp_publish.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sd_events.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sdes.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/ts_fanout.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/ts_tee.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/cloud_auth.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/lan_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/settings.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/setup.cfg +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_aac_track.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_abandoned_attempt_skips_the_doomed_serve.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_abandoned_wait_still_maps_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_answer_pt_kinds.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_answer_section_selection.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_arrival_timestamps.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_auth_error_routing.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_auto_alarm.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_auto_light_settings.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_avio_header_single_layout.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_avio_request.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_battery_cap_spans_sessions.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_battery_stale_offer_is_abandoned_early.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_battery_wake_settles_before_the_command.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ble_gateway_is_gone.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_camera_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_canary_log_names_the_camera.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_close_mid_ice_leaves_nothing_pending.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_cloud_count.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_cloud_error_log_names_the_exception.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_cloud_error_severity.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_cloud_plan.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_declared_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_detection_types.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_direct_publish_attaches_negotiated_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_direct_ts_keyframe_signal.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_direct_ts_serve.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_direct_ts_serve_only_for_http.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_drain_is_cancellable.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_open_abort_covers_every_exit.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_recorder_wiring.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_refusal_after_the_echo.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_resolution_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_serve_ice_wait.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_tap_states_no_reorder.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_ts_copy_recording.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_dtls_video_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_every_exception_is_exported.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_feature_probe_needs_a_live_session.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ffmpeg_rx_sdp_single_template.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_first_media_abandon_rules.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_futile_keepalive.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_gate_requires_a_decoded_frame.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_h264_sps.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_health_line.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_health_line_counts_pictures.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_hls_ts_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_hls_ts_wiring.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ice_credentials_match_the_app.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ipc_envelope_single_builder.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_login_accessors.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_retry_short_sessions.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_retry_silences_upstream_over_ceiling.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_retry_stops_on_close.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_lan_sweep_only_with_lights.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_late_repeats_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_live_session_carries_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_live_validate_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_live_video_canary.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_liveplay_echo_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_local_only_controls.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_log_hygiene.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_login_connect_deadline.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_login_reconnect_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_loop_break_ends_the_loop.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_media_counted_only_when_plaintext.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_media_path_is_reported.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mqtt_client_id_is_exact.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mqtt_thread_not_orphaned.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_mypy_new_findings.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_nack.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_no_key_material_in_logs.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_no_private_notes_on_the_public_surface.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_no_unbounded_hex_dumps.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_nominates_as_soon_as_the_answer_lands.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_off_subnet_candidate.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_on_frame_is_an_av_frame.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_open_errors_are_aidot_errors.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_peer_id_client_type.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_phase_returns_reach_the_caller.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_phase_state_is_complete.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_plaintext_mqtt_refused.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_public_signature_matches_impl.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_publish_target_readiness.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_quality_campaign_in_session.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_reboot_action.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_recent_recordings.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_region_allowlist.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_remb.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_remb_goes_out_the_relay_aware_sender.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_removed_keepalive_arguments_are_tolerated.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_resent_frames_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_review_lan_retry_and_graces.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_rtp_h264.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_rtsp_publish.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_rtsp_publish_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sctp_init_ack_tsn.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sctp_reassembly.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sctp_sack.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_card_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_event_decode.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_event_probe_payloads.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_controls.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_open.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_connection_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_echo_wait_is_not_dead_time.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_first_media_stall_report.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_ice_teardown.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_nack_bridge.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_offer_video_pin.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_open_yields_the_loop.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_pinned_codec_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_bound_survives_arrival_stamping.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_input_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_loop_releases_on_error.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_pt_respects_the_pin.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_session_mode_seq.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_session_stop_twice.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_settings_registry.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_signalling_is_attributable_to_a_camera.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_skip_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_snapshot_cancellation.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_speak.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sprop_unstable_public.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_srtp_rx_rekey.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_srtp_tx_key_note.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stable_terminal_id.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stall_nudge_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stall_reports_reach_the_artifact.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_status_and_trace_channels.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_stun_attr_single_builder.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_swallowed_exception_names.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_tap_to_mux_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_tmmbr.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_transport_failure_reporting.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ts_fanout.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_ts_tee.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_turn_target_shared.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_unbuildable_summary_repeats_at_debug.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_unreachable_nominee_is_abandoned_early.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_upstream_seam_adapter.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_upstream_secrets_redacted.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_validator_shares_one_token.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_vendored_aiortc_integrity.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_video_presence_needs_a_keyframe.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_video_pts_dts.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_video_ts_unwrap.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_wake_readiness_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/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.0rc64
|
|
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.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/client.py
RENAMED
|
@@ -2792,6 +2792,12 @@ class CameraMixin(
|
|
|
2792
2792
|
Taken from the SDES bridge thread (SESSION_MODE, control commands) as
|
|
2793
2793
|
well as the event loop (livePlayReq), so read-and-increment is one
|
|
2794
2794
|
locked step: a repeated seq is a SESSION_MODE the camera ignores.
|
|
2795
|
+
|
|
2796
|
+
Starting again at 100 in every new client is safe: the camera keeps
|
|
2797
|
+
the last accepted seq per session, not per camera. Back-to-back
|
|
2798
|
+
playbacks from fresh clients, each counting from 100 where the one
|
|
2799
|
+
before had already gone past it, had SD mode echoed every time on all
|
|
2800
|
+
three models (2026-10-27), as the app's own restarts rely on.
|
|
2795
2801
|
"""
|
|
2796
2802
|
with _DSEQ_LOCK:
|
|
2797
2803
|
d = self._live_dseq
|
|
@@ -274,16 +274,6 @@ class _CameraSdPlaybackMixin:
|
|
|
274
274
|
record=record,
|
|
275
275
|
sd_channel=sd_channel,
|
|
276
276
|
)
|
|
277
|
-
peer_id = self.generate_webrtc_peer_id(
|
|
278
|
-
live_type=PLAY_CMD_SDCARD,
|
|
279
|
-
# Field 4, a literal 0 in the app - not the StreamID, which is
|
|
280
|
-
# field 5 (``stream``).
|
|
281
|
-
stream_id=0,
|
|
282
|
-
sdes=bool(self.is_sdes_camera),
|
|
283
|
-
device_id=self.device_id,
|
|
284
|
-
client_class=PLAYBACK_CLIENT_CLASS,
|
|
285
|
-
stream=PLAYBACK_STREAM,
|
|
286
|
-
)
|
|
287
277
|
return await self.async_open_webrtc_stream(
|
|
288
278
|
on_frame,
|
|
289
279
|
timeout=timeout,
|
|
@@ -291,11 +281,30 @@ class _CameraSdPlaybackMixin:
|
|
|
291
281
|
max_seconds=max_seconds,
|
|
292
282
|
status_callback=status_callback,
|
|
293
283
|
rtsp_push_url=rtsp_push_url,
|
|
294
|
-
reuse_peer_id=
|
|
284
|
+
reuse_peer_id=playback_peer_id(self, sdes=bool(self.is_sdes_camera)),
|
|
295
285
|
_sd_playback=start,
|
|
296
286
|
)
|
|
297
287
|
|
|
298
288
|
|
|
289
|
+
def playback_peer_id(camera: Any, *, sdes: bool) -> str:
|
|
290
|
+
"""The peer id a playback open announces, for the SDES or DTLS transport.
|
|
291
|
+
|
|
292
|
+
The SDES-to-DTLS fallback builds a fresh one with ``sdes=False``: the
|
|
293
|
+
SDES id's transport digit is wrong for DTLS, and a live id (class 2,
|
|
294
|
+
PRE_LINK) would get audio alone from an H265-recording camera.
|
|
295
|
+
"""
|
|
296
|
+
return camera.generate_webrtc_peer_id(
|
|
297
|
+
live_type=PLAY_CMD_SDCARD,
|
|
298
|
+
# Field 4, a literal 0 in the app - not the StreamID, which is
|
|
299
|
+
# field 5 (``stream``).
|
|
300
|
+
stream_id=0,
|
|
301
|
+
sdes=sdes,
|
|
302
|
+
device_id=camera.device_id,
|
|
303
|
+
client_class=PLAYBACK_CLIENT_CLASS,
|
|
304
|
+
stream=PLAYBACK_STREAM,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
299
308
|
# --- DTLS (A000088): the recording's H265 is taken raw ----------------------
|
|
300
309
|
#
|
|
301
310
|
# aiortc has no H265, and the camera sends a recording it holds as H265 on a
|
|
@@ -459,13 +468,21 @@ class H265AccessUnits:
|
|
|
459
468
|
self, seq: int, ts: int, marker: bool, payload: bytes
|
|
460
469
|
) -> "list[tuple[bytes, int, bool]]":
|
|
461
470
|
out = []
|
|
462
|
-
|
|
471
|
+
missing = 0
|
|
472
|
+
if self._last_seq is not None:
|
|
473
|
+
missing = (seq - self._last_seq - 1) & 0xFFFF
|
|
474
|
+
gap = missing != 0
|
|
463
475
|
if self._ts is not None and ts != self._ts:
|
|
464
|
-
#
|
|
465
|
-
#
|
|
466
|
-
|
|
476
|
+
# A unit in hand that never saw its marker lost at least its last
|
|
477
|
+
# packet; one already emitted at its marker lost nothing. Whatever
|
|
478
|
+
# else went missing is the head of this new unit (its VPS/SPS, or
|
|
479
|
+
# the first slice of a keyframe), so it is not handed on either.
|
|
480
|
+
pending = bool(self._nals) or self._fu is not None
|
|
481
|
+
if gap and pending:
|
|
467
482
|
self._broken = True
|
|
468
483
|
out += self._emit()
|
|
484
|
+
if missing > (1 if pending else 0):
|
|
485
|
+
self._broken = True
|
|
469
486
|
elif gap:
|
|
470
487
|
self._broken = True
|
|
471
488
|
self._fu = None
|
|
@@ -478,6 +495,9 @@ class H265AccessUnits:
|
|
|
478
495
|
i = 2
|
|
479
496
|
while i + 2 <= len(payload):
|
|
480
497
|
n = struct.unpack_from("!H", payload, i)[0]
|
|
498
|
+
if n == 0 or i + 2 + n > len(payload):
|
|
499
|
+
self._broken = True # truncated or corrupt
|
|
500
|
+
break
|
|
481
501
|
self._nals.append(bytes(payload[i + 2 : i + 2 + n]))
|
|
482
502
|
i += 2 + n
|
|
483
503
|
elif t == 49 and len(payload) >= 3: # fragmentation unit
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sdes_open.py
RENAMED
|
@@ -8,6 +8,7 @@ breaks the client<->sdes_open cycle). CameraMixin inherits this mixin, so
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
import collections
|
|
11
12
|
import functools
|
|
12
13
|
import hashlib as _hashlib_mod
|
|
13
14
|
import logging
|
|
@@ -1109,9 +1110,18 @@ def _session_mode_echoed(frame: bytes) -> "Optional[int]":
|
|
|
1109
1110
|
|
|
1110
1111
|
#: How many packets a playback open holds for its serve (see _new_media_hold).
|
|
1111
1112
|
#: The serve is up within a second; ~70 packets/s on an A001064 recording.
|
|
1113
|
+
#: The hold is released once the serve binds or 4.5 s after it is launched
|
|
1114
|
+
#: (the bind wait and its fallback), so the cap is several times what one
|
|
1115
|
+
#: open can hold; a drop past it is logged as a warning, since the served
|
|
1116
|
+
#: clip then has a hole after its opening.
|
|
1112
1117
|
_MEDIA_HOLD_MAX = 4000
|
|
1113
1118
|
|
|
1114
1119
|
|
|
1120
|
+
#: How many recent sequence numbers per stream a playback remembers, to drop
|
|
1121
|
+
#: a packet that arrives twice. A few seconds of video at 2560x1440.
|
|
1122
|
+
_DUPLICATE_WINDOW = 2048
|
|
1123
|
+
|
|
1124
|
+
|
|
1115
1125
|
class _MediaHold:
|
|
1116
1126
|
"""Packets for the serve, held in order until it is listening.
|
|
1117
1127
|
|
|
@@ -1119,6 +1129,14 @@ class _MediaHold:
|
|
|
1119
1129
|
loopback port; ``release`` by the open once the serve is bound. The first
|
|
1120
1130
|
``send`` (or ``flush``) after the release hands over everything held -
|
|
1121
1131
|
still on the bridge thread, so nothing can overtake it.
|
|
1132
|
+
|
|
1133
|
+
It also drops an RTP packet already forwarded (same port, SSRC and
|
|
1134
|
+
sequence number). On a host that reaches the camera by two routes every
|
|
1135
|
+
packet can arrive twice: measured on the reference box 2026-10-28, each
|
|
1136
|
+
playback audio packet came twice and video packets up to six times. ffmpeg
|
|
1137
|
+
discards a repeated video packet, but the G.711 is copied as data, so
|
|
1138
|
+
every repeat was written too and the clip's sound ran twice as long as its
|
|
1139
|
+
picture.
|
|
1122
1140
|
"""
|
|
1123
1141
|
|
|
1124
1142
|
def __init__(self, limit: int = 0) -> None:
|
|
@@ -1126,6 +1144,26 @@ class _MediaHold:
|
|
|
1126
1144
|
self._released = False
|
|
1127
1145
|
self._limit = limit or _MEDIA_HOLD_MAX
|
|
1128
1146
|
self.dropped = 0
|
|
1147
|
+
self.duplicates = 0
|
|
1148
|
+
self._seen: "dict" = {}
|
|
1149
|
+
|
|
1150
|
+
def _repeat(self, pkt: bytes, port: int) -> bool:
|
|
1151
|
+
"""Is ``pkt`` an RTP packet this hold has already passed on?"""
|
|
1152
|
+
if len(pkt) < 12 or pkt[0] >> 6 != 2 or 192 <= pkt[1] <= 223:
|
|
1153
|
+
return False # not RTP, or RTCP (payload types 192-223)
|
|
1154
|
+
ssrc = int.from_bytes(pkt[8:12], "big")
|
|
1155
|
+
seq = int.from_bytes(pkt[2:4], "big")
|
|
1156
|
+
seen = self._seen.get((port, ssrc))
|
|
1157
|
+
if seen is None:
|
|
1158
|
+
seen = self._seen[(port, ssrc)] = (set(), collections.deque())
|
|
1159
|
+
recent, order = seen
|
|
1160
|
+
if seq in recent:
|
|
1161
|
+
return True
|
|
1162
|
+
recent.add(seq)
|
|
1163
|
+
order.append(seq)
|
|
1164
|
+
if len(order) > _DUPLICATE_WINDOW:
|
|
1165
|
+
recent.discard(order.popleft())
|
|
1166
|
+
return False
|
|
1129
1167
|
|
|
1130
1168
|
def release(self) -> None:
|
|
1131
1169
|
self._released = True
|
|
@@ -1134,6 +1172,14 @@ class _MediaHold:
|
|
|
1134
1172
|
return self._released and self._held is not None
|
|
1135
1173
|
|
|
1136
1174
|
def send(self, sock, pkt: bytes, port: int, *, pace=None, log=None) -> None:
|
|
1175
|
+
if self._repeat(pkt, port):
|
|
1176
|
+
self.duplicates += 1
|
|
1177
|
+
if self.duplicates == 1 and log is not None:
|
|
1178
|
+
try:
|
|
1179
|
+
log("bridge: dropping RTP packets that arrive twice")
|
|
1180
|
+
except Exception:
|
|
1181
|
+
pass
|
|
1182
|
+
return
|
|
1137
1183
|
if self._held is not None:
|
|
1138
1184
|
if not self._released:
|
|
1139
1185
|
if len(self._held) < self._limit:
|
|
@@ -1149,6 +1195,14 @@ class _MediaHold:
|
|
|
1149
1195
|
first keyframe alone is ~130 packets and one burst of all of them can
|
|
1150
1196
|
overrun the serve's socket. Returns how many were sent."""
|
|
1151
1197
|
held, self._held = self._held or [], None
|
|
1198
|
+
if self.dropped:
|
|
1199
|
+
_LOGGER.warning(
|
|
1200
|
+
"SD playback: %d packet(s) over the %d-packet hold were"
|
|
1201
|
+
" dropped before the serve listened; the clip has a gap"
|
|
1202
|
+
" after its opening",
|
|
1203
|
+
self.dropped,
|
|
1204
|
+
self._limit,
|
|
1205
|
+
)
|
|
1152
1206
|
sent = 0
|
|
1153
1207
|
for i, (sock, pkt, port) in enumerate(held):
|
|
1154
1208
|
try:
|
|
@@ -2458,6 +2458,18 @@ async def _dtls_open_sdes_branch(self, ps: _DtlsOpenState) -> _PhaseReturn | Non
|
|
|
2458
2458
|
await asyncio.wait_for(ps.mqtt_fut, timeout=5.0)
|
|
2459
2459
|
except Exception:
|
|
2460
2460
|
pass # MQTT thread exit errors don't affect the retry
|
|
2461
|
+
# A playback keeps what it asked for across the fallback: its own
|
|
2462
|
+
# peer id rebuilt for DTLS (the live default is class 2 and
|
|
2463
|
+
# PRE_LINK, which an H265-recording camera sends audio alone),
|
|
2464
|
+
# and rtsp_push_url, so the DTLS open refuses what it cannot
|
|
2465
|
+
# serve instead of dropping it. A live open mints a fresh id.
|
|
2466
|
+
_fallback_peer_id = None
|
|
2467
|
+
_fallback_push_url = None
|
|
2468
|
+
if ps._sd_playback is not None:
|
|
2469
|
+
from .sd_playback import playback_peer_id
|
|
2470
|
+
|
|
2471
|
+
_fallback_peer_id = playback_peer_id(self, sdes=False)
|
|
2472
|
+
_fallback_push_url = ps.rtsp_push_url
|
|
2461
2473
|
# Call the UNGATED impl, not the public async_open_webrtc_stream:
|
|
2462
2474
|
# we are already holding the global open-gate permit (acquired in
|
|
2463
2475
|
# async_open_webrtc_stream), and the gate's semaphore is not
|
|
@@ -2472,8 +2484,11 @@ async def _dtls_open_sdes_branch(self, ps: _DtlsOpenState) -> _PhaseReturn | Non
|
|
|
2472
2484
|
stream_id=ps.stream_id,
|
|
2473
2485
|
timeout=ps.timeout,
|
|
2474
2486
|
output_path=ps.output_path,
|
|
2487
|
+
max_seconds=ps.max_seconds,
|
|
2475
2488
|
status_callback=ps.status_callback,
|
|
2476
2489
|
force_sdes=False,
|
|
2490
|
+
rtsp_push_url=_fallback_push_url,
|
|
2491
|
+
reuse_peer_id=_fallback_peer_id,
|
|
2477
2492
|
_skip_ice_config=True,
|
|
2478
2493
|
_ice_config=(
|
|
2479
2494
|
ps.ice_config_fut.result() if ps.ice_config_fut.done() else None
|
|
@@ -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.0rc64"
|
|
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.0rc64
|
|
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.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_dtls.py
RENAMED
|
@@ -415,3 +415,42 @@ def test_the_sd_mode_echo_has_one_parser():
|
|
|
415
415
|
== _session_mode_echoed(avio_header(1, 5377, 0, 12, 0) + body)
|
|
416
416
|
)
|
|
417
417
|
assert session_mode_of(b"\x00" * 8) is None
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def test_a_lost_head_drops_the_new_unit_not_the_one_already_emitted():
|
|
421
|
+
aus = _hevc_access_units(5)
|
|
422
|
+
units = H265AccessUnits()
|
|
423
|
+
got, seq = [], 300
|
|
424
|
+
for i, au in enumerate(aus):
|
|
425
|
+
pkts = _packetize(au, i * 6000, seq, mtu=200)
|
|
426
|
+
seq += len(pkts)
|
|
427
|
+
for j, p in enumerate(pkts):
|
|
428
|
+
if i == 1 and j == 0:
|
|
429
|
+
continue # unit 1 loses its first packet, after 0's marker
|
|
430
|
+
got += units.feed(*p)
|
|
431
|
+
assert units.dropped == 1
|
|
432
|
+
assert [ts for _, ts, _ in got] == [0, 12000, 18000, 24000]
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_a_gap_wider_than_the_lost_marker_drops_both_units():
|
|
436
|
+
aus = _hevc_access_units(5)
|
|
437
|
+
units = H265AccessUnits()
|
|
438
|
+
got, seq = [], 900
|
|
439
|
+
for i, au in enumerate(aus):
|
|
440
|
+
pkts = _packetize(au, i * 6000, seq, mtu=200)
|
|
441
|
+
seq += len(pkts)
|
|
442
|
+
for j, p in enumerate(pkts):
|
|
443
|
+
if (i == 0 and j == len(pkts) - 1) or (i == 1 and j == 0):
|
|
444
|
+
continue # 0's marker and 1's head, back to back
|
|
445
|
+
got += units.feed(*p)
|
|
446
|
+
assert units.dropped == 2
|
|
447
|
+
assert [ts for _, ts, _ in got] == [12000, 18000, 24000]
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def test_a_truncated_aggregation_packet_breaks_its_unit():
|
|
451
|
+
units = H265AccessUnits()
|
|
452
|
+
vps = bytes([32 << 1, 1, 0x0C])
|
|
453
|
+
ap = bytes([48 << 1, 1]) + struct.pack("!H", 3) + vps + struct.pack("!H", 1200)
|
|
454
|
+
ap += bytes([19 << 1, 1]) + b"\x00" * 298
|
|
455
|
+
assert units.feed(1, 0, True, ap) == []
|
|
456
|
+
assert units.dropped == 1
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/tests/test_sd_playback_entry.py
RENAMED
|
@@ -31,7 +31,7 @@ class _Task:
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
class _Camera(_CameraSdPlaybackMixin):
|
|
34
|
-
device_id = "
|
|
34
|
+
device_id = "0123456789ab"
|
|
35
35
|
generate_webrtc_peer_id = staticmethod(CameraMixin.generate_webrtc_peer_id)
|
|
36
36
|
|
|
37
37
|
def __init__(self, sdes=True, live=False, streaming=False):
|
|
@@ -110,3 +110,47 @@ def test_every_camera_client_has_it():
|
|
|
110
110
|
assert CameraMixin.async_open_sd_playback is (
|
|
111
111
|
_CameraSdPlaybackMixin.async_open_sd_playback
|
|
112
112
|
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_the_dtls_fallback_keeps_the_playback_peer_id_and_its_options():
|
|
116
|
+
# An SDES-flagged camera that answers only DTLS: the retry must still
|
|
117
|
+
# ask for the recording (class 0, SD card), not open a live view.
|
|
118
|
+
from aidot_cameras.camera.webrtc_open import _dtls_open_sdes_branch
|
|
119
|
+
|
|
120
|
+
class _Fut:
|
|
121
|
+
def done(self):
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
class _Ps:
|
|
125
|
+
use_sdes = True
|
|
126
|
+
terminal_error_fut = _Fut()
|
|
127
|
+
ice_config_fut = _Fut()
|
|
128
|
+
mqtt_fut = None
|
|
129
|
+
output_path = "clip.part.ts"
|
|
130
|
+
max_seconds = 70.0
|
|
131
|
+
rtsp_push_url = "rtsp://127.0.0.1:1/x"
|
|
132
|
+
_sd_playback = SdPlaybackStart(request=b"", record=REC)
|
|
133
|
+
|
|
134
|
+
def _status(self, _msg):
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def __getattr__(self, _name):
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
class _Cam(_Camera):
|
|
141
|
+
async def _open_sdes_stream(self, **_kw):
|
|
142
|
+
raise CameraMixin._SdesNoAnswerError()
|
|
143
|
+
|
|
144
|
+
async def _async_open_webrtc_stream_impl(self, on_frame, **kw):
|
|
145
|
+
self.opens.append((on_frame, kw))
|
|
146
|
+
return "dtls"
|
|
147
|
+
|
|
148
|
+
cam = _Cam()
|
|
149
|
+
got = asyncio.run(_dtls_open_sdes_branch(cam, _Ps()))
|
|
150
|
+
assert got.value == "dtls"
|
|
151
|
+
((_, kw),) = cam.opens
|
|
152
|
+
assert kw["force_sdes"] is False
|
|
153
|
+
assert PEER.match(kw["reuse_peer_id"]).groups()[:3] == ("0", "1", "0")
|
|
154
|
+
assert kw["max_seconds"] == 70.0
|
|
155
|
+
# Carried so the DTLS open refuses it rather than dropping it.
|
|
156
|
+
assert kw["rtsp_push_url"] == "rtsp://127.0.0.1:1/x"
|
|
@@ -68,7 +68,7 @@ def test_the_flush_is_paced_and_reported():
|
|
|
68
68
|
assert lines == ["bridge: released 40 held packet(s) to the serve"]
|
|
69
69
|
|
|
70
70
|
|
|
71
|
-
def test_the_hold_is_bounded():
|
|
71
|
+
def test_the_hold_is_bounded(caplog):
|
|
72
72
|
sent = []
|
|
73
73
|
hold = _MediaHold(limit=3)
|
|
74
74
|
for i in range(5):
|
|
@@ -77,6 +77,11 @@ def test_the_hold_is_bounded():
|
|
|
77
77
|
hold.release()
|
|
78
78
|
assert hold.flush() == 3
|
|
79
79
|
assert _MEDIA_HOLD_MAX >= 1000
|
|
80
|
+
# The hole it leaves is not a debug line only.
|
|
81
|
+
assert any(
|
|
82
|
+
r.levelname == "WARNING" and "2 packet(s)" in r.getMessage()
|
|
83
|
+
for r in caplog.records
|
|
84
|
+
)
|
|
80
85
|
|
|
81
86
|
|
|
82
87
|
def test_every_loopback_send_in_the_bridge_goes_through_the_hold():
|
|
@@ -138,3 +143,63 @@ def calls_to_serve_cmd():
|
|
|
138
143
|
return calls_to(
|
|
139
144
|
module_tree("aidot_cameras.camera.sdes_open"), "_build_sdes_serve_cmd"
|
|
140
145
|
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _rtp(seq, ssrc=0x1257, pt=8):
|
|
149
|
+
return (
|
|
150
|
+
bytes([0x80, pt])
|
|
151
|
+
+ seq.to_bytes(2, "big")
|
|
152
|
+
+ bytes(4)
|
|
153
|
+
+ ssrc.to_bytes(4, "big")
|
|
154
|
+
+ b"x"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_a_packet_that_arrives_twice_is_forwarded_once():
|
|
159
|
+
# The reference box, 2026-10-28: every audio packet came twice (two
|
|
160
|
+
# routes to the camera), and the copied G.711 ran twice as long as the
|
|
161
|
+
# picture.
|
|
162
|
+
sent, lines = [], []
|
|
163
|
+
hold = _MediaHold()
|
|
164
|
+
hold.release()
|
|
165
|
+
for seq in (1, 1, 2, 3, 2, 3, 4):
|
|
166
|
+
hold.send(_Sock(sent), _rtp(seq), 5000, log=lines.append)
|
|
167
|
+
assert [int.from_bytes(p[2:4], "big") for p, _ in sent] == [1, 2, 3, 4]
|
|
168
|
+
assert hold.duplicates == 3
|
|
169
|
+
assert lines.count("bridge: dropping RTP packets that arrive twice") == 1
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_only_the_same_stream_and_port_count_as_a_repeat():
|
|
173
|
+
sent = []
|
|
174
|
+
hold = _MediaHold()
|
|
175
|
+
hold.release()
|
|
176
|
+
hold.send(_Sock(sent), _rtp(7), 5000)
|
|
177
|
+
hold.send(_Sock(sent), _rtp(7, ssrc=0x1259, pt=97), 5000) # other SSRC
|
|
178
|
+
hold.send(_Sock(sent), _rtp(7), 5002) # other port
|
|
179
|
+
rtcp = bytes([0x80, 200]) + bytes(10)
|
|
180
|
+
hold.send(_Sock(sent), rtcp, 5000)
|
|
181
|
+
hold.send(_Sock(sent), rtcp, 5000) # RTCP is never deduplicated
|
|
182
|
+
assert len(sent) == 5 and hold.duplicates == 0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_a_repeat_while_held_is_dropped_too():
|
|
186
|
+
sent = []
|
|
187
|
+
hold = _MediaHold()
|
|
188
|
+
for seq in (10, 10, 11):
|
|
189
|
+
hold.send(_Sock(sent), _rtp(seq), 5000)
|
|
190
|
+
hold.release()
|
|
191
|
+
hold.flush()
|
|
192
|
+
assert len(sent) == 2 and hold.duplicates == 1
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_the_window_forgets_old_numbers_so_a_wrapped_sequence_plays():
|
|
196
|
+
from aidot_cameras.camera.sdes_open import _DUPLICATE_WINDOW
|
|
197
|
+
|
|
198
|
+
sent = []
|
|
199
|
+
hold = _MediaHold()
|
|
200
|
+
hold.release()
|
|
201
|
+
n = 65536 + 10 # past a full wrap of the 16-bit sequence number
|
|
202
|
+
for i in range(n):
|
|
203
|
+
hold.send(_Sock(sent), _rtp(i & 0xFFFF), 5000)
|
|
204
|
+
assert len(sent) == n and hold.duplicates == 0
|
|
205
|
+
assert _DUPLICATE_WINDOW < 65536 // 4
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_log_redaction.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/_upstream.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/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.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/aac_track.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/agc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/constants.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/controls.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/go2rtc.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/h264_sps.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/health.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/hls_ts.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/hwaccel.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/models.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/protocol.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/recording.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/rtp_h264.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sd_events.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sd_listing.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/sdes.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/ts_fanout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/ts_tee.py
RENAMED
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/camera/webrtc.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/cloud_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/credentials.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/device_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-1.0.0rc63 → python_aidot_cameras-1.0.0rc64}/aidot_cameras/lan_retry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|