Extracts a new PlayerPanel widget (video + its own seek slider/marker strip + its own centered frame-step/play/mute transport row) into player_panel.py. Both Source and Preview tabs now use one, so switching tabs switches a complete, independent monitor -- its own scrubber, its own play state, its own frame-stepping -- not just the video frame with a single shared play button underneath. main.py's global keyboard shortcuts (,/./space/left/right) now dispatch to whichever panel is active via a small _active_panel() helper, so they follow the same 'whichever monitor you're looking at' rule as the on-screen controls. MarkerStrip (clap detection) and the Mark Audio/Video/offset/export row stay outside the tabs, at the MainWindow level -- marking and exporting are inherently about the source file, not the preview clip, so only the timeline+transport moved, not the whole marking workflow. Updated every existing smoke test for the new win.source_panel.* / win.preview_panel.* structure (previously flat win.player, win.seek_slider, etc.), and added a new check to smoke_test_preview.py confirming the Preview tab's slider is a genuinely separate, functional widget -- dragging it seeks the preview player without moving the source player, which is the actual thing this change was for. Verified the full suite on the virtual Xvfb display, plus a live click-through (mark both events, build preview, confirm the Preview tab shows its own populated timeline/transport, not the source's).
88 lines
3.1 KiB
Python
88 lines
3.1 KiB
Python
"""Drives the real MainWindow's clap-detection buttons (not a copy of the
|
|
logic) against the synthetic fixture with a known beep at t=5.350s, then
|
|
simulates a real mouse click on the MarkerStrip widget to confirm
|
|
click-to-seek actually moves the player -- not just that a signal exists.
|
|
"""
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from PyQt6.QtCore import Qt, QPointF
|
|
from PyQt6.QtGui import QMouseEvent
|
|
from PyQt6.QtWidgets import QApplication
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
import main as app_main
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
CLIP = str(ROOT / "test_media" / "sync_test_OUTOFSYNC_audio+350ms.mp4")
|
|
OUT_DIR = ROOT / "test_media" / "seek_validation"
|
|
|
|
|
|
def wait_for(predicate, qapp, timeout=20):
|
|
deadline = time.time() + timeout
|
|
while not predicate() and time.time() < deadline:
|
|
qapp.processEvents()
|
|
time.sleep(0.05)
|
|
return predicate()
|
|
|
|
|
|
def main():
|
|
OUT_DIR.mkdir(exist_ok=True)
|
|
qapp = QApplication(sys.argv)
|
|
win = app_main.MainWindow()
|
|
win.show()
|
|
qapp.processEvents()
|
|
|
|
win._load(CLIP)
|
|
qapp.processEvents()
|
|
time.sleep(0.3)
|
|
qapp.processEvents()
|
|
|
|
assert win.detect_all_action.isEnabled()
|
|
win.detect_all_action.trigger()
|
|
qapp.processEvents()
|
|
assert not win.detect_all_action.isEnabled(), "action should disable while detection runs"
|
|
|
|
ok = wait_for(lambda: win.detect_all_action.isEnabled(), qapp)
|
|
assert ok, "detection did not finish in time"
|
|
|
|
markers = win.source_panel.marker_strip.markers()
|
|
print(f"detected markers: {markers}")
|
|
assert len(markers) >= 1, "expected at least one clap marker"
|
|
# ground truth beep is at t=5.350s in this fixture; onset detection has
|
|
# some inherent lag, so allow a generous window rather than exact match.
|
|
assert any(abs(m - 5.350) < 0.5 for m in markers), f"no marker near the known beep: {markers}"
|
|
|
|
win.grab().save(str(OUT_DIR / "clap_markers_on_timeline.png"))
|
|
|
|
# Simulate a real mouse click on the marker to prove click-to-seek works
|
|
# end to end, not just that the signal is wired.
|
|
target_time = min(markers, key=lambda m: abs(m - 5.350))
|
|
strip = win.source_panel.marker_strip
|
|
strip.resize(win.source_panel.seek_slider.width(), strip.height())
|
|
x = strip._x_for_time(target_time)
|
|
click_pos = QPointF(x, 5)
|
|
|
|
before_pos = win.source_panel.player.get_position()
|
|
event = QMouseEvent(
|
|
QMouseEvent.Type.MouseButtonPress, click_pos, click_pos,
|
|
Qt.MouseButton.LeftButton, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier,
|
|
)
|
|
strip.mousePressEvent(event)
|
|
qapp.processEvents()
|
|
time.sleep(0.2)
|
|
qapp.processEvents()
|
|
|
|
after_pos = win.source_panel.player.get_position()
|
|
print(f"clicked marker at x={x:.1f} (t={target_time:.3f}s); player position before={before_pos:.3f}s after={after_pos:.3f}s")
|
|
assert abs(after_pos - target_time) < 0.1, "clicking the marker should seek the player to that timestamp"
|
|
|
|
win.source_panel.shutdown()
|
|
win.preview_panel.shutdown()
|
|
print("\nCLAP DETECTION SMOKE TEST PASSED")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|