Added tests for Video & Audio

feat: video/audio metadata scanning, profile rename fix, route tests

  - Scan .mp4/.mov/.avi/.mkv and .mp3/.flac/.ogg/.m4a/.wma (+ 7 more)
    for GPS coordinates, artist/author, title, comment — metadata only,
    no frame or audio analysis. Uses mutagen (added to requirements.txt).
    GPS-tagged phone recordings now flag with gps_location like photos.

  - Fix _extract_audio_metadata silently returning empty results:
    mutagen.File() first positional arg is `filename`, not `fileobj` —
    was passing BytesIO as the filename. Fixed to keyword args.

  - Fix profile copy rename not reflected in left column until modal
    reopen: _pmgmtSaveFullEdit called loadProfiles() but never
    _renderProfileMgmt(). Added re-render and active-row highlight.

  - Add TestProfileRoutes (10 tests) covering all profile API endpoints
    including a rename regression test. Total: 182 tests.

  - generate_fixtures.py now produces 6 audio/video fixtures (14–19):
    2 MP3, 2 FLAC, 2 MP4 — 4 flagged, 2 negative cases.
This commit is contained in:
StyxX65 committed 2026-04-21 21:26:58 +02:00
1 parent 2a2d79de90
commit d42518dc81
16 files changed
+476 -21

No files matched your search

+21 -6
View File
@@ -99,6 +99,8 @@ except ImportError:
# Stubs for standalone import — overwritten by gdpr_scanner.py injections
LANG: dict = {}
PHOTO_EXTS: set = set()
VIDEO_EXTS: set = set()
AUDIO_EXTS: set = set()
SUPPORTED_EXTS: set = set()
# cpr_detector helpers — injected by gdpr_scanner.py
@@ -106,6 +108,8 @@ def _scan_bytes(content, filename, poppler_path=None): return {"cprs": [], "date
def _scan_bytes_timeout(content, filename, timeout=60): return {"cprs": [], "dates": []} # type: ignore[misc]
def _detect_photo_faces(content, filename): return 0 # type: ignore[misc]
def _extract_exif(content, filename): return {} # type: ignore[misc]
def _extract_video_metadata(content, filename): return {} # type: ignore[misc]
def _extract_audio_metadata(content, filename): return {} # type: ignore[misc]
def _make_thumb(content, filename): return "" # type: ignore[misc]
def _placeholder_svg(ext, name): return "" # type: ignore[misc]
def _check_special_category(text, cprs): return [] # type: ignore[misc]
@@ -227,9 +231,9 @@ def run_file_scan(source: dict):
ext = Path(rel_path).suffix.lower()
# CPR scan — skip for images (no text layer; EXIF/face detection handles them)
# CPR scan — skip for images, video and audio (no text layer)
result: dict = {"cprs": [], "dates": []}
if ext not in PHOTO_EXTS:
if ext not in PHOTO_EXTS and ext not in VIDEO_EXTS and ext not in AUDIO_EXTS:
try:
result = _scan_bytes_timeout(content, rel_path)
except Exception as e:
@@ -238,13 +242,17 @@ def run_file_scan(source: dict):
cprs = result.get("cprs", [])
# Photo / biometric scan + EXIF extraction
# Photo / biometric scan + EXIF/video/audio metadata extraction
_face_count = 0
_exif = {}
if ext in PHOTO_EXTS:
if scan_photos:
_face_count = _detect_photo_faces(content, rel_path)
_exif = _extract_exif(content, rel_path)
elif ext in VIDEO_EXTS:
_exif = _extract_video_metadata(content, rel_path)
elif ext in AUDIO_EXTS:
_exif = _extract_audio_metadata(content, rel_path)
# Apply filters: distinct CPR threshold and GPS suppression
_distinct_cprs = list(dict.fromkeys(c["formatted"] for c in cprs))
@@ -1084,16 +1092,23 @@ def run_scan(options: dict):
content = conn.download_drive_item_for(uid, item_id)
else:
content = conn.download_item(meta)
result = _scan_bytes(content, name)
cprs = result.get("cprs", [])
# ── Biometric photo scan (#9) + EXIF (#18) ───────────────
# CPR scan — skip for video and audio (metadata-only; no text layer)
_media_only = ext in VIDEO_EXTS or ext in AUDIO_EXTS
result = {"cprs": [], "dates": []} if _media_only else _scan_bytes(content, name)
cprs = result.get("cprs", [])
# ── Biometric photo scan (#9) + EXIF/video/audio metadata (#18) ─
_face_count = 0
_exif = {}
if ext in PHOTO_EXTS:
if scan_photos:
_face_count = _detect_photo_faces(content, name)
_exif = _extract_exif(content, name)
elif ext in VIDEO_EXTS:
_exif = _extract_video_metadata(content, name)
elif ext in AUDIO_EXTS:
_exif = _extract_audio_metadata(content, name)
# Apply filters: distinct CPR threshold and GPS suppression
_distinct_cprs = list(dict.fromkeys(c["formatted"] for c in cprs))