python-aidot-cameras 1.0.0rc43__tar.gz → 1.0.0rc45__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.0rc43/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc45}/PKG-INFO +57 -50
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/README.md +56 -49
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/aac_track.py +6 -2
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/client.py +115 -183
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/h264_sps.py +0 -13
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/hls_ts.py +12 -8
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/protocol.py +95 -22
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/rtsp_publish.py +23 -40
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sdes_open.py +216 -387
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/ts_fanout.py +23 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/ts_tee.py +11 -3
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/webrtc_open.py +6 -18
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/lan_retry.py +89 -5
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/pyproject.toml +1 -1
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45/python_aidot_cameras.egg-info}/PKG-INFO +57 -50
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/SOURCES.txt +4 -7
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_aac_track.py +1 -1
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_abandoned_attempt_skips_the_doomed_serve.py +2 -27
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_detection.py +3 -39
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_ts_serve.py +4 -20
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_ts_serve_only_for_http.py +1 -1
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_tap_states_no_reorder.py +5 -14
- python_aidot_cameras-1.0.0rc45/tests/test_first_media_abandon_rules.py +116 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_hls_ts_wiring.py +16 -11
- python_aidot_cameras-1.0.0rc45/tests/test_idle_release_both_loops.py +112 -0
- python_aidot_cameras-1.0.0rc45/tests/test_lan_login_accessors.py +85 -0
- python_aidot_cameras-1.0.0rc45/tests/test_lan_retry_silences_upstream_over_ceiling.py +186 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_stream_param.py +1 -11
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_validate_policy.py +3 -3
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_remb_goes_out_the_relay_aware_sender.py +1 -1
- python_aidot_cameras-1.0.0rc45/tests/test_removed_keepalive_arguments_are_tolerated.py +30 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_rtsp_publish.py +13 -76
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_connection_mode.py +3 -5
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_first_media_stall_report.py +43 -0
- python_aidot_cameras-1.0.0rc45/tests/test_sdes_idle_release.py +351 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_offer_video_pin.py +17 -6
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_pinned_codec_retry.py +1 -1
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ts_fanout.py +50 -2
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ts_tee.py +137 -1
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_viewer_presence.py +14 -0
- python_aidot_cameras-1.0.0rc43/tests/test_battery_skip_turn.py +0 -63
- python_aidot_cameras-1.0.0rc43/tests/test_battery_wake_gate_app_parity.py +0 -90
- python_aidot_cameras-1.0.0rc43/tests/test_bitrate_campaign_arms.py +0 -126
- python_aidot_cameras-1.0.0rc43/tests/test_idle_release_both_loops.py +0 -49
- python_aidot_cameras-1.0.0rc43/tests/test_offer_bandwidth_line.py +0 -159
- python_aidot_cameras-1.0.0rc43/tests/test_sdes_adaptive.py +0 -134
- python_aidot_cameras-1.0.0rc43/tests/test_sdes_idle_release.py +0 -157
- python_aidot_cameras-1.0.0rc43/tests/test_sdes_offer_video_order.py +0 -285
- python_aidot_cameras-1.0.0rc43/tests/test_sdes_tmmbr_bridge.py +0 -236
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/LICENSE +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/MANIFEST.in +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_log_redaction.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_upstream.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/hwaccel.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/recording.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/rtp_h264.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sd_events.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sdes.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/cloud_auth.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/setup.cfg +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_abandoned_wait_still_maps_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_answer_pt_kinds.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_answer_sdp_video_pt.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_answer_section_selection.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_arrival_timestamps.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_audio_mux_offline.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_auth_error_routing.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_auto_alarm.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_auto_light_settings.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_request.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_response_parse.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_response_router.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_evidence_corroboration.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_stale_offer_is_abandoned_early.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_wake_settles_before_the_command.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ble_gateway_is_gone.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_bridge_state_sentinels.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_busy_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_camera_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_canary_log_names_the_camera.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_count.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_error_log_names_the_exception.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_error_severity.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_plan.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_constants_imports_resolve.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_control_verdicts.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_declared_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_detection_types.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_device_cmd_seq_match.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_publish_attaches_negotiated_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_ts_keyframe_signal.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dormant_camera_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_drain_is_cancellable.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_mux_terminal_write.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_recorder_wiring.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_resolution_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_serve_ice_wait.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_stdout_producer.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_ts_copy_recording.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_video_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_env_seams.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_feature_probe_needs_a_live_session.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_futile_keepalive.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_gate_requires_a_decoded_frame.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_h264_sps.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_hls_ts_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_hwaccel_probe.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ice_credentials_match_the_app.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ice_peer_reflexive.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_key_restart_sdp.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_lan_sweep_only_with_lights.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_late_repeats_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_session_carries_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_validate_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_video_canary.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_liveplay_echo_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_local_only_controls.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_log_hygiene.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_login_connect_deadline.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_login_reconnect_backoff.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_counted_only_when_plaintext.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_path_is_reported.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_socket_rcvbuf.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_wait_floor.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_client_id_is_exact.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_thread_not_orphaned.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_nack.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_key_material_in_logs.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_private_notes_on_the_public_surface.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_self_referential_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_unbounded_hex_dumps.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_nominates_as_soon_as_the_answer_lands.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_off_subnet_candidate.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_on_frame_is_an_av_frame.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_peer_id_class_env.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_peer_id_client_type.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_peerid_reuse.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_public_signature_matches_impl.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_publish_target_readiness.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_quality_campaign_in_session.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_reboot_action.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_recent_recordings.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_remb.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_resent_frames_are_dropped.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_resolution_persists.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_restart_cycle.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_rtp_h264.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_rtsp_publish_go2rtc.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_scripts_importable.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sctp_init_ack_tsn.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sctp_reassembly.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sctp_sack.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_card_presence.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_event_decode.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_event_probe_payloads.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_listing.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_playback.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_avio_dispatch.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_echo_wait_is_not_dead_time.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_ice_teardown.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_nack_bridge.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_sctp_abort.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_serve_audio.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_serve_stderr_drain.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_session_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_terminal_ack_latency.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdp_narrowing_properties.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdp_payload_type_narrowing.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_bound_survives_arrival_stamping.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_input_timeout.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_loss_tolerance.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_pt_respects_the_pin.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_session_stop_twice.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_shared_house_seam.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_signalling_is_attributable_to_a_camera.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_skip_guard.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_snapshot_cancellation.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_speak.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_capture_gate.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_staleness.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_unstable_public.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_srtp_rx_rekey.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_srtp_tx_key_note.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stable_terminal_id.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_nudge_policy.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_reports_reach_the_artifact.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_status_and_trace_channels.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_slot_cap.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_talk.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_tap_to_mux_end_to_end.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_tmmbr.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_transport_failure_reporting.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_turn_create_permission.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_tutk_path_is_out_of_reach.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_unbuildable_summary_repeats_at_debug.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_unreachable_nominee_is_abandoned_early.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_upstream_seam_adapter.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_upstream_secrets_redacted.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_validator_shares_one_token.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_vendored_aiortc_integrity.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_presence_needs_a_keyframe.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_profile_log.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_pts_dts.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_ts_unwrap.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_viewer_check_throttle.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_viewer_present_push_mode.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_wait_or_event.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_wake_gate_camera_evidence.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_wake_readiness_retry.py +0 -0
- {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/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.0rc45
|
|
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
|
|
@@ -100,11 +100,11 @@ not going to change in this release. Neither is a fault to report.
|
|
|
100
100
|
records to the cloud or to its own SD card is per-camera, not per-model, and the
|
|
101
101
|
device reports it as `IsSupportPlayback`. This library lists cloud recordings
|
|
102
102
|
(`async_get_cloud_recordings`) and resolves a playable HLS URL for one
|
|
103
|
-
(`async_get_event_video_media`);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
(`async_get_event_video_media`); for a camera storing to a card it lists the
|
|
104
|
+
on-device recordings (`async_get_sd_recordings`) but cannot play one back, so
|
|
105
|
+
such a camera records normally and nothing here can show you the video. On the
|
|
106
|
+
reference fleet that is four of seven cameras. Playback from the card is closed
|
|
107
|
+
as not feasible; see `docs/CAMERAS.md`, "On-device (SD card) recordings".
|
|
108
108
|
|
|
109
109
|
**The A001064 (PTZ) streams at roughly 2.2 Mbps, about 24% above the vendor
|
|
110
110
|
app.** Measured from a capture of the app on the same camera: it takes about
|
|
@@ -131,10 +131,10 @@ Two things stand between that and a saving you can switch on:
|
|
|
131
131
|
- **We cannot select it.** Narrowing the offer to 97 does not select it, it
|
|
132
132
|
removes the option - no video at all in 3 of 3 rounds (see
|
|
133
133
|
`AIDOT_SDES_VIDEO_PT` below). Reordering the preference list without
|
|
134
|
-
narrowing it
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
narrowing it was then tested directly: eight sessions in blocks of two
|
|
135
|
+
returned H265 in 2 of 4 with the reorder and 2 of 4 without, with receipts
|
|
136
|
+
confirming the reordered list reached the SDP. The camera chooses, roughly
|
|
137
|
+
a coin flip, and neither knob moves it; the reorder knob is gone.
|
|
138
138
|
- **H.265 may not suit the consumer.** Browser HEVC support over Media Source
|
|
139
139
|
Extensions is far narrower than H.264, and MSE playback is the reason the
|
|
140
140
|
bitrate matters here at all. Halving the bitrate is no help if the client
|
|
@@ -268,9 +268,9 @@ decides it:
|
|
|
268
268
|
|
|
269
269
|
| your camera | use | why |
|
|
270
270
|
| --- | --- | --- |
|
|
271
|
-
| **DTLS** (mains, e.g. A000088) | `-` |
|
|
271
|
+
| **DTLS** (mains, e.g. A000088) | `'{output}'` or `-` | `{output}` is published by the library itself (direct publish, on by default): the camera's 8 kHz A-law plus an in-process AAC track, no ffmpeg. `-` writes the muxed MPEG-TS with its 48 kHz AAC to stdout instead |
|
|
272
272
|
| **SDES** (the A001513 and A001064 families) | `'{output}'` | these stream by pushing RTSP; there is nothing to read from stdout |
|
|
273
|
-
| either, with `AIDOT_DIRECT_PUBLISH=
|
|
273
|
+
| either, with `AIDOT_DIRECT_PUBLISH=0` | `'{output}'` | the push goes through ffmpeg instead, which has to transcode audio down to 8 kHz G.711. A session that negotiates H.265 takes this path whatever the setting |
|
|
274
274
|
| either, if you want to pull | an `http://` URL | the process serves there and waits for a consumer to connect |
|
|
275
275
|
|
|
276
276
|
`'{output}'` is a placeholder that go2rtc substitutes with the stream's own push
|
|
@@ -301,22 +301,24 @@ go2rtc itself gets its environment: export them in the shell before starting it,
|
|
|
301
301
|
or set them in its service unit or container definition. If a stream fails
|
|
302
302
|
immediately with an authentication error, that is the thing to check first.
|
|
303
303
|
|
|
304
|
-
**ffmpeg must be on PATH.**
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
304
|
+
**ffmpeg must be on PATH.** pip cannot install a system binary, and the
|
|
305
|
+
library still needs one: recordings, snapshots, the `-` stdout form, `http://`
|
|
306
|
+
pull serves and any session that negotiates H.265 run through an ffmpeg
|
|
307
|
+
subprocess, and it is the DTLS fallback when a serve port cannot be bound. A
|
|
308
|
+
live `{output}` push does not use it (next paragraph), and a DTLS `-` serve
|
|
309
|
+
sends the muxed MPEG-TS straight to the consumer, because that hop was the only
|
|
310
|
+
component in the chain that lost timestamps.
|
|
311
|
+
|
|
312
|
+
**Live pushes skip ffmpeg: direct publish is on by default** (since 1.0.0rc44;
|
|
313
|
+
`AIDOT_DIRECT_PUBLISH=0` restores the ffmpeg path). An `rtsp://` / `{output}`
|
|
314
|
+
push is published into go2rtc by the library
|
|
313
315
|
itself - no ffmpeg process per stream - for **both** transports, so a DTLS
|
|
314
316
|
camera can use `{output}` too. It takes H.264 sessions only; a session that
|
|
315
317
|
negotiates H.265 keeps the ffmpeg serve. Audio goes out as the camera's own
|
|
316
318
|
G.711 A-law plus an in-process AAC track (see "Audio" under
|
|
317
319
|
[Getting an RTSP URL](#getting-an-rtsp-url)).
|
|
318
|
-
ffmpeg is still used for recordings and snapshots
|
|
319
|
-
|
|
320
|
+
ffmpeg is still used for recordings and snapshots; see
|
|
321
|
+
[`docs/DESIGN-direct-publish.md`](docs/DESIGN-direct-publish.md).
|
|
320
322
|
|
|
321
323
|
```bash
|
|
322
324
|
sudo apt install ffmpeg # Debian/Ubuntu
|
|
@@ -502,14 +504,16 @@ the last one leaves - nothing talks to your cameras while nobody is watching:
|
|
|
502
504
|
|
|
503
505
|
```yaml
|
|
504
506
|
streams:
|
|
505
|
-
driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera
|
|
507
|
+
driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera (stdout form; {output} works too)
|
|
506
508
|
frontdoor: exec:aidot-go2rtc 5e6f7a8b... {output} # SDES camera
|
|
507
509
|
```
|
|
508
510
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
511
|
+
`{output}` works for both transports: the library publishes the stream into
|
|
512
|
+
go2rtc itself (direct publish, on by default since 1.0.0rc44). A DTLS camera
|
|
513
|
+
can also take `-`, which hands go2rtc the muxed MPEG-TS on stdout; an SDES
|
|
514
|
+
camera cannot, and `--list` told you which is which. (`{output}` is a
|
|
515
|
+
placeholder go2rtc fills in with its own publish URL. It is not something you
|
|
516
|
+
type at a shell; `-` is the form for that.) go2rtc needs the same `AIDOT_*` environment
|
|
513
517
|
variables the CLI does, so set them wherever go2rtc starts.
|
|
514
518
|
|
|
515
519
|
**3. Use the URL.** go2rtc serves every stream you named at:
|
|
@@ -531,20 +535,20 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
|
|
|
531
535
|
- **Which camera is which.** `--list` reports the transport per device. DTLS is
|
|
532
536
|
the mains A000088; SDES is the A001064 / A001513 family. Passing the wrong
|
|
533
537
|
form is not silent - the CLI tells you which one to use and exits.
|
|
534
|
-
- **Audio.**
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
source is needed for
|
|
538
|
+
- **Audio.** A `{output}` push (direct publish, the default) carries the
|
|
539
|
+
camera's own **PCMA (G.711 A-law, 8 kHz)** first - WebRTC viewers play it
|
|
540
|
+
natively and nothing is transcoded - and, unless `AIDOT_PUBLISH_AAC=0`, an
|
|
541
|
+
**AAC-LC 48 kHz mono** track encoded in-process; an AAC-only consumer (for
|
|
542
|
+
example Home Assistant's HLS player) selects it with `?audio=aac`. The `-`
|
|
543
|
+
form carries AAC at 48 kHz mono, resampled from the camera's 8 kHz A-law,
|
|
544
|
+
because 8 kHz AAC plays silent in a lot of browsers and browsers on the MSE
|
|
545
|
+
path have no mapping for G.711 at all. Audio is on by default; see
|
|
546
|
+
`AIDOT_SDES_SERVE_AUDIO` to turn it off. No ffmpeg transcode
|
|
547
|
+
source is needed for the AAC - see
|
|
544
548
|
[`docs/CAMERAS.md`](https://github.com/cbrightly/python-aidot-cameras/blob/main/docs/CAMERAS.md#an-aac-transcoding-source-makes-every-player-crawl)
|
|
545
549
|
for why the old `ffmpeg:<stream>#audio=aac` advice made playback crawl.
|
|
546
|
-
The AAC track is
|
|
547
|
-
gets no AAC track either way.
|
|
550
|
+
The AAC track is on by default since 1.0.0rc44. A camera whose audio is
|
|
551
|
+
mu-law (PCMU) gets no AAC track either way.
|
|
548
552
|
- **A stream that starts with no video** and picks it up a few seconds later is
|
|
549
553
|
normal: the mux waits for a keyframe so the first GOP is decodable. A stream
|
|
550
554
|
that stays audio-only is a camera that never sent one - retry the view.
|
|
@@ -553,9 +557,11 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
|
|
|
553
557
|
spawns. That process has to stay running to keep the port bound, so it holds
|
|
554
558
|
a camera session whether or not anyone is watching. Prefer the `exec:` forms.
|
|
555
559
|
- **Pushing to your own RTSP server** rather than go2rtc: `rtsp_push_url=`
|
|
556
|
-
from Python, or an `rtsp://` output on the CLI. Both transports support it
|
|
557
|
-
|
|
558
|
-
|
|
560
|
+
from Python, or an `rtsp://` output on the CLI. Both transports support it,
|
|
561
|
+
and by default the library publishes it directly, with the audio described
|
|
562
|
+
above. Only on the `AIDOT_DIRECT_PUBLISH=0` ffmpeg path does a DTLS push
|
|
563
|
+
transcode audio to G.711 A-law - ffmpeg's RTSP muxer will not accept the AAC
|
|
564
|
+
that MPEG-TS carries.
|
|
559
565
|
`output_path=` records to a file. `Go2RtcClient.rtsp_url(name)` builds the
|
|
560
566
|
address above, and `ensure_stream(name, source)` registers a stream with a
|
|
561
567
|
running go2rtc if you would rather not edit YAML.
|
|
@@ -582,7 +588,7 @@ The library reads the following environment variables.
|
|
|
582
588
|
|
|
583
589
|
### Credentials
|
|
584
590
|
|
|
585
|
-
Used by the credential helper (`
|
|
591
|
+
Used by the credential helper (`aidot_cameras.credentials`); they take priority over any
|
|
586
592
|
stored credentials file. See [`aidot_cameras/credentials.py`](https://github.com/cbrightly/python-aidot-cameras/blob/main/aidot_cameras/credentials.py).
|
|
587
593
|
|
|
588
594
|
| Variable | Purpose | Default |
|
|
@@ -603,13 +609,13 @@ knob is in
|
|
|
603
609
|
| `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
|
|
604
610
|
| `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
|
|
605
611
|
| `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
|
|
606
|
-
| `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
|
|
607
|
-
| `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. |
|
|
612
|
+
| `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, though 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`) |
|
|
613
|
+
| `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. | `96` (H.264; `none` leaves the camera its choice) |
|
|
608
614
|
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
609
|
-
| `AIDOT_DIRECT_PUBLISH` | **
|
|
610
|
-
| `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless.
|
|
611
|
-
| `AIDOT_HLS_DIRECT_TS` | **
|
|
612
|
-
| `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone
|
|
615
|
+
| `AIDOT_DIRECT_PUBLISH` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Publish a live `rtsp://` push into go2rtc from the library itself instead of through ffmpeg, for both transports: SDES hands the bridge's plain RTP straight to an RTSP publisher, DTLS packetizes the tapped H.264 and A-law directly. No ffmpeg process per stream, audio is PCMA plus an in-process AAC track, packets put back in order before publishing, and audio attached from the camera's negotiated answer rather than waiting to observe a packet. Recordings, snapshots and `-`/`http://` serves are unchanged. Takes H.264 sessions only - an H.265 session keeps the ffmpeg serve. Live-validated with H.264 on A000088, A001064 and A001513 (see `docs/DESIGN-direct-publish.md`). Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
|
|
616
|
+
| `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless. **On by default** (since 1.0.0rc44); set `0`/`false`/`no`/`off` to turn it off. | `1` (on) |
|
|
617
|
+
| `AIDOT_HLS_DIRECT_TS` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Give each DTLS camera a library-muxed MPEG-TS on a loopback listener (`CameraMixin.hls_ts_url()`), for Home Assistant's HLS view and recordings to read instead of go2rtc's RTSP. go2rtc re-bases each track for each consumer, so a reader that joins a running stream had its sound 0.1-0.75 s late, differently each time; this TS carries the publisher's video and AAC on one clock, so every reader gets them in the same step, whenever it joins. Needs `AIDOT_DIRECT_PUBLISH` and `AIDOT_PUBLISH_AAC`. An SDES camera gets a URL only on a model whose media the bridge decrypts (A001064, A001513) with its offer pinned to H.264 (`AIDOT_SDES_VIDEO_PT=96`), so that every session feeds it; pulled (non-publishing) sessions get none. When a camera's SPS changes (the A001064 changes it between sessions), the TS's readers are disconnected so they rebuild their decoder. Each camera's URL carries a secret made per listener, as `?auth=<secret>` (Home Assistant masks that query parameter when it logs a stream's URL); a request without it gets 404. For the owner's own tools, the listener's URL for any camera (`http://127.0.0.1:<port>/{name}.ts?auth=<secret>`, where `{name}` is `aidot_<first 12 of the device id>`) is left in `hls-ts-url` in the library's state directory (`AIDOT_SPROP_DIR`), owner-readable only, while the listener runs. A reader of the TS counts as a viewer. Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
|
|
618
|
+
| `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone: discovery and the library's own retry loop stop kicking it, and when something else still asks for a login (Home Assistant's light poll does, every five minutes) the attempt is made quietly - upstream's per-attempt warning is dropped - until the device's address changes or a login gets through. One warning marks the moment. Applies to every device, camera or not - the devices that hit this are lights on another subnet. | `6` |
|
|
613
619
|
|
|
614
620
|
### Security hardening
|
|
615
621
|
|
|
@@ -622,3 +628,4 @@ the permissive default.
|
|
|
622
628
|
| --- | --- | --- |
|
|
623
629
|
| `AIDOT_ALLOW_LAN_SERVE` | Silences the warning emitted when decrypted media is served on a non-loopback bind (e.g. `0.0.0.0`), where any host on the LAN can read the unencrypted stream. Set when an exposed bind is intentional. | unset (warn on non-loopback) |
|
|
624
630
|
| `AIDOT_CRED_KEY_FILE` | Path to the Fernet key file for stored credentials. Point it outside the config dir (ideally a separate secret store) so the key isn't co-located with the ciphertext. Applies to the default credentials path only (ignored when an explicit `creds_path` is passed). | `$XDG_CONFIG_HOME/aidot/.key` (falls back to `~/.config/aidot/.key`) |
|
|
631
|
+
| `AIDOT_PLAYBACK_TLS_VERIFY` | Set to `1` to require full certificate and hostname verification on the TLS connection to a camera's playback server. The camera presents a self-signed certificate, so this needs a trust anchor it chains to; unset, verification is skipped and a one-time warning is logged. | unset (skip verification, warn once) |
|
|
@@ -60,11 +60,11 @@ not going to change in this release. Neither is a fault to report.
|
|
|
60
60
|
records to the cloud or to its own SD card is per-camera, not per-model, and the
|
|
61
61
|
device reports it as `IsSupportPlayback`. This library lists cloud recordings
|
|
62
62
|
(`async_get_cloud_recordings`) and resolves a playable HLS URL for one
|
|
63
|
-
(`async_get_event_video_media`);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
(`async_get_event_video_media`); for a camera storing to a card it lists the
|
|
64
|
+
on-device recordings (`async_get_sd_recordings`) but cannot play one back, so
|
|
65
|
+
such a camera records normally and nothing here can show you the video. On the
|
|
66
|
+
reference fleet that is four of seven cameras. Playback from the card is closed
|
|
67
|
+
as not feasible; see `docs/CAMERAS.md`, "On-device (SD card) recordings".
|
|
68
68
|
|
|
69
69
|
**The A001064 (PTZ) streams at roughly 2.2 Mbps, about 24% above the vendor
|
|
70
70
|
app.** Measured from a capture of the app on the same camera: it takes about
|
|
@@ -91,10 +91,10 @@ Two things stand between that and a saving you can switch on:
|
|
|
91
91
|
- **We cannot select it.** Narrowing the offer to 97 does not select it, it
|
|
92
92
|
removes the option - no video at all in 3 of 3 rounds (see
|
|
93
93
|
`AIDOT_SDES_VIDEO_PT` below). Reordering the preference list without
|
|
94
|
-
narrowing it
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
narrowing it was then tested directly: eight sessions in blocks of two
|
|
95
|
+
returned H265 in 2 of 4 with the reorder and 2 of 4 without, with receipts
|
|
96
|
+
confirming the reordered list reached the SDP. The camera chooses, roughly
|
|
97
|
+
a coin flip, and neither knob moves it; the reorder knob is gone.
|
|
98
98
|
- **H.265 may not suit the consumer.** Browser HEVC support over Media Source
|
|
99
99
|
Extensions is far narrower than H.264, and MSE playback is the reason the
|
|
100
100
|
bitrate matters here at all. Halving the bitrate is no help if the client
|
|
@@ -228,9 +228,9 @@ decides it:
|
|
|
228
228
|
|
|
229
229
|
| your camera | use | why |
|
|
230
230
|
| --- | --- | --- |
|
|
231
|
-
| **DTLS** (mains, e.g. A000088) | `-` |
|
|
231
|
+
| **DTLS** (mains, e.g. A000088) | `'{output}'` or `-` | `{output}` is published by the library itself (direct publish, on by default): the camera's 8 kHz A-law plus an in-process AAC track, no ffmpeg. `-` writes the muxed MPEG-TS with its 48 kHz AAC to stdout instead |
|
|
232
232
|
| **SDES** (the A001513 and A001064 families) | `'{output}'` | these stream by pushing RTSP; there is nothing to read from stdout |
|
|
233
|
-
| either, with `AIDOT_DIRECT_PUBLISH=
|
|
233
|
+
| either, with `AIDOT_DIRECT_PUBLISH=0` | `'{output}'` | the push goes through ffmpeg instead, which has to transcode audio down to 8 kHz G.711. A session that negotiates H.265 takes this path whatever the setting |
|
|
234
234
|
| either, if you want to pull | an `http://` URL | the process serves there and waits for a consumer to connect |
|
|
235
235
|
|
|
236
236
|
`'{output}'` is a placeholder that go2rtc substitutes with the stream's own push
|
|
@@ -261,22 +261,24 @@ go2rtc itself gets its environment: export them in the shell before starting it,
|
|
|
261
261
|
or set them in its service unit or container definition. If a stream fails
|
|
262
262
|
immediately with an authentication error, that is the thing to check first.
|
|
263
263
|
|
|
264
|
-
**ffmpeg must be on PATH.**
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
264
|
+
**ffmpeg must be on PATH.** pip cannot install a system binary, and the
|
|
265
|
+
library still needs one: recordings, snapshots, the `-` stdout form, `http://`
|
|
266
|
+
pull serves and any session that negotiates H.265 run through an ffmpeg
|
|
267
|
+
subprocess, and it is the DTLS fallback when a serve port cannot be bound. A
|
|
268
|
+
live `{output}` push does not use it (next paragraph), and a DTLS `-` serve
|
|
269
|
+
sends the muxed MPEG-TS straight to the consumer, because that hop was the only
|
|
270
|
+
component in the chain that lost timestamps.
|
|
271
|
+
|
|
272
|
+
**Live pushes skip ffmpeg: direct publish is on by default** (since 1.0.0rc44;
|
|
273
|
+
`AIDOT_DIRECT_PUBLISH=0` restores the ffmpeg path). An `rtsp://` / `{output}`
|
|
274
|
+
push is published into go2rtc by the library
|
|
273
275
|
itself - no ffmpeg process per stream - for **both** transports, so a DTLS
|
|
274
276
|
camera can use `{output}` too. It takes H.264 sessions only; a session that
|
|
275
277
|
negotiates H.265 keeps the ffmpeg serve. Audio goes out as the camera's own
|
|
276
278
|
G.711 A-law plus an in-process AAC track (see "Audio" under
|
|
277
279
|
[Getting an RTSP URL](#getting-an-rtsp-url)).
|
|
278
|
-
ffmpeg is still used for recordings and snapshots
|
|
279
|
-
|
|
280
|
+
ffmpeg is still used for recordings and snapshots; see
|
|
281
|
+
[`docs/DESIGN-direct-publish.md`](docs/DESIGN-direct-publish.md).
|
|
280
282
|
|
|
281
283
|
```bash
|
|
282
284
|
sudo apt install ffmpeg # Debian/Ubuntu
|
|
@@ -462,14 +464,16 @@ the last one leaves - nothing talks to your cameras while nobody is watching:
|
|
|
462
464
|
|
|
463
465
|
```yaml
|
|
464
466
|
streams:
|
|
465
|
-
driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera
|
|
467
|
+
driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera (stdout form; {output} works too)
|
|
466
468
|
frontdoor: exec:aidot-go2rtc 5e6f7a8b... {output} # SDES camera
|
|
467
469
|
```
|
|
468
470
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
471
|
+
`{output}` works for both transports: the library publishes the stream into
|
|
472
|
+
go2rtc itself (direct publish, on by default since 1.0.0rc44). A DTLS camera
|
|
473
|
+
can also take `-`, which hands go2rtc the muxed MPEG-TS on stdout; an SDES
|
|
474
|
+
camera cannot, and `--list` told you which is which. (`{output}` is a
|
|
475
|
+
placeholder go2rtc fills in with its own publish URL. It is not something you
|
|
476
|
+
type at a shell; `-` is the form for that.) go2rtc needs the same `AIDOT_*` environment
|
|
473
477
|
variables the CLI does, so set them wherever go2rtc starts.
|
|
474
478
|
|
|
475
479
|
**3. Use the URL.** go2rtc serves every stream you named at:
|
|
@@ -491,20 +495,20 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
|
|
|
491
495
|
- **Which camera is which.** `--list` reports the transport per device. DTLS is
|
|
492
496
|
the mains A000088; SDES is the A001064 / A001513 family. Passing the wrong
|
|
493
497
|
form is not silent - the CLI tells you which one to use and exits.
|
|
494
|
-
- **Audio.**
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
source is needed for
|
|
498
|
+
- **Audio.** A `{output}` push (direct publish, the default) carries the
|
|
499
|
+
camera's own **PCMA (G.711 A-law, 8 kHz)** first - WebRTC viewers play it
|
|
500
|
+
natively and nothing is transcoded - and, unless `AIDOT_PUBLISH_AAC=0`, an
|
|
501
|
+
**AAC-LC 48 kHz mono** track encoded in-process; an AAC-only consumer (for
|
|
502
|
+
example Home Assistant's HLS player) selects it with `?audio=aac`. The `-`
|
|
503
|
+
form carries AAC at 48 kHz mono, resampled from the camera's 8 kHz A-law,
|
|
504
|
+
because 8 kHz AAC plays silent in a lot of browsers and browsers on the MSE
|
|
505
|
+
path have no mapping for G.711 at all. Audio is on by default; see
|
|
506
|
+
`AIDOT_SDES_SERVE_AUDIO` to turn it off. No ffmpeg transcode
|
|
507
|
+
source is needed for the AAC - see
|
|
504
508
|
[`docs/CAMERAS.md`](https://github.com/cbrightly/python-aidot-cameras/blob/main/docs/CAMERAS.md#an-aac-transcoding-source-makes-every-player-crawl)
|
|
505
509
|
for why the old `ffmpeg:<stream>#audio=aac` advice made playback crawl.
|
|
506
|
-
The AAC track is
|
|
507
|
-
gets no AAC track either way.
|
|
510
|
+
The AAC track is on by default since 1.0.0rc44. A camera whose audio is
|
|
511
|
+
mu-law (PCMU) gets no AAC track either way.
|
|
508
512
|
- **A stream that starts with no video** and picks it up a few seconds later is
|
|
509
513
|
normal: the mux waits for a keyframe so the first GOP is decodable. A stream
|
|
510
514
|
that stays audio-only is a camera that never sent one - retry the view.
|
|
@@ -513,9 +517,11 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
|
|
|
513
517
|
spawns. That process has to stay running to keep the port bound, so it holds
|
|
514
518
|
a camera session whether or not anyone is watching. Prefer the `exec:` forms.
|
|
515
519
|
- **Pushing to your own RTSP server** rather than go2rtc: `rtsp_push_url=`
|
|
516
|
-
from Python, or an `rtsp://` output on the CLI. Both transports support it
|
|
517
|
-
|
|
518
|
-
|
|
520
|
+
from Python, or an `rtsp://` output on the CLI. Both transports support it,
|
|
521
|
+
and by default the library publishes it directly, with the audio described
|
|
522
|
+
above. Only on the `AIDOT_DIRECT_PUBLISH=0` ffmpeg path does a DTLS push
|
|
523
|
+
transcode audio to G.711 A-law - ffmpeg's RTSP muxer will not accept the AAC
|
|
524
|
+
that MPEG-TS carries.
|
|
519
525
|
`output_path=` records to a file. `Go2RtcClient.rtsp_url(name)` builds the
|
|
520
526
|
address above, and `ensure_stream(name, source)` registers a stream with a
|
|
521
527
|
running go2rtc if you would rather not edit YAML.
|
|
@@ -542,7 +548,7 @@ The library reads the following environment variables.
|
|
|
542
548
|
|
|
543
549
|
### Credentials
|
|
544
550
|
|
|
545
|
-
Used by the credential helper (`
|
|
551
|
+
Used by the credential helper (`aidot_cameras.credentials`); they take priority over any
|
|
546
552
|
stored credentials file. See [`aidot_cameras/credentials.py`](https://github.com/cbrightly/python-aidot-cameras/blob/main/aidot_cameras/credentials.py).
|
|
547
553
|
|
|
548
554
|
| Variable | Purpose | Default |
|
|
@@ -563,13 +569,13 @@ knob is in
|
|
|
563
569
|
| `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
|
|
564
570
|
| `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
|
|
565
571
|
| `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
|
|
566
|
-
| `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
|
|
567
|
-
| `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. |
|
|
572
|
+
| `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, though 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`) |
|
|
573
|
+
| `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. | `96` (H.264; `none` leaves the camera its choice) |
|
|
568
574
|
| `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
|
|
569
|
-
| `AIDOT_DIRECT_PUBLISH` | **
|
|
570
|
-
| `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless.
|
|
571
|
-
| `AIDOT_HLS_DIRECT_TS` | **
|
|
572
|
-
| `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone
|
|
575
|
+
| `AIDOT_DIRECT_PUBLISH` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Publish a live `rtsp://` push into go2rtc from the library itself instead of through ffmpeg, for both transports: SDES hands the bridge's plain RTP straight to an RTSP publisher, DTLS packetizes the tapped H.264 and A-law directly. No ffmpeg process per stream, audio is PCMA plus an in-process AAC track, packets put back in order before publishing, and audio attached from the camera's negotiated answer rather than waiting to observe a packet. Recordings, snapshots and `-`/`http://` serves are unchanged. Takes H.264 sessions only - an H.265 session keeps the ffmpeg serve. Live-validated with H.264 on A000088, A001064 and A001513 (see `docs/DESIGN-direct-publish.md`). Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
|
|
576
|
+
| `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless. **On by default** (since 1.0.0rc44); set `0`/`false`/`no`/`off` to turn it off. | `1` (on) |
|
|
577
|
+
| `AIDOT_HLS_DIRECT_TS` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Give each DTLS camera a library-muxed MPEG-TS on a loopback listener (`CameraMixin.hls_ts_url()`), for Home Assistant's HLS view and recordings to read instead of go2rtc's RTSP. go2rtc re-bases each track for each consumer, so a reader that joins a running stream had its sound 0.1-0.75 s late, differently each time; this TS carries the publisher's video and AAC on one clock, so every reader gets them in the same step, whenever it joins. Needs `AIDOT_DIRECT_PUBLISH` and `AIDOT_PUBLISH_AAC`. An SDES camera gets a URL only on a model whose media the bridge decrypts (A001064, A001513) with its offer pinned to H.264 (`AIDOT_SDES_VIDEO_PT=96`), so that every session feeds it; pulled (non-publishing) sessions get none. When a camera's SPS changes (the A001064 changes it between sessions), the TS's readers are disconnected so they rebuild their decoder. Each camera's URL carries a secret made per listener, as `?auth=<secret>` (Home Assistant masks that query parameter when it logs a stream's URL); a request without it gets 404. For the owner's own tools, the listener's URL for any camera (`http://127.0.0.1:<port>/{name}.ts?auth=<secret>`, where `{name}` is `aidot_<first 12 of the device id>`) is left in `hls-ts-url` in the library's state directory (`AIDOT_SPROP_DIR`), owner-readable only, while the listener runs. A reader of the TS counts as a viewer. Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
|
|
578
|
+
| `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone: discovery and the library's own retry loop stop kicking it, and when something else still asks for a login (Home Assistant's light poll does, every five minutes) the attempt is made quietly - upstream's per-attempt warning is dropped - until the device's address changes or a login gets through. One warning marks the moment. Applies to every device, camera or not - the devices that hit this are lights on another subnet. | `6` |
|
|
573
579
|
|
|
574
580
|
### Security hardening
|
|
575
581
|
|
|
@@ -582,3 +588,4 @@ the permissive default.
|
|
|
582
588
|
| --- | --- | --- |
|
|
583
589
|
| `AIDOT_ALLOW_LAN_SERVE` | Silences the warning emitted when decrypted media is served on a non-loopback bind (e.g. `0.0.0.0`), where any host on the LAN can read the unencrypted stream. Set when an exposed bind is intentional. | unset (warn on non-loopback) |
|
|
584
590
|
| `AIDOT_CRED_KEY_FILE` | Path to the Fernet key file for stored credentials. Point it outside the config dir (ideally a separate secret store) so the key isn't co-located with the ciphertext. Applies to the default credentials path only (ignored when an explicit `creds_path` is passed). | `$XDG_CONFIG_HOME/aidot/.key` (falls back to `~/.config/aidot/.key`) |
|
|
591
|
+
| `AIDOT_PLAYBACK_TLS_VERIFY` | Set to `1` to require full certificate and hostname verification on the TLS connection to a camera's playback server. The camera presents a self-signed certificate, so this needs a trust anchor it chains to; unset, verification is skipped and a one-time warning is logged. | unset (skip verification, warn once) |
|
{python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/aac_track.py
RENAMED
|
@@ -70,8 +70,12 @@ _OFF = ("0", "false", "no", "off")
|
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
def publish_aac_enabled() -> bool:
|
|
73
|
-
"""Whether the direct publish adds the AAC track (
|
|
74
|
-
|
|
73
|
+
"""Whether the direct publish adds the AAC track (on unless turned off).
|
|
74
|
+
|
|
75
|
+
On by default since 1.0.0rc44: it is what gives an AAC-only consumer
|
|
76
|
+
(Home Assistant's HLS player, ``camera.record``) sound at all.
|
|
77
|
+
"""
|
|
78
|
+
return os.environ.get(ENV_PUBLISH_AAC, "1").strip().lower() not in _OFF
|
|
75
79
|
|
|
76
80
|
|
|
77
81
|
def audio_specific_config(
|