From 66f0cc8d069644af4fd8da888664a85784d57339 Mon Sep 17 00:00:00 2001 From: David Vazgenovich Shakaryan Date: Mon, 19 May 2025 21:05:32 -0700 Subject: add movie posters as an OSD overlay --- main.lua | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 146 insertions(+), 7 deletions(-) diff --git a/main.lua b/main.lua index 10ccb03..db9f3c4 100644 --- a/main.lua +++ b/main.lua @@ -28,6 +28,9 @@ local xc_user = mp.get_opt('iptv_menu.xc_user') local xc_pass = mp.get_opt('iptv_menu.xc_pass') local osd = mp.create_osd_overlay('ass-events') +local osd_width = 0 +local osd_height = 0 +local osd_scale = 1 local osd_lines = math.floor((720 / font_size) + 0.5) - 1 local osd_padding = math.floor((720 - (osd_lines * font_size)) / 2) local osd_cursor_glyph = '{\\p1\\pbo' .. math.floor(font_size / 5) .. '}' .. @@ -38,6 +41,7 @@ local osd_bg = mp.create_osd_overlay('ass-events') osd_bg.z = -1 osd_bg.data = '{\\pos(0,0)}' .. colours.bg .. '{\\p1}m 0 0 l 7680 0 7680 720 0 720{\\p0}' +local osd_img local objects = {} local epg = {} @@ -124,6 +128,24 @@ local function write_json_file(fn, data) f:close() end +local function get_image_path(url, dl) + local path = 'img/' .. url:gsub('%W', '_') + + local f = utils.file_info(path) + if not f then + if not dl then + return + end + + local cmd = 'curl -sSfLo \'' .. path .. '\'' .. + ' \'' .. url .. '\'' + print('exec: ' .. cmd) + os.execute(cmd) + end + + return path +end + local function add_object(obj) objects[obj.id] = obj @@ -370,6 +392,80 @@ local function osd_option_path(opt, info) return str end +local function update_osd_image(path, menu_res) + -- images require *real* dimensions and coordinates, unlike other OSD + -- functions which scale the given values automatically + local padding = osd_scale * osd_padding + local start = osd_scale * menu_res.x1 + local fs = osd_scale * font_size + + -- width is generally computed to 2/3 (standard aspect ratio of movie + -- posters) of OSD height. when longer OSD text extends into this area, + -- the remaining space is used, with a minimum font-size-derived value + -- when there is not enough (or any) space left, which may overlap the + -- tail end of text. + -- + -- for consistency, any source images with a different aspect ratio are + -- resized, confining them to the area of a "standard" poster. + -- + -- because we have only the total width of the current OSD text, we + -- cannot slightly reduce the image width to avoid overlapping some + -- longer line at the bottom, as we do not know where the offending + -- line is. if it were possible to place images *under* text, maybe + -- we'd allow overlap, but mpv currently hard-codes this order. + local ratio = 2/3 + local w = math.floor(math.max(3 * fs * ratio, + math.min(osd_width - start - 2*padding, + (osd_height - 2*padding) * ratio))) + local h = math.floor(w / ratio) + local x = math.floor(osd_width - padding - w) + local y = math.floor(padding) + + local cmd + + if osd_img and path == osd_img.path and w == osd_img.cmd.w and + h == osd_img.cmd.h then + if x == osd_img.cmd.x and y == osd_img.cmd.y then + return + end + + goto disp + end + + cmd = 'magick \'' .. path .. '\'' .. + ' -background none' .. + ' -gravity northwest' .. + ' -resize ' .. w .. 'x' .. h .. + ' -extent ' .. w .. 'x' .. h .. + ' tmp.rgba' + print('exec: ' .. cmd) + os.execute(cmd) + + ::disp:: + print('reloading img') + osd_img = { + path=path, + cmd={ + name='overlay-add', + id=0, + file='tmp.rgba', + w=w, + h=h, + x=x, + y=y, + fmt='bgra', + offset=0, + stride=4*w, + }, + } + mp.command_native(osd_img.cmd) +end + +local function remove_osd_image() + mp.command_native({name='overlay-remove', id=osd_img.cmd.id}) + osd_img = nil +end + local function update_osd() local out = {} @@ -381,23 +477,42 @@ local function update_osd() end local menu = menus[depth] + + local img + if menu.img_url then + img = get_image_path(menu.img_url, true) + end + for i = menu.view_top, math.min( menu.view_top + osd_menu_lines() - 1, #menu.options) do local opt = menu.options[i] + + local selected = i == menu.cursor and not menu.search_active local info = { - selected=(i == menu.cursor and not menu.search_active), + selected=selected, empty=(opt.type == 'group' and not opt.lazy and #opt.children == 0), } out[#out+1] = osd_option_icons(opt, info) .. osd_option_text(opt, info) .. osd_option_path(opt, info) + + if selected and opt.stream_icon then + img = get_image_path(opt.stream_icon) + end end -- \q2 disables line wrapping osd.data = '{\\q2\\fs' .. font_size .. '\\pos(' .. osd_padding .. ',' .. osd_padding .. ')}' .. table.concat(out, '\\N') - osd:update() + + osd.compute_bounds = not not img + local res = osd:update() + if img then + update_osd_image(img, res) + elseif osd_img then + remove_osd_image() + end osd_bg:update() end @@ -921,15 +1036,16 @@ local function open_option_movie_info(opt) end end - push_menu({ + local m = { options=options, title='Movie Info: ' .. opt.name, - }) - update_osd() - + } if info.cover_big then - mp.commandv('loadfile', info.cover_big) + m.img_url = info.cover_big end + + push_menu(m) + update_osd() end local function open_option_info() @@ -953,6 +1069,10 @@ local function search_menu_options_build(options, t, path) local path = path or {} for _, v in ipairs(options) do + if not v.type then + goto continue + end + if not t[v.type] then t[v.type] = {} end @@ -968,6 +1088,8 @@ local function search_menu_options_build(options, t, path) search_menu_options_build( group_menu_options(v), t, path) end + + ::continue:: end end @@ -1196,6 +1318,15 @@ local function toggle_menu() osd_bg.hidden = osd.hidden osd_bg:update() + if osd_img then + if osd.hidden then + mp.command_native( + {name='overlay-remove', id=osd_img.cmd.id}) + else + mp.command_native(osd_img.cmd) + end + end + if osd.hidden then unbind_keys() elseif menus[depth].search_active then @@ -1205,6 +1336,14 @@ local function toggle_menu() end end +mp.observe_property('osd-dimensions', 'native', function(_, val) + osd_width = val.w + osd_height = val.h + osd_scale = osd_height / 720 + + update_osd() +end) + mp.register_event('start-file', function() playing_id = mp.get_opt('iptv_menu.playing_id') update_osd() -- cgit v1.2.3-70-g09d2