fdsfjlk
This commit is contained in:
380
MPV/LUA/main.lua
380
MPV/LUA/main.lua
@@ -268,6 +268,9 @@ local _cached_store_names = {}
|
||||
local _store_cache_loaded = false
|
||||
|
||||
local _pipeline_helper_started = false
|
||||
local _last_ipc_error = ''
|
||||
local _last_ipc_last_req_json = ''
|
||||
local _last_ipc_last_resp_json = ''
|
||||
|
||||
local function _is_pipeline_helper_ready()
|
||||
local ready = mp.get_property_native(PIPELINE_READY_PROP)
|
||||
@@ -431,8 +434,10 @@ end
|
||||
local ensure_pipeline_helper_running
|
||||
|
||||
local function _run_helper_request_response(req, timeout_seconds)
|
||||
_last_ipc_error = ''
|
||||
if not ensure_pipeline_helper_running() then
|
||||
_lua_log('ipc: helper not running; cannot execute request')
|
||||
_last_ipc_error = 'helper not running'
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -445,7 +450,9 @@ local function _run_helper_request_response(req, timeout_seconds)
|
||||
mp.wait_event(0.05)
|
||||
end
|
||||
if not _is_pipeline_helper_ready() then
|
||||
_lua_log('ipc: helper not ready; ready=' .. tostring(mp.get_property_native(PIPELINE_READY_PROP)))
|
||||
local rv = tostring(mp.get_property_native(PIPELINE_READY_PROP))
|
||||
_lua_log('ipc: helper not ready; ready=' .. rv)
|
||||
_last_ipc_error = 'helper not ready (ready=' .. rv .. ')'
|
||||
_pipeline_helper_started = false
|
||||
return nil
|
||||
end
|
||||
@@ -471,13 +478,21 @@ local function _run_helper_request_response(req, timeout_seconds)
|
||||
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, utils.format_json(req))
|
||||
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))
|
||||
@@ -488,6 +503,7 @@ local function _run_helper_request_response(req, timeout_seconds)
|
||||
end
|
||||
|
||||
_lua_log('ipc: timeout waiting response; ' .. label)
|
||||
_last_ipc_error = 'timeout waiting response (' .. label .. ')'
|
||||
_pipeline_helper_started = false
|
||||
return nil
|
||||
end
|
||||
@@ -593,12 +609,39 @@ end)
|
||||
local _pending_download = nil
|
||||
local _pending_format_change = nil
|
||||
|
||||
-- Per-file state (class-like) for format caching.
|
||||
local FileState = {}
|
||||
FileState.__index = FileState
|
||||
|
||||
function FileState.new()
|
||||
return setmetatable({
|
||||
url = nil,
|
||||
formats = nil,
|
||||
formats_table = nil, -- back-compat alias
|
||||
}, FileState)
|
||||
end
|
||||
|
||||
function FileState:has_formats()
|
||||
return type(self.formats) == 'table'
|
||||
and type(self.formats.rows) == 'table'
|
||||
and #self.formats.rows > 0
|
||||
end
|
||||
|
||||
function FileState:set_formats(url, tbl)
|
||||
self.url = url
|
||||
self.formats = tbl
|
||||
self.formats_table = tbl
|
||||
end
|
||||
|
||||
M.file = M.file or FileState.new()
|
||||
|
||||
-- Cache yt-dlp format lists per URL so Change Format is instant.
|
||||
M.file = M.file or {}
|
||||
M.file.formats_table = nil
|
||||
M.file.url = nil
|
||||
local _formats_cache = {}
|
||||
local _formats_inflight = {}
|
||||
local _formats_waiters = {}
|
||||
|
||||
local _ipc_async_busy = false
|
||||
local _ipc_async_queue = {}
|
||||
|
||||
local function _is_http_url(u)
|
||||
if type(u) ~= 'string' then
|
||||
@@ -615,8 +658,13 @@ local function _cache_formats_for_url(url, tbl)
|
||||
return
|
||||
end
|
||||
_formats_cache[url] = { table = tbl, ts = mp.get_time() }
|
||||
M.file.url = url
|
||||
M.file.formats_table = tbl
|
||||
if type(M.file) == 'table' and M.file.set_formats then
|
||||
M.file:set_formats(url, tbl)
|
||||
else
|
||||
M.file.url = url
|
||||
M.file.formats = tbl
|
||||
M.file.formats_table = tbl
|
||||
end
|
||||
end
|
||||
|
||||
local function _get_cached_formats_table(url)
|
||||
@@ -630,42 +678,175 @@ local function _get_cached_formats_table(url)
|
||||
return nil
|
||||
end
|
||||
|
||||
local function _prefetch_formats_for_url(url)
|
||||
url = tostring(url or '')
|
||||
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)
|
||||
local url = tostring(self.url or '')
|
||||
if url == '' or not _is_http_url(url) then
|
||||
if cb then cb(false, 'not a url') end
|
||||
return
|
||||
end
|
||||
|
||||
-- Only applies to plain URLs (not store hash URLs).
|
||||
if _extract_store_hash(url) then
|
||||
if cb then cb(false, 'store-hash url') end
|
||||
return
|
||||
end
|
||||
|
||||
if _get_cached_formats_table(url) then
|
||||
-- Cache hit.
|
||||
local cached = _get_cached_formats_table(url)
|
||||
if type(cached) == 'table' then
|
||||
self:set_formats(url, cached)
|
||||
if cb then cb(true, nil) end
|
||||
return
|
||||
end
|
||||
|
||||
-- In-flight: register waiter.
|
||||
if _formats_inflight[url] then
|
||||
_formats_waiters[url] = _formats_waiters[url] or {}
|
||||
if cb then table.insert(_formats_waiters[url], cb) end
|
||||
return
|
||||
end
|
||||
_formats_inflight[url] = true
|
||||
_formats_waiters[url] = _formats_waiters[url] or {}
|
||||
if cb then table.insert(_formats_waiters[url], cb) end
|
||||
|
||||
mp.add_timeout(0.01, function()
|
||||
if _get_cached_formats_table(url) then
|
||||
_formats_inflight[url] = nil
|
||||
return
|
||||
end
|
||||
|
||||
ensure_mpv_ipc_server()
|
||||
local resp = _run_helper_request_response({ op = 'ytdlp-formats', data = { url = url } }, 20)
|
||||
-- Async request so the UI never blocks.
|
||||
_run_helper_request_async({ op = 'ytdlp-formats', data = { url = url } }, 90, function(resp, err)
|
||||
_formats_inflight[url] = nil
|
||||
|
||||
local ok = false
|
||||
local reason = err
|
||||
if resp and resp.success and type(resp.table) == 'table' then
|
||||
ok = true
|
||||
reason = nil
|
||||
self:set_formats(url, resp.table)
|
||||
_cache_formats_for_url(url, resp.table)
|
||||
_lua_log('formats: cached ' .. tostring((resp.table.rows and #resp.table.rows) or 0) .. ' rows for url')
|
||||
else
|
||||
if type(resp) == 'table' then
|
||||
if resp.error and tostring(resp.error) ~= '' then
|
||||
reason = tostring(resp.error)
|
||||
elseif resp.stderr and tostring(resp.stderr) ~= '' then
|
||||
reason = tostring(resp.stderr)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local waiters = _formats_waiters[url] or {}
|
||||
_formats_waiters[url] = nil
|
||||
for _, fn in ipairs(waiters) do
|
||||
pcall(fn, ok, reason)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function _prefetch_formats_for_url(url)
|
||||
url = tostring(url or '')
|
||||
if url == '' or not _is_http_url(url) then
|
||||
return
|
||||
end
|
||||
if type(M.file) == 'table' then
|
||||
M.file.url = url
|
||||
if M.file.fetch_formats then
|
||||
M.file:fetch_formats(nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _open_loading_formats_menu(title)
|
||||
_uosc_open_list_picker(DOWNLOAD_FORMAT_MENU_TYPE, title or 'Pick format', {
|
||||
{
|
||||
@@ -676,6 +857,34 @@ local function _open_loading_formats_menu(title)
|
||||
})
|
||||
end
|
||||
|
||||
local function _debug_dump_formatted_formats(url, tbl, items)
|
||||
local row_count = 0
|
||||
if type(tbl) == 'table' and type(tbl.rows) == 'table' then
|
||||
row_count = #tbl.rows
|
||||
end
|
||||
local item_count = 0
|
||||
if type(items) == 'table' then
|
||||
item_count = #items
|
||||
end
|
||||
|
||||
_lua_log('formats-dump: url=' .. tostring(url or '') .. ' rows=' .. tostring(row_count) .. ' menu_items=' .. tostring(item_count))
|
||||
|
||||
-- Dump the formatted picker items (first 30) so we can confirm the
|
||||
-- list is being built and looks sane.
|
||||
if type(items) == 'table' then
|
||||
local limit = 30
|
||||
for i = 1, math.min(#items, limit) do
|
||||
local it = items[i] or {}
|
||||
local title = tostring(it.title or '')
|
||||
local hint = tostring(it.hint or '')
|
||||
_lua_log('formats-item[' .. tostring(i) .. ']: ' .. title .. (hint ~= '' and (' | ' .. hint) or ''))
|
||||
end
|
||||
if #items > limit then
|
||||
_lua_log('formats-dump: (truncated; total=' .. tostring(#items) .. ')')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function _current_ytdl_format_string()
|
||||
-- Preferred: mpv exposes the active ytdl format string.
|
||||
local fmt = trim(tostring(mp.get_property_native('ytdl-format') or ''))
|
||||
@@ -857,8 +1066,18 @@ mp.register_script_message('medios-change-format-current', function()
|
||||
|
||||
local url = tostring(target)
|
||||
|
||||
-- Ensure file state is tracking the current URL.
|
||||
if type(M.file) == 'table' then
|
||||
M.file.url = url
|
||||
end
|
||||
|
||||
-- If formats were already prefetched for this URL, open instantly.
|
||||
local cached_tbl = _get_cached_formats_table(url)
|
||||
local cached_tbl = nil
|
||||
if type(M.file) == 'table' and type(M.file.formats) == 'table' then
|
||||
cached_tbl = M.file.formats
|
||||
else
|
||||
cached_tbl = _get_cached_formats_table(url)
|
||||
end
|
||||
if type(cached_tbl) == 'table' and type(cached_tbl.rows) == 'table' and #cached_tbl.rows > 0 then
|
||||
_pending_format_change = { url = url, token = 'cached', formats_table = cached_tbl }
|
||||
|
||||
@@ -890,6 +1109,7 @@ mp.register_script_message('medios-change-format-current', function()
|
||||
}
|
||||
end
|
||||
|
||||
_debug_dump_formatted_formats(url, cached_tbl, items)
|
||||
_uosc_open_list_picker(DOWNLOAD_FORMAT_MENU_TYPE, 'Change format', items)
|
||||
return
|
||||
end
|
||||
@@ -898,77 +1118,69 @@ mp.register_script_message('medios-change-format-current', function()
|
||||
_pending_format_change = { url = url, token = token }
|
||||
_open_loading_formats_menu('Change format')
|
||||
|
||||
mp.add_timeout(0.05, function()
|
||||
if type(_pending_format_change) ~= 'table' or _pending_format_change.token ~= token then
|
||||
return
|
||||
end
|
||||
|
||||
ensure_mpv_ipc_server()
|
||||
_lua_log('change-format: requesting formats via helper op for url')
|
||||
|
||||
local resp = _run_helper_request_response({ op = 'ytdlp-formats', data = { url = url } }, 30)
|
||||
if type(_pending_format_change) ~= 'table' or _pending_format_change.token ~= token then
|
||||
return
|
||||
end
|
||||
|
||||
if not resp or not resp.success or type(resp.table) ~= 'table' then
|
||||
local err = ''
|
||||
if type(resp) == 'table' then
|
||||
if resp.error and tostring(resp.error) ~= '' then err = tostring(resp.error) end
|
||||
if resp.stderr and tostring(resp.stderr) ~= '' then
|
||||
err = (err ~= '' and (err .. ' | ') or '') .. tostring(resp.stderr)
|
||||
-- Non-blocking: ask the per-file state to fetch formats in the background.
|
||||
if type(M.file) == 'table' and M.file.fetch_formats then
|
||||
_lua_log('change-format: formats not cached yet; fetching in background')
|
||||
M.file:fetch_formats(function(ok, err)
|
||||
if type(_pending_format_change) ~= 'table' or _pending_format_change.token ~= token then
|
||||
return
|
||||
end
|
||||
if not ok then
|
||||
local msg2 = tostring(err or '')
|
||||
if msg2 == '' then
|
||||
msg2 = 'unknown'
|
||||
end
|
||||
_lua_log('change-format: formats failed: ' .. msg2)
|
||||
mp.osd_message('Failed to load format list: ' .. msg2, 7)
|
||||
_uosc_open_list_picker(DOWNLOAD_FORMAT_MENU_TYPE, 'Change format', {
|
||||
{
|
||||
title = 'Failed to load format list',
|
||||
hint = msg2,
|
||||
value = { 'script-message-to', mp.get_script_name(), 'medios-nop', '{}' },
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
_lua_log('change-format: formats failed: ' .. (err ~= '' and err or '(no details)'))
|
||||
mp.osd_message('Failed to load format list', 5)
|
||||
_uosc_open_list_picker(DOWNLOAD_FORMAT_MENU_TYPE, 'Change format', {
|
||||
{
|
||||
title = 'Failed to load format list',
|
||||
hint = 'Check logs (medeia-mpv-lua.log / medeia-mpv-helper.log)',
|
||||
value = { 'script-message-to', mp.get_script_name(), 'medios-nop', '{}' },
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local tbl = resp.table
|
||||
if type(tbl.rows) ~= 'table' or #tbl.rows == 0 then
|
||||
mp.osd_message('No formats available', 4)
|
||||
return
|
||||
end
|
||||
|
||||
local items = {}
|
||||
for idx, row in ipairs(tbl.rows) do
|
||||
local cols = row.columns or {}
|
||||
local id_val = ''
|
||||
local res_val = ''
|
||||
local ext_val = ''
|
||||
local size_val = ''
|
||||
for _, c in ipairs(cols) do
|
||||
if c.name == 'ID' then id_val = tostring(c.value or '') end
|
||||
if c.name == 'Resolution' then res_val = tostring(c.value or '') end
|
||||
if c.name == 'Ext' then ext_val = tostring(c.value or '') end
|
||||
if c.name == 'Size' then size_val = tostring(c.value or '') end
|
||||
local tbl = (type(M.file.formats) == 'table') and M.file.formats or _get_cached_formats_table(url)
|
||||
if type(tbl) ~= 'table' or type(tbl.rows) ~= 'table' or #tbl.rows == 0 then
|
||||
mp.osd_message('No formats available', 4)
|
||||
return
|
||||
end
|
||||
local label = id_val ~= '' and id_val or ('Format ' .. tostring(idx))
|
||||
local hint_parts = {}
|
||||
if res_val ~= '' and res_val ~= 'N/A' then table.insert(hint_parts, res_val) end
|
||||
if ext_val ~= '' then table.insert(hint_parts, ext_val) end
|
||||
if size_val ~= '' and size_val ~= 'N/A' then table.insert(hint_parts, size_val) end
|
||||
local hint = table.concat(hint_parts, ' | ')
|
||||
|
||||
local payload = { index = idx }
|
||||
items[#items + 1] = {
|
||||
title = label,
|
||||
hint = hint,
|
||||
value = { 'script-message-to', mp.get_script_name(), 'medios-change-format-pick', utils.format_json(payload) },
|
||||
}
|
||||
end
|
||||
local items = {}
|
||||
for idx, row in ipairs(tbl.rows) do
|
||||
local cols = row.columns or {}
|
||||
local id_val = ''
|
||||
local res_val = ''
|
||||
local ext_val = ''
|
||||
local size_val = ''
|
||||
for _, c in ipairs(cols) do
|
||||
if c.name == 'ID' then id_val = tostring(c.value or '') end
|
||||
if c.name == 'Resolution' then res_val = tostring(c.value or '') end
|
||||
if c.name == 'Ext' then ext_val = tostring(c.value or '') end
|
||||
if c.name == 'Size' then size_val = tostring(c.value or '') end
|
||||
end
|
||||
local label = id_val ~= '' and id_val or ('Format ' .. tostring(idx))
|
||||
local hint_parts = {}
|
||||
if res_val ~= '' and res_val ~= 'N/A' then table.insert(hint_parts, res_val) end
|
||||
if ext_val ~= '' then table.insert(hint_parts, ext_val) end
|
||||
if size_val ~= '' and size_val ~= 'N/A' then table.insert(hint_parts, size_val) end
|
||||
local hint = table.concat(hint_parts, ' | ')
|
||||
|
||||
_pending_format_change.formats_table = tbl
|
||||
_cache_formats_for_url(url, tbl)
|
||||
_uosc_open_list_picker(DOWNLOAD_FORMAT_MENU_TYPE, 'Change format', items)
|
||||
end)
|
||||
local payload = { index = idx }
|
||||
items[#items + 1] = {
|
||||
title = label,
|
||||
hint = hint,
|
||||
value = { 'script-message-to', mp.get_script_name(), 'medios-change-format-pick', utils.format_json(payload) },
|
||||
}
|
||||
end
|
||||
|
||||
_pending_format_change.formats_table = tbl
|
||||
_debug_dump_formatted_formats(url, tbl, items)
|
||||
_uosc_open_list_picker(DOWNLOAD_FORMAT_MENU_TYPE, 'Change format', items)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Prefetch formats for yt-dlp-supported URLs on load so Change Format is instant.
|
||||
|
||||
@@ -575,6 +575,46 @@ class MPV:
|
||||
debug("Starting MPV")
|
||||
subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs)
|
||||
|
||||
# Start the persistent pipeline helper eagerly so MPV Lua can issue
|
||||
# non-blocking requests (e.g., format list prefetch) without needing
|
||||
# to spawn the helper on-demand from inside mpv.
|
||||
try:
|
||||
helper_path = (repo_root / "MPV" / "pipeline_helper.py").resolve()
|
||||
if helper_path.exists():
|
||||
py = sys.executable or "python"
|
||||
helper_cmd = [
|
||||
py,
|
||||
str(helper_path),
|
||||
"--ipc",
|
||||
str(self.ipc_path),
|
||||
"--timeout",
|
||||
"30",
|
||||
]
|
||||
|
||||
helper_kwargs: Dict[str, Any] = {}
|
||||
if platform.system() == "Windows":
|
||||
flags = 0
|
||||
try:
|
||||
flags |= int(getattr(subprocess, "DETACHED_PROCESS", 0x00000008))
|
||||
except Exception:
|
||||
flags |= 0x00000008
|
||||
try:
|
||||
flags |= int(getattr(subprocess, "CREATE_NO_WINDOW", 0x08000000))
|
||||
except Exception:
|
||||
flags |= 0x08000000
|
||||
helper_kwargs["creationflags"] = flags
|
||||
helper_kwargs.update({k: v for k, v in _windows_hidden_subprocess_kwargs().items() if k != "creationflags"})
|
||||
|
||||
subprocess.Popen(
|
||||
helper_cmd,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
**helper_kwargs,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_ipc_pipe_path() -> str:
|
||||
"""Get the fixed IPC pipe/socket path for persistent MPV connection.
|
||||
|
||||
@@ -29,6 +29,7 @@ import tempfile
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
@@ -259,6 +260,53 @@ def _run_op(op: str, data: Any) -> Dict[str, Any]:
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl: # type: ignore[attr-defined]
|
||||
info = ydl.extract_info(url, download=False)
|
||||
|
||||
# Debug: dump a short summary of the format list to the helper log.
|
||||
try:
|
||||
formats_any = info.get("formats") if isinstance(info, dict) else None
|
||||
count = len(formats_any) if isinstance(formats_any, list) else 0
|
||||
_append_helper_log(f"[ytdlp-formats] extracted formats count={count} url={url}")
|
||||
|
||||
if isinstance(formats_any, list) and formats_any:
|
||||
limit = 60
|
||||
for i, f in enumerate(formats_any[:limit], start=1):
|
||||
if not isinstance(f, dict):
|
||||
continue
|
||||
fid = str(f.get("format_id") or "")
|
||||
ext = str(f.get("ext") or "")
|
||||
note = f.get("format_note") or f.get("format") or ""
|
||||
vcodec = str(f.get("vcodec") or "")
|
||||
acodec = str(f.get("acodec") or "")
|
||||
size = f.get("filesize") or f.get("filesize_approx")
|
||||
res = str(f.get("resolution") or "")
|
||||
if not res:
|
||||
try:
|
||||
w = f.get("width")
|
||||
h = f.get("height")
|
||||
if w and h:
|
||||
res = f"{int(w)}x{int(h)}"
|
||||
elif h:
|
||||
res = f"{int(h)}p"
|
||||
except Exception:
|
||||
res = ""
|
||||
_append_helper_log(
|
||||
f"[ytdlp-format {i:02d}] id={fid} ext={ext} res={res} note={note} codecs={vcodec}/{acodec} size={size}"
|
||||
)
|
||||
if count > limit:
|
||||
_append_helper_log(f"[ytdlp-formats] (truncated; total={count})")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Optional: dump the full extracted JSON for inspection.
|
||||
try:
|
||||
dump = os.environ.get("MEDEIA_MPV_YTDLP_DUMP", "").strip()
|
||||
if dump and dump != "0" and isinstance(info, dict):
|
||||
h = hashlib.sha1(url.encode("utf-8", errors="replace")).hexdigest()[:10]
|
||||
out_path = _repo_root() / "Log" / f"ytdlp-probe-{h}.json"
|
||||
out_path.write_text(json.dumps(info, ensure_ascii=False, indent=2), encoding="utf-8", errors="replace")
|
||||
_append_helper_log(f"[ytdlp-formats] wrote probe json: {out_path}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not isinstance(info, dict):
|
||||
return {
|
||||
"success": False,
|
||||
@@ -577,7 +625,9 @@ def main(argv: Optional[list[str]] = None) -> int:
|
||||
# Mirror mpv's own log messages into our helper log file so debugging does
|
||||
# not depend on the mpv on-screen console or mpv's log-file.
|
||||
try:
|
||||
level = "debug" if debug_enabled else "warn"
|
||||
# IMPORTANT: mpv debug logs can be extremely chatty (especially ytdl_hook)
|
||||
# and can starve request handling. Default to warn unless explicitly overridden.
|
||||
level = os.environ.get("MEDEIA_MPV_HELPER_MPVLOG", "").strip() or "warn"
|
||||
client.send_command_no_wait(["request_log_messages", level])
|
||||
_append_helper_log(f"[helper] requested mpv log messages level={level}")
|
||||
except Exception:
|
||||
@@ -666,8 +716,17 @@ def main(argv: Optional[list[str]] = None) -> int:
|
||||
if msg.get("id") != OBS_ID_REQUEST:
|
||||
continue
|
||||
|
||||
req = _parse_request(msg.get("data"))
|
||||
raw = msg.get("data")
|
||||
req = _parse_request(raw)
|
||||
if not req:
|
||||
try:
|
||||
if isinstance(raw, str) and raw.strip():
|
||||
snippet = raw.strip().replace("\r", "").replace("\n", " ")
|
||||
if len(snippet) > 220:
|
||||
snippet = snippet[:220] + "…"
|
||||
_append_helper_log(f"[request-raw] could not parse request json: {snippet}")
|
||||
except Exception:
|
||||
pass
|
||||
continue
|
||||
|
||||
req_id = str(req.get("id") or "")
|
||||
|
||||
Reference in New Issue
Block a user