Root cause of poor discrimination: MFCC[0] (energy) dominated the
feature vector, making cosine similarity see all audio as similar.
Changes:
- Skip MFCC[0], use 12 coefficients instead of 20
- Add delta MFCCs for temporal dynamics
- Add 7-band spectral contrast for tonal vs noise quality
- Switch from cosine similarity to euclidean-distance-based score
- Pre-compute STFT once for whole file (10-20x faster)
- Vectorized sliding window via cumulative sums (no Python loop)
- Lower sample rate 22050→16000 Hz (faster, no quality loss)
- 62-dim feature vector (was 40-dim mean+std of raw MFCCs)
- Default threshold 0.05 (new similarity scale)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mean-only vectors were too similar across different audio segments,
causing everything to match even at threshold 0.99. Adding std
captures temporal dynamics and makes the similarity scores much
more spread out.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Rename ScanWorker.finished → scan_done to stop shadowing
QThread.finished. Previously, cancelled scans leaked the QThread
because the custom signal was never emitted.
2. Block signals on combobox reset in _on_scan_ref_changed to
prevent re-entrant call when user cancels folder dialog.
3. Merge overlapping scan regions into clusters before S-key
navigation so it jumps to the next distinct match, not 1s forward
through overlapping windows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When cancelling a scan during file change, connect finished signal
to deleteLater instead of calling it immediately on a running thread.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Scan button, threshold spinner, mode combobox, and reference source
combobox to the settings row. Implement handler methods for starting scans,
handling results/errors, cleanup of workers, and reference folder selection.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add scan region storage and rendering to TimelineWidget:
- _scan_regions list in __init__ for (start, end, score) tuples
- set_scan_regions() and clear_scan_regions() methods
- paintEvent draws semi-transparent blue rectangles with score-based opacity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Swap Task 5/6 order so get_all_export_paths exists before UI uses it
- Remove cosine similarity clamping to preserve anti-correlation signal
- Use os.path.exists instead of os.path.isfile (handles image sequences)
- Add worker cleanup to disconnect stale signals before new scan
- Remove lock from get_all_export_paths (matches read-only convention)
- Always use get_all_export_paths for Current Profile (not current-file-first)
- Filter export paths with os.path.exists for deleted files
- Use abs() for float comparison in tests instead of ==
- Add cancel_flag to ScanWorker and scan_video for interruptible scans
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Pass integer index (-1) to mpv loadfile command for newer mpv versions
- Poll /api/cache/status instead of streaming endpoints to avoid
downloading video bodies during readiness checks
- Cancel previous polling when selecting a new file
- Fix sidebar flex-shrink and file name text overflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sidebar file browser, canvas timeline, transport bar, export panel,
profile bar, keyboard shortcuts, quality-reactive stream reload.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Persistent BufReader + request_id matching for correct event handling.
Audio-file passed during loadfile for frame-accurate sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15-task plan covering Rust install, Tauri scaffold, mpv sidecar,
API client, stores, UI components, keyboard shortcuts, and packaging.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Triggers on pushes to server branch or version tags when
core/, server/, or Docker files change. Pushes to ghcr.io.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Base: nvidia/cuda:12.6.3-runtime-ubuntu24.04
- ffmpeg from apt has NVENC support when GPU runtime is available
- docker-compose reserves all GPUs via deploy.resources
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Switch DELETE /export to query param (path param strips leading /)
- Add CropKeyframe Pydantic model for typed keyframe validation
- Convert keyframes to tuples before passing to apply_keyframes_to_jobs
- Remove dead QFrame import from main.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- ExportRunner: stop batch on first error (was continuing, overwriting
error status with done)
- Export route: validate input_path against MEDIA_DIRS
- Export route: validate encoder, portrait_ratio, folder_suffix, name
- Export route: fix format check for WebP sequence
- Export route: add _ separator in folder_suffix (match GUI)
- Export route: use realpath consistently in delete endpoint
- Export route: drop runner ref on completion (prevent memory leak)
- ProcessedDB: use cursor-level row_factory (thread-safe)
- WebSocket: catch all exceptions in connect, cleanup in finally
- Dockerfile: use uvicorn[standard] for websockets support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix keyframe 6-tuple → 4-tuple mismatch crashing ExportRunner
- Fix ws.broadcast() using wrong event loop from background threads
- Fix export counter hardcoded to 1, now auto-increments
- Add path traversal protection to file/stream/delete endpoints
- Use proper HTTP error codes (was returning 200 for errors)
- Add thread safety to WebSocket connection list
- Record exports to DB so markers appear
- Move WS endpoint to /ws/export (was /api/ws/export)
- Prune dead threads from cache job tracker
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>