Each export generates clip_NNN_0, clip_NNN_1, clip_NNN_2 offset by the
spread value (2–8s, default 3s). Preview plays the full span covered by
all three clips. Marker click still overwrites a single clip.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Targets last export by default; targets marker-selected clip when one is
active (shown as "Delete <name>"). Clears on cursor move if no last export.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each exported clip writes an entry to <folder>/dataset.json containing
its relative path, sound label, and fps. Re-exporting to the same path
updates the existing entry (upsert). Empty labels are skipped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Queue ✓: mark files green after export and on drop if already in DB
- Export folder auto-created if missing
- Scrub while playing: cursor drag seeks and continues playback
- Counter auto-advances past existing files on disk
- Instant pending marker on timeline when export starts
- WebP quality 92 + lanczos scaling
- seek() guard against unloaded state (SystemError fix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Editable label combo with DB history (most recent first)
- Click marker to arm overwrite mode (↺ indicator, re-exports same file)
- Portrait crop overlay: red bands on cut regions (paused only)
- Cache video dimensions to avoid mpv property reads in paintEvent
- Queue marks done files with ✓ on drop and after export
- Export folder created automatically if missing
- Play continues from new position when scrubbing during playback
- Counter auto-advances past existing files on disk
- Instant marker on timeline when export starts (optimistic)
- Marker right-click delete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
mpv embedding:
- Replace wid/QOpenGLWidget with QOffscreenSurface + QOpenGLFramebufferObject
+ QPainter readback — works on Wayland/KDE without sub-surface compositing
- Force desktop OpenGL 3.3 core profile before QApplication (fixes black output on GLES)
- Timer-based render polling (16 ms) replaces signal-flood from mpv C thread;
fixes playback animation and scrubbing preview
- Fix AB-loop: set ab-loop-a/b to "no" on stop (0 means infinite in mpv)
Timeline:
- Full redesign: time ruler with adaptive major/minor ticks, playhead triangle
handle, selection region with edge lines, numbered marker badges
- Height 160 px; layout collapsed from 4 rows to 2 below timeline
- Markers appear immediately on export (optimistic update before ffmpeg finishes)
- Right-click marker → context menu to delete from DB
Hotkeys:
- Replace keyPressEvent with QShortcut(ApplicationShortcut) so keys work
regardless of focused widget; MpvWidget gets NoFocus policy
Export:
- WebP: switch lossless→lossy quality 85, compression_level 1 (~10x faster)
- Add -threads 0 for full CPU utilisation during decode/filter
- Remember last export folder across sessions via QSettings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TimelineWidget.set_cursor: skip update() when clamped value is
unchanged (e.g. clicking at the boundary repeatedly).
- MainWindow: cache container_fps in self._fps on file load; keyPressEvent
reads the cached value instead of hitting the mpv property on every key.
- PlaylistWidget: add _path_set (set[str]) alongside _paths (list[str])
so duplicate detection in add_files is O(1) instead of O(N).
- SettingsDialog._on_install: quit+wait any previous SetupWorker before
starting a new one, preventing leaked threads and duplicate signal
connections on repeated installs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PlaylistWidget._select: only update the two items whose label changes
(old current → plain name, new current → add ▶) instead of rewriting
every item in the playlist.
- TimelineWidget: pre-compute (t/duration, path) fractions in
_hover_cache on set_markers/set_duration so mouseMoveEvent avoids
dividing by duration on every pixel of mouse movement.
- CropBarWidget: cache QPen in __init__ instead of recreating per frame.
- _step_cursor: update label/state immediately and start the timeline's
existing _seek_timer instead of calling _on_cursor_changed directly,
so held arrow keys are debounced the same as mouse drag.
- _refresh_markers: call _get_markers_for(filename) directly after
export (exact match known) and only fall back to fuzzy find_similar
when no exact-match rows exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TimelineWidget: debounce cursor_changed signal with 16ms timer so
mpv.seek is called at most ~60/s during drag; flush on mouseRelease.
Cache QPen/QFont objects in __init__ instead of recreating per frame.
- _normalize_filename: compile _QUALITY_RE and _SEP_RE once at module
level instead of on every call.
- ProcessedDB: add check_same_thread=False; add _get_markers_for() to
avoid a second find_similar pass; store db path.
- _DBWorker(QThread): runs find_similar + _get_markers_for off the main
thread. _after_load starts the worker instead of blocking; stale
results discarded if the user loads a different file first.
- MainWindow: reuse self._settings instead of creating a new QSettings
instance in the mask row setup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Arrow keys / J / L: step one frame; Shift = 1 second jump
Space / P: toggle play/pause
K: pause and return to cursor
E: trigger export
M: jump to next export marker (wraps)
Keys are suppressed when a text field has focus.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds build_audio_extract_command and runs it in ExportWorker after the
frame sequence completes. Audio written to <sequence_dir>.wav (lossless
pcm_s16le). Extraction failure (no audio stream) is silently ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>