f
This commit is contained in:
501
MPV/LUA/main.lua
501
MPV/LUA/main.lua
@@ -463,6 +463,193 @@ local function ensure_mpv_ipc_server()
|
|||||||
return (now and now ~= '') and true or false
|
return (now and now ~= '') and true or false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function ensure_pipeline_helper_running()
|
||||||
|
-- IMPORTANT: do NOT spawn Python from inside mpv.
|
||||||
|
-- The Python side (MPV.mpv_ipc) starts pipeline_helper.py using Windows
|
||||||
|
-- no-console flags; spawning here can flash a console window.
|
||||||
|
return _is_pipeline_helper_ready() and true or false
|
||||||
|
end
|
||||||
|
|
||||||
|
local _ipc_async_busy = false
|
||||||
|
local _ipc_async_queue = {}
|
||||||
|
|
||||||
|
local function _run_helper_request_async(req, timeout_seconds, cb)
|
||||||
|
cb = cb or function() end
|
||||||
|
|
||||||
|
if _ipc_async_busy then
|
||||||
|
_ipc_async_queue[#_ipc_async_queue + 1] = { req = req, timeout = timeout_seconds, cb = cb }
|
||||||
|
return
|
||||||
|
end
|
||||||
|
_ipc_async_busy = true
|
||||||
|
|
||||||
|
local function done(resp, err)
|
||||||
|
_ipc_async_busy = false
|
||||||
|
cb(resp, err)
|
||||||
|
|
||||||
|
if #_ipc_async_queue > 0 then
|
||||||
|
local next_job = table.remove(_ipc_async_queue, 1)
|
||||||
|
-- Schedule next job slightly later to let mpv deliver any pending events.
|
||||||
|
mp.add_timeout(0.01, function()
|
||||||
|
_run_helper_request_async(next_job.req, next_job.timeout, next_job.cb)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(req) ~= 'table' then
|
||||||
|
done(nil, 'invalid request')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure_mpv_ipc_server()
|
||||||
|
if not ensure_pipeline_helper_running() then
|
||||||
|
done(nil, 'helper not running')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Assign id.
|
||||||
|
local id = tostring(req.id or '')
|
||||||
|
if id == '' then
|
||||||
|
id = tostring(math.floor(mp.get_time() * 1000)) .. '-' .. tostring(math.random(100000, 999999))
|
||||||
|
req.id = id
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = ''
|
||||||
|
if req.op then
|
||||||
|
label = 'op=' .. tostring(req.op)
|
||||||
|
elseif req.pipeline then
|
||||||
|
label = 'cmd=' .. tostring(req.pipeline)
|
||||||
|
else
|
||||||
|
label = '(unknown)'
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Wait for helper READY without blocking the UI.
|
||||||
|
local ready_deadline = mp.get_time() + 3.0
|
||||||
|
local ready_timer
|
||||||
|
ready_timer = mp.add_periodic_timer(0.05, function()
|
||||||
|
if _is_pipeline_helper_ready() then
|
||||||
|
ready_timer:kill()
|
||||||
|
|
||||||
|
_lua_log('ipc-async: send request id=' .. tostring(id) .. ' ' .. label)
|
||||||
|
local req_json = utils.format_json(req)
|
||||||
|
_last_ipc_last_req_json = req_json
|
||||||
|
|
||||||
|
mp.set_property(PIPELINE_RESP_PROP, '')
|
||||||
|
mp.set_property(PIPELINE_REQ_PROP, req_json)
|
||||||
|
|
||||||
|
local deadline = mp.get_time() + (timeout_seconds or 5)
|
||||||
|
local poll_timer
|
||||||
|
poll_timer = mp.add_periodic_timer(0.05, function()
|
||||||
|
if mp.get_time() >= deadline then
|
||||||
|
poll_timer:kill()
|
||||||
|
done(nil, 'timeout waiting response (' .. label .. ')')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local resp_json = mp.get_property(PIPELINE_RESP_PROP)
|
||||||
|
if resp_json and resp_json ~= '' then
|
||||||
|
_last_ipc_last_resp_json = resp_json
|
||||||
|
local ok, resp = pcall(utils.parse_json, resp_json)
|
||||||
|
if ok and resp and resp.id == id then
|
||||||
|
poll_timer:kill()
|
||||||
|
_lua_log('ipc-async: got response id=' .. tostring(id) .. ' success=' .. tostring(resp.success))
|
||||||
|
done(resp, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if mp.get_time() >= ready_deadline then
|
||||||
|
ready_timer:kill()
|
||||||
|
done(nil, 'helper not ready')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function _run_helper_request_response(req, timeout_seconds)
|
||||||
|
_last_ipc_error = ''
|
||||||
|
if not ensure_pipeline_helper_running() then
|
||||||
|
local rv = tostring(mp.get_property(PIPELINE_READY_PROP) or mp.get_property_native(PIPELINE_READY_PROP) or '')
|
||||||
|
_lua_log('ipc: helper not ready (ready=' .. rv .. '); attempting request anyway')
|
||||||
|
_last_ipc_error = 'helper not ready'
|
||||||
|
end
|
||||||
|
|
||||||
|
do
|
||||||
|
-- Best-effort wait for heartbeat, but do not hard-fail the request.
|
||||||
|
local deadline = mp.get_time() + 1.5
|
||||||
|
while mp.get_time() < deadline do
|
||||||
|
if _is_pipeline_helper_ready() then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
mp.wait_event(0.05)
|
||||||
|
end
|
||||||
|
if not _is_pipeline_helper_ready() then
|
||||||
|
local rv = tostring(mp.get_property(PIPELINE_READY_PROP) or mp.get_property_native(PIPELINE_READY_PROP) or '')
|
||||||
|
_lua_log('ipc: proceeding without helper heartbeat; ready=' .. rv)
|
||||||
|
_last_ipc_error = 'helper heartbeat missing (ready=' .. rv .. ')'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(req) ~= 'table' then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local id = tostring(req.id or '')
|
||||||
|
if id == '' then
|
||||||
|
id = tostring(math.floor(mp.get_time() * 1000)) .. '-' .. tostring(math.random(100000, 999999))
|
||||||
|
req.id = id
|
||||||
|
end
|
||||||
|
|
||||||
|
local label = ''
|
||||||
|
if req.op then
|
||||||
|
label = 'op=' .. tostring(req.op)
|
||||||
|
elseif req.pipeline then
|
||||||
|
label = 'cmd=' .. tostring(req.pipeline)
|
||||||
|
else
|
||||||
|
label = '(unknown)'
|
||||||
|
end
|
||||||
|
_lua_log('ipc: send request id=' .. tostring(id) .. ' ' .. label)
|
||||||
|
|
||||||
|
local req_json = utils.format_json(req)
|
||||||
|
_last_ipc_last_req_json = req_json
|
||||||
|
mp.set_property(PIPELINE_RESP_PROP, '')
|
||||||
|
mp.set_property(PIPELINE_REQ_PROP, req_json)
|
||||||
|
-- Read-back for debugging: confirms MPV accepted the property write.
|
||||||
|
local echoed = mp.get_property(PIPELINE_REQ_PROP) or ''
|
||||||
|
if echoed == '' then
|
||||||
|
_lua_log('ipc: WARNING request property echoed empty after set')
|
||||||
|
end
|
||||||
|
|
||||||
|
local deadline = mp.get_time() + (timeout_seconds or 5)
|
||||||
|
while mp.get_time() < deadline do
|
||||||
|
local resp_json = mp.get_property(PIPELINE_RESP_PROP)
|
||||||
|
if resp_json and resp_json ~= '' then
|
||||||
|
_last_ipc_last_resp_json = resp_json
|
||||||
|
local ok, resp = pcall(utils.parse_json, resp_json)
|
||||||
|
if ok and resp and resp.id == id then
|
||||||
|
_lua_log('ipc: got response id=' .. tostring(id) .. ' success=' .. tostring(resp.success))
|
||||||
|
return resp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
mp.wait_event(0.05)
|
||||||
|
end
|
||||||
|
|
||||||
|
_lua_log('ipc: timeout waiting response; ' .. label)
|
||||||
|
_last_ipc_error = 'timeout waiting response (' .. label .. ')'
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- IPC helper: return the whole response object (stdout/stderr/error/table)
|
||||||
|
local function run_pipeline_via_ipc_response(pipeline_cmd, seeds, timeout_seconds)
|
||||||
|
local req = { pipeline = pipeline_cmd }
|
||||||
|
if seeds then
|
||||||
|
req.seeds = seeds
|
||||||
|
end
|
||||||
|
return _run_helper_request_response(req, timeout_seconds)
|
||||||
|
end
|
||||||
|
|
||||||
local function quote_pipeline_arg(s)
|
local function quote_pipeline_arg(s)
|
||||||
-- Ensure URLs with special characters (e.g. &, #) survive pipeline parsing.
|
-- Ensure URLs with special characters (e.g. &, #) survive pipeline parsing.
|
||||||
s = tostring(s or '')
|
s = tostring(s or '')
|
||||||
@@ -758,11 +945,25 @@ local function _capture_screenshot()
|
|||||||
local temp_dir = mp.get_property('user-data/medeia-config-temp') or os.getenv('TEMP') or os.getenv('TMP') or '/tmp'
|
local temp_dir = mp.get_property('user-data/medeia-config-temp') or os.getenv('TEMP') or os.getenv('TMP') or '/tmp'
|
||||||
local out_path = utils.join_path(temp_dir, filename)
|
local out_path = utils.join_path(temp_dir, filename)
|
||||||
|
|
||||||
local ok = pcall(function()
|
local function do_screenshot(mode)
|
||||||
mp.commandv('screenshot-to-file', out_path, 'video')
|
mode = mode or 'video'
|
||||||
|
local ok, err = pcall(function()
|
||||||
|
return mp.commandv('screenshot-to-file', out_path, mode)
|
||||||
end)
|
end)
|
||||||
|
return ok, err
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Try 'video' first (no OSD). If that fails (e.g. audio mode without video/art),
|
||||||
|
-- try 'window' as fallback.
|
||||||
|
local ok = do_screenshot('video')
|
||||||
if not ok then
|
if not ok then
|
||||||
mp.osd_message('Screenshot failed', 2)
|
_lua_log('screenshot: video-mode failed; trying window-mode')
|
||||||
|
ok = do_screenshot('window')
|
||||||
|
end
|
||||||
|
|
||||||
|
if not ok then
|
||||||
|
_lua_log('screenshot: BOTH video and window modes FAILED')
|
||||||
|
mp.osd_message('Screenshot failed (no frames)', 2)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -776,29 +977,19 @@ local function _capture_screenshot()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local python_exe = _resolve_python_exe(true)
|
mp.osd_message('Saving screenshot...', 1)
|
||||||
if not python_exe or python_exe == '' then
|
|
||||||
mp.osd_message('Screenshot saved; Python not found', 3)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local start_dir = mp.get_script_directory() or ''
|
-- optimization: use persistent pipeline helper instead of spawning new python process
|
||||||
local cli_py = find_file_upwards(start_dir, 'CLI.py', 8)
|
-- escape paths for command line
|
||||||
if not cli_py or cli_py == '' or not utils.file_info(cli_py) then
|
local cmd = 'add-file -store "' .. selected_store .. '" -path "' .. out_path .. '"'
|
||||||
mp.osd_message('Screenshot saved; CLI.py not found', 3)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local res = utils.subprocess({
|
local resp = run_pipeline_via_ipc_response(cmd, nil, 10)
|
||||||
args = { python_exe, cli_py, 'add-file', '-store', selected_store, '-path', out_path },
|
|
||||||
cancellable = false,
|
|
||||||
})
|
|
||||||
|
|
||||||
if res and res.status == 0 then
|
if resp and resp.success then
|
||||||
mp.osd_message('Screenshot saved to store: ' .. selected_store, 3)
|
mp.osd_message('Screenshot saved to store: ' .. selected_store, 3)
|
||||||
else
|
else
|
||||||
local stderr = (res and res.stderr) or 'unknown error'
|
local err = (resp and resp.error) or (resp and resp.stderr) or 'IPC error'
|
||||||
mp.osd_message('Screenshot upload failed: ' .. tostring(stderr), 5)
|
mp.osd_message('Screenshot upload failed: ' .. tostring(err), 5)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1054,91 +1245,6 @@ local function _pick_folder_windows()
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Forward declaration: used by run_pipeline_via_ipc_response before definition.
|
|
||||||
local ensure_pipeline_helper_running
|
|
||||||
|
|
||||||
local function _run_helper_request_response(req, timeout_seconds)
|
|
||||||
_last_ipc_error = ''
|
|
||||||
if not ensure_pipeline_helper_running() then
|
|
||||||
local rv = tostring(mp.get_property(PIPELINE_READY_PROP) or mp.get_property_native(PIPELINE_READY_PROP) or '')
|
|
||||||
_lua_log('ipc: helper not ready (ready=' .. rv .. '); attempting request anyway')
|
|
||||||
_last_ipc_error = 'helper not ready'
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
-- Best-effort wait for heartbeat, but do not hard-fail the request.
|
|
||||||
local deadline = mp.get_time() + 1.5
|
|
||||||
while mp.get_time() < deadline do
|
|
||||||
if _is_pipeline_helper_ready() then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
mp.wait_event(0.05)
|
|
||||||
end
|
|
||||||
if not _is_pipeline_helper_ready() then
|
|
||||||
local rv = tostring(mp.get_property(PIPELINE_READY_PROP) or mp.get_property_native(PIPELINE_READY_PROP) or '')
|
|
||||||
_lua_log('ipc: proceeding without helper heartbeat; ready=' .. rv)
|
|
||||||
_last_ipc_error = 'helper heartbeat missing (ready=' .. rv .. ')'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(req) ~= 'table' then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local id = tostring(req.id or '')
|
|
||||||
if id == '' then
|
|
||||||
id = tostring(math.floor(mp.get_time() * 1000)) .. '-' .. tostring(math.random(100000, 999999))
|
|
||||||
req.id = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local label = ''
|
|
||||||
if req.op then
|
|
||||||
label = 'op=' .. tostring(req.op)
|
|
||||||
elseif req.pipeline then
|
|
||||||
label = 'cmd=' .. tostring(req.pipeline)
|
|
||||||
else
|
|
||||||
label = '(unknown)'
|
|
||||||
end
|
|
||||||
_lua_log('ipc: send request id=' .. tostring(id) .. ' ' .. label)
|
|
||||||
|
|
||||||
local req_json = utils.format_json(req)
|
|
||||||
_last_ipc_last_req_json = req_json
|
|
||||||
mp.set_property(PIPELINE_RESP_PROP, '')
|
|
||||||
mp.set_property(PIPELINE_REQ_PROP, req_json)
|
|
||||||
-- Read-back for debugging: confirms MPV accepted the property write.
|
|
||||||
local echoed = mp.get_property(PIPELINE_REQ_PROP) or ''
|
|
||||||
if echoed == '' then
|
|
||||||
_lua_log('ipc: WARNING request property echoed empty after set')
|
|
||||||
end
|
|
||||||
|
|
||||||
local deadline = mp.get_time() + (timeout_seconds or 5)
|
|
||||||
while mp.get_time() < deadline do
|
|
||||||
local resp_json = mp.get_property(PIPELINE_RESP_PROP)
|
|
||||||
if resp_json and resp_json ~= '' then
|
|
||||||
_last_ipc_last_resp_json = resp_json
|
|
||||||
local ok, resp = pcall(utils.parse_json, resp_json)
|
|
||||||
if ok and resp and resp.id == id then
|
|
||||||
_lua_log('ipc: got response id=' .. tostring(id) .. ' success=' .. tostring(resp.success))
|
|
||||||
return resp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
mp.wait_event(0.05)
|
|
||||||
end
|
|
||||||
|
|
||||||
_lua_log('ipc: timeout waiting response; ' .. label)
|
|
||||||
_last_ipc_error = 'timeout waiting response (' .. label .. ')'
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- IPC helper: return the whole response object (stdout/stderr/error/table)
|
|
||||||
local function run_pipeline_via_ipc_response(pipeline_cmd, seeds, timeout_seconds)
|
|
||||||
local req = { pipeline = pipeline_cmd }
|
|
||||||
if seeds then
|
|
||||||
req.seeds = seeds
|
|
||||||
end
|
|
||||||
return _run_helper_request_response(req, timeout_seconds)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function _store_names_key(names)
|
local function _store_names_key(names)
|
||||||
if type(names) ~= 'table' or #names == 0 then
|
if type(names) ~= 'table' or #names == 0 then
|
||||||
return ''
|
return ''
|
||||||
@@ -1438,101 +1544,6 @@ local function _get_cached_formats_table(url)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function _run_helper_request_async(req, timeout_seconds, cb)
|
|
||||||
cb = cb or function() end
|
|
||||||
|
|
||||||
if _ipc_async_busy then
|
|
||||||
_ipc_async_queue[#_ipc_async_queue + 1] = { req = req, timeout = timeout_seconds, cb = cb }
|
|
||||||
return
|
|
||||||
end
|
|
||||||
_ipc_async_busy = true
|
|
||||||
|
|
||||||
local function done(resp, err)
|
|
||||||
_ipc_async_busy = false
|
|
||||||
cb(resp, err)
|
|
||||||
|
|
||||||
if #_ipc_async_queue > 0 then
|
|
||||||
local next_job = table.remove(_ipc_async_queue, 1)
|
|
||||||
-- Schedule next job slightly later to let mpv deliver any pending events.
|
|
||||||
mp.add_timeout(0.01, function()
|
|
||||||
_run_helper_request_async(next_job.req, next_job.timeout, next_job.cb)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if type(req) ~= 'table' then
|
|
||||||
done(nil, 'invalid request')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
ensure_mpv_ipc_server()
|
|
||||||
if not ensure_pipeline_helper_running() then
|
|
||||||
done(nil, 'helper not running')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Assign id.
|
|
||||||
local id = tostring(req.id or '')
|
|
||||||
if id == '' then
|
|
||||||
id = tostring(math.floor(mp.get_time() * 1000)) .. '-' .. tostring(math.random(100000, 999999))
|
|
||||||
req.id = id
|
|
||||||
end
|
|
||||||
|
|
||||||
local label = ''
|
|
||||||
if req.op then
|
|
||||||
label = 'op=' .. tostring(req.op)
|
|
||||||
elseif req.pipeline then
|
|
||||||
label = 'cmd=' .. tostring(req.pipeline)
|
|
||||||
else
|
|
||||||
label = '(unknown)'
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Wait for helper READY without blocking the UI.
|
|
||||||
local ready_deadline = mp.get_time() + 3.0
|
|
||||||
local ready_timer
|
|
||||||
ready_timer = mp.add_periodic_timer(0.05, function()
|
|
||||||
if _is_pipeline_helper_ready() then
|
|
||||||
ready_timer:kill()
|
|
||||||
|
|
||||||
_lua_log('ipc-async: send request id=' .. tostring(id) .. ' ' .. label)
|
|
||||||
local req_json = utils.format_json(req)
|
|
||||||
_last_ipc_last_req_json = req_json
|
|
||||||
|
|
||||||
mp.set_property(PIPELINE_RESP_PROP, '')
|
|
||||||
mp.set_property(PIPELINE_REQ_PROP, req_json)
|
|
||||||
|
|
||||||
local deadline = mp.get_time() + (timeout_seconds or 5)
|
|
||||||
local poll_timer
|
|
||||||
poll_timer = mp.add_periodic_timer(0.05, function()
|
|
||||||
if mp.get_time() >= deadline then
|
|
||||||
poll_timer:kill()
|
|
||||||
done(nil, 'timeout waiting response (' .. label .. ')')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local resp_json = mp.get_property(PIPELINE_RESP_PROP)
|
|
||||||
if resp_json and resp_json ~= '' then
|
|
||||||
_last_ipc_last_resp_json = resp_json
|
|
||||||
local ok, resp = pcall(utils.parse_json, resp_json)
|
|
||||||
if ok and resp and resp.id == id then
|
|
||||||
poll_timer:kill()
|
|
||||||
_lua_log('ipc-async: got response id=' .. tostring(id) .. ' success=' .. tostring(resp.success))
|
|
||||||
done(resp, nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if mp.get_time() >= ready_deadline then
|
|
||||||
ready_timer:kill()
|
|
||||||
done(nil, 'helper not ready')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
function FileState:fetch_formats(cb)
|
function FileState:fetch_formats(cb)
|
||||||
local url = tostring(self.url or '')
|
local url = tostring(self.url or '')
|
||||||
if url == '' or not _is_http_url(url) then
|
if url == '' or not _is_http_url(url) then
|
||||||
@@ -2077,13 +2088,6 @@ mp.register_script_message('medios-download-pick-path', function()
|
|||||||
_pending_download = nil
|
_pending_download = nil
|
||||||
end)
|
end)
|
||||||
|
|
||||||
ensure_pipeline_helper_running = function()
|
|
||||||
-- IMPORTANT: do NOT spawn Python from inside mpv.
|
|
||||||
-- The Python side (MPV.mpv_ipc) starts pipeline_helper.py using Windows
|
|
||||||
-- no-console flags; spawning here can flash a console window.
|
|
||||||
return _is_pipeline_helper_ready() and true or false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function run_pipeline_via_ipc(pipeline_cmd, seeds, timeout_seconds)
|
local function run_pipeline_via_ipc(pipeline_cmd, seeds, timeout_seconds)
|
||||||
if not ensure_pipeline_helper_running() then
|
if not ensure_pipeline_helper_running() then
|
||||||
return nil
|
return nil
|
||||||
@@ -2177,72 +2181,8 @@ if not opts.cli_path then
|
|||||||
opts.cli_path = find_file_upwards(script_dir, "CLI.py", 6) or "CLI.py"
|
opts.cli_path = find_file_upwards(script_dir, "CLI.py", 6) or "CLI.py"
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Clean API wrapper for executing Python functions from Lua
|
-- Ref: mpv_lua_api.py was removed in favor of pipeline_helper (run_pipeline_via_ipc_response).
|
||||||
local function _call_mpv_api(request)
|
-- This placeholder comment ensures we don't have code shifting issues.
|
||||||
-- Call the MPV Lua API (mpv_lua_api.py) with a JSON request.
|
|
||||||
-- Returns: JSON-decoded response object with {success, stdout, stderr, error, ...}
|
|
||||||
local request_json = utils.format_json(request)
|
|
||||||
|
|
||||||
-- Try to get log file path; skip if not available
|
|
||||||
local log_file = ''
|
|
||||||
local home = os.getenv('USERPROFILE') or os.getenv('HOME') or ''
|
|
||||||
if home ~= '' then
|
|
||||||
log_file = home .. '/../../medios/Medios-Macina/Log/medeia-mpv-helper.log'
|
|
||||||
end
|
|
||||||
|
|
||||||
_lua_log('api: calling mpv_lua_api cmd=' .. tostring(request.cmd))
|
|
||||||
|
|
||||||
local python_exe = _resolve_python_exe(true)
|
|
||||||
if not python_exe or python_exe == '' then
|
|
||||||
_lua_log('api: FAILED - no python exe')
|
|
||||||
return { success = false, error = 'could not find Python' }
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Try to locate API script
|
|
||||||
local api_script = nil
|
|
||||||
local script_dir = mp.get_script_directory()
|
|
||||||
if script_dir and script_dir ~= '' then
|
|
||||||
api_script = script_dir .. '/mpv_lua_api.py'
|
|
||||||
if not utils.file_info(api_script) then
|
|
||||||
api_script = script_dir .. '/../mpv_lua_api.py'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not api_script or api_script == '' or not utils.file_info(api_script) then
|
|
||||||
-- Fallback: try absolute path
|
|
||||||
local repo_root = os.getenv('USERPROFILE')
|
|
||||||
if repo_root then
|
|
||||||
api_script = repo_root .. '/../../../medios/Medios-Macina/MPV/mpv_lua_api.py'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if not api_script or api_script == '' then
|
|
||||||
_lua_log('api: FAILED - could not locate mpv_lua_api.py')
|
|
||||||
return { success = false, error = 'could not locate mpv_lua_api.py' }
|
|
||||||
end
|
|
||||||
|
|
||||||
_lua_log('api: python=' .. tostring(python_exe) .. ' script=' .. tostring(api_script))
|
|
||||||
|
|
||||||
local res = utils.subprocess({
|
|
||||||
args = { python_exe, api_script, request_json, log_file },
|
|
||||||
cancellable = false,
|
|
||||||
})
|
|
||||||
|
|
||||||
if res and res.status == 0 and res.stdout then
|
|
||||||
local ok, response = pcall(utils.parse_json, res.stdout)
|
|
||||||
if ok and response then
|
|
||||||
_lua_log('api: response success=' .. tostring(response.success))
|
|
||||||
return response
|
|
||||||
else
|
|
||||||
_lua_log('api: failed to parse response: ' .. tostring(res.stdout))
|
|
||||||
return { success = false, error = 'malformed response', stdout = res.stdout }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
local stderr = res and res.stderr or 'unknown error'
|
|
||||||
_lua_log('api: subprocess failed status=' .. tostring(res and res.status or 'nil') .. ' stderr=' .. stderr)
|
|
||||||
return { success = false, error = stderr }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Run a Medeia pipeline command via the Python pipeline helper (IPC request/response).
|
-- Run a Medeia pipeline command via the Python pipeline helper (IPC request/response).
|
||||||
-- Calls the callback with stdout on success or error message on failure.
|
-- Calls the callback with stdout on success or error message on failure.
|
||||||
@@ -2551,13 +2491,14 @@ local function _start_trim_with_range(range)
|
|||||||
end
|
end
|
||||||
|
|
||||||
_lua_log('trim: final upload_cmd=' .. pipeline_cmd)
|
_lua_log('trim: final upload_cmd=' .. pipeline_cmd)
|
||||||
_lua_log('trim: === CALLING API FOR UPLOAD ===')
|
_lua_log('trim: === CALLING PIPELINE HELPER FOR UPLOAD ===')
|
||||||
|
|
||||||
-- Call the API to handle metadata/storage
|
-- Optimization: use persistent pipeline helper
|
||||||
local response = _call_mpv_api({
|
local response = run_pipeline_via_ipc_response(pipeline_cmd, nil, 60)
|
||||||
cmd = 'execute_pipeline',
|
|
||||||
pipeline = pipeline_cmd,
|
if not response then
|
||||||
})
|
response = { success = false, error = "Timeout or IPC error" }
|
||||||
|
end
|
||||||
|
|
||||||
_lua_log('trim: api response success=' .. tostring(response.success))
|
_lua_log('trim: api response success=' .. tostring(response.success))
|
||||||
_lua_log('trim: api response error=' .. tostring(response.error or 'nil'))
|
_lua_log('trim: api response error=' .. tostring(response.error or 'nil'))
|
||||||
|
|||||||
@@ -262,13 +262,14 @@ class MPV:
|
|||||||
def send(self,
|
def send(self,
|
||||||
command: Dict[str,
|
command: Dict[str,
|
||||||
Any] | List[Any],
|
Any] | List[Any],
|
||||||
silent: bool = False) -> Optional[Dict[str,
|
silent: bool = False,
|
||||||
|
wait: bool = True) -> Optional[Dict[str,
|
||||||
Any]]:
|
Any]]:
|
||||||
client = self.client(silent=bool(silent))
|
client = self.client(silent=bool(silent))
|
||||||
try:
|
try:
|
||||||
if not client.connect():
|
if not client.connect():
|
||||||
return None
|
return None
|
||||||
return client.send_command(command)
|
return client.send_command(command, wait=wait)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if not silent:
|
if not silent:
|
||||||
debug(f"MPV IPC error: {exc}")
|
debug(f"MPV IPC error: {exc}")
|
||||||
@@ -627,7 +628,8 @@ class MPV:
|
|||||||
|
|
||||||
# Ensure uosc.conf is available at the location uosc expects.
|
# Ensure uosc.conf is available at the location uosc expects.
|
||||||
try:
|
try:
|
||||||
src_uosc_conf = repo_root / "MPV" / "LUA" / "uosc" / "uosc.conf"
|
# Source uosc.conf is located within the bundled scripts.
|
||||||
|
src_uosc_conf = repo_root / "MPV" / "portable_config" / "scripts" / "uosc" / "uosc.conf"
|
||||||
dst_uosc_conf = portable_config_dir / "script-opts" / "uosc.conf"
|
dst_uosc_conf = portable_config_dir / "script-opts" / "uosc.conf"
|
||||||
if src_uosc_conf.exists():
|
if src_uosc_conf.exists():
|
||||||
# Only seed a default config if the user doesn't already have one.
|
# Only seed a default config if the user doesn't already have one.
|
||||||
@@ -639,16 +641,8 @@ class MPV:
|
|||||||
cmd: List[str] = [
|
cmd: List[str] = [
|
||||||
"mpv",
|
"mpv",
|
||||||
f"--config-dir={str(portable_config_dir)}",
|
f"--config-dir={str(portable_config_dir)}",
|
||||||
# Allow mpv to auto-load scripts from <config-dir>/scripts/ (e.g., thumbfast).
|
|
||||||
"--load-scripts=yes",
|
"--load-scripts=yes",
|
||||||
"--osc=no",
|
"--osc=no",
|
||||||
"--load-console=no",
|
|
||||||
"--load-commands=no",
|
|
||||||
"--load-select=no",
|
|
||||||
"--load-context-menu=no",
|
|
||||||
"--load-positioning=no",
|
|
||||||
"--load-stats-overlay=no",
|
|
||||||
"--load-auto-profiles=no",
|
|
||||||
"--ytdl=yes",
|
"--ytdl=yes",
|
||||||
f"--input-ipc-server={self.ipc_path}",
|
f"--input-ipc-server={self.ipc_path}",
|
||||||
"--idle=yes",
|
"--idle=yes",
|
||||||
@@ -656,14 +650,21 @@ class MPV:
|
|||||||
]
|
]
|
||||||
|
|
||||||
# uosc and other scripts are expected to be auto-loaded from portable_config/scripts.
|
# uosc and other scripts are expected to be auto-loaded from portable_config/scripts.
|
||||||
# We keep the back-compat fallback only if the user hasn't installed uosc.lua there.
|
# If --load-scripts=yes is set (standard), mpv will already pick up the loader shim
|
||||||
|
# at scripts/uosc.lua. We only add a manual --script fallback if that file is missing.
|
||||||
try:
|
try:
|
||||||
uosc_entry = portable_config_dir / "scripts" / "uosc.lua"
|
uosc_entry = portable_config_dir / "scripts" / "uosc.lua"
|
||||||
if not uosc_entry.exists() and self.lua_script_path:
|
if not uosc_entry.exists() and self.lua_script_path:
|
||||||
lua_dir = Path(self.lua_script_path).resolve().parent
|
lua_dir = Path(self.lua_script_path).resolve().parent
|
||||||
uosc_main = lua_dir / "uosc" / "scripts" / "uosc" / "main.lua"
|
# Check different possible source locations for uosc core.
|
||||||
if uosc_main.exists():
|
uosc_paths = [
|
||||||
cmd.append(f"--script={str(uosc_main)}")
|
portable_config_dir / "scripts" / "uosc" / "scripts" / "uosc" / "main.lua",
|
||||||
|
lua_dir / "uosc" / "scripts" / "uosc" / "main.lua"
|
||||||
|
]
|
||||||
|
for p in uosc_paths:
|
||||||
|
if p.exists():
|
||||||
|
cmd.append(f"--script={str(p)}")
|
||||||
|
break
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1002,12 +1003,14 @@ class MPVIPCClient:
|
|||||||
|
|
||||||
def send_command(self,
|
def send_command(self,
|
||||||
command_data: Dict[str,
|
command_data: Dict[str,
|
||||||
Any] | List[Any]) -> Optional[Dict[str,
|
Any] | List[Any],
|
||||||
|
wait: bool = True) -> Optional[Dict[str,
|
||||||
Any]]:
|
Any]]:
|
||||||
"""Send a command to mpv and get response.
|
"""Send a command to mpv and get response.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
command_data: Command dict (e.g. {"command": [...]}) or list (e.g. ["loadfile", ...])
|
command_data: Command dict (e.g. {"command": [...]}) or list (e.g. ["loadfile", ...])
|
||||||
|
wait: If True, wait for the command response.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response dict with 'error' key (value 'success' on success), or None on error.
|
Response dict with 'error' key (value 'success' on success), or None on error.
|
||||||
@@ -1030,6 +1033,7 @@ class MPVIPCClient:
|
|||||||
if "request_id" not in request:
|
if "request_id" not in request:
|
||||||
request["request_id"] = int(_time.time() * 1000) % 100000
|
request["request_id"] = int(_time.time() * 1000) % 100000
|
||||||
|
|
||||||
|
rid = request["request_id"]
|
||||||
payload = json.dumps(request) + "\n"
|
payload = json.dumps(request) + "\n"
|
||||||
|
|
||||||
# Debug: log the command being sent
|
# Debug: log the command being sent
|
||||||
@@ -1040,6 +1044,9 @@ class MPVIPCClient:
|
|||||||
# Send command
|
# Send command
|
||||||
self._write_payload(payload)
|
self._write_payload(payload)
|
||||||
|
|
||||||
|
if not wait:
|
||||||
|
return {"error": "success", "request_id": rid, "async": True}
|
||||||
|
|
||||||
# Receive response
|
# Receive response
|
||||||
# We need to read lines until we find the one with matching request_id
|
# We need to read lines until we find the one with matching request_id
|
||||||
# or until timeout/error. MPV might send events in between.
|
# or until timeout/error. MPV might send events in between.
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
"""MPV Lua API - Clean interface for Lua scripts to call Python functions.
|
|
||||||
|
|
||||||
This module provides a streamlined way for mpv Lua scripts to execute Python
|
|
||||||
functions and commands without relying on the broken observe_property IPC pattern.
|
|
||||||
|
|
||||||
Instead, Lua calls Python CLI directly via subprocess, and Python returns JSON
|
|
||||||
responses that Lua can parse.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Dict, Optional
|
|
||||||
|
|
||||||
# Add parent directory to path so we can import CLI, pipeline, cmdlet_catalog from root
|
|
||||||
_SCRIPT_DIR = Path(__file__).parent
|
|
||||||
_ROOT_DIR = _SCRIPT_DIR.parent
|
|
||||||
if str(_ROOT_DIR) not in sys.path:
|
|
||||||
sys.path.insert(0, str(_ROOT_DIR))
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(log_file: Optional[Path] = None) -> logging.Logger:
|
|
||||||
"""Setup logging for MPV API calls."""
|
|
||||||
logger = logging.getLogger("mpv-lua-api")
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
if not logger.handlers:
|
|
||||||
if log_file:
|
|
||||||
handler = logging.FileHandler(str(log_file), encoding="utf-8")
|
|
||||||
else:
|
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
|
||||||
|
|
||||||
formatter = logging.Formatter(
|
|
||||||
"[%(asctime)s][%(levelname)s] %(message)s",
|
|
||||||
datefmt="%Y-%m-%d %H:%M:%S"
|
|
||||||
)
|
|
||||||
handler.setFormatter(formatter)
|
|
||||||
logger.addHandler(handler)
|
|
||||||
|
|
||||||
return logger
|
|
||||||
|
|
||||||
|
|
||||||
def log_to_helper(msg: str, log_file: Optional[Path] = None) -> None:
|
|
||||||
"""Log a message that will appear in the helper log."""
|
|
||||||
if log_file:
|
|
||||||
with open(log_file, "a", encoding="utf-8") as f:
|
|
||||||
f.write(f"[lua] {msg}\n")
|
|
||||||
|
|
||||||
|
|
||||||
def execute_pipeline(
|
|
||||||
pipeline_cmd: str,
|
|
||||||
log_file: Optional[Path] = None,
|
|
||||||
dry_run: bool = False,
|
|
||||||
) -> Dict[str,
|
|
||||||
Any]:
|
|
||||||
"""Execute a pipeline command and return result as JSON.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
pipeline_cmd: Pipeline command string (e.g. "trim-file -path ... | add-file -store ...")
|
|
||||||
log_file: Optional path to helper log file for logging
|
|
||||||
dry_run: If True, log but don't execute
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
JSON object with keys: success, stdout, stderr, error, returncode
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
if log_file:
|
|
||||||
log_to_helper(f"[api] execute_pipeline cmd={pipeline_cmd}", log_file)
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
return {
|
|
||||||
"success": True,
|
|
||||||
"stdout": "",
|
|
||||||
"stderr": "DRY RUN - command not executed",
|
|
||||||
"error": None,
|
|
||||||
"returncode": 0,
|
|
||||||
"cmd": pipeline_cmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Call the CLI directly as subprocess
|
|
||||||
import subprocess
|
|
||||||
import shlex
|
|
||||||
|
|
||||||
# Parse the pipeline command into separate arguments
|
|
||||||
cmd_args = shlex.split(pipeline_cmd)
|
|
||||||
|
|
||||||
result = subprocess.run(
|
|
||||||
[sys.executable,
|
|
||||||
"-m",
|
|
||||||
"CLI"] + cmd_args,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
cwd=str(_ROOT_DIR),
|
|
||||||
env={
|
|
||||||
**dict(__import__("os").environ),
|
|
||||||
"MEDEIA_MPV_CALLER": "lua"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
if log_file:
|
|
||||||
log_to_helper(
|
|
||||||
f"[api] result returncode={result.returncode} len_stdout={len(result.stdout or '')} len_stderr={len(result.stderr or '')}",
|
|
||||||
log_file,
|
|
||||||
)
|
|
||||||
if result.stderr:
|
|
||||||
log_to_helper(f"[api] stderr: {result.stderr[:500]}", log_file)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": result.returncode == 0,
|
|
||||||
"stdout": result.stdout or "",
|
|
||||||
"stderr": result.stderr or "",
|
|
||||||
"error": None if result.returncode == 0 else result.stderr,
|
|
||||||
"returncode": result.returncode,
|
|
||||||
"cmd": pipeline_cmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
msg = f"{type(exc).__name__}: {exc}"
|
|
||||||
if log_file:
|
|
||||||
log_to_helper(f"[api] exception {msg}", log_file)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"success": False,
|
|
||||||
"stdout": "",
|
|
||||||
"stderr": str(exc),
|
|
||||||
"error": msg,
|
|
||||||
"returncode": 1,
|
|
||||||
"cmd": pipeline_cmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def handle_api_request(request_json: str, log_file: Optional[Path] = None) -> str:
|
|
||||||
"""Handle an API request from Lua and return JSON response.
|
|
||||||
|
|
||||||
Request format:
|
|
||||||
{
|
|
||||||
"cmd": "execute_pipeline",
|
|
||||||
"pipeline": "trim-file -path ... | add-file -store ...",
|
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
Response format: JSON with result of the operation.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
request = json.loads(request_json)
|
|
||||||
cmd = request.get("cmd")
|
|
||||||
|
|
||||||
if cmd == "execute_pipeline":
|
|
||||||
pipeline_cmd = request.get("pipeline", "")
|
|
||||||
result = execute_pipeline(pipeline_cmd, log_file)
|
|
||||||
return json.dumps(result)
|
|
||||||
|
|
||||||
else:
|
|
||||||
return json.dumps({
|
|
||||||
"success": False,
|
|
||||||
"error": f"Unknown command: {cmd}",
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as exc:
|
|
||||||
return json.dumps(
|
|
||||||
{
|
|
||||||
"success": False,
|
|
||||||
"error": f"{type(exc).__name__}: {exc}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# When called from Lua via subprocess:
|
|
||||||
# python mpv_lua_api.py <json-request>
|
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print(json.dumps({
|
|
||||||
"success": False,
|
|
||||||
"error": "No request provided"
|
|
||||||
}))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
request_json = sys.argv[1]
|
|
||||||
log_file = Path(sys.argv[2]) if len(sys.argv) > 2 else None
|
|
||||||
|
|
||||||
response = handle_api_request(request_json, log_file)
|
|
||||||
print(response)
|
|
||||||
@@ -5,33 +5,27 @@ osd-bar=no
|
|||||||
# uosc will draw its own window controls and border if you disable window border
|
# uosc will draw its own window controls and border if you disable window border
|
||||||
border=no
|
border=no
|
||||||
|
|
||||||
# Keep the window size stable when loading files (don't resize to match aspect).
|
|
||||||
# Ensure uosc texture/icon fonts are discoverable by libass.
|
# Ensure uosc texture/icon fonts are discoverable by libass.
|
||||||
osd-fonts-dir=~~/scripts/uosc/fonts
|
osd-fonts-dir=~~/scripts/uosc/fonts
|
||||||
sub-fonts-dir=~~/scripts/uosc/
|
sub-fonts-dir=~~/scripts/uosc/
|
||||||
|
|
||||||
|
|
||||||
ontop=yes
|
ontop=yes
|
||||||
autofit=100%
|
autofit=100%
|
||||||
|
# Avoid showing embedded cover art for audio-only files if uosc isn't working,
|
||||||
save-position-on-quit=yes
|
# but we keep it enabled for now to ensure a window exists.
|
||||||
|
audio-display=yes
|
||||||
# Avoid showing embedded cover art for audio-only files.
|
|
||||||
audio-display=no
|
|
||||||
# Stretch the video to fill the window (ignore aspect ratio, may distort)
|
|
||||||
keepaspect=no
|
keepaspect=no
|
||||||
video-unscaled=no
|
video-unscaled=no
|
||||||
cursor-autohide=1000
|
cursor-autohide=1000
|
||||||
|
|
||||||
# gpu-next can be fragile on some Windows/D3D11 setups; prefer the stable VO.
|
# modern gpu-next is preferred in recent mpv builds
|
||||||
vo=gpu
|
vo=gpu-next,gpu,direct3d
|
||||||
|
|
||||||
# Show this after loading a new file. You can show text permanently instead by setting osd-msg1.
|
# Show this after loading a new file.
|
||||||
osd-playing-msg=${!playlist-count==1:[${playlist-pos-1}/${playlist-count}] }${media-title} ${?width:${width}x${height}} ${?current-tracks/video/image==no:${?percent-pos==0:${duration}}${!percent-pos==0:${time-pos} / ${duration} (${percent-pos}%)}}
|
osd-playing-msg=${!playlist-count==1:[${playlist-pos-1}/${playlist-count}] }${media-title} ${?width:${width}x${height}} ${?current-tracks/video/image==no:${?percent-pos==0:${duration}}${!percent-pos==0:${time-pos} / ${duration} (${percent-pos}%)}}
|
||||||
osd-playing-msg-duration=7000
|
osd-playing-msg-duration=7000
|
||||||
|
|
||||||
# On most platforms you can make the background transparent and avoid black
|
# Restore transparency options
|
||||||
# bars while still having all the screen space available for zooming in:
|
|
||||||
background=none
|
background=none
|
||||||
background-color=0/0
|
background-color=0/0
|
||||||
|
|
||||||
@@ -64,7 +58,7 @@ input-commands=no-osd del user-data/mpv/image; disable-section image # disable i
|
|||||||
|
|
||||||
# Loop short videos like gifs.
|
# Loop short videos like gifs.
|
||||||
[loop-short]
|
[loop-short]
|
||||||
profile-cond=duration < 30 and p['current-tracks/video/image'] == false
|
profile-cond=get('duration', 100) < 30 and p['current-tracks/video/image'] == false
|
||||||
profile-restore=copy
|
profile-restore=copy
|
||||||
loop-file
|
loop-file
|
||||||
|
|
||||||
@@ -75,7 +69,7 @@ profile-restore=copy
|
|||||||
stop-screensaver=no
|
stop-screensaver=no
|
||||||
|
|
||||||
[manga]
|
[manga]
|
||||||
profile-cond=path:find('manga')
|
profile-cond=get('path', ''):find('manga')
|
||||||
video-align-y=-1 # start from the top
|
video-align-y=-1 # start from the top
|
||||||
reset-on-next-file-remove=video-zoom # preserve the zoom when changing file
|
reset-on-next-file-remove=video-zoom # preserve the zoom when changing file
|
||||||
reset-on-next-file-remove=panscan
|
reset-on-next-file-remove=panscan
|
||||||
|
|||||||
@@ -29,9 +29,17 @@ end
|
|||||||
if type(scripts_root) ~= 'string' then
|
if type(scripts_root) ~= 'string' then
|
||||||
scripts_root = ''
|
scripts_root = ''
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local msg = require('mp.msg')
|
||||||
|
msg.info('[uosc-shim] loading uosc...')
|
||||||
|
|
||||||
-- Your current folder layout is: scripts/uosc/scripts/uosc/main.lua
|
-- Your current folder layout is: scripts/uosc/scripts/uosc/main.lua
|
||||||
local uosc_dir = utils.join_path(scripts_root, 'uosc/scripts/uosc')
|
local uosc_dir = utils.join_path(scripts_root, 'uosc/scripts/uosc')
|
||||||
|
|
||||||
|
if not utils.file_info(utils.join_path(uosc_dir, 'main.lua')) then
|
||||||
|
msg.error('[uosc-shim] ERROR: main.lua not found at ' .. tostring(uosc_dir))
|
||||||
|
end
|
||||||
|
|
||||||
-- uosc uses mp.get_script_directory() to find its adjacent resources (bin/, lib/, etc).
|
-- uosc uses mp.get_script_directory() to find its adjacent resources (bin/, lib/, etc).
|
||||||
-- Because this loader lives in scripts/, override it so uosc resolves paths correctly.
|
-- Because this loader lives in scripts/, override it so uosc resolves paths correctly.
|
||||||
local _orig_get_script_directory = mp.get_script_directory
|
local _orig_get_script_directory = mp.get_script_directory
|
||||||
@@ -41,4 +49,11 @@ end
|
|||||||
|
|
||||||
add_package_path(uosc_dir)
|
add_package_path(uosc_dir)
|
||||||
|
|
||||||
dofile(utils.join_path(uosc_dir, 'main.lua'))
|
local ok, err = pcall(dofile, utils.join_path(uosc_dir, 'main.lua'))
|
||||||
|
if not ok then
|
||||||
|
msg.error('[uosc-shim] ERROR during dofile: ' .. tostring(err))
|
||||||
|
else
|
||||||
|
msg.info('[uosc-shim] uosc loaded successfully')
|
||||||
|
-- Notify main.lua that we are alive
|
||||||
|
mp.commandv('script-message', 'uosc-version', 'bundled')
|
||||||
|
end
|
||||||
|
|||||||
@@ -145,20 +145,6 @@ function Controls:init_options()
|
|||||||
})
|
})
|
||||||
table_assign(control, {element = element, sizing = 'static', scale = 1, ratio = 1})
|
table_assign(control, {element = element, sizing = 'static', scale = 1, ratio = 1})
|
||||||
if badge then self:register_badge_updater(badge, element) end
|
if badge then self:register_badge_updater(badge, element) end
|
||||||
|
|
||||||
-- Medeia integration: show the persisted store name in the tooltip.
|
|
||||||
-- Triggered by a matching command string and backed by a mpv user-data prop.
|
|
||||||
if type(params[2]) == 'string' and params[2]:find('medeia%-store%-picker', 1, true) then
|
|
||||||
local store_prop = 'user-data/medeia-selected-store'
|
|
||||||
local function update_store_tooltip()
|
|
||||||
local v = mp.get_property(store_prop) or ''
|
|
||||||
v = trim(tostring(v))
|
|
||||||
element.tooltip = (v ~= '' and ('Store: ' .. v) or 'Store: (none)')
|
|
||||||
request_render()
|
|
||||||
end
|
|
||||||
element:observe_mp_property(store_prop, function() update_store_tooltip() end)
|
|
||||||
update_store_tooltip()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
elseif kind == 'cycle' then
|
elseif kind == 'cycle' then
|
||||||
if #params ~= 3 then
|
if #params ~= 3 then
|
||||||
|
|||||||
@@ -190,11 +190,11 @@ def _ensure_lyric_overlay(mpv: MPV) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _send_ipc_command(command: Dict[str, Any], silent: bool = False) -> Optional[Any]:
|
def _send_ipc_command(command: Dict[str, Any], silent: bool = False, wait: bool = True) -> Optional[Any]:
|
||||||
"""Send a command to the MPV IPC pipe and return the response."""
|
"""Send a command to the MPV IPC pipe and return the response."""
|
||||||
try:
|
try:
|
||||||
mpv = MPV()
|
mpv = MPV()
|
||||||
return mpv.send(command, silent=silent)
|
return mpv.send(command, silent=silent, wait=wait)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not silent:
|
if not silent:
|
||||||
debug(f"IPC Error: {e}", file=sys.stderr)
|
debug(f"IPC Error: {e}", file=sys.stderr)
|
||||||
@@ -883,12 +883,14 @@ def _queue_items(
|
|||||||
Any]] = None,
|
Any]] = None,
|
||||||
start_opts: Optional[Dict[str,
|
start_opts: Optional[Dict[str,
|
||||||
Any]] = None,
|
Any]] = None,
|
||||||
|
wait: bool = True,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Queue items to MPV, starting it if necessary.
|
"""Queue items to MPV, starting it if necessary.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
items: List of items to queue
|
items: List of items to queue
|
||||||
clear_first: If True, the first item will replace the current playlist
|
clear_first: If True, the first item will replace the current playlist
|
||||||
|
wait: If True, wait for MPV to acknowledge the loadfile command.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if MPV was started, False if items were queued via IPC.
|
True if MPV was started, False if items were queued via IPC.
|
||||||
@@ -1125,8 +1127,8 @@ def _queue_items(
|
|||||||
"request_id": 200
|
"request_id": 200
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
debug(f"Sending MPV {command_name}: {target_to_send} mode={mode}")
|
debug(f"Sending MPV {command_name}: {target_to_send} mode={mode} wait={wait}")
|
||||||
resp = _send_ipc_command(cmd, silent=True)
|
resp = _send_ipc_command(cmd, silent=True, wait=wait)
|
||||||
debug(f"MPV {command_name} response: {resp}")
|
debug(f"MPV {command_name} response: {resp}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug(f"Exception sending {command_name} to MPV: {e}", file=sys.stderr)
|
debug(f"Exception sending {command_name} to MPV: {e}", file=sys.stderr)
|
||||||
@@ -1249,6 +1251,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
list_mode = parsed.get("list")
|
list_mode = parsed.get("list")
|
||||||
play_mode = parsed.get("play")
|
play_mode = parsed.get("play")
|
||||||
pause_mode = parsed.get("pause")
|
pause_mode = parsed.get("pause")
|
||||||
|
replace_mode = parsed.get("replace")
|
||||||
save_mode = parsed.get("save")
|
save_mode = parsed.get("save")
|
||||||
load_mode = parsed.get("load")
|
load_mode = parsed.get("load")
|
||||||
current_mode = parsed.get("current")
|
current_mode = parsed.get("current")
|
||||||
@@ -1259,7 +1262,7 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
only_log = bool(
|
only_log = bool(
|
||||||
log_requested and not url_arg and index_arg is None and not clear_mode
|
log_requested and not url_arg and index_arg is None and not clear_mode
|
||||||
and not list_mode and not play_mode and not pause_mode and not save_mode
|
and not list_mode and not play_mode and not pause_mode and not save_mode
|
||||||
and not load_mode and not current_mode
|
and not load_mode and not current_mode and not replace_mode
|
||||||
)
|
)
|
||||||
if only_log:
|
if only_log:
|
||||||
return 0
|
return 0
|
||||||
@@ -1302,10 +1305,19 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
# Handle URL queuing
|
# Handle URL queuing
|
||||||
mpv_started = False
|
mpv_started = False
|
||||||
if url_arg:
|
if url_arg:
|
||||||
mpv_started = _queue_items([url_arg], config=config, start_opts=start_opts)
|
# If -replace is used, or if we have a URL and -play is requested,
|
||||||
# Auto-play the URL when it's queued via .pipe "url" (without explicit flags)
|
# we prefer 'replace' mode which starts playback immediately and avoids IPC overhead.
|
||||||
# unless other flags are present
|
# NOTE: Use wait=False for URLs because yt-dlp resolution can be slow and
|
||||||
if not (clear_mode or play_mode or pause_mode or save_mode or load_mode):
|
# would cause the calling Lua script to timeout.
|
||||||
|
queue_replace = bool(replace_mode)
|
||||||
|
if play_mode and not replace_mode:
|
||||||
|
# If -play is used with a URL, treat it as "play this now".
|
||||||
|
# For better UX, we'll replace the current playlist.
|
||||||
|
queue_replace = True
|
||||||
|
|
||||||
|
mpv_started = _queue_items([url_arg], clear_first=queue_replace, config=config, start_opts=start_opts, wait=False)
|
||||||
|
|
||||||
|
if not (clear_mode or play_mode or pause_mode or save_mode or load_mode or replace_mode):
|
||||||
if mpv_started:
|
if mpv_started:
|
||||||
# MPV was just started, wait a moment for it to be ready, then play first item
|
# MPV was just started, wait a moment for it to be ready, then play first item
|
||||||
import time
|
import time
|
||||||
@@ -1314,30 +1326,13 @@ def _run(result: Any, args: Sequence[str], config: Dict[str, Any]) -> int:
|
|||||||
index_arg = "1" # 1-based index for first item
|
index_arg = "1" # 1-based index for first item
|
||||||
play_mode = True
|
play_mode = True
|
||||||
else:
|
else:
|
||||||
# MPV was already running, get playlist and play the newly added item
|
# MPV was already running, just show the updated playlist.
|
||||||
playlist = _get_playlist(silent=True)
|
|
||||||
if playlist and len(playlist) > 0:
|
|
||||||
# Auto-play the last item in the playlist (the one we just added)
|
|
||||||
# Use 1-based indexing
|
|
||||||
index_arg = str(len(playlist))
|
|
||||||
play_mode = True
|
|
||||||
else:
|
|
||||||
# Fallback: just list the playlist if we can't determine index
|
|
||||||
list_mode = True
|
list_mode = True
|
||||||
|
|
||||||
# If the user explicitly requested -play while queueing a URL, interpret that
|
# If we used queue_replace, the URL is already playing. Clear play/index args to avoid redundant commands.
|
||||||
# as "play the URL I just queued" (not merely "unpause whatever is currently playing").
|
if queue_replace:
|
||||||
if play_mode and index_arg is None:
|
play_mode = False
|
||||||
if mpv_started:
|
index_arg = None
|
||||||
# MPV was just started; give it a moment, then play first item.
|
|
||||||
import time
|
|
||||||
|
|
||||||
time.sleep(0.5)
|
|
||||||
index_arg = "1"
|
|
||||||
else:
|
|
||||||
playlist = _get_playlist(silent=True)
|
|
||||||
if playlist and len(playlist) > 0:
|
|
||||||
index_arg = str(len(playlist))
|
|
||||||
|
|
||||||
# Ensure lyric overlay is running (auto-discovery handled by MPV.lyric).
|
# Ensure lyric overlay is running (auto-discovery handled by MPV.lyric).
|
||||||
try:
|
try:
|
||||||
@@ -2140,8 +2135,13 @@ CMDLET = Cmdlet(
|
|||||||
description="Remove the selected item, or clear entire playlist if no index provided",
|
description="Remove the selected item, or clear entire playlist if no index provided",
|
||||||
),
|
),
|
||||||
CmdletArg(name="list", type="flag", description="List items (default)"),
|
CmdletArg(name="list", type="flag", description="List items (default)"),
|
||||||
CmdletArg(name="play", type="flag", description="Resume playback"),
|
CmdletArg(name="play", type="flag", description="Resume playback or play specific index/URL"),
|
||||||
CmdletArg(name="pause", type="flag", description="Pause playback"),
|
CmdletArg(name="pause", type="flag", description="Pause playback"),
|
||||||
|
CmdletArg(
|
||||||
|
name="replace",
|
||||||
|
type="flag",
|
||||||
|
description="Replace current playlist when adding index or URL",
|
||||||
|
),
|
||||||
CmdletArg(
|
CmdletArg(
|
||||||
name="save",
|
name="save",
|
||||||
type="flag",
|
type="flag",
|
||||||
|
|||||||
@@ -92,11 +92,21 @@ class ProgressBar:
|
|||||||
if self.quiet:
|
if self.quiet:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
term_width = shutil.get_terminal_size((80, 20)).columns
|
||||||
percent = int(100 * (self.current / self.total))
|
percent = int(100 * (self.current / self.total))
|
||||||
filled = int(self.bar_width * self.current // self.total)
|
filled = int(self.bar_width * self.current // self.total)
|
||||||
bar = "█" * filled + "░" * (self.bar_width - filled)
|
bar = "█" * filled + "░" * (self.bar_width - filled)
|
||||||
|
|
||||||
sys.stdout.write(f"\r [{bar}] {percent:3}% | {step_name.ljust(30)}")
|
# Overwrite previous bar/label by moving up if not the first step
|
||||||
|
if self.current > 1:
|
||||||
|
sys.stdout.write("\033[2A")
|
||||||
|
|
||||||
|
bar_line = f"[{bar}]"
|
||||||
|
info_line = f"{percent}% | {step_name}"
|
||||||
|
|
||||||
|
sys.stdout.write(f"\r{bar_line.center(term_width)}\n")
|
||||||
|
# Clear line and print info line centered
|
||||||
|
sys.stdout.write(f"\r\033[K{info_line.center(term_width)}\r")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
|
||||||
if self.current == self.total:
|
if self.current == self.total:
|
||||||
@@ -685,8 +695,6 @@ def main() -> int:
|
|||||||
os.system("cls" if os.name == "nt" else "clear")
|
os.system("cls" if os.name == "nt" else "clear")
|
||||||
# Center the logo
|
# Center the logo
|
||||||
logo_lines = LOGO.strip().splitlines()
|
logo_lines = LOGO.strip().splitlines()
|
||||||
# Filter out the ruler lines if they are still there
|
|
||||||
logo_lines = [line for line in logo_lines if not any(c.isdigit() for c in line.strip())]
|
|
||||||
|
|
||||||
print("\n" * 2)
|
print("\n" * 2)
|
||||||
for line in logo_lines:
|
for line in logo_lines:
|
||||||
@@ -925,8 +933,6 @@ def main() -> int:
|
|||||||
os.system('cls' if os.name == 'nt' else 'clear')
|
os.system('cls' if os.name == 'nt' else 'clear')
|
||||||
term_width = shutil.get_terminal_size((80, 20)).columns
|
term_width = shutil.get_terminal_size((80, 20)).columns
|
||||||
logo_lines = LOGO.strip().splitlines()
|
logo_lines = LOGO.strip().splitlines()
|
||||||
# Filter out the ruler lines
|
|
||||||
logo_lines = [line for line in logo_lines if not any(c.isdigit() for c in line.strip())]
|
|
||||||
print("\n" * 2)
|
print("\n" * 2)
|
||||||
for line in logo_lines:
|
for line in logo_lines:
|
||||||
print(line.center(term_width))
|
print(line.center(term_width))
|
||||||
|
|||||||
Reference in New Issue
Block a user