pumaguard 21.post17__py3-none-any.whl → 21.post21__py3-none-any.whl

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.
@@ -6,6 +6,7 @@ from __future__ import (
6
6
 
7
7
  import logging
8
8
  import os
9
+ import subprocess
9
10
  from typing import (
10
11
  TYPE_CHECKING,
11
12
  )
@@ -15,6 +16,9 @@ from flask import (
15
16
  jsonify,
16
17
  request,
17
18
  )
19
+ from werkzeug.utils import (
20
+ secure_filename,
21
+ )
18
22
  from yaml.representer import (
19
23
  YAMLError,
20
24
  )
@@ -313,8 +317,8 @@ def register_settings_routes(app: "Flask", webui: "WebUI") -> None:
313
317
  404,
314
318
  )
315
319
 
316
- # Supported audio formats
317
- audio_extensions = {".mp3", ".wav", ".ogg", ".flac", ".m4a"}
320
+ # Only MP3 files are supported (mpg123 player)
321
+ audio_extensions = {".mp3"}
318
322
 
319
323
  sound_files = []
320
324
  for filename in os.listdir(sound_path):
@@ -338,3 +342,132 @@ def register_settings_routes(app: "Flask", webui: "WebUI") -> None:
338
342
  except Exception as e: # pylint: disable=broad-except
339
343
  logger.exception("Error getting available sounds")
340
344
  return jsonify({"error": str(e)}), 500
345
+
346
+ def _validate_uploaded_file():
347
+ """Validate uploaded file exists and has proper format."""
348
+ if "file" not in request.files:
349
+ return {"error": "No file provided"}, 400
350
+
351
+ logger.debug("Verifying uploaded sound file")
352
+ uploaded_file = request.files["file"]
353
+
354
+ if uploaded_file.filename == "" or uploaded_file.filename is None:
355
+ return {"error": "No file selected"}, 400
356
+
357
+ filename = os.path.basename(uploaded_file.filename)
358
+ ext = os.path.splitext(filename)[1].lower()
359
+
360
+ if ext != ".mp3":
361
+ return {
362
+ "error": (
363
+ "Unsupported file type. Only MP3 files are supported."
364
+ )
365
+ }, 400
366
+
367
+ return None
368
+
369
+ def _validate_mp3_file(filepath):
370
+ """Validate MP3 file integrity and format."""
371
+ # Check file size first
372
+ file_size = os.path.getsize(filepath)
373
+ if file_size == 0:
374
+ os.remove(filepath)
375
+ return {"error": "Uploaded file is empty"}, 400
376
+
377
+ # Check MP3 file signature
378
+ with open(filepath, "rb") as f:
379
+ header = f.read(3)
380
+ is_id3 = header == b"ID3"
381
+ is_mpeg = (
382
+ len(header) >= 2
383
+ and header[0] == 0xFF
384
+ and (header[1] & 0xE0) == 0xE0
385
+ )
386
+
387
+ if not (is_id3 or is_mpeg):
388
+ os.remove(filepath)
389
+ return {
390
+ "error": ("File does not appear to be a valid MP3 file")
391
+ }, 400
392
+
393
+ # Test with mpg123 to ensure it can play
394
+ try:
395
+ result = subprocess.run(
396
+ ["mpg123", "--test", filepath],
397
+ capture_output=True,
398
+ timeout=5,
399
+ check=False,
400
+ )
401
+
402
+ if result.returncode != 0:
403
+ os.remove(filepath)
404
+ return {
405
+ "error": (
406
+ "MP3 file validation failed. File may be corrupted."
407
+ )
408
+ }, 400
409
+
410
+ except subprocess.TimeoutExpired:
411
+ os.remove(filepath)
412
+ return {"error": "File validation timeout"}, 400
413
+ except FileNotFoundError:
414
+ logger.warning("mpg123 not found, skipping audio validation")
415
+ except Exception as e: # pylint: disable=broad-except
416
+ logger.warning("Audio validation failed: %s", e)
417
+ # Don't fail upload if validation fails, just log it
418
+
419
+ return None
420
+
421
+ @app.route("/api/sounds/upload", methods=["POST"])
422
+ def upload_sound():
423
+ """Upload a new sound file."""
424
+ try:
425
+ # Validate uploaded file
426
+ error = _validate_uploaded_file()
427
+ if error:
428
+ return jsonify(error[0]), error[1]
429
+
430
+ file = request.files["file"]
431
+ # Type guard: _validate_uploaded_file ensures filename is not None
432
+ assert file.filename is not None
433
+ filename = secure_filename(file.filename)
434
+
435
+ # Ensure sound path exists
436
+ sound_path = webui.presets.sound_path
437
+ os.makedirs(sound_path, exist_ok=True)
438
+
439
+ filepath = os.path.join(sound_path, filename)
440
+
441
+ # Check if file already exists
442
+ if os.path.exists(filepath):
443
+ msg = (
444
+ f"File '{filename}' already exists. "
445
+ "Please rename your file or "
446
+ "delete the existing one."
447
+ )
448
+ return jsonify({"error": msg}), 409
449
+
450
+ # Save the file
451
+ file.save(filepath)
452
+ logger.info("Uploaded sound file: %s", filepath)
453
+
454
+ # Validate MP3 file
455
+ error = _validate_mp3_file(filepath)
456
+ if error:
457
+ return jsonify(error[0]), error[1]
458
+
459
+ # Get file size
460
+ size_mb = os.path.getsize(filepath) / (1024 * 1024)
461
+
462
+ return jsonify(
463
+ {
464
+ "success": True,
465
+ "message": f"Sound file uploaded: {filename}",
466
+ "filename": filename,
467
+ "size_mb": size_mb,
468
+ }
469
+ )
470
+
471
+ except Exception as e: # pylint: disable=broad-except
472
+ logger.exception("Error uploading sound file")
473
+ return jsonify({"error": str(e)}), 500
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pumaguard
3
- Version: 21.post17
3
+ Version: 21.post21
4
4
  Author-email: Nicolas Bock <nicolasbock@gmail.com>
5
5
  Project-URL: Homepage, http://pumaguard.rtfd.io/
6
6
  Project-URL: Repository, https://github.com/PEEC-Nature-Youth-Group/pumaguard
@@ -17,20 +17,20 @@ pumaguard/completions/pumaguard-classify-completions.sh,sha256=5QySg-2Jdinj15qpU
17
17
  pumaguard/completions/pumaguard-completions.sh,sha256=bRx3Q3_gM__3w0PyfQSCVdxylhhr3QlzaLCav24dfNc,1196
18
18
  pumaguard/completions/pumaguard-server-completions.sh,sha256=33c6GjbTImBOHn0SSNUOJoxqJ2mMHuDv3P3GQJGGHhA,1161
19
19
  pumaguard/completions/pumaguard-train-completions.sh,sha256=lI8LG-QrncvhUqCeKtfrSU1MSRBn52KnDsiJJQm37W4,1184
20
- pumaguard/pumaguard-ui/.last_build_id,sha256=MqeXJXz-uOBq3M0XNvGxIcX4cKB19A_LfS3HU_820DM,32
20
+ pumaguard/pumaguard-ui/.last_build_id,sha256=wGsLJ2ssxZhUGdnO6VTlA-dz-cnyybEVIgLzChObUi4,32
21
21
  pumaguard/pumaguard-ui/favicon.png,sha256=erJSX0uGtl0-THA1ihfloar29Df5nLzARtrXPVm7kBU,917
22
22
  pumaguard/pumaguard-ui/flutter.js,sha256=7V1ZIKmGiouT15CpquQWWmKWJyjUq77FoU9gDXPFO9M,9412
23
- pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=Nyt0e7m8U4hq4CmcHhK1ij4hNgUqANM1o4YieKUx4rA,9692
23
+ pumaguard/pumaguard-ui/flutter_bootstrap.js,sha256=GjnKX2JxUgtWmnYNM0jnpsrXewvVk-j1EPqqOe4_Jvs,9692
24
24
  pumaguard/pumaguard-ui/flutter_service_worker.js,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  pumaguard/pumaguard-ui/index.html,sha256=901-ZY0WysVAZWPwj2xGatoezwm9TX9IV_jpMrlsaXg,1205
26
- pumaguard/pumaguard-ui/main.dart.js,sha256=qqPacko98nvwMbhQQD86iGWuLjJoJfHrvI9eXnNGGy4,2820675
26
+ pumaguard/pumaguard-ui/main.dart.js,sha256=MvnwRaImOQV-Edvx5Hbc02wkhIsGu0IxJo-NSQ89yUw,2839605
27
27
  pumaguard/pumaguard-ui/manifest.json,sha256=Hhnw_eLUivdrOlL7O9KGBsGXCKKt3lix17Fh3GB0g-s,920
28
28
  pumaguard/pumaguard-ui/version.json,sha256=uXZ6musTJUZaO0N2bEbr3cy9rpx2aesAS2YFMcu2WF8,94
29
29
  pumaguard/pumaguard-ui/assets/AssetManifest.bin,sha256=Qzp1G9iPlHSW-PnHyszTxZO31_NjmTlvSBWY_REPH_8,562
30
30
  pumaguard/pumaguard-ui/assets/AssetManifest.bin.json,sha256=_6pfLT_4Bcd6SkcHE6GNc8Uoh6UyL4dxCaK7bypu5lc,754
31
31
  pumaguard/pumaguard-ui/assets/FontManifest.json,sha256=TbzXC0njmfIhJ8D_sqF4P6NGmuI9BBSOl8AzaY-zNMo,598
32
32
  pumaguard/pumaguard-ui/assets/NOTICES,sha256=pXK1o4s9viUW6snqVEqfZsFJa-43d8tCPzyBAanqn0I,1383980
33
- pumaguard/pumaguard-ui/assets/fonts/MaterialIcons-Regular.otf,sha256=NgifqWa38b-ow8_qGP6DUEJssJaYV7rjPhr2yE7cJoc,12472
33
+ pumaguard/pumaguard-ui/assets/fonts/MaterialIcons-Regular.otf,sha256=VEHZorBgkXvxE-zgZoxjQwMZ0eOJYlvkvXuOGTf0Pos,12592
34
34
  pumaguard/pumaguard-ui/assets/fonts/Roboto-Bold.ttf,sha256=YfifjbSSYcL2EG6NzMNd97L37ZCQINtAo_yQXpX5kzQ,514260
35
35
  pumaguard/pumaguard-ui/assets/fonts/Roboto-Light.ttf,sha256=Ao-EOxmQukbiocTvG4JynE2pqUaw2djb9Z5iPRCV5FQ,518580
36
36
  pumaguard/pumaguard-ui/assets/fonts/Roboto-Medium.ttf,sha256=KHml7Lf7-hOn_D4s3X_sv3OqRekbVB39-ixELu0KrCE,511592
@@ -64,28 +64,20 @@ pumaguard/web_routes/diagnostics.py,sha256=EIIbjuixJyGXdnVQf8RQ6xQxJar0UHZO8dF-9
64
64
  pumaguard/web_routes/directories.py,sha256=yy5TghCEyB4reRGAcVHIEfr2vlHnuiDChIXl9ZFquRM,2410
65
65
  pumaguard/web_routes/folders.py,sha256=Z63ap6dRi6NWye70HYurpCnsSXmFgzTbTsFKYdZ1Bjk,6305
66
66
  pumaguard/web_routes/photos.py,sha256=Tac_CbaZSeZzOfaJ73vlp3iyZbvfD7ei1YM3tsb0nTY,5106
67
- pumaguard/web_routes/settings.py,sha256=GA7MERNRRnR2lfrG-aSRx8bJO5OTqVzyQTYSqPYP8wc,11754
67
+ pumaguard/web_routes/settings.py,sha256=01nHNvQVCs_16_wi0_fuKMa7Muv_rjHCyXu_ARBsAtY,16168
68
68
  pumaguard/web_routes/sync.py,sha256=Zvv6VARGE5xP29C5gWH3ul81PISRxoF8n472DITItE0,6378
69
- pumaguard-21.post17.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
69
+ pumaguard-21.post21.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
70
70
  pumaguard-sounds/cougar_call.mp3,sha256=jdPzi7Qneect3ez2G6XAeHWtetU5vSOSB6pceuB26Wc,129048
71
71
  pumaguard-sounds/cougarsounds.wav,sha256=hwVmmQ75dkOP3qd07YAvVOSm1neYtxLSzxw3Ulvs2cM,96346
72
- pumaguard-sounds/dark-engine-logo-141942.mp3,sha256=Vw-qyLTMPJZvsgQcZtH0DpGcP1dd7nJq-9BnHuNPGug,372819
73
72
  pumaguard-sounds/deterrent_puma.mp3,sha256=Jp14Kq9y6NmMZsYJ2ScPWZawl1THk4LXeW63ty12imI,164783
74
73
  pumaguard-sounds/detterent_ringtail.mp3,sha256=7UyOboW5mehjY2ZhefypLtxv95W5DHq6zVG8PQzkbvg,97283
75
- pumaguard-sounds/forest-ambience-296528.mp3,sha256=8IDNPvfu61r5JBaXIVVNDb-wX69QGbqQsBn9xgZD1W8,6815242
76
74
  pumaguard-sounds/lion-roar-6011.mp3,sha256=M9-kKJI0PpQDiZI7oDfXv9dHuVO8h5Hpp3b8Bi-LJqk,77280
77
75
  pumaguard-sounds/lion_cry.wav,sha256=6DgEguM5UQQ84yksvwYH20tut8HE7mIxKk8AcSLSGeg,51676
78
- pumaguard-sounds/mixkit-cinematic-laser-gun-thunder-1287.wav,sha256=tPVhcUw5H6uSv7egLuOklcBPwlbPJ6fKq891ifLNfGk,1272536
79
- pumaguard-sounds/mixkit-classic-alarm-995.wav,sha256=3Y0nnKkOCGU1e3OJ-uOc18OC0S-nZ17SS4SIZWqPZJY,886612
80
- pumaguard-sounds/mixkit-dog-barking-twice-1.wav,sha256=UlPmHfz9i6JdKu6hVnxlmAhUZX3PRRNDNzvZh3GXO78,286330
81
- pumaguard-sounds/mixkit-sad-game-over-trombone-471.wav,sha256=MEZ26kzIoueqi8rFpw57Ej99BCSSCVkgofIez_XSIV8,990152
82
- pumaguard-sounds/mixkit-small-group-cheer-and-applause-518.wav,sha256=GZv1Pvw2PsDEQHdzA9gKG5JxOiUa9P7in6M8R516dnM,1869442
83
- pumaguard-sounds/mixkit-vintage-telephone-ringtone-1356.wav,sha256=zWWY2uFF0-l7PMN58M9UDDvgYSzlCD6D00P_F_uZQhA,1303078
84
76
  pumaguard-sounds/pumaguard-warning.mp3,sha256=wcCfHsulPo5P5s8MjpQAG2NYHQDsRpjqoMig1-o_MDI,232249
85
77
  pumaguard-sounds/short-round-110940.mp3,sha256=vdskGD94SeH1UJyJyR0Ek_7xGXPIZfnPdoBvxGnUt98,450816
86
78
  pumaguard-ui/ios/Flutter/ephemeral/flutter_lldb_helper.py,sha256=Bc_jl3_e5ZPvrSBJpPYtN05VxpztyKq-7lVms3rLg4Q,1276
87
- pumaguard-21.post17.dist-info/METADATA,sha256=EsO16RhyyuJ4e0LBJZtpU5j8UzWJG8YaijWUVaLB3Zo,8617
88
- pumaguard-21.post17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
89
- pumaguard-21.post17.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
90
- pumaguard-21.post17.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
91
- pumaguard-21.post17.dist-info/RECORD,,
79
+ pumaguard-21.post21.dist-info/METADATA,sha256=kiKCxusHFVGpOB79GZkXDEBTQBZWU9qIeXw3Jl32XX0,8617
80
+ pumaguard-21.post21.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
81
+ pumaguard-21.post21.dist-info/entry_points.txt,sha256=rmCdBTPWrbJQvPPwABSVobXE9D7hrKsITGZ6nvCrko8,127
82
+ pumaguard-21.post21.dist-info/top_level.txt,sha256=B-PzS4agkQNhOYbLLIrMVOyMD_pl5F-yujPBm5zYYjY,40
83
+ pumaguard-21.post21.dist-info/RECORD,,
Binary file