Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784-- Compatibility shim for lua 5.2/5.3unpack = unpack or table.unpack
-- these are used internally by lua.cmp.UNKNOWN_TYPE.info = "this value is inserted if the C type is not supported"mp.UNKNOWN_TYPE.type = "UNKNOWN_TYPE"
mp.ARRAY.info = "native array"mp.ARRAY.type = "ARRAY"
mp.MAP.info = "native map"mp.MAP.type = "MAP"
function mp.get_script_name() return mp.script_nameend
function mp.get_opt(key, def) local opts = mp.get_property_native("options/script-opts") local val = opts[key] if val == nil then val = def end return valend
function mp.input_define_section(section, contents, flags) if flags == nil or flags == "" then flags = "default" end mp.commandv("define-section", section, contents, flags)end
function mp.input_enable_section(section, flags) if flags == nil then flags = "" end mp.commandv("enable-section", section, flags)end
function mp.input_disable_section(section) mp.commandv("disable-section", section)end
-- For dispatching script-binding. This is sent as:-- script-message-to $script_name $binding_name $keystate-- The array is indexed by $binding_name, and has functions like this as value:-- fn($binding_name, $keystate)local dispatch_key_bindings = {}
local message_id = 0local function reserve_binding() message_id = message_id + 1 return "__keybinding" .. tostring(message_id)end
local function dispatch_key_binding(name, state, key_name, key_text) local fn = dispatch_key_bindings[name] if fn then fn(name, state, key_name, key_text) endend
-- "Old", deprecated API
-- each script has its own section, so that they don't conflictlocal default_section = "input_dispatch_" .. mp.script_name
-- Set the list of key bindings. These will override the user's bindings, so-- you should use this sparingly.-- A call to this function will remove all bindings previously set with this-- function. For example, set_key_bindings({}) would remove all script defined-- key bindings.-- Note: the bindings are not active by default. Use enable_key_bindings().---- list is an array of key bindings, where each entry is an array as follow:-- {key, callback_press, callback_down, callback_up}-- key is the key string as used in input.conf, like "ctrl+a"---- callback can be a string too, in which case the following will be added like-- an input.conf line: key .. " " .. callback-- (And callback_down is ignored.)function mp.set_key_bindings(list, section, flags) local cfg = "" for i = 1, #list do local entry = list[i] local key = entry[1] local cb = entry[2] local cb_down = entry[3] local cb_up = entry[4] if type(cb) ~= "string" then local mangle = reserve_binding() dispatch_key_bindings[mangle] = function(name, state) local event = state:sub(1, 1) local is_mouse = state:sub(2, 2) == "m" local def = (is_mouse and "u") or "d" if event == "r" then return end if event == "p" and cb then cb() elseif event == "d" and cb_down then cb_down() elseif event == "u" and cb_up then cb_up() elseif event == def and cb then cb() end end cfg = cfg .. key .. " script-binding " .. mp.script_name .. "/" .. mangle .. "\n" else cfg = cfg .. key .. " " .. cb .. "\n" end end mp.input_define_section(section or default_section, cfg, flags)end
function mp.enable_key_bindings(section, flags) mp.input_enable_section(section or default_section, flags)end
function mp.disable_key_bindings(section) mp.input_disable_section(section or default_section)end
function mp.set_mouse_area(x0, y0, x1, y1, section) mp.input_set_section_mouse_area(section or default_section, x0, y0, x1, y1)end
-- "Newer" and more convenient API
local key_bindings = {}local key_binding_counter = 0local key_bindings_dirty = false
function mp.flush_keybindings() if not key_bindings_dirty then return end key_bindings_dirty = false
for i = 1, 2 do local section, flags local def = i == 1 if def then section = "input_" .. mp.script_name flags = "default" else section = "input_forced_" .. mp.script_name flags = "force" end local bindings = {} for k, v in pairs(key_bindings) do if v.bind and v.forced ~= def then bindings[#bindings + 1] = v end end table.sort(bindings, function(a, b) return a.priority < b.priority end) local cfg = "" for _, v in ipairs(bindings) do cfg = cfg .. v.bind .. "\n" end mp.input_define_section(section, cfg, flags) -- TODO: remove the section if the script is stopped mp.input_enable_section(section, "allow-hide-cursor+allow-vo-dragging") endend
local function add_binding(attrs, key, name, fn, rp) if (type(name) ~= "string") and (name ~= nil) then rp = fn fn = name name = nil end rp = rp or "" if name == nil then name = reserve_binding() end local repeatable = rp == "repeatable" or rp["repeatable"] if rp["forced"] then attrs.forced = true end local key_cb, msg_cb if not fn then fn = function() end end if rp["complex"] then local key_states = { ["u"] = "up", ["d"] = "down", ["r"] = "repeat", ["p"] = "press", } key_cb = function(name, state, key_name, key_text) if key_text == "" then key_text = nil end fn({ event = key_states[state:sub(1, 1)] or "unknown", is_mouse = state:sub(2, 2) == "m", key_name = key_name, key_text = key_text, }) end msg_cb = function() fn({event = "press", is_mouse = false}) end else key_cb = function(name, state) -- Emulate the same semantics as input.c uses for most bindings: -- For keyboard, "down" runs the command, "up" does nothing; -- for mouse, "down" does nothing, "up" runs the command. -- Also, key repeat triggers the binding again. local event = state:sub(1, 1) local is_mouse = state:sub(2, 2) == "m" if event == "r" and not repeatable then return end if is_mouse and (event == "u" or event == "p") then fn() elseif (not is_mouse) and (event == "d" or event == "r" or event == "p") then fn() end end msg_cb = fn end if key and #key > 0 then attrs.bind = key .. " script-binding " .. mp.script_name .. "/" .. name end attrs.name = name -- new bindings override old ones (but do not overwrite them) key_binding_counter = key_binding_counter + 1 attrs.priority = key_binding_counter key_bindings[name] = attrs key_bindings_dirty = true dispatch_key_bindings[name] = key_cb mp.register_script_message(name, msg_cb)end
function mp.add_key_binding(...) add_binding({forced=false}, ...)end
function mp.add_forced_key_binding(...) add_binding({forced=true}, ...)end
function mp.remove_key_binding(name) key_bindings[name] = nil dispatch_key_bindings[name] = nil key_bindings_dirty = true mp.unregister_script_message(name)end
local timers = {}
local timer_mt = {}timer_mt.__index = timer_mt
function mp.add_timeout(seconds, cb) local t = mp.add_periodic_timer(seconds, cb) t.oneshot = true return tend
function mp.add_periodic_timer(seconds, cb) local t = { timeout = seconds, cb = cb, oneshot = false, } setmetatable(t, timer_mt) t:resume() return tend
function timer_mt.stop(t) if timers[t] then timers[t] = nil t.next_deadline = t.next_deadline - mp.get_time() endend
function timer_mt.kill(t) timers[t] = nil t.next_deadline = nilendmp.cancel_timer = timer_mt.kill
function timer_mt.resume(t) if not timers[t] then local timeout = t.next_deadline if timeout == nil then timeout = t.timeout end t.next_deadline = mp.get_time() + timeout timers[t] = t endend
function timer_mt.is_enabled(t) return timers[t] ~= nilend
-- Return the timer that expires next.local function get_next_timer() local best = nil for t, _ in pairs(timers) do if (best == nil) or (t.next_deadline < best.next_deadline) then best = t end end return bestend
function mp.get_next_timeout() local timer = get_next_timer() if not timer then return end local now = mp.get_time() return timer.next_deadline - nowend
-- Run timers that have met their deadline.-- Return: next absolute time a timer expires as number, or nil if no timerslocal function process_timers() while true do local timer = get_next_timer() if not timer then return end local now = mp.get_time() local wait = timer.next_deadline - now if wait > 0 then return wait else if timer.oneshot then timer:kill() else timer.next_deadline = now + timer.timeout end timer.cb() end endend
local messages = {}
function mp.register_script_message(name, fn) messages[name] = fnend
function mp.unregister_script_message(name) messages[name] = nilend
local function message_dispatch(ev) if #ev.args > 0 then local handler = messages[ev.args[1]] if handler then handler(unpack(ev.args, 2)) end endend
local property_id = 0local properties = {}
function mp.observe_property(name, t, cb) local id = property_id + 1 property_id = id properties[id] = cb mp.raw_observe_property(id, name, t)end
function mp.unobserve_property(cb) for prop_id, prop_cb in pairs(properties) do if cb == prop_cb then properties[prop_id] = nil mp.raw_unobserve_property(prop_id) end endend
local function property_change(ev) local prop = properties[ev.id] if prop then prop(ev.name, ev.data) endend
-- used by default event loop (mp_event_loop()) to decide when to quitmp.keep_running = true
local event_handlers = {}
function mp.register_event(name, cb) local list = event_handlers[name] if not list then list = {} event_handlers[name] = list end list[#list + 1] = cb return mp.request_event(name, true)end
function mp.unregister_event(cb) for name, sub in pairs(event_handlers) do local found = false for i, e in ipairs(sub) do if e == cb then found = true break end end if found then -- create a new array, just in case this function was called -- from an event handler local new = {} for i = 1, #sub do if sub[i] ~= cb then new[#new + 1] = sub[i] end end event_handlers[name] = new if #new == 0 then mp.request_event(name, false) end end endend
-- default handlersmp.register_event("shutdown", function() mp.keep_running = false end)mp.register_event("client-message", message_dispatch)mp.register_event("property-change", property_change)
-- called before the event loop goes back to sleeplocal idle_handlers = {}
function mp.register_idle(cb) idle_handlers[#idle_handlers + 1] = cbend
function mp.unregister_idle(cb) local new = {} for _, handler in ipairs(idle_handlers) do if handler ~= cb then new[#new + 1] = handler end end idle_handlers = newend
-- sent by "script-binding"mp.register_script_message("key-binding", dispatch_key_binding)
mp.msg = { log = mp.log, fatal = function(...) return mp.log("fatal", ...) end, error = function(...) return mp.log("error", ...) end, warn = function(...) return mp.log("warn", ...) end, info = function(...) return mp.log("info", ...) end, verbose = function(...) return mp.log("v", ...) end, debug = function(...) return mp.log("debug", ...) end, trace = function(...) return mp.log("trace", ...) end,}
_G.print = mp.msg.info
package.loaded["mp"] = mppackage.loaded["mp.msg"] = mp.msg
function mp.wait_event(t) local r = mp.raw_wait_event(t) if r and r.file_error and not r.error then -- compat; deprecated r.error = r.file_error end return rend
_G.mp_event_loop = function() mp.dispatch_events(true)end
local function call_event_handlers(e) local handlers = event_handlers[e.event] if handlers then for _, handler in ipairs(handlers) do handler(e) end endend
mp.use_suspend = false
local suspend_warned = false
function mp.dispatch_events(allow_wait) local more_events = true if mp.use_suspend then if not suspend_warned then mp.msg.error("mp.use_suspend is now ignored.") suspend_warned = true end end while mp.keep_running do local wait = 0 if not more_events then wait = process_timers() or 1e20 -- infinity for all practical purposes for _, handler in ipairs(idle_handlers) do handler() end -- Resume playloop - important especially if an error happened while -- suspended, and the error was handled, but no resume was done. mp.resume_all() if allow_wait ~= true then return end end local e = mp.wait_event(wait) more_events = false if e.event ~= "none" then call_event_handlers(e) more_events = true end endend
mp.register_idle(mp.flush_keybindings)
-- additional helpers
function mp.osd_message(text, duration) if not duration then duration = "-1" else duration = tostring(math.floor(duration * 1000)) end mp.commandv("show-text", text, duration)end
local hook_table = {}
mp.register_event("hook", function(ev) local fn = hook_table[tonumber(ev.id)] if fn then fn() end mp.raw_hook_continue(ev.hook_id)end)
function mp.add_hook(name, pri, cb) local id = #hook_table + 1 hook_table[id] = cb -- The C API suggests using 0 for a neutral priority, but lua.rst suggests -- 50 (?), so whatever. mp.raw_hook_add(id, name, pri - 50)end
local async_call_table = {}local async_next_id = 1
function mp.command_native_async(node, cb) local id = async_next_id async_next_id = async_next_id + 1 local res, err = mp.raw_command_native_async(id, node) if not res then cb(false, nil, err) return res, err end local t = {cb = cb, id = id} async_call_table[id] = t return tend
mp.register_event("command-reply", function(ev) local id = tonumber(ev.id) local t = async_call_table[id] local cb = t.cb t.id = nil async_call_table[id] = nil if ev.error then cb(false, nil, ev.error) else cb(true, ev.result, nil) endend)
function mp.abort_async_command(t) if t.id ~= nil then mp.raw_abort_async_command(t.id) endend
local overlay_mt = {}overlay_mt.__index = overlay_mtlocal overlay_new_id = 0
function mp.create_osd_overlay(format) overlay_new_id = overlay_new_id + 1 local overlay = { format = format, id = overlay_new_id, data = "", res_x = 0, res_y = 720, } setmetatable(overlay, overlay_mt) return overlayend
function overlay_mt.update(ov) local cmd = {} for k, v in pairs(ov) do cmd[k] = v end cmd.name = "osd-overlay" cmd.res_x = math.floor(cmd.res_x) cmd.res_y = math.floor(cmd.res_y) return mp.command_native(cmd)end
function overlay_mt.remove(ov) mp.command_native { name = "osd-overlay", id = ov.id, format = "none", data = "", }end
-- legacy APIfunction mp.set_osd_ass(res_x, res_y, data) if not mp._legacy_overlay then mp._legacy_overlay = mp.create_osd_overlay("ass-events") end if mp._legacy_overlay.res_x ~= res_x or mp._legacy_overlay.res_y ~= res_y or mp._legacy_overlay.data ~= data then mp._legacy_overlay.res_x = res_x mp._legacy_overlay.res_y = res_y mp._legacy_overlay.data = data mp._legacy_overlay:update() endend
function mp.get_osd_size() local prop = mp.get_property_native("osd-dimensions") return prop.w, prop.h, prop.aspectend
function mp.get_osd_margins() local prop = mp.get_property_native("osd-dimensions") return prop.ml, prop.mt, prop.mr, prop.mbend
local mp_utils = package.loaded["mp.utils"]
function mp_utils.format_table(t, set) if not set then set = { [t] = true } end local res = "{" -- pretty expensive but simple way to distinguish array and map parts of t local keys = {} local vals = {} local arr = 0 for i = 1, #t do if t[i] == nil then break end keys[i] = i vals[i] = t[i] arr = i end for k, v in pairs(t) do if not (type(k) == "number" and k >= 1 and k <= arr and keys[k]) then keys[#keys + 1] = k vals[#keys] = v end end for i = 1, #keys do if #res > 1 then res = res .. ", " end if i > arr then res = res .. mp_utils.to_string(keys[i], set) .. " = " end res = res .. mp_utils.to_string(vals[i], set) end res = res .. "}" return resend
function mp_utils.to_string(v, set) if type(v) == "string" then return "\"" .. v .. "\"" elseif type(v) == "table" then if set then if set[v] then return "[cycle]" end set[v] = true end return mp_utils.format_table(v, set) else return tostring(v) endend
function mp_utils.getcwd() return mp.get_property("working-directory")end
function mp_utils.format_bytes_humanized(b) local d = {"Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"} local i = 1 while b >= 1024 do b = b / 1024 i = i + 1 end return string.format("%0.2f %s", b, d[i] and d[i] or "*1024^" .. (i-1))end
function mp_utils.subprocess(t) local cmd = {} cmd.name = "subprocess" cmd.capture_stdout = true for k, v in pairs(t) do if k == "cancellable" then k = "playback_only" elseif k == "max_size" then k = "capture_size" end cmd[k] = v end local res, err = mp.command_native(cmd) if res == nil then -- an error usually happens only if parsing failed (or no args passed) res = {error_string = err, status = -1} end if res.error_string ~= "" then res.error = res.error_string end return resend
function mp_utils.subprocess_detached(t) mp.commandv("run", unpack(t.args))end
function mp_utils.shared_script_property_set(name, value) if value ~= nil then -- no such thing as change-list with mpv_node, so build a string value mp.commandv("change-list", "shared-script-properties", "append", name .. "=" .. value) else mp.commandv("change-list", "shared-script-properties", "remove", name) endend
function mp_utils.shared_script_property_get(name) local map = mp.get_property_native("shared-script-properties") return map and map[name]end
-- cb(name, value) on change and on initfunction mp_utils.shared_script_property_observe(name, cb) -- it's _very_ wasteful to observe the mpv core "super" property for every -- shared sub-property, but then again you shouldn't use this mp.observe_property("shared-script-properties", "native", function(_, val) cb(name, val and val[name]) end)end
return {}