The audio extract length is meant for visualizing/grabbing sequences that can
run minutes long, but the control capped it and stepped in fiddly 0.10s
increments. Raise the range to effectively unlimited (24h; ffmpeg stops cleanly
at end-of-file if the source is shorter) and make the arrows step 1s — typing
still allows sub-second precision. Widen the field for the larger values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_on_extract_audio clamped the duration to (timeline_duration - cursor), so with
the playhead within the requested length of the end (or any under-reported
duration) a 30s request was silently truncated to whatever remained — the user
asked for 30s and got 3s with no indication why.
Drop the clamp: pass the requested length straight to ffmpeg, which stops
cleanly at end-of-file if the source is shorter. Then ffprobe the result and,
when it comes up short, say so ("Saved 3.0s — source ended before 30.0s
requested") instead of silently shrinking. When there's room, 30s now yields
exactly 30s.
Adds core.ffmpeg.probe_duration(). Verified end-to-end: a fitting request
returns the exact length; a genuine near-end request returns the available
audio (rc=0) and is reported as truncated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A dedicated "♪ Extract audio" button on the transport row grabs an exact
length of audio (set via the adjacent length box, from the playhead) and opens
a Save As dialog. Output format follows the chosen extension — WAV (pcm_s16le),
MP3 (libmp3lame), FLAC, m4a/aac, ogg/opus — re-encoding as needed; unknown
extensions let ffmpeg pick from the container.
- core.ffmpeg.build_audio_clip_command(input, start, duration, out_path):
fast-seek + exact -t duration + -vn, codec by extension. Verified end-to-end
(wav/mp3/flac all land at exactly the requested duration).
- Timeline shows the audio area as a distinct teal dashed band spanning
[cursor, cursor+length], updated live as the playhead or length changes, so
you see exactly what will be extracted.
- Length + last save dir persist in QSettings; button enabled once a file loads.
Tests: 3 core (codec-by-extension, exact length, case-insensitive) + 2 GUI
(controls exist, band tracks cursor/length).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two issues with the per-subprofile (subcategory) export buttons:
1. Visibility was decided by a fuzzy `f.endswith("_" + suffix)` match against
the hidden-subcats set. A ghost "_blowjob" (empty-base leftover from the
trailing-slash folder bug) or an unrelated "mp4_no_clap" would match and
hide the wrong button — so enabling a subcategory in the Sub menu never
revealed its export button. Match the exact "<base>_<suffix>" folder name
instead (same name the menu shows and _hidden_subcats stores).
2. The buttons were crammed into the transport row after Export. Move them to
their own row with stretches on both ends so the (often many) "▸ name"
buttons stay centered and out of the transport controls.
Also cleared the polluted hidden_subcats/POV_Front set in the user's QSettings
(ghost "_*" names + a hide-all'd set of real "mp4_*"), so every subcategory is
visible again. Regression test added for the exact-match predicate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A folder ending in "/" made os.path.basename() return "", so subprofile
folders/labels became "_blowjob" instead of "mp4_blowjob" — cluttering the
subcategory menu and breaking the marker↔category match. rstrip the trailing
separator in _tab_export_folder and the three basename(_txt_folder) sites.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirror the manual export path: re-export and auto-export now read the
active tab's LTX-2 params via _ltx2_export_params() and override
short_side/duration plus thread target_fps/snap32/frames through to
ExportWorker. Foley tabs return None and keep byte-identical behavior.
For auto-export, params are captured at batch-build time so queued
batches keep their own geometry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
".../AlexisCrystal/" + "_copy" was producing ".../AlexisCrystal/_copy"; rstrip
the trailing separator first → ".../AlexisCrystal_copy". Regression test uses a
trailing-slash source folder.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Constructing MainWindow loads and (on close) re-saves the playlist tabs; a test
that mutated tab state could persist into the user's real session. Redirect
QSettings to a temp dir at import time.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New tab copies the source tab's video list + separators, gets a unique
"<name> copy" label and an adapted own export folder ("<folder>_copy"), and
inherits the tab-named-folder flag. No files are moved or copied — you export
into the new tab's folder. Keeps Foley/variant datasets separate without the
file-shuffling that a misexport used to require.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump APP_VERSION to 1.1 with a "What's new" entry covering the menu bar,
tabbed control deck, side-by-side panels, status bar, and visual polish.
Add an Interface section to the README. Shortcuts unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pinning 2 of 3 panels previously showed a 3rd "leftover" tab-column, which
read as all-three-pinned and was confusing. Now the split view shows exactly
the pinned panels (pin 2 -> 2 columns, pin 3 -> 3). Adds an always-available
View > Side-by-side panels submenu of checkable toggles as the way to pin a
panel while already in split view (the right-click-tab gesture only works in
tabbed mode). Tests assert exactly-N-columns and the menu-pin path; the win
fixture now resets deck state so tests don't depend on persisted layout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test_deck_stack_exists: _deck_stack present; default shows _control_deck.
- test_pinning_two_panels_switches_to_split: pin 2 panels + refresh →
stack shows _deck_split_container.
Pin via _pinned flags directly (not the toggle handler) so no QSettings
write leaks into other function-scoped windows; existing 6 tests run in
default/tabbed state and still pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mirror the playlist pin→side-by-side pattern for the Export / Crop &
Track / Scan control-deck panels. Right-click a deck tab → "Show
side-by-side"; pinning 2+ panels lays them out as resizable QSplitter
columns, with any unpinned panel kept reachable in a leftover tab-column.
The ✕ header returns a panel to tabs. State persists across launches via
the deck_pinned QSettings key.
- _DeckTabBar: minimal QTabBar emitting pin_toggle_requested(idx).
- _build_control_deck wraps _control_deck + a split container in a
QStackedWidget (_deck_stack), mounted in right_layout in its place;
sets _pinned/_label/_deck_key on each page; builds _deck_panels.
- _refresh_deck_layout / _detach_deck_panels / _clear_deck_split /
_on_deck_pin_toggle / _on_deck_unpin / _save_deck_layout, guarded by
_deck_loading. Reparented pages are setVisible(True) so they don't
render blank (same gotcha the playlist documents).
- Restore block at the end of __init__ reads deck_pinned (str/list).
- Height-pin now targets _deck_stack and fits the tallest split-mode
column (22px header + content) so split mode never clips.
Default (nothing pinned) behaves exactly like the prior tabbed deck.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pin deck panels (Export/Crop/Scan) side-by-side as resizable columns,
mirroring the playlist pin pattern; unpinned panels stay reachable as a
tab-column. Spec for the multi-pane feature.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds MainWindow._build_menubar building File/Edit/Scan/View/Help menus
whose actions reuse the existing handler methods. Profile combo and the
? shortcuts button move from top_bar into a TopRightCorner widget. Adds
_show_about and _rebuild_remove_subprofile_menu helpers. Bidirectional
sync for Hide exported / Show hidden; forward-only sync for Review mode
(reverse added in a later stage).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Tabbed control deck reorg of MainWindow: menu bar for rare actions,
always-visible transport bar, 3-tab control deck (Export / Crop & Track /
Scan), real status bar, plus a visual-polish pass. No behavior, shortcut,
or core/ changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Subcategory marker groups cycled through only 5 colors, so the 6th repeated.
Generate 24 colors across the hue wheel, interleaved (coprime step) so
consecutive groups are ~105 deg apart and colors only repeat after 24.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When autoclip (A / back button) or a wheel scroll reduces the clip span, seek
playback to 3s before the new end and loop there so the shorter cut point can
be reviewed immediately. Growing the play area is unaffected.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The x2/x4 toggle always derives the right speed (1/2/4), but mpv's speed could
still drift out of sync during the ab-loop playback (e.g. across loop seeks),
making it feel half-speed after x2 -> x4 -> x2. Reassert the desired speed on
each render tick while playing so mpv always matches the buttons.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When you move the highlight start (click/drag the timeline), leave a single
faint dashed line at where it was, so an accidental move is easy to undo by
eye. Only the most recent prior position is kept, it's suppressed when leaving
an already-exported spot (it has a marker), and it clears on a new file.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The clip preview loops via ab-loop; mpv's speed could drift out of sync with
the x2/x4 buttons, so toggling a speed off didn't reliably return to normal.
Centralize the desired speed in MpvWidget and reapply it on every play_loop,
and derive the effective speed from the button state on each click so a
re-click cleanly returns to 1x.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The lower/back side button (Qt BackButton) fits the clip count to the current
play position — same as the A hotkey / autoclip. Forward button left unused.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Restore right-click to the marker/keyframe delete menu (no longer toggles
lock); kept the no-seek guard so it doesn't nudge the cursor.
- Middle-click (no drag) now toggles cursor lock; middle-drag still pans.
- Plain mouse wheel over the timeline adds/removes clips (up +1, down -1,
clamped); Ctrl+wheel still zooms.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mousePressEvent fell through to _seek for right-click, and mouseMoveEvent
scrubbed on any held button — so a middle/right click that nudged the mouse a
pixel moved the cursor. Right-click now returns early in press/release, and the
drag-seek in mouseMoveEvent is restricted to the left button. Middle still
bumps the clip count, right still toggles lock / opens the delete menu, left
still scrubs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Right-click on empty timeline toggles cursor lock (G). Right-clicking a
marker/keyframe still opens the delete menu.
- Middle-click (press+release without dragging) adds one to the clip count
and wraps at the max; middle-drag still pans the zoomed view.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A focused QPushButton swallows Space/Enter and a focused spin box swallows
every key (its line edit also makes _KeyFilter suppress the app shortcuts), so
clicking Export or the clip-count spinner left the timeline hotkeys dead.
- Give all main-window buttons (incl. dynamic subprofile/format/unpin buttons)
NoFocus so they never trap keyboard focus.
- Spin boxes clearFocus on editingFinished so hotkeys resume after Enter
(clicking elsewhere already releases focus via _KeyFilter).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
load_for_file no longer runs three DB queries on the UI thread during file
load. A _ScanLoadWorker reads the bundle (hard negatives, scan-export times,
latest scan results) via its own short-lived connection — safe alongside the
main connection now that WAL is on. The table rebuild stays on the UI thread
in _on_scan_bundle_loaded; the timeline scan regions are synced from the new
loaded(filename) signal. Stale results from rapid file switches are ignored,
and the worker is drained on shutdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Group clips by export folder in one scan instead of re-scanning every row for
each folder; also drops the extra get_export_folders() query. Speeds up the
train-dialog stats with many subcategories.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>