Files
DCGOS/dotfiles/.config/mpv/scripts/evafast.lua
dichgrem 62fe668348 feat:mpv
2025-08-07 10:56:21 +08:00

314 lines
11 KiB
Lua
Vendored

-- evafast.lua
--
-- Much speed.
--
-- Jumps forwards when right arrow is pressed, speeds up when it's held.
-- Inspired by bilibili.com's player. Allows you to have both seeking and fast-forwarding on the same key.
-- Also supports toggling fastforward mode with a keypress.
-- Adjust --input-ar-delay to define when to start fastforwarding.
-- Define --hr-seek if you want accurate seeking.
-- If you just want a nicer fastforward.lua without hybrid key behavior, set seek_distance to 0.
local options = {
-- How far to jump on press, set to 0 to disable seeking and force fastforward
seek_distance = 5,
-- Playback speed modifier, applied once every speed_interval until cap is reached
speed_increase = 0.1,
speed_decrease = 0.1,
-- At what interval to apply speed modifiers
speed_interval = 0.05,
-- Playback speed cap
speed_cap = 2,
-- Playback speed cap when subtitles are displayed, 'no' for same as speed_cap
subs_speed_cap = 1.6,
-- Multiply current speed by modifier before adjustment (exponential speedup)
-- Use much lower values than default e.g. speed_increase=0.05, speed_decrease=0.025
multiply_modifier = false,
-- Show current speed on the osd (or flash speed if using uosc)
show_speed = true,
-- Show current speed on the osd when toggled (or flash speed if using uosc)
show_speed_toggled = true,
-- Show current speed on the osd when speeding up towards a target time (or flash speed if using uosc)
show_speed_target = false,
-- Show seek actions on the osd (or flash timeline if using uosc)
show_seek = true,
-- Look ahead for smoother transition when subs_speed_cap is set
lookahead = false
}
mp.options = require "mp.options"
mp.options.read_options(options, "evafast")
local uosc_available = false
local repeated = false
local speed_timer = nil
local speedup = true
local no_speedup = false
local jumps_reset_speed = true
local toggle_display = false
local toggle_state = false
local freeze = false
local forced_speed_cap = nil
local use_forced_speed_cap = false
local speedup_target = nil
local function speed_transition(test_speed, target)
local time_for_correction = 0
if not freeze then
while test_speed ~= target do
time_for_correction = time_for_correction + options.speed_interval
if test_speed <= target then
if options.multiply_modifier then
test_speed = math.min(test_speed + (test_speed * options.speed_increase), target)
else
test_speed = math.min(test_speed + options.speed_increase, target)
end
else
if options.multiply_modifier then
test_speed = math.max(test_speed - (test_speed * options.speed_decrease), 1)
else
test_speed = math.max(test_speed - options.speed_decrease, 1)
end
end
if test_speed == 1 then break end
end
end
return time_for_correction
end
local function adjust_speed()
local no_sub_speed = not options.subs_speed_cap or mp.get_property("sub-start") == nil
local effective_speed_cap = no_sub_speed and options.speed_cap or options.subs_speed_cap
local speed = mp.get_property_number("speed", 1)
local old_speed = speed
if options.lookahead and options.subs_speed_cap and no_sub_speed and not use_forced_speed_cap then
local sub_delay = mp.get_property_native("sub-delay")
local sub_visible = mp.get_property_bool("sub-visibility")
if sub_visible then
mp.set_property_bool("sub-visibility", false)
end
mp.command("no-osd sub-step 1")
local sub_next_delay = mp.get_property_native("sub-delay")
local sub_next = sub_delay - sub_next_delay
mp.set_property("sub-delay", sub_delay)
if sub_visible then
mp.set_property_bool("sub-visibility", sub_visible)
end
-- calculate how long it takes to get from current speed to target speed, and use that as threshold for sub_next
local time_for_correction = speed_transition(speed, options.subs_speed_cap)
if sub_next ~= 0 and sub_next <= (time_for_correction * speed) then
effective_speed_cap = options.subs_speed_cap
use_forced_speed_cap = true
forced_speed_cap = effective_speed_cap
end
end
if speedup_target ~= nil then
local current_time = mp.get_property_number("time-pos", 0)
if current_time >= speedup_target then
jumps_reset_speed = true
no_speedup = true
repeated = false
freeze = false
else
local time_for_correction = speed_transition(speed, math.max(math.min(options.speed_cap, options.subs_speed_cap and options.subs_speed_cap or options.speed_cap), 1.1)) -- not effective_speed_cap because it may lead to huge fluctuations in transition speed
if (time_for_correction * speed + current_time) > speedup_target then
effective_speed_cap = 1.1 -- >1 so we don't get stuck trying to catch the target
use_forced_speed_cap = true
forced_speed_cap = effective_speed_cap
else
forced_speed_cap = nil
use_forced_speed_cap = false
end
end
end
if not freeze then
if forced_speed_cap ~= nil then
if speed ~= forced_speed_cap or mp.get_property_bool("pause") then
use_forced_speed_cap = true
end
effective_speed_cap = forced_speed_cap
end
if speedup and not no_speedup and speed <= effective_speed_cap then
if options.multiply_modifier then
speed = math.min(speed + (speed * options.speed_increase), effective_speed_cap)
else
speed = math.min(speed + options.speed_increase, effective_speed_cap)
end
else
if options.multiply_modifier then
speed = math.max(speed - (speed * options.speed_decrease), 1)
else
speed = math.max(speed - options.speed_decrease, 1)
end
end
if forced_speed_cap ~= nil and not use_forced_speed_cap then
forced_speed_cap = nil
end
if speed == options.subs_speed_cap then
if use_forced_speed_cap then
use_forced_speed_cap = false
end
end
end
if speed ~= old_speed then
mp.set_property("speed", speed)
if (options.show_speed and not toggle_display) or (options.show_speed_toggled and toggle_display and speedup_target == nil) or (options.show_speed_target and speedup_target ~= nil) then
if uosc_available then
mp.command("script-binding uosc/flash-speed")
else
mp.osd_message(("▶▶ x%.1f"):format(speed))
end
end
end
if speed == 1 and effective_speed_cap ~= 1 then
if speed_timer ~= nil and not toggle_state then
speed_timer:kill()
speed_timer = nil
end
repeated = false
jumps_reset_speed = true
toggle_display = false
toggle_state = false
speedup_target = nil
elseif speed_timer == nil then
speed_timer = mp.add_periodic_timer(options.speed_interval, adjust_speed)
end
end
local function evafast(keypress)
if jumps_reset_speed and not toggle_state and (keypress["event"] == "up" or keypress["event"] == "press") then
speedup = false
speedup_target = nil
end
if options.seek_distance == 0 then
if keypress["event"] == "up" or keypress["event"] == "press" then
speedup = false
no_speedup = true
repeated = false
speedup_target = nil
end
if keypress["event"] == "down" then
keypress["event"] = "repeat"
end
end
if keypress["event"] == "up" or keypress["event"] == "press" then
toggle_display = toggle_state
if toggle_state and jumps_reset_speed then
speedup = false
speedup_target = nil
end
if speed_timer ~= nil and not toggle_state and mp.get_property_number("speed") == 1 and ((not options.subs_speed_cap or mp.get_property("sub-start") == nil) and options.speed_cap or options.subs_speed_cap) ~= 1 then
speed_timer:kill()
speed_timer = nil
jumps_reset_speed = true
toggle_display = false
toggle_state = false
speedup_target = nil
end
freeze = false
end
if keypress["event"] == "down" then
repeated = false
speedup = true
freeze = true
toggle_display = false
if options.show_seek and not repeated and not uosc_available then
mp.osd_message("▶▶")
end
elseif (keypress["event"] == "up" and (not repeated or speedup_target)) or keypress["event"] == "press" then
if options.seek_distance ~= 0 then
mp.commandv("seek", options.seek_distance)
if options.show_seek and uosc_available then
mp.command("script-binding uosc/flash-timeline")
end
end
repeated = false
if jumps_reset_speed and not toggle_state then
no_speedup = true
end
elseif keypress["event"] == "repeat" then
freeze = false
speedup = true
no_speedup = false
if not repeated then
adjust_speed()
end
repeated = true
end
end
local function evafast_speedup()
no_speedup = false
speedup = true
jumps_reset_speed = false
toggle_display = true
toggle_state = true
evafast({event = "repeat"})
end
local function evafast_slowdown()
jumps_reset_speed = true
no_speedup = true
repeated = false
freeze = false
speedup_target = nil
end
local function evafast_toggle()
if (repeated or not jumps_reset_speed) and speedup then
evafast_slowdown()
else
evafast_speedup()
end
end
mp.register_script_message("uosc-version", function(version)
uosc_available = true
end)
mp.register_script_message("speedup-target", function(time)
time = tonumber(time) or 0
if mp.get_property_number("time-pos", 0) >= time then
if speedup_target ~= nil then
use_forced_speed_cap = false
forced_speed_cap = nil
speedup_target = nil
evafast_slowdown()
end
return
end
speedup_target = time
evafast_speedup()
end)
mp.register_script_message("get-version", function(script)
mp.commandv("script-message-to", script, "evafast-version", "1.0")
end)
mp.add_key_binding("RIGHT", "evafast", evafast, {repeatable = true, complex = true})
mp.add_key_binding(nil, "speedup", evafast_speedup)
mp.add_key_binding(nil, "slowdown", evafast_slowdown)
mp.add_key_binding(nil, "toggle", evafast_toggle)
mp.commandv("script-message-to", "uosc", "get-version", mp.get_script_name())