"""Verifies the sync preview feature: building a short offset-corrected clip and playing it in a popup PreviewDialog, plus the Sync markers panel now showing Mark Audio Event before Mark Video Event with a Detect All Claps button alongside them. PreviewDialog.exec() is a real modal event loop, which would block this script's own flow with no way to close it from inside. Monkeypatches it to show() non-modally instead (capturing the instance) purely for test purposes -- production code still calls the real modal .exec(). """ import sys import time from pathlib import Path 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") def main(): qapp = QApplication(sys.argv) # -- Marker panel order: Mark Audio Event before Mark Video Event, with # Detect All Claps in the same panel. win = app_main.MainWindow() win.show() qapp.processEvents() marker_layout = win.mark_audio_button.parentWidget().layout() widgets = [marker_layout.itemAt(i).widget() for i in range(marker_layout.count())] widgets = [w for w in widgets if w is not None] audio_idx = widgets.index(win.mark_audio_button) video_idx = widgets.index(win.mark_video_button) detect_idx = widgets.index(win.detect_all_button) assert audio_idx < video_idx, "Mark Audio Event should come before Mark Video Event" assert win.detect_all_button in widgets, "Detect All Claps button should be in the same panel" print(f"PASS: marker panel order is audio({audio_idx}) < video({video_idx}), detect button present at {detect_idx}") # -- Preview: mark both events on the known-shifted fixture, build a # preview clip, and confirm it plays back with the events much closer # together than the original 350ms. captured = {} def fake_exec(self): captured["dialog"] = self # This is the only point guaranteed to run before the real # _on_preview_ready's cleanup (os.unlink) fires -- a real .exec() # would block here until the dialog closes, but this fake returns # immediately, so the app's own cleanup-after-exec() code runs # right after this function returns, whether or not the dialog is # still visually open. Check existence here, not after. captured["existed_while_open"] = Path(win._preview_temp_path).exists() self.show() return 0 app_main.PreviewDialog.exec = fake_exec win._load(CLIP) qapp.processEvents() time.sleep(0.2) qapp.processEvents() win.player.seek(5.000) qapp.processEvents() win._mark_video() win.player.seek(5.350) qapp.processEvents() win._mark_audio() assert win.preview_button.isEnabled() win.preview_button.click() qapp.processEvents() assert not win.preview_button.isEnabled(), "preview button should disable while building" temp_path_ref = win._preview_temp_path print(f"building preview clip at: {temp_path_ref}") deadline = time.time() + 20 while "dialog" not in captured and time.time() < deadline: qapp.processEvents() time.sleep(0.05) assert "dialog" in captured, "preview dialog was never shown" dialog = captured["dialog"] qapp.processEvents() time.sleep(0.3) qapp.processEvents() duration = dialog.player.mpv.duration print(f"preview clip duration: {duration:.3f}s (expected ~{win.PREVIEW_DURATION_SEC}s, clamped to file bounds)") assert duration is not None and 0 < duration <= win.PREVIEW_DURATION_SEC + 0.5 assert captured["existed_while_open"], "temp preview file should have existed while the dialog was showing" dialog.close() qapp.processEvents() time.sleep(0.2) qapp.processEvents() assert not Path(temp_path_ref).exists(), "temp preview file should be cleaned up after the (real) exec() returns" print("PASS: preview clip built, played, and cleaned up correctly") win.player.shutdown() print("\nPREVIEW SMOKE TEST PASSED") if __name__ == "__main__": main()