Replace the bottom status bar with a right-aligned label on the
settings row, saving vertical space. Add "Export complete" message
when a batch finishes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a pure function that returns the latest keyframe at or before a
given time (with tolerance), replacing the inline lookup pattern that
appears multiple times in main.py. Includes 6 tests covering empty
list, before-first, exact match, between, after-last, and tolerance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove fuzzy matching references (feature was removed), correct
test count (54 → 46), clarify that category is only saved to the
database not dataset.json, and add missing G and ?/F1 shortcuts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extended selection mode (Ctrl+click, Shift+click) enabled. Right-click
context menu adapts to selection count — hide or remove multiple files
at once. Single click without modifiers still loads the file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of hiding QListWidget items (which causes Qt to miscalculate
scroll position), the playlist now rebuilds its contents from scratch
whenever visibility changes. Only visible items exist in the widget.
- _rebuild() clears and repopulates from _paths filtered by visibility
- mark_done/unmark_done update in-place for visible items
- set_hidden_basenames and set_hide_exported trigger _rebuild
- Removes _LockedScrollBar, all scroll hacks, and workarounds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_LockedScrollBar subclasses QScrollBar and blocks setValue when locked.
This catches ALL scroll sources — Qt layout, auto-scroll, item changes —
not just scrollTo. Locked during setCurrentRow, visibility changes,
playlist checks, and video load (200ms after load to catch late events).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keyframes were skipped when ratio was None (random mode), and random
crop was overwriting the resolved center with base_center. Now
keyframes resolve the center for all jobs first, then random crop
assigns the ratio while preserving the per-job center.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ExportWorker now uses Popen instead of subprocess.run so running
ffmpeg processes can be killed on cancel. Cancel button appears
between Export and worker count spinner, enabled during export.
Clips already finished before cancel are kept in the DB.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace timer hacks and scrollbar save/restore with a proper fix:
PlaylistWidget.scrollTo() is overridden to no-op when _scroll_locked
is set. Lock is held during setCurrentRow, visibility changes,
playlist checks, and video load — all operations where Qt's internal
auto-scroll was causing the list to jump.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mpv video surface resize triggers layout events across multiple event
loop cycles. A single deferred restore was too early.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Qt processes layout changes asynchronously, so restoring the scrollbar
immediately after _after_load was too early. Use QTimer.singleShot(0)
to restore after Qt finishes processing pending layout events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Video load triggers layout changes (buttons, crop bar, preview window)
that cause Qt to recalculate the playlist scrollbar position. Now saves
the scroll value in _load_file and restores it at the end of _after_load.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
setCurrentRow, setHidden, and setText all trigger Qt internal scroll
recalculation. Now save scrollbar value before these operations and
restore it after, then use scrollToItem only to bring off-screen
selections into view.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lock-mode path was only updating the crop bar but not the video
overlay or random overlays. Now sets _crop_center and calls the
appropriate overlay update.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Portrait lines are red, square lines are blue — matching the video
overlay. Both shown simultaneously when both random options are on.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_apply_playlist_filters now syncs _hide_exported from checkbox before
refreshing visibility, so exported files are hidden immediately on
startup without needing to toggle the checkbox.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PreviewLabel widget replaces plain QLabel, draws dimmed areas outside
the crop window and blue border lines matching the crop bar position.
Updates live when portrait ratio or crop center changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- _apply_visibility wraps setHidden loop with setUpdatesEnabled and
scrolls back to current item after
- _refresh_playlist_checks wraps mark_done/unmark_done loop similarly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Centralize item visibility in _apply_visibility: hidden if
profile-hidden OR (hide_exported AND done)
- mark_done/unmark_done no longer touch setHidden directly
- Session resume selects first visible item after filters applied
- add_files defers to _select_first_visible to skip hidden items
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Resume last session: reload previous playlist files on startup
- Hide exported checkbox: filter out files with existing clips
- Profile-based hiding: right-click → "Hide in profile" persists via DB
- Playlist scrollbar fix: disable updates during batch add
- Drop event and profile switch use unified _apply_playlist_filters()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Lock button (G key) freezes export cursor, timeline scrubs playback only
- In lock mode, clicking crop bar sets a keyframe at current playback time
- Orange diamonds on timeline show keyframe positions
- Export resolves per-clip crop center from nearest preceding keyframe
- Crop bar/overlay updates while scrubbing to preview effective crop
- Unlocking clears all keyframes
- Replace fuzzy filename matching with exact match to prevent marker bleed
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add WindowStaysOnTopHint to keep preview above other windows
- Raise preview on main window activation (alt-tab, taskbar click)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Track subject checkbox: auto-adjusts crop center per sub-clip using
YOLOv8-nano detection on each start frame
- Detects target nearest to user's crop click, follows same class across clips
- Graceful fallback when ultralytics not installed or detection fails
- Fix end-frame preview not updating on clip/spread change (retry on busy grabber)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Playlist marks show [N] instead of ✓ to indicate how many clips exported
- Export counter scans from 1 to find first available slot instead of only advancing
- Refresh playlist checks and counter after marker/group deletion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- VAAPI: hwdownload before CPU filters, skip HW setup for image sequences
- Delete old DB rows before overwrite re-insert to prevent duplicates
- Stash cursor at export time so async completion uses correct position
- Restore crop_center on marker click, fix falsy 0-value checks
- Remove stale pending marker on export error
- Guard double mpv termination in closeEvent
- SnapPreviewWindow recursion guard for dock/follow moves
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add HW encode checkbox (auto-detects NVENC/VAAPI/QSV/AMF/VideoToolbox),
caps GPU workers to 3 to avoid concurrent session limits
- Add workers spinbox next to Export button to control parallel ffmpeg count
- Enable mpv hwdec=auto for GPU-accelerated video decoding during playback
- Add timestamped terminal logging for startup, file load, export, errors
- Reorder settings row: video settings (Resize, Portrait) then encoding
(Format, HW, Clips, Spread, random crops)
- Fix filename label wrapping by using QSizePolicy.Ignored + no word wrap
- Alternating row colors in playlist for readability
- Snap-to-edge preview window: docks to main window edges, follows on move
- Fix SEGV on exit: explicit mpv shutdown in MainWindow.closeEvent before
Qt tears down QObjects
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add "1 random square" checkbox (1:1 crop) alongside random portrait (9:16);
both share the same ~1-per-3 quota when enabled together
- Multi-overlay system: portrait guides in red, square guides in blue, both
visible simultaneously on the paused frame
- Replace editable profile QComboBox with non-editable dropdown + "New profile..."
item via QInputDialog — fixes markers not updating on profile switch
- Refresh playlist checkmarks on profile switch (add/remove done marks)
- Add "?" button and ?/F1 shortcut to show keyboard shortcuts dialog
- Re-render video on widget resize to preserve aspect ratio
- Compute letterbox/pillarbox video rect for correct overlay + click mapping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Switching profiles now clears the overwrite path, button states, and
next-label so stale state from the old profile doesn't persist.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>