python-aidot-cameras 1.0.0b29__tar.gz → 1.0.0b31__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.0b29/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0b31}/PKG-INFO +2 -1
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/README.md +1 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/__main__.py +10 -4
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/client.py +39 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/hwaccel.py +3 -3
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/protocol.py +24 -14
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/sdes.py +10 -1
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/sdes_open.py +251 -129
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/cloud_auth.py +45 -8
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/pyproject.toml +1 -1
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31/python_aidot_cameras.egg-info}/PKG-INFO +2 -1
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/python_aidot_cameras.egg-info/SOURCES.txt +2 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_env_seams.py +2 -1
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_hwaccel_probe.py +1 -1
- python_aidot_cameras-1.0.0b31/tests/test_media_path_is_reported.py +88 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_offer_bandwidth_line.py +5 -2
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_remb_goes_out_the_relay_aware_sender.py +18 -9
- python_aidot_cameras-1.0.0b31/tests/test_sdes_connection_mode.py +221 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_sprop.py +24 -5
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_tmmbr_bridge.py +16 -7
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_security_hardening.py +2 -2
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sprop_unstable_public.py +3 -3
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sprop_write_is_reported_truthfully.py +1 -1
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_validator_shares_one_token.py +59 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/LICENSE +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/MANIFEST.in +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/ble_gateway.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/sd_events.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/lan_retry.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/setup.cfg +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_answer_section_selection.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_avio_request.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_battery_detection.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_battery_skip_turn.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_bitrate_campaign_arms.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_ble_gateway.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_canary_log_names_the_camera.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_cloud_count.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_cloud_plan.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_direct_ts_serve.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_drain_is_cancellable.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_resolution_ack.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_serve_ice_wait.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_dtls_video_presence.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_feature_probe_needs_a_live_session.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_futile_keepalive.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_gate_requires_a_decoded_frame.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_idle_release_both_loops.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_late_repeats_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_live_session_carries_talk.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_live_validate_policy.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_live_video_canary.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_local_only_controls.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_login_connect_deadline.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_login_reconnect_backoff.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_media_counted_only_when_plaintext.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_mqtt_client_id_is_exact.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_mqtt_thread_not_orphaned.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_nack.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_no_key_material_in_logs.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_on_frame_is_an_av_frame.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_public_signature_matches_impl.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_quality_campaign_in_session.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_recent_recordings.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_remb.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_resent_frames_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sctp_init_ack_tsn.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sd_card_presence.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sd_event_decode.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sd_event_probe_payloads.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_first_media_stall_report.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_nack_bridge.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_offer_video_order.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_offer_video_pin.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_serve_pt_respects_the_pin.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_snapshot_cancellation.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_speak.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_srtp_rx_rekey.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_srtp_tx_key_note.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stall_reports_reach_the_artifact.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_talk.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_tap_to_mux_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_tmmbr.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_tutk_path_is_out_of_reach.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_video_pts_dts.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_video_ts_unwrap.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_viewer_presence.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/tests/test_wake_readiness_retry.py +0 -0
- {python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/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.0b31
|
|
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
|
|
@@ -415,6 +415,7 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
415
415
|
| `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
|
|
416
416
|
| `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
|
|
417
417
|
| `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~2-3 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
|
|
418
|
+
| `AIDOT_SDES_CONNECTION_MODE` | Which media path the SDES offer proposes: `auto` (default - every reachable candidate, and ICE priority prefers the LAN with the relay as last resort; measured on the full fleet, six of seven cameras stream direct and only the unit with no route to us rides the relay), `lan` (no relay at all - same lever as `AIDOT_SDES_SKIP_TURN_PREALLOC`, and battery cameras keep the relay regardless because a cloud-woken camera has no other path back), or `relay` (EXPERIMENTAL, and on the reference fleet it does not steer: even with `c=`/`m=` moved to the relay allocation and the WAN permission pre-installed, an A001064 and an A001513 both dialed our host address directly, learning it from our own ICE probes - forcing the relay for real would mean suppressing every direct-path outbound, which is unbuilt). Per-open `sdes_connection_mode` beats the env. Every session reports the path its media actually took as `media_stats().media_path` (`direct`/`relay`). | unset (`auto`) |
|
|
418
419
|
| `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. Ignored for battery cameras, where the fast path cannot win and its short grace truncates the cold start. | unset (off) |
|
|
419
420
|
| `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
420
421
|
| `AIDOT_SDES_NACK` | Ask the camera to resend video RTP packets that never arrived (RTCP Generic NACK). A camera losing packets on a weak link otherwise delivers truncated H.264 slices, which a browser's WebRTC decoder conceals but Media Source Extensions treats as fatal. Measured on an A001064 at ~1-2% loss: 98.4% of losses recovered against none at all without it. Costs nothing on a clean link, where no requests are generated. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
@@ -375,6 +375,7 @@ audio, idle release, the sprop cache path) are documented in
|
|
|
375
375
|
| `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
|
|
376
376
|
| `AIDOT_FAST_CONNECT` | Enable LAN-direct "fast connect" (STUN-only, skips several cloud signaling waits) when truthy. On-LAN only - off-subnet/strict-NAT viewers must leave it off. | unset (off) |
|
|
377
377
|
| `AIDOT_SDES_SKIP_TURN_PREALLOC` | Skip the SDES TURN relay pre-allocation (~2-3 s of cold-start latency) so signaling goes straight out with the host candidate. Faster on a LAN, at the cost of no relay fallback for a camera on a different segment / behind strict NAT. Experimental, opt-in (truthy = `1`/`true`/`yes`/`on`). | unset (off) |
|
|
378
|
+
| `AIDOT_SDES_CONNECTION_MODE` | Which media path the SDES offer proposes: `auto` (default - every reachable candidate, and ICE priority prefers the LAN with the relay as last resort; measured on the full fleet, six of seven cameras stream direct and only the unit with no route to us rides the relay), `lan` (no relay at all - same lever as `AIDOT_SDES_SKIP_TURN_PREALLOC`, and battery cameras keep the relay regardless because a cloud-woken camera has no other path back), or `relay` (EXPERIMENTAL, and on the reference fleet it does not steer: even with `c=`/`m=` moved to the relay allocation and the WAN permission pre-installed, an A001064 and an A001513 both dialed our host address directly, learning it from our own ICE probes - forcing the relay for real would mean suppressing every direct-path outbound, which is unbuilt). Per-open `sdes_connection_mode` beats the env. Every session reports the path its media actually took as `media_stats().media_path` (`direct`/`relay`). | unset (`auto`) |
|
|
378
379
|
| `AIDOT_SDES_ADAPTIVE` | Adaptive fast-with-fallback for the SDES keepalive loop: try the fast path first and fall back to the full relay path if a fast attempt delivers no media. A per-device cache skips the fast attempt on later views once it has failed. Truthy value enables. Ignored for battery cameras, where the fast path cannot win and its short grace truncates the cold start. | unset (off) |
|
|
379
380
|
| `AIDOT_SDES_FAST_LIVEPLAY` | Don't block on the `livePlayResp` wait for eligible SDES cameras (~4.5 s faster cold start). Role-reversal models (A001064 PTZ) always excluded for correctness. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
380
381
|
| `AIDOT_SDES_NACK` | Ask the camera to resend video RTP packets that never arrived (RTCP Generic NACK). A camera losing packets on a weak link otherwise delivers truncated H.264 slices, which a browser's WebRTC decoder conceals but Media Source Extensions treats as fatal. Measured on an A001064 at ~1-2% loss: 98.4% of losses recovered against none at all without it. Costs nothing on a clean link, where no requests are generated. **On by default**; set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
@@ -77,6 +77,7 @@ import aiohttp
|
|
|
77
77
|
|
|
78
78
|
from .client import AidotClient
|
|
79
79
|
from .cloud_auth import ( # noqa: F401 (re-exported for callers and tests)
|
|
80
|
+
CloudAuthUnavailable,
|
|
80
81
|
_install_token_cache,
|
|
81
82
|
_make_client,
|
|
82
83
|
_read_token_file,
|
|
@@ -86,8 +87,6 @@ from .const import CONF_DEVICE_LIST, CONF_ID, CONF_MODEL_ID, CONF_NAME
|
|
|
86
87
|
|
|
87
88
|
_LOGGER = logging.getLogger("aidot.go2rtc")
|
|
88
89
|
|
|
89
|
-
DEFAULT_COUNTRY = os.environ.get("AIDOT_COUNTRY", "US")
|
|
90
|
-
|
|
91
90
|
|
|
92
91
|
def _env_bool(name: str) -> bool | None:
|
|
93
92
|
"""Tri-state env flag: '1' -> True, '0' -> False, unset -> None (library default)."""
|
|
@@ -131,7 +130,11 @@ def _go2rtc_source(dev_id: str, is_sdes: bool) -> str:
|
|
|
131
130
|
async def cmd_list() -> int:
|
|
132
131
|
"""Print device id + transport for every camera, plus paste-ready go2rtc YAML."""
|
|
133
132
|
async with aiohttp.ClientSession() as session:
|
|
134
|
-
|
|
133
|
+
# CloudAuthUnavailable is the library-side signal; a CLI exits on it.
|
|
134
|
+
try:
|
|
135
|
+
client = await _make_client(session)
|
|
136
|
+
except CloudAuthUnavailable as exc:
|
|
137
|
+
sys.exit(str(exc))
|
|
135
138
|
cams = await _cameras(client)
|
|
136
139
|
if not cams:
|
|
137
140
|
print("No IPC cameras found on this account.")
|
|
@@ -158,7 +161,10 @@ async def cmd_list() -> int:
|
|
|
158
161
|
async def cmd_stream(dev_id: str, output_url: str) -> int:
|
|
159
162
|
"""Open one camera's keepalive and push/serve it to ``output_url``."""
|
|
160
163
|
async with aiohttp.ClientSession() as session:
|
|
161
|
-
|
|
164
|
+
try:
|
|
165
|
+
client = await _make_client(session)
|
|
166
|
+
except CloudAuthUnavailable as exc:
|
|
167
|
+
sys.exit(str(exc))
|
|
162
168
|
cams = await _cameras(client)
|
|
163
169
|
device = next((d for d in cams if d[CONF_ID] == dev_id), None)
|
|
164
170
|
if device is None:
|
{python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/client.py
RENAMED
|
@@ -3540,6 +3540,7 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
3540
3540
|
stream_idle_s: Optional[float] = None,
|
|
3541
3541
|
sdes_fast_liveplay: Optional[bool] = None,
|
|
3542
3542
|
sdes_skip_turn: Optional[bool] = None,
|
|
3543
|
+
sdes_connection_mode: Optional[str] = None,
|
|
3543
3544
|
sdes_adaptive: Optional[bool] = None,
|
|
3544
3545
|
sdes_audio_gain_db: Optional[float] = None,
|
|
3545
3546
|
) -> None:
|
|
@@ -3616,6 +3617,8 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
3616
3617
|
self._sdes_fast_liveplay_opt = sdes_fast_liveplay
|
|
3617
3618
|
if sdes_skip_turn is not None:
|
|
3618
3619
|
self._sdes_skip_turn_opt = sdes_skip_turn
|
|
3620
|
+
if sdes_connection_mode is not None:
|
|
3621
|
+
self._sdes_connection_mode_opt = sdes_connection_mode
|
|
3619
3622
|
if sdes_adaptive is not None:
|
|
3620
3623
|
self._sdes_adaptive_opt = sdes_adaptive
|
|
3621
3624
|
if self._stream_task is not None and not self._stream_task.done():
|
|
@@ -4701,6 +4704,33 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4701
4704
|
)
|
|
4702
4705
|
return False
|
|
4703
4706
|
|
|
4707
|
+
def _resolve_sdes_connection_mode(self) -> str:
|
|
4708
|
+
"""Which media path the session should offer: auto, lan, or relay.
|
|
4709
|
+
|
|
4710
|
+
``auto`` (default) offers every reachable candidate and lets ICE
|
|
4711
|
+
priority prefer the LAN, with the relay as last resort - the shipped
|
|
4712
|
+
behaviour, verified on the full fleet (six of seven direct, only the
|
|
4713
|
+
unreachable unit relayed). ``lan`` drops the relay entirely (routed
|
|
4714
|
+
through _resolve_sdes_skip_turn so the battery force-keep applies).
|
|
4715
|
+
``relay`` offers only the relay candidate and force-keeps the
|
|
4716
|
+
pre-allocation.
|
|
4717
|
+
|
|
4718
|
+
Per-open ``sdes_connection_mode`` wins; else AIDOT_SDES_CONNECTION_MODE;
|
|
4719
|
+
else auto. Anything unrecognised is auto - this is read while opening
|
|
4720
|
+
a stream, and a typo must not stop video.
|
|
4721
|
+
"""
|
|
4722
|
+
for raw in (getattr(self, "_sdes_connection_mode_opt", None),
|
|
4723
|
+
os.environ.get("AIDOT_SDES_CONNECTION_MODE")):
|
|
4724
|
+
if raw is None:
|
|
4725
|
+
continue
|
|
4726
|
+
mode = str(raw).strip().lower()
|
|
4727
|
+
if mode in ("auto", "lan", "relay"):
|
|
4728
|
+
return mode
|
|
4729
|
+
_LOGGER.warning(
|
|
4730
|
+
"camera %s: unknown sdes_connection_mode %r; using auto",
|
|
4731
|
+
getattr(self, "device_id", "?"), raw)
|
|
4732
|
+
return "auto"
|
|
4733
|
+
|
|
4704
4734
|
def _resolve_sdes_skip_turn(self) -> bool:
|
|
4705
4735
|
"""EXPERIMENTAL (opt-in, default off): skip the blocking SDES TURN relay
|
|
4706
4736
|
pre-allocation, for cameras reachable LAN-direct.
|
|
@@ -4730,6 +4760,15 @@ class CameraMixin(_CameraControlsMixin, _CameraSdMixin, _WebRTCOpenMixin, _SdesO
|
|
|
4730
4760
|
candidate on the HA segment."""
|
|
4731
4761
|
if getattr(self, "is_battery_camera", False):
|
|
4732
4762
|
return False
|
|
4763
|
+
# The connection mode composes here rather than in a parallel path, so
|
|
4764
|
+
# everything hanging off this resolver (instrumentation, the adaptive
|
|
4765
|
+
# interplay) sees one answer. relay force-keeps the pre-allocation -
|
|
4766
|
+
# forcing the relay while skipping its allocation would offer nothing.
|
|
4767
|
+
_mode = self._resolve_sdes_connection_mode()
|
|
4768
|
+
if _mode == "lan":
|
|
4769
|
+
return True
|
|
4770
|
+
if _mode == "relay":
|
|
4771
|
+
return False
|
|
4733
4772
|
opt = getattr(self, "_sdes_skip_turn_opt", None)
|
|
4734
4773
|
if opt is not None:
|
|
4735
4774
|
return bool(opt)
|
{python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/hwaccel.py
RENAMED
|
@@ -40,7 +40,7 @@ import threading
|
|
|
40
40
|
import time
|
|
41
41
|
from typing import List, Optional
|
|
42
42
|
|
|
43
|
-
from .protocol import
|
|
43
|
+
from .protocol import _sprop_dir # reuse the same per-user cache root
|
|
44
44
|
|
|
45
45
|
_LOGGER = logging.getLogger(__name__)
|
|
46
46
|
|
|
@@ -139,7 +139,7 @@ def _ffmpeg_identity() -> str:
|
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
def _cache_path() -> str:
|
|
142
|
-
return os.path.join(
|
|
142
|
+
return os.path.join(_sprop_dir(), "decoders.json")
|
|
143
143
|
|
|
144
144
|
|
|
145
145
|
def _load_cache() -> dict:
|
|
@@ -152,7 +152,7 @@ def _load_cache() -> dict:
|
|
|
152
152
|
|
|
153
153
|
def _save_cache(data: dict) -> None:
|
|
154
154
|
try:
|
|
155
|
-
os.makedirs(
|
|
155
|
+
os.makedirs(_sprop_dir(), exist_ok=True)
|
|
156
156
|
tmp = _cache_path() + ".tmp"
|
|
157
157
|
with open(tmp, "w") as fh:
|
|
158
158
|
json.dump(data, fh)
|
{python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/protocol.py
RENAMED
|
@@ -684,8 +684,18 @@ _XDG_CONFIG_HOME = os.environ.get("XDG_CONFIG_HOME") or os.path.join(
|
|
|
684
684
|
os.path.expanduser("~"), ".config")
|
|
685
685
|
|
|
686
686
|
|
|
687
|
-
|
|
688
|
-
|
|
687
|
+
def _sprop_dir() -> str:
|
|
688
|
+
"""The sprop cache directory - AIDOT_SPROP_DIR read at CALL time.
|
|
689
|
+
|
|
690
|
+
A consumer embedded in a larger process (the Home Assistant integration)
|
|
691
|
+
can only learn its persistent storage path at setup, after this module is
|
|
692
|
+
long imported; a module-level snapshot of the env var silently ignored
|
|
693
|
+
that setting and the cache landed back in the container-ephemeral home
|
|
694
|
+
directory, so every container recreation cost a startup-churn window.
|
|
695
|
+
Same lesson as cloud_auth's country default: bind config at call time.
|
|
696
|
+
"""
|
|
697
|
+
return os.environ.get("AIDOT_SPROP_DIR") or os.path.join(
|
|
698
|
+
_XDG_CONFIG_HOME, "aidot", "sprop")
|
|
689
699
|
|
|
690
700
|
|
|
691
701
|
def _extract_param_sets_from_rtp(pkt: bytes) -> dict:
|
|
@@ -740,12 +750,12 @@ def _build_sprop(sps: bytes, pps: bytes) -> str:
|
|
|
740
750
|
def _sprop_cache_path(devid: str) -> str:
|
|
741
751
|
# `devid` originates from the cloud/device and is interpolated into a
|
|
742
752
|
# filesystem path, so a value containing "/", "\\", ".." or an absolute path
|
|
743
|
-
# could escape
|
|
753
|
+
# could escape the sprop dir (arbitrary read/write). Reduce it to a safe,
|
|
744
754
|
# collision-resistant filename component.
|
|
745
755
|
safe = re.sub(r"[^A-Za-z0-9_-]", "_", devid or "")
|
|
746
756
|
if not safe or len(devid or "") > 128:
|
|
747
757
|
safe = hashlib.sha256((devid or "").encode("utf-8", "replace")).hexdigest()
|
|
748
|
-
return os.path.join(
|
|
758
|
+
return os.path.join(_sprop_dir(), f"{safe}.sprop")
|
|
749
759
|
|
|
750
760
|
|
|
751
761
|
def _sprop_unstable_path(devid: str) -> str:
|
|
@@ -834,14 +844,14 @@ def _save_sprop(devid: str, sprop: str) -> bool:
|
|
|
834
844
|
devid,
|
|
835
845
|
)
|
|
836
846
|
try:
|
|
837
|
-
os.makedirs(
|
|
847
|
+
os.makedirs(_sprop_dir(), exist_ok=True)
|
|
838
848
|
with open(_sprop_unstable_path(devid), "w") as fh:
|
|
839
849
|
fh.write("parameter sets changed between sessions\n")
|
|
840
850
|
os.remove(_sprop_cache_path(devid))
|
|
841
851
|
except OSError:
|
|
842
852
|
pass
|
|
843
853
|
return False
|
|
844
|
-
os.makedirs(
|
|
854
|
+
os.makedirs(_sprop_dir(), exist_ok=True)
|
|
845
855
|
tmp = _sprop_cache_path(devid) + ".tmp"
|
|
846
856
|
with open(tmp, "w") as fh:
|
|
847
857
|
fh.write(sprop)
|
|
@@ -851,7 +861,7 @@ def _save_sprop(devid: str, sprop: str) -> bool:
|
|
|
851
861
|
# Surface (don't swallow): if the cache dir isn't writable the whole
|
|
852
862
|
# sprop feature is silently inert. AIDOT_SPROP_DIR can redirect it.
|
|
853
863
|
_LOGGER.warning("sprop cache write failed (%s): %s - set AIDOT_SPROP_DIR "
|
|
854
|
-
"to a writable path",
|
|
864
|
+
"to a writable path", _sprop_dir(), exc)
|
|
855
865
|
return False
|
|
856
866
|
|
|
857
867
|
|
|
@@ -2950,6 +2960,12 @@ def decode_remb_bitrate(pkt: bytes) -> int:
|
|
|
2950
2960
|
return mantissa << exp
|
|
2951
2961
|
|
|
2952
2962
|
|
|
2963
|
+
#: RTPFB feedback message types (RFC 4585 s6.1, RFC 5104 s4.2).
|
|
2964
|
+
_RTPFB_PT = 205
|
|
2965
|
+
_FMT_NACK = 1
|
|
2966
|
+
_FMT_TMMBR = 3
|
|
2967
|
+
|
|
2968
|
+
|
|
2953
2969
|
def build_nack(sender_ssrc: int, media_ssrc: int, lost_seqs: "list") -> bytes:
|
|
2954
2970
|
"""An RTCP Generic NACK (RFC 4585 s6.2.1) asking for ``lost_seqs`` again.
|
|
2955
2971
|
|
|
@@ -2985,7 +3001,7 @@ def build_nack(sender_ssrc: int, media_ssrc: int, lost_seqs: "list") -> bytes:
|
|
|
2985
3001
|
i += 1
|
|
2986
3002
|
fci.append(struct.pack("!HH", pid, blp))
|
|
2987
3003
|
|
|
2988
|
-
pkt = (struct.pack("!BBH", 0x80 |
|
|
3004
|
+
pkt = (struct.pack("!BBH", 0x80 | _FMT_NACK, _RTPFB_PT, 0)
|
|
2989
3005
|
+ struct.pack("!II", sender_ssrc & 0xFFFFFFFF,
|
|
2990
3006
|
media_ssrc & 0xFFFFFFFF)
|
|
2991
3007
|
+ b"".join(fci))
|
|
@@ -2993,12 +3009,6 @@ def build_nack(sender_ssrc: int, media_ssrc: int, lost_seqs: "list") -> bytes:
|
|
|
2993
3009
|
return pkt[:2] + struct.pack("!H", len(pkt) // 4 - 1) + pkt[4:]
|
|
2994
3010
|
|
|
2995
3011
|
|
|
2996
|
-
#: RTPFB feedback message types (RFC 4585 s6.1, RFC 5104 s4.2).
|
|
2997
|
-
_RTPFB_PT = 205
|
|
2998
|
-
_FMT_NACK = 1
|
|
2999
|
-
_FMT_TMMBR = 3
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
3012
|
def build_tmmbr(sender_ssrc: int, media_ssrc: int, bitrate_bps: int,
|
|
3003
3013
|
overhead: int = 60) -> bytes:
|
|
3004
3014
|
"""A TMMBR (RFC 5104 s4.2.1) telling ``media_ssrc`` not to exceed a rate.
|
{python_aidot_cameras-1.0.0b29 → python_aidot_cameras-1.0.0b31}/aidot_cameras/camera/sdes.py
RENAMED
|
@@ -53,6 +53,7 @@ class SdesSession(AvioRequestMixin):
|
|
|
53
53
|
teardown_requested=None,
|
|
54
54
|
first_video_pt=None,
|
|
55
55
|
first_audio_pt=None,
|
|
56
|
+
media_path=None,
|
|
56
57
|
device_id=None,
|
|
57
58
|
responses=None,
|
|
58
59
|
abort_chan=None,
|
|
@@ -81,6 +82,12 @@ class SdesSession(AvioRequestMixin):
|
|
|
81
82
|
# sent (video 96/97/98, audio 0/8), or None before first media.
|
|
82
83
|
self._first_video_pt = first_video_pt if first_video_pt is not None else [None]
|
|
83
84
|
self._first_audio_pt = first_audio_pt if first_audio_pt is not None else [None]
|
|
85
|
+
# Shared with the bridge thread: [0] becomes "direct" or "relay" when
|
|
86
|
+
# the first media packet arrives, classified by its source address
|
|
87
|
+
# against the session's TURN servers. The connection mode is only a
|
|
88
|
+
# preference - the camera decides where to send from - and this is the
|
|
89
|
+
# receipt that says what actually happened.
|
|
90
|
+
self._media_path = media_path if media_path is not None else [None]
|
|
84
91
|
# Mutable one-element list shared with the bridge thread: [0] flips True
|
|
85
92
|
# the moment ANY locally-initiated ffmpeg-kill path fires (this stop(),
|
|
86
93
|
# the cold-open _reap(), the key-restart proc replace, or the DTLS-
|
|
@@ -229,7 +236,8 @@ class SdesSession(AvioRequestMixin):
|
|
|
229
236
|
the DTLS path allows. These counters are the supported substitute:
|
|
230
237
|
non-zero ``packets``/``bytes`` means real media reached the consumer.
|
|
231
238
|
|
|
232
|
-
Returns ``{packets, bytes, last_media_monotonic, video_pt, audio_pt
|
|
239
|
+
Returns ``{packets, bytes, last_media_monotonic, video_pt, audio_pt,
|
|
240
|
+
media_path}``;
|
|
233
241
|
the payload types are ``None`` until the camera's first packet of that
|
|
234
242
|
kind arrives.
|
|
235
243
|
"""
|
|
@@ -239,6 +247,7 @@ class SdesSession(AvioRequestMixin):
|
|
|
239
247
|
"last_media_monotonic": self._media_progress[0],
|
|
240
248
|
"video_pt": self._first_video_pt[0],
|
|
241
249
|
"audio_pt": self._first_audio_pt[0],
|
|
250
|
+
"media_path": self._media_path[0],
|
|
242
251
|
}
|
|
243
252
|
|
|
244
253
|
def _drained_stderr_tail(self) -> bytes:
|