This commit is contained in:
2026-04-03 11:33:51 +02:00
parent 64922e1ae3
commit 0ed904319d
57 changed files with 2935 additions and 1377 deletions
+93 -94
View File
@@ -130,12 +130,14 @@ function tween(from, to, setter, duration_or_callback, callback)
return finish
end
-- Returns signed distance (negative values mean how deep inside the rect the point is).
---@param point Point
---@param rect Rect
function get_point_to_rectangle_proximity(point, rect)
local dx = math.max(rect.ax - point.x, 0, point.x - rect.bx)
local dy = math.max(rect.ay - point.y, 0, point.y - rect.by)
return math.sqrt(dx * dx + dy * dy)
local dx = math.max(rect.ax - point.x, point.x - rect.bx)
local dy = math.max(rect.ay - point.y, point.y - rect.by)
local distance = math.sqrt(math.max(0, dx)^2 + math.max(0, dy)^2)
return distance + math.min(0, math.max(dx, dy))
end
---@param point_a Point
@@ -149,7 +151,7 @@ end
---@param hitbox Hitbox
function point_collides_with(point, hitbox)
return (hitbox.r and get_point_to_point_proximity(point, hitbox.point) <= hitbox.r) or
(not hitbox.r and get_point_to_rectangle_proximity(point, hitbox --[[@as Rect]]) == 0)
(not hitbox.r and get_point_to_rectangle_proximity(point, hitbox --[[@as Rect]]) <= 0)
end
---@param lax number
@@ -221,6 +223,37 @@ function get_ray_to_rectangle_distance(ax, ay, bx, by, rect)
return closest
end
-- Converts a flat table of points to a smooth curve using Catmull-Rom to Bezier conversion.
---@param points number[] Flat table: x1, y1, x2, y2, ...
---@return number[] Flat table: start point followed by segment entries cp1x, cp1y, cp2x, cp2y, px, py, ...
function points_to_bezier(points)
if not points or #points < 4 then return {} end
local function catmullrom_to_bezier(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y)
local cp1x = p1x + (p2x - p0x) / 6
local cp1y = p1y + (p2y - p0y) / 6
local cp2x = p2x - (p3x - p1x) / 6
local cp2y = p2y - (p3y - p1y) / 6
return cp1x, cp1y, cp2x, cp2y
end
-- Helper to get x, y from flat table
local function get_xy(i)
return points[i * 2 - 1], points[i * 2]
end
local curve = {points[1], points[2]}
local xy_pairs = #points / 2
for i = 1, xy_pairs - 1 do
local p0x, p0y = get_xy(math.max(i - 1, 1))
local p1x, p1y = get_xy(i)
local p2x, p2y = get_xy(i+1)
local p3x, p3y = get_xy(math.min(i + 2, xy_pairs))
local cp1x, cp1y, cp2x, cp2y = catmullrom_to_bezier(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y)
local n = #curve
curve[n+1], curve[n+2], curve[n+3], curve[n+4], curve[n+5], curve[n+6] =
cp1x, cp1y, cp2x, cp2y, p2x, p2y
end
return curve
end
-- Extracts the properties used by property expansion of that string.
---@param str string
---@param res { [string] : boolean } | nil
@@ -892,79 +925,17 @@ function call_ziggy_async(args, callback)
end
end
---@param url string
---@param method string
---@param callback fun(error: string|nil, data: table|nil)
---@return fun() abort Function to abort the request.
function http_request_async(method, url, headers, body, callback)
local args = { 'curl', '-s', '-L', '-X', method, url }
if headers then
for k, v in pairs(headers) do
table.insert(args, '-H')
table.insert(args, string.format('%s: %s', k, v))
end
end
if body then
table.insert(args, '-d')
table.insert(args, utils.format_json(body))
end
local abort_signal = mp.command_native_async({
name = 'subprocess',
capture_stdout = true,
capture_stderr = true,
playback_only = false,
args = args
}, function(success, res, error)
local error = error ~= '' and error or res and res.stderr ~= '' and res.stderr or nil
if not success or not res or res.status ~= 0 then
msg.error('HTTP request failed: ' .. (res.stderr or 'unknown error'))
callback(error, nil)
return
end
local data = utils.parse_json(res.stdout)
callback(error, data)
end)
return function()
mp.abort_async_command(abort_signal)
end
end
---@return string|nil
function get_clipboard()
if state.current_clipboard_backend then
if state.platform == 'windows' or state.platform == 'darwin' then
return mp.get_property('clipboard/text', '')
end
if state.platform == 'linux' then
-- Wayland
if os.getenv('WAYLAND_DISPLAY') or os.getenv('WAYLAND_SOCKET') then
if state.current_clipboard_backend == "wayland" or mp.get_property_native("focused") then
return mp.get_property('clipboard/text', '')
end
local res = utils.subprocess({
args = { 'wl-paste', '-n' },
playback_only = false,
})
if not res.error then
return res.stdout
end
end
-- X11
local res = utils.subprocess({
args = { 'xclip', '-selection', 'clipboard', '-out' },
playback_only = false,
})
if not res.error then
return res.stdout
end
end
local data, err = mp.get_property('clipboard/text')
if data then
return data
end
-- Fallback to ziggy
if err and err ~= 'property not found' and err ~= 'property unavailable' then
mp.commandv('show-text', 'Get clipboard error: ' .. err)
return nil
end
local err, data = call_ziggy({'get-clipboard'})
if err then
mp.commandv('show-text', 'Get clipboard error. See console for details.')
@@ -977,23 +948,17 @@ end
---@return string|nil payload String that was copied to clipboard.
function set_clipboard(payload)
payload = tostring(payload)
if state.current_clipboard_backend then
if state.platform == 'windows' or state.platform == 'darwin' then
return mp.commandv('set', 'clipboard/text', payload)
end
if state.platform == 'linux' then
-- Wayland
if os.getenv('WAYLAND_DISPLAY') or os.getenv('WAYLAND_SOCKET') then
return utils.subprocess({ args = { 'wl-copy' }, stdin_data = payload })
end
-- X11
return utils.subprocess({
args = { 'xclip', '-silent', '-selection', 'clipboard', '-in' },
stdin_data = payload
})
end
local success, err = mp.set_property('clipboard/text', payload)
if success then
mp.commandv('show-text', t('Copied to clipboard') .. ': ' .. payload, 3000)
return payload
end
-- Fallback to ziggy
if err and err ~= 'property not found' and err ~= 'property unavailable' then
mp.commandv('show-text', 'Set clipboard error: ' .. err)
return nil
end
local err, data = call_ziggy({'set-clipboard', payload})
if err then
mp.commandv('show-text', 'Set clipboard error. See console for details.')
@@ -1004,6 +969,43 @@ function set_clipboard(payload)
return data and data.payload
end
-- Returns Youtube heatmap data if available.
---@return number[]|nil Flat table of normalized points (01)
function load_youtube_heatmap()
if not state.path or not is_protocol(state.path) then return end
-- Match mpv's ytdl whitelist
if not (state.path:match('^https?://%w+%.youtube%.com/') or
state.path:match('^https?://youtube%.com/') or
state.path:match('^https?://youtu%.be/')) then return end
local r = mp.get_property_native('user-data/mpv/ytdl/json-subprocess-result')
local ytdl_result = r and utils.parse_json(r.stdout)
if ytdl_result and ytdl_result.heatmap then
local data = ytdl_result.heatmap
local max_val = 0
local vid_length = data[#data].end_time
for _, seg in ipairs(data) do
max_val = math.max(max_val, seg.value)
end
-- Normalize and clamp to avoid gaps in heatmap
local is_above = options.timeline_heatmap == 'above'
local min_height, graph_height = 4, is_above and 40 or options.timeline_size
local max_norm_y = 1 - (min_height / graph_height)
local norm = {0, 1}
for _, seg in ipairs(data) do
local center_time = (seg.start_time + seg.end_time) / 2
local norm_x = center_time / vid_length
local norm_y = math.min(max_norm_y, 1 - (seg.value / max_val))
norm[#norm + 1], norm[#norm + 2] = norm_x, norm_y
end
-- Add final anchor
local last_y = math.min(max_norm_y, 1 - (data[#data].value / max_val))
norm[#norm + 1], norm[#norm + 2] = 1, last_y
norm[#norm + 1], norm[#norm + 2] = 1, 1
return points_to_bezier(norm)
end
end
--[[ RENDERING ]]
function render()
@@ -1012,9 +1014,6 @@ function render()
cursor:clear_zones()
-- Click on empty area detection
if setup_click_detection then setup_click_detection() end
-- Actual rendering
local ass = assdraw.ass_new()
@@ -1076,4 +1075,4 @@ function request_render()
local timeout = math.max(0, state.render_delay - (mp.get_time() - state.render_last_time))
state.render_timer.timeout = timeout
state.render_timer:resume()
end
end