- Left/Right arrow keys skip -/+5s (clamped to file bounds), pausing
first. QShortcut auto-repeats on its own while held, so no extra
timer/logic was needed for 'hold to keep skipping'.
- ffmpeg log panel starts hidden (view_menu default flipped to
unchecked) rather than visible by default.
- _load() now shows 'Loading <name>...' in the status bar and forces
one repaint before the blocking mpv load call, then 'Loaded <name>'
afterward -- player.load() itself stays synchronous, this just makes
the wait visible instead of a silent freeze.
Also loosened an unrelated flaky assertion in smoke_test_ui.py: hr-seek
was observed landing within ~2 frames of the nominal target across
runs (decode-order/B-frame timing, not a regression), and the old
34ms/1-frame tolerance was tighter than that natural variance.
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.