Commit Graph
10 Commits
Author SHA1 Message Date
ThePendulum fdb858d88c Add double-clickable launcher inside the project folder itself
Same Exec as packaging/av-sync-marker.desktop, but placed at the repo
root so it shows up right in the file manager when browsing to
WeAreSyncing/, not just in the app menu. Nemo requires such an
in-folder .desktop file to be both executable and marked 'trusted'
(gio set ... metadata::trusted true) before it'll launch without a
security prompt -- done locally, but note that trust flag lives in
GVFS's metadata store keyed off this file's inode, not in the file
itself, so it won't survive the file being rewritten (e.g. by a fresh
git checkout) and may need to be re-set with the same gio command.
2026-07-26 02:05:04 +02:00
ThePendulum f37e8ed5ea Add .desktop launcher for file-browser/menu launching
packaging/av-sync-marker.desktop wraps run.sh so the app can be
launched from Cinnamon's menu or double-clicked/opened-with from a
file manager (Nemo etc.), including passing a selected video file as
the initial file to load via %f -- main.py already supported a CLI
file argument, this just gives the file manager a way to invoke it.

Installed to ~/.local/share/applications/ (user-level, no sudo) and
verified with `gio launch`, which is what a file manager's 'Open
With' actually invokes under the hood, not just desktop-file-validate.
2026-07-26 02:01:29 +02:00
ThePendulum 5a6c4e6e84 UI/UX polish: Tools menu, clickable timeline, live drag-seek, drag-and-drop load
- Clap detection moved from inline buttons to a Tools menu, plain labels
  (no more '(whole file)' / '(from cursor)' parentheticals) -- status
  messages moved to the window's status bar since the dedicated label
  widget went away with the button row.
- ClickableSlider subclass: clicking anywhere along the seek track jumps
  straight there instead of QSlider's default page-by-a-step behavior;
  reposition-then-delegate-to-super keeps native drag behavior intact.
- Dragging now seeks live via sliderMoved instead of waiting for
  sliderReleased.
- PlayerWidget accepts drag-and-drop of a local file to load it.

Verified with real Qt events (QMouseEvent clicks, a QDropEvent, a real
mouse click via xdotool opening the Tools menu on an actual running
window) rather than just calling the underlying methods directly.
2026-07-26 01:55:32 +02:00
ThePendulum f7c15e924c Add clap/onset auto-detection feature
Ports scripts/detect_claps.py (a librosa-based onset detector from an
earlier session) into clap_detection.py, shared by both the CLI script
and a new GUI feature: 'Detect All Claps' (whole-file scan) and
'Detect Next Clap' (from cursor, first result only), with results
shown as clickable markers on a new MarkerStrip widget above the seek
slider -- clicking one seeks the player to that exact timestamp.

Caught a real correctness bug while testing against the out-of-sync
fixture (not just the plain in-sync one): ffmpeg's default -vn audio
extraction silently drops the audio stream's own start_time and
renormalizes to t=0 -- which is exactly the offset this whole tool
exists to fix, so losing it meant detected timestamps pointed at the
wrong track's timing entirely. Fixed by probing the true start_time
via ffprobe and re-inserting it as explicit silence (adelay) before
analysis. Also dropped an earlier attempt at an ffmpeg -ss seek
optimization for the from-cursor case, since combining it with the
start_time fix meant two independent timestamp-origin corrections
stacked on each other -- correct in theory, too easy to get subtly
wrong in a tool where a silent wrong-timestamp bug is the worst
possible failure mode. Full-file decode + filter in Python is slower
but unambiguous.

Verified with a smoke test that drives the real MainWindow buttons,
confirms the detected marker lands near the fixture's known beep
(t=5.350s, allowing for onset-detection lag), and simulates an actual
QMouseEvent click on the marker to confirm click-to-seek works
end-to-end.
2026-07-26 01:46:11 +02:00
ThePendulum f6e6f79a34 Add View menu with a toggle to hide/show the ffmpeg log panel 2026-07-26 01:24:34 +02:00
ThePendulum 660275bf02 Build the full UI (build order step 4)
main.py wires up PlayerWidget with transport controls (play/pause,
frame-step buttons + ,/. shortcuts, seek slider), mute toggle, mark
video/audio buttons, live offset readout with manual override, and an
async export flow (QThread worker so ffmpeg doesn't block the UI) that
surfaces ffmpeg's stderr log without treating stream-copy warnings as
failures.

Verified two ways, not just by reading the code: a headless smoke test
that drives the real MainWindow/ExportWorker classes through a full
mark -> offset -> export cycle on the synthetic fixture, and a live
run (actual process, real X11 window, xdotool + import screenshot)
confirming the embedded mpv surface actually renders video inside the
Qt window rather than staying blank.
2026-07-26 00:53:51 +02:00
ThePendulum f634dda5bb Implement sync_logic.py and ffmpeg_export.py (build order steps 2-3)
sync_logic.py: pure delay_ms computation with the documented sign
convention. Caught and fixed a sign error in my own test-fixture
ground-truth doc while writing the unit tests: the audio+350ms fixture
actually requires delay_ms=-350 (delay video), not +350, since the
audio is already the late one.

ffmpeg_export.py: ports ffdelay's two-branch command structure
(offset video input vs audio input depending on delay_ms sign) plus
its documented quirks (leading-zero float formatting, -ss/-t applied
to both inputs for clip mode, non-fatal stream-copy warnings surfaced
via stderr rather than swallowed).

Added an end-to-end integration test (scripts/test_export_roundtrip.py)
that independently re-detects event timing in the exported output via
ffmpeg scene-change/silence detection (not the same code path used to
build the export) and confirms both the positive- and negative-delay
branches bring drift down from ~350ms to under 1ms.
2026-07-26 00:50:57 +02:00
ThePendulum 91390f751d Validate frame-accurate seeking with python-mpv (build order step 1)
Confirms single-frame stepping is reliable: monotonic frame counter
across steps, and the synthetic clip's 1-frame flash event lands on
exactly one frame with clean neighbors. Also documents/works around
three environment quirks hit along the way: locally-built libmpv not
in ldconfig's cache, QApplication resetting LC_NUMERIC and breaking
libmpv's locale requirement, and the playback-restart event not firing
reliably after frame-step (switched to polling estimated_frame_number).
2026-07-26 00:37:42 +02:00
ThePendulum 7584970498 Add synthetic ground-truth test clips (in-sync + audio+350ms) 2026-07-25 23:11:38 +02:00
ThePendulum e5abb1293b Add project brief 2026-07-25 23:09:55 +02:00