python-aidot-cameras 0.11.7__tar.gz → 0.11.9__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.
Files changed (131) hide show
  1. {python_aidot_cameras-0.11.7/python_aidot_cameras.egg-info → python_aidot_cameras-0.11.9}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/client.py +13 -3
  3. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/sdes.py +24 -2
  4. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/pyproject.toml +1 -1
  5. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  6. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/python_aidot_cameras.egg-info/SOURCES.txt +2 -0
  7. python_aidot_cameras-0.11.9/tests/test_open_fail_logger.py +29 -0
  8. python_aidot_cameras-0.11.9/tests/test_sdes_ffmpeg_stderr_log.py +52 -0
  9. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/LICENSE +0 -0
  10. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/MANIFEST.in +0 -0
  11. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/README.md +0 -0
  12. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/__init__.py +0 -0
  13. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/__main__.py +0 -0
  14. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/__init__.py +0 -0
  15. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/__init__.py +0 -0
  16. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/clock.py +0 -0
  17. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/__init__.py +0 -0
  18. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/base.py +0 -0
  19. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/g711.py +0 -0
  20. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/g722.py +0 -0
  21. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/h264.py +0 -0
  22. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/opus.py +0 -0
  23. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/codecs/vpx.py +0 -0
  24. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/contrib/__init__.py +0 -0
  25. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/contrib/media.py +0 -0
  26. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/contrib/signaling.py +0 -0
  27. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/events.py +0 -0
  28. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/exceptions.py +0 -0
  29. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/jitterbuffer.py +0 -0
  30. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/mediastreams.py +0 -0
  31. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rate.py +0 -0
  32. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcconfiguration.py +0 -0
  33. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcdatachannel.py +0 -0
  34. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcdtlstransport.py +0 -0
  35. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcicetransport.py +0 -0
  36. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcpeerconnection.py +0 -0
  37. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcrtpparameters.py +0 -0
  38. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  39. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcrtpsender.py +0 -0
  40. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  41. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcsctptransport.py +0 -0
  42. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtcsessiondescription.py +0 -0
  43. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/rtp.py +0 -0
  44. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/sdp.py +0 -0
  45. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/stats.py +0 -0
  46. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/_vendor/aiortc/utils.py +0 -0
  47. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/aes_utils.py +0 -0
  48. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/__init__.py +0 -0
  49. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/constants.py +0 -0
  50. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/controls.py +0 -0
  51. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/go2rtc.py +0 -0
  52. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/lan_control.py +0 -0
  53. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/models.py +0 -0
  54. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/playback.py +0 -0
  55. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/protocol.py +0 -0
  56. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/sdes_open.py +0 -0
  57. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/tutk.py +0 -0
  58. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/webrtc.py +0 -0
  59. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/camera/webrtc_open.py +0 -0
  60. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/client.py +0 -0
  61. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/const.py +0 -0
  62. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/credentials.py +0 -0
  63. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/device_client.py +0 -0
  64. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/discover.py +0 -0
  65. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/exceptions.py +0 -0
  66. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/g711.py +0 -0
  67. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/login_const.py +0 -0
  68. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/models/__init__.py +0 -0
  69. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/models/device_client_model.py +0 -0
  70. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/models/device_model.py +0 -0
  71. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/models/discover_model.py +0 -0
  72. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/aidot/py.typed +0 -0
  73. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  74. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  75. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/python_aidot_cameras.egg-info/requires.txt +0 -0
  76. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  77. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/setup.cfg +0 -0
  78. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_aioice_compat.py +0 -0
  79. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_alarm_event.py +0 -0
  80. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_backoff.py +0 -0
  81. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_deferred_hardening.py +0 -0
  82. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_device_login_guard.py +0 -0
  83. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_device_user_info_cache.py +0 -0
  84. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_dtls_not_ready_burst.py +0 -0
  85. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_dtls_pinning.py +0 -0
  86. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_dtls_skip_signaling_wait.py +0 -0
  87. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_egress_guard.py +0 -0
  88. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_go2rtc.py +0 -0
  89. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_go2rtc_cli.py +0 -0
  90. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_highport_nomination.py +0 -0
  91. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_ice_config_cache.py +0 -0
  92. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_keepalive_renew.py +0 -0
  93. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_keyframe_prompter.py +0 -0
  94. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_lan_control.py +0 -0
  95. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_live_stream_param.py +0 -0
  96. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_logging_caps.py +0 -0
  97. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_motion_poll.py +0 -0
  98. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_mqtt_session_reconnect.py +0 -0
  99. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_narrow_pc_ice.py +0 -0
  100. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_no_undefined_names.py +0 -0
  101. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_offline_keepalive_pause.py +0 -0
  102. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_open_gate_delay.py +0 -0
  103. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_persistent_mqtt.py +0 -0
  104. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_persistent_mqtt_robustness.py +0 -0
  105. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_playback_tls.py +0 -0
  106. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_post_merge_hardening.py +0 -0
  107. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_retry_policy.py +0 -0
  108. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_adaptive.py +0 -0
  109. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_echo_wait_timeout.py +0 -0
  110. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_fast_liveplay.py +0 -0
  111. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_idle_release.py +0 -0
  112. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_open_cleanup.py +0 -0
  113. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_serve_audio.py +0 -0
  114. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_serve_cmd.py +0 -0
  115. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_sprop.py +0 -0
  116. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_talk.py +0 -0
  117. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_sdes_watchdog.py +0 -0
  118. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_security_hardening.py +0 -0
  119. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_serializable_login_info.py +0 -0
  120. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_serve_relay.py +0 -0
  121. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_session_stats.py +0 -0
  122. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_speak.py +0 -0
  123. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_stream_cap.py +0 -0
  124. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_stream_drain_ownership.py +0 -0
  125. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_stream_idle.py +0 -0
  126. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_stream_teardown.py +0 -0
  127. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_talk.py +0 -0
  128. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_terminal_ack.py +0 -0
  129. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_token_file_robustness.py +0 -0
  130. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_token_refresh.py +0 -0
  131. {python_aidot_cameras-0.11.7 → python_aidot_cameras-0.11.9}/tests/test_wait_or_event.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.11.7
3
+ Version: 0.11.9
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
@@ -792,6 +792,16 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
792
792
  self._cloud_online_explicit = True
793
793
  return self.status
794
794
 
795
+ def _open_fail_logger(self):
796
+ """Pick the log level for a stream-open failure in the serve/keepalive
797
+ loops: WARNING for a genuine failure, but DEBUG when the cloud has
798
+ explicitly reported the device offline. A dead/unpowered camera is
799
+ already throttled to ~probe cadence by _backoff_or_offline_pause, so its
800
+ expected open failures should not drip WARNINGs into the log every probe.
801
+ """
802
+ _offline = (not self.status.online) and getattr(self, "_cloud_online_explicit", False)
803
+ return _LOGGER.debug if _offline else _LOGGER.warning
804
+
795
805
  async def _backoff_or_offline_pause(self, delay: float) -> None:
796
806
  """Sleep a reconnect backoff, extended while the device is cloud-offline.
797
807
 
@@ -2732,7 +2742,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
2732
2742
  "falling back to full relay path", self.device_id,
2733
2743
  _FAST_OPEN_TIMEOUT)
2734
2744
  _delay = _pacer.fail_delay()
2735
- _LOGGER.warning(
2745
+ self._open_fail_logger()(
2736
2746
  "SDES keepalive: stream open failed for %s (retry in %.0fs): %s",
2737
2747
  self.device_id, _delay, exc,
2738
2748
  )
@@ -2908,7 +2918,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
2908
2918
  continue
2909
2919
  except Exception as exc:
2910
2920
  _delay = _pacer.fail_delay()
2911
- _LOGGER.warning(
2921
+ self._open_fail_logger()(
2912
2922
  "Stream open failed for %s (retry in %.0fs): %s",
2913
2923
  self.device_id, _delay, exc,
2914
2924
  )
@@ -3409,7 +3419,7 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
3409
3419
  continue
3410
3420
  except Exception as exc:
3411
3421
  _delay = _pacer.fail_delay()
3412
- _LOGGER.warning(
3422
+ self._open_fail_logger()(
3413
3423
  "DTLS serve: open failed for %s (retry %.0fs): %s",
3414
3424
  self.device_id, _delay, exc,
3415
3425
  )
@@ -132,6 +132,29 @@ class SdesSession:
132
132
  """time.monotonic() of the last media packet forwarded, 0.0 if none yet."""
133
133
  return self._media_progress[0]
134
134
 
135
+ def _log_ffmpeg_stderr(self, stderr_bytes: bytes) -> None:
136
+ """Log ffmpeg's captured stderr at a level that matches what happened.
137
+
138
+ The common NO_MEDIA case - the camera's SRTP never arrives, so ffmpeg
139
+ finds no keyframe and writes an empty file - would otherwise spam a
140
+ WARNING on every serve retry for a camera that never delivers media.
141
+ Demote to debug ONLY when no media ever arrived AND the stderr is that
142
+ expected shape. Anything else - media WAS flowing (a genuine mid-stream
143
+ error), or an unexpected ffmpeg failure with no media (malformed SDP, bad
144
+ SRTP key, protocol reject) - still logs at WARNING, so real problems are
145
+ not hidden. Unknown ffmpeg wording degrades safely to WARNING.
146
+ """
147
+ if not stderr_bytes:
148
+ return
149
+ text = stderr_bytes.decode(errors="replace")
150
+ expected_no_media = self.last_media_monotonic == 0.0 and (
151
+ "Output file is empty" in text
152
+ or "Could not find codec parameters" in text
153
+ )
154
+ (_LOGGER.debug if expected_no_media else _LOGGER.warning)(
155
+ "ffmpeg SDES stderr:\n%s", text
156
+ )
157
+
135
158
  @staticmethod
136
159
  def is_stalled(
137
160
  last_media: float,
@@ -213,8 +236,7 @@ class SdesSession:
213
236
  self._proc.stderr.close()
214
237
  except Exception:
215
238
  _LOGGER.debug("swallowed exception in %s", 'stop', exc_info=True)
216
- if stderr_bytes:
217
- _LOGGER.warning("ffmpeg SDES stderr:\n%s", stderr_bytes.decode(errors="replace"))
239
+ self._log_ffmpeg_stderr(stderr_bytes)
218
240
  import os
219
241
  try:
220
242
  os.unlink(self._sdp_path)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.11.7"
7
+ version = "0.11.9"
8
8
  description = "Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.11.7
3
+ Version: 0.11.9
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
@@ -95,6 +95,7 @@ tests/test_mqtt_session_reconnect.py
95
95
  tests/test_narrow_pc_ice.py
96
96
  tests/test_no_undefined_names.py
97
97
  tests/test_offline_keepalive_pause.py
98
+ tests/test_open_fail_logger.py
98
99
  tests/test_open_gate_delay.py
99
100
  tests/test_persistent_mqtt.py
100
101
  tests/test_persistent_mqtt_robustness.py
@@ -104,6 +105,7 @@ tests/test_retry_policy.py
104
105
  tests/test_sdes_adaptive.py
105
106
  tests/test_sdes_echo_wait_timeout.py
106
107
  tests/test_sdes_fast_liveplay.py
108
+ tests/test_sdes_ffmpeg_stderr_log.py
107
109
  tests/test_sdes_idle_release.py
108
110
  tests/test_sdes_open_cleanup.py
109
111
  tests/test_sdes_serve_audio.py
@@ -0,0 +1,29 @@
1
+ """Serve/keepalive open-failure logging level.
2
+
3
+ A stream-open failure logs at WARNING normally, but at DEBUG when the cloud has
4
+ explicitly reported the device offline - a dead/unpowered camera is throttled to
5
+ probe cadence by the offline pause and should not drip WARNINGs every probe.
6
+ """
7
+ import types
8
+
9
+ from aidot.camera import client as camclient
10
+
11
+
12
+ def _dc(online, explicit):
13
+ o = types.SimpleNamespace()
14
+ o.status = types.SimpleNamespace(online=online)
15
+ o._cloud_online_explicit = explicit
16
+ return camclient.CameraMixin._open_fail_logger.__get__(o)
17
+
18
+
19
+ def test_debug_when_cloud_reports_offline():
20
+ assert _dc(online=False, explicit=True)() == camclient._LOGGER.debug
21
+
22
+
23
+ def test_warning_when_online():
24
+ assert _dc(online=True, explicit=True)() == camclient._LOGGER.warning
25
+
26
+
27
+ def test_warning_when_online_state_unknown():
28
+ # Never explicitly reported offline -> treat a failure as genuine (WARNING).
29
+ assert _dc(online=False, explicit=False)() == camclient._LOGGER.warning
@@ -0,0 +1,52 @@
1
+ """ffmpeg SDES stderr log level: quiet the expected NO_MEDIA case, keep real errors.
2
+
3
+ A camera whose SRTP never reaches us (NO_MEDIA - e.g. media not traversing the
4
+ relay) produces the expected "could not find codec parameters / empty output"
5
+ ffmpeg stderr on every serve retry. That must log at DEBUG (not spam WARNING).
6
+ But a genuine ffmpeg error - whether media was flowing, or an unexpected failure
7
+ with no media - still logs at WARNING so real problems are not hidden.
8
+ """
9
+ import logging
10
+ import types
11
+
12
+ from aidot.camera.sdes import SdesSession
13
+
14
+
15
+ def _log_fn(last_media):
16
+ obj = types.SimpleNamespace(last_media_monotonic=last_media)
17
+ return SdesSession._log_ffmpeg_stderr.__get__(obj)
18
+
19
+
20
+ def _stderr_records(caplog):
21
+ return [r for r in caplog.records if "ffmpeg SDES stderr" in r.getMessage()]
22
+
23
+
24
+ def test_no_media_expected_shape_logs_debug(caplog):
25
+ for msg in (b"...Output file is empty, nothing was encoded",
26
+ b"Could not find codec parameters for stream 1 (Video: h264, none)"):
27
+ caplog.clear()
28
+ with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
29
+ _log_fn(0.0)(msg)
30
+ recs = _stderr_records(caplog)
31
+ assert recs and recs[0].levelno == logging.DEBUG, msg
32
+
33
+
34
+ def test_no_media_unexpected_error_still_warns(caplog):
35
+ # No media, but NOT the expected empty-output shape -> a real ffmpeg failure.
36
+ with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
37
+ _log_fn(0.0)(b"/tmp/x.sdp: Permission denied")
38
+ recs = _stderr_records(caplog)
39
+ assert recs and recs[0].levelno == logging.WARNING
40
+
41
+
42
+ def test_media_flowed_logs_warning(caplog):
43
+ with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
44
+ _log_fn(1234.5)(b"Could not find codec parameters for stream 1")
45
+ recs = _stderr_records(caplog)
46
+ assert recs and recs[0].levelno == logging.WARNING
47
+
48
+
49
+ def test_empty_stderr_logs_nothing(caplog):
50
+ with caplog.at_level(logging.DEBUG, logger="aidot.camera.sdes"):
51
+ _log_fn(0.0)(b"")
52
+ assert _stderr_records(caplog) == []