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>
- 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>
- Autoclip (A): adjusts clip count to fit current pause position
- Pause no longer resets playback position — stays where paused
- Play resumes from pause point instead of restarting
- Spread/clips changes update loop end without restarting playback
- Number keys 1-9 export to subprofiles
- Click-away clears focus from spinboxes so hotkeys work again
- Lock mode: double-click marker jumps cursor to end of clip span
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Subprofiles: lightweight export variants that append a suffix to the
export folder (e.g. _soft, _intense). Each gets its own export button
in the transport row. Managed via "+" menu, persisted in QSettings.
- Play loop now updates immediately when spread/clips spinboxes change.
- Lock mode: ignore stale mpv position updates while user is dragging
to prevent the play position from jumping back.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- setup-windows.ps1: downloads libmpv DLL and ffmpeg, installs pip deps
- 8cut.bat: double-click launcher
- main.py: add_dll_directory for libmpv next to script (not just frozen)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enables cross-platform builds for Windows and macOS. Adds _bin() helper
to resolve bundled ffmpeg in frozen builds, and configures ctypes library
path for bundled libmpv.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>