Add frame seeking script for mpv

This commit is contained in:
Thomas Avé 2026-03-13 14:54:48 +07:00
parent 9941dba87a
commit 8a56e69f33
1 changed files with 180 additions and 0 deletions

View File

@ -0,0 +1,180 @@
-- frame-seek.lua
-- Allows seeking to a specific frame number or timestamp
local input = require("mp.input")
local jump_mode = nil -- "frame" or "time"
local relative = false
local minus = false
local fps = 0
function parse_timestamp(input_str)
-- Formats:
-- HH:MM:SS.ms
-- MM:SS.ms
-- SS.ms
-- .ms
-- More than 60 minutes or seconds can be entered - it will seek any amount accurately
-- First try to match HH:MM:SS.ms
local hours, minutes, seconds = input_str:match("^(%d+):(%d+):(%d+%.?%d*)$")
if hours and minutes and seconds then
return tonumber(hours) * 3600 + tonumber(minutes) * 60 + tonumber(seconds)
end
-- Try to match MM:SS.ms
local minutes, seconds = input_str:match("^(%d+):(%d+%.?%d*)$")
if minutes and seconds then
return tonumber(minutes) * 60 + tonumber(seconds)
end
-- Try to match just seconds (with or without decimal)
local seconds = input_str:match("^(%d+%.?%d*)$")
if seconds then
return tonumber(seconds)
end
local milliseconds = input_str:match("^%.(%d+)$")
if milliseconds ~= nil then
return tonumber("0." .. milliseconds)
end
return nil
end
function seek_to_frame(frame_num)
fps = mp.get_property_number("estimated-vf-fps")
if not fps or fps <= 0 then
mp.osd_message("Error: Cannot determine framerate")
return
end
local timestamp = frame_num / fps
seek_to_timestamp(timestamp)
end
function seek_to_timestamp(timestamp)
if minus then timestamp = -timestamp end
local cur_time = mp.get_property_number("time-pos")
if not cur_time then return end
if relative then
if timestamp == 0 then return end
if math.abs(timestamp) < 10 then
mp.commandv("seek", timestamp, "exact")
else
-- Only show OSD if seek >10s
mp.command("seek " .. timestamp .. " exact")
end
else
-- Handle imprecise float
if math.abs(timestamp - cur_time) < 1e-7 then return end
mp.command("seek " .. timestamp .. " absolute+exact")
end
mp.observe_property("time-pos", "number", display_osd_message)
end
function display_osd_message(_, timestamp)
if timestamp == nil then return end
mp.unobserve_property(display_osd_message)
-- Format the display nicely
local hours = math.floor(timestamp / 3600)
local minutes = math.floor((timestamp % 3600) / 60)
local seconds = math.floor(timestamp % 60)
local milliseconds = math.floor((timestamp % 1) * 1000 + 0.5)
local display_time = string.format("%02d:%02d", minutes, seconds)
if hours ~= 0 then
display_time = string.format("%d:", hours) .. display_time
end
if milliseconds ~= 0 or jump_mode == "frame" then
display_time = display_time .. string.format(".%03d", milliseconds)
end
if jump_mode == "frame" and fps and fps > 0 then
local frame_num = math.floor(timestamp * fps + 0.5)
mp.osd_message(string.format("Seeking to frame %d (%s)", frame_num, display_time))
else
mp.osd_message(string.format("Seeking to %s", display_time))
end
end
function jump_submit(input)
if not input or input == "" then
reset()
return
end
-- Handle relative marker
if input:sub(1, 1) == "r" then
relative = true
input = input:sub(2)
end
-- Handle negative input
minus = false
if input:sub(1, 1) == "-" then
minus = true
input = input:sub(2)
end
if jump_mode == "frame" then
local frame_num = tonumber(input)
if frame_num then
seek_to_frame(math.floor(frame_num))
else
mp.osd_message("Invalid frame number")
end
elseif jump_mode == "time" then
local timestamp = parse_timestamp(input)
if timestamp then
seek_to_timestamp(timestamp)
else
mp.osd_message("Invalid timestamp format")
end
end
end
function reset()
jump_mode = nil
relative = false
minus = false
end
function run_script(mode, prompt, relative_flag)
if mp.get_property("path") == nil then return end
reset()
jump_mode = mode
relative = relative_flag
input.get({
prompt = prompt,
submit = jump_submit,
})
end
-- Register key bindings
mp.add_key_binding("ctrl+t", "seek-timestamp", function()
run_script("time", "Seek to time:", false)
end)
mp.add_key_binding("ctrl+T", "seek-frame", function()
run_script("frame", "Seek to frame:", false)
end)
mp.add_key_binding(nil, "seek-timestamp-relative", function()
run_script("time", "Seek forward by time:", true)
end)
mp.add_key_binding(nil, "seek-frame-relative", function()
run_script("frame", "Seek forward by frame:", true)
end)