Last active
December 26, 2025 09:59
-
-
Save onyx-nxt/750578ac505dc3560ea80c15bb21112b to your computer and use it in GitHub Desktop.
MPV script that fixes video sync on some broken videos where both the audio and video PTS are incorrect by a constant factor. This is appearent on some videos exported from Topaz VEAI from the looks of it. This essentially works by measuring how much frames drop due to display sync and calculating the correct video fps and changes it via the set…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| local mp = require 'mp' | |
| function fix_sync() | |
| -- Capture the first snapshot | |
| local start_dropped = mp.get_property_number("mistimed-frame-count") or 0 | |
| local fps = mp.get_property_number("container-fps") or 0 | |
| if fps == 0 then return end | |
| mp.add_timeout(1, function() | |
| -- Capture the second snapshot | |
| local end_dropped = mp.get_property_number("mistimed-frame-count") or 0 | |
| local drops_per_second = end_dropped - start_dropped | |
| local dps_threshold = fps * 0.5 | |
| -- If no significant portion of frames were dropped, no need to adjust | |
| if drops_per_second <= dps_threshold then | |
| return | |
| end | |
| local override_fps = fps + drops_per_second | |
| local raw_multiplier = fps / override_fps | |
| local pts_multiplier = math.floor(raw_multiplier * 10 + 0.5) / 10 | |
| -- Apply settings to compensate for drop rate | |
| mp.commandv("vf", "add", "@sync:setpts=".. pts_multiplier .."*PTS") | |
| mp.commandv("af", "add", "@sync:asetpts=" .. pts_multiplier .. "*PTS") | |
| mp.msg.info("Sync fix applied: A/V PTS multiplier set to " .. pts_multiplier) | |
| end) | |
| end | |
| mp.register_event("file-loaded", function() | |
| -- Reset any previous adjustments | |
| mp.command("no-osd vf remove @sync") | |
| mp.command("no-osd af remove @sync") | |
| if mp.get_property_bool("pause") == false then | |
| -- If already playing, start the sync fix | |
| mp.add_timeout(1, fix_sync) | |
| else | |
| -- If paused, wait for unpause. We need the video to be playing to measure drops | |
| local function wait_for_start(name, paused) | |
| if paused == false then | |
| -- The video is playing, start the sync fix | |
| mp.unobserve_property(wait_for_start) | |
| mp.add_timeout(1, fix_sync) | |
| end | |
| end | |
| mp.observe_property("pause", "bool", wait_for_start) | |
| end | |
| end) |
Author
Author
Also this does not work with both video-sync=display-* and the asetpts=N/SR/TB af filter. Just remove that asetpts, its useless with this script.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Full description: