python-aidot-cameras 0.7.1__tar.gz → 0.7.3__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 (42) hide show
  1. {python_aidot_cameras-0.7.1/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.7.3}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/pyproject.toml +1 -1
  3. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/camera/client.py +25 -6
  4. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  5. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/LICENSE +0 -0
  6. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/README.md +0 -0
  7. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/setup.cfg +0 -0
  8. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/__init__.py +0 -0
  9. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/aes_utils.py +0 -0
  10. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/camera/__init__.py +0 -0
  11. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/camera/lan_control.py +0 -0
  12. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/client.py +0 -0
  13. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/const.py +0 -0
  14. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/credentials.py +0 -0
  15. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/device_client.py +0 -0
  16. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/discover.py +0 -0
  17. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/exceptions.py +0 -0
  18. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/g711.py +0 -0
  19. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/login_const.py +0 -0
  20. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/models/__init__.py +0 -0
  21. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/models/device_client_model.py +0 -0
  22. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/models/device_model.py +0 -0
  23. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/aidot/models/discover_model.py +0 -0
  24. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  25. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  26. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  27. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  28. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_alarm_event.py +0 -0
  29. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_highport_nomination.py +0 -0
  30. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_lan_control.py +0 -0
  31. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_motion_poll.py +0 -0
  32. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_no_undefined_names.py +0 -0
  33. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_post_merge_hardening.py +0 -0
  34. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_sdes_idle_release.py +0 -0
  35. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_sdes_sprop.py +0 -0
  36. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_sdes_talk.py +0 -0
  37. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_sdes_watchdog.py +0 -0
  38. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_speak.py +0 -0
  39. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_stream_cap.py +0 -0
  40. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_talk.py +0 -0
  41. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_terminal_ack.py +0 -0
  42. {python_aidot_cameras-0.7.1 → python_aidot_cameras-0.7.3}/tests/test_token_refresh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.7.1
3
+ Version: 0.7.3
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.7.1"
7
+ version = "0.7.3"
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"
@@ -2168,8 +2168,15 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2168
2168
  _target = _db2amp(float(os.environ.get("AIDOT_AUDIO_TARGET_DBFS", "-15"))) * 32767.0
2169
2169
  _maxg = _db2amp(float(os.environ.get("AIDOT_AUDIO_MAXGAIN_DB", "30")))
2170
2170
  _ming = _db2amp(float(os.environ.get("AIDOT_AUDIO_MINGAIN_DB", "-12")))
2171
+ # Noise gate: below this RMS the input is (near) silence, so the AGC
2172
+ # must NOT crank toward +maxg - doing so amplifies the A-law
2173
+ # quantization floor into audible high-frequency clicking on a quiet
2174
+ # camera. Gain is scaled down quadratically below the gate. Set very
2175
+ # low (e.g. -120) to disable.
2176
+ _gate = _db2amp(float(os.environ.get("AIDOT_AUDIO_GATE_DBFS", "-45"))) * 32767.0
2171
2177
  except (ValueError, TypeError):
2172
2178
  _target, _maxg, _ming = _db2amp(-15) * 32767.0, _db2amp(30), _db2amp(-12)
2179
+ _gate = _db2amp(-45) * 32767.0
2173
2180
  _agc_ms = [None] # smoothed mean-square (level tracker)
2174
2181
  try:
2175
2182
  import numpy as _np
@@ -2204,7 +2211,7 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2204
2211
  except Exception:
2205
2212
  pass
2206
2213
 
2207
- def _flush_audio():
2214
+ def _flush_audio(drain=False):
2208
2215
  if not have_audio:
2209
2216
  try:
2210
2217
  while True:
@@ -2232,6 +2239,12 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2232
2239
  _agc_ms[0] = _agc_ms[0] * 0.95 + _ms * 0.05
2233
2240
  _rms = (_agc_ms[0] ** 0.5) + 1.0
2234
2241
  _gain = min(_maxg, max(_ming, _target / _rms))
2242
+ # Noise gate: when the level is below the gate floor the
2243
+ # signal is essentially silence; scale the gain down
2244
+ # quadratically so the A-law quantization noise is not
2245
+ # amplified into audible clicking.
2246
+ if _rms < _gate:
2247
+ _gain *= (_rms / _gate) ** 2
2235
2248
  # tanh soft-limiter catches transient peaks past target.
2236
2249
  _y = _np.tanh(_x * (_gain / 32767.0)) * 32767.0
2237
2250
  _g = av.AudioFrame.from_ndarray(
@@ -2247,10 +2260,16 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2247
2260
  while True:
2248
2261
  fr = fifo.read(1024) # AAC wants 1024-sample frames
2249
2262
  if fr is None:
2250
- # 160 PCMA samples 960 resampled @ 48 kHz, but AAC needs 1024.
2251
- # Every ~17 packets (~340 ms) the FIFO falls 64 samples short
2252
- # and would skip an entire 21 ms frame - audible choppiness.
2253
- # Pad the leftover with silence so the frame is complete.
2263
+ # FIFO has < 1024 samples. The audio source is continuous
2264
+ # (measured: 100% timeline coverage, no lost packets), so the
2265
+ # remainder must stay in the FIFO and be completed by the next
2266
+ # decoded packet - NOT padded with silence here. Padding on
2267
+ # every 5 ms flush injected a micro-silence whenever the FIFO was
2268
+ # momentarily short (almost always, given ~40 ms packet spacing) -
2269
+ # that was the choppiness. Only on the final drain do we pad the
2270
+ # last partial frame so the encoder can flush it.
2271
+ if not drain:
2272
+ break
2254
2273
  s = getattr(fifo, "samples", 0)
2255
2274
  if s == 0:
2256
2275
  break
@@ -2288,7 +2307,7 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2288
2307
  _flush_audio()
2289
2308
  _t.sleep(0.005) # 5 ms - processes audio within 5 ms of arrival vs 20 ms
2290
2309
  _flush_video()
2291
- _flush_audio()
2310
+ _flush_audio(drain=True) # final: pad the last partial frame so it flushes
2292
2311
  try:
2293
2312
  if have_audio:
2294
2313
  for opkt in aenc.encode(None): # flush
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.7.1
3
+ Version: 0.7.3
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