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).
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import locale
|
||||
locale.setlocale(locale.LC_NUMERIC, "C")
|
||||
import mpv
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
CLIP = str(Path(__file__).resolve().parent.parent / "test_media" / "sync_test_INSYNC.mp4")
|
||||
|
||||
m = mpv.MPV(hr_seek="yes", hr_seek_framedrop="no", keep_open="yes", pause=True, vo="null")
|
||||
|
||||
events_seen = []
|
||||
@m.event_callback('playback-restart', 'seek', 'file-loaded')
|
||||
def cb(evt):
|
||||
events_seen.append(str(evt))
|
||||
|
||||
m.play(CLIP)
|
||||
m.wait_for_property("duration", timeout=10)
|
||||
m.pause = True
|
||||
print("loaded, duration=", m.duration, "fps=", m.container_fps)
|
||||
|
||||
m.seek(4.9, reference="absolute", precision="exact")
|
||||
time.sleep(0.5)
|
||||
print("after seek: time_pos=", m.time_pos, "frame=", m.estimated_frame_number)
|
||||
|
||||
for i in range(5):
|
||||
before = m.time_pos
|
||||
try:
|
||||
with m.prepare_and_wait_for_event('playback-restart', timeout=2.0):
|
||||
m.command('frame-step')
|
||||
print(f"step {i}: OK via event, time_pos={m.time_pos} frame={m.estimated_frame_number}")
|
||||
except TimeoutError:
|
||||
print(f"step {i}: TIMEOUT waiting for playback-restart, time_pos={m.time_pos} frame={m.estimated_frame_number} (before={before})")
|
||||
|
||||
m.terminate()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Standalone validation harness for build-order step 1: confirm python-mpv
|
||||
gives frame-accurate seeking before any of the rest of the UI is built.
|
||||
|
||||
Steps frame-by-frame across the known flash event in the synthetic test clip
|
||||
(white flash at t=5.000s, frame 150 @ 30fps) and dumps a screenshot + reported
|
||||
position/frame-number per step, so the results can be inspected directly
|
||||
instead of trusted on faith.
|
||||
"""
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from player_widget import PlayerWidget
|
||||
|
||||
CLIP = str(Path(__file__).resolve().parent.parent / "test_media" / "sync_test_INSYNC.mp4")
|
||||
OUT_DIR = Path(__file__).resolve().parent.parent / "test_media" / "seek_validation"
|
||||
|
||||
|
||||
def main():
|
||||
OUT_DIR.mkdir(exist_ok=True)
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
w = PlayerWidget()
|
||||
w.resize(640, 480)
|
||||
w.show()
|
||||
app.processEvents()
|
||||
|
||||
w.load(CLIP)
|
||||
app.processEvents()
|
||||
print(f"fps reported by mpv: {w.fps()}")
|
||||
|
||||
# Land a few frames before the flash (frame 150 == t=5.000s), then
|
||||
# single-step across it.
|
||||
w.seek(4.900)
|
||||
app.processEvents()
|
||||
time.sleep(0.2)
|
||||
app.processEvents()
|
||||
|
||||
results = []
|
||||
for i in range(8):
|
||||
pos = w.get_position()
|
||||
frame = w.get_estimated_frame()
|
||||
shot = OUT_DIR / f"step_{i:02d}_frame{frame}.png"
|
||||
w.screenshot(str(shot))
|
||||
results.append((i, pos, frame, shot.name))
|
||||
print(f"step {i}: time_pos={pos:.4f}s estimated_frame={frame} -> {shot.name}")
|
||||
w.step_frame(1)
|
||||
app.processEvents()
|
||||
|
||||
w.shutdown()
|
||||
|
||||
print("\nExpected: flash frame is frame index 150 (t=5.000-5.033s).")
|
||||
print("Check the dumped PNGs in test_media/seek_validation/ to confirm")
|
||||
print("exactly one frame is white/bright and it lines up with the")
|
||||
print("reported frame number close to 150.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user