diff options
Diffstat (limited to 'main.lua')
| -rw-r--r-- | main.lua | 324 |
1 files changed, 20 insertions, 304 deletions
@@ -1,34 +1,16 @@ -- Copyright 2025 David Vazgenovich Shakaryan local cacher = require('cacher') +local config = require('config') local util = require('util') local _catalogue = require('catalogue') local _downloader = require('downloader') local _epg = require('epg') +local _osd = require('osd') local _xc = require('xc') local mp_utils = require('mp.utils') --- font size is in units of osd height, which is scaled to 720 -local font_size = 20 -local colours = { - bg = '{\\alpha&H44&\\c&H&}', - title = '{\\c&999999&}', - option = '{\\c}', - info = '{\\c&666666&}', - group = '{\\c&H99DDFF&}', - group_empty = '{\\c&H446677&}', - selected = '{\\c&HFF00&}', - selected_empty = '{\\c&H337733&}', - search_hl = '{\\c&FFDD&}', - search_hl_empty = '{\\c&8877&}', - search_path = '{\\c&666666&}', - search_path_empty = '{\\c&444444&}', - icon_playing = '{\\c&HFF6633&}', - icon_favourite = '{\\c&HFF00FF&}', - icon_active = '{\\c&H99FF&}', -} - local script_name = mp.get_script_name() local script_dir = mp.get_script_directory() @@ -39,7 +21,7 @@ local xc = _xc.new({ pass = mp.get_opt('iptv_menu.xc_pass'), }) xc = cacher.wrap(xc, { - directory = mp_utils.join_path(script_dir, 'cache'), + directory = config.cache_dir, prefix = (xc.server:gsub('%W', '_')), time = 24*60*60, functions = { @@ -55,21 +37,7 @@ xc = cacher.wrap(xc, { }, }) -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) .. '}' .. - 'm 0 0 l ' .. math.ceil(font_size / 32) .. ' 0 ' .. - math.ceil(font_size / 32) .. ' ' .. font_size .. - ' 0 ' .. font_size .. '{\\p0}' -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 osd = _osd.new() local catalogue = _catalogue.new() local epg = _epg.new() @@ -80,10 +48,18 @@ local depth = 0 local menus = {} local key_bindings = {} -local update_osd +local function update_osd() + osd:redraw(menus, depth, favourites, playing_id) +end + +local function osd_menu_lines() + return osd:menu_lines(depth) +end -local function get_image_path(url, dl) - local path = 'img/' .. url:gsub('%W', '_') +-- FIXME leaving this here as a global for now since the image is downloaded +-- during osd redraw but this function has other dependencies +function get_image_path(url, dl) + local path = mp_utils.join_path(config.img_dir, url:gsub('%W', '_')) local f = mp_utils.file_info(path) if f then @@ -92,7 +68,7 @@ local function get_image_path(url, dl) if dl then downloader:schedule(url, path, function(_, file) - if osd_img and file == osd_img.path then + if osd.img and file == osd.img.path then update_osd() end end) @@ -126,250 +102,6 @@ local function load_data() end end -local function asscape(str) - -- remove newlines before escaping, since we don't want output to - -- unexpectedly span multiple lines - return mp.command_native({'escape-ass', (str:gsub('\n', ' '))}) -end - -local function osd_menu_lines() - if depth > 1 then - -- leaves an extra line for padding between titles and options - return osd_lines - depth - else - return osd_lines - end -end - -local function osd_menu_title(menu) - local str = asscape(menu.title) - local col = menu.search_active and colours.selected or colours.title - - if menu.type == 'search' then - str = str:gsub('<text>', asscape(menu.search_text)) - - if str:find('<text_with_cursor>', 1, true) then - str = str:gsub('<text_with_cursor>', - asscape(menu.search_text:sub( - 1, menu.search_cursor - 1)) .. - osd_cursor_glyph .. - asscape(menu.search_text:sub( - menu.search_cursor))) - end - - str = str:gsub('<colours.info>', colours.info) - str = str:gsub('<num_matches>', #menu.options) - str = str:gsub('<num_total>', #menu.search_options) - end - - return col .. ' » ' .. str -end - -local function osd_option_icons(opt, info) - local str = '' - if info.selected then - str = colours.selected .. '› ' - end - if opt.id and opt.id == playing_id then - str = str .. colours.icon_playing .. '\226\143\186 ' - end - if opt.id and favourites[opt.id] then - str = str .. colours.icon_favourite .. '★ ' - end - if opt.active then - str = str .. colours.icon_active .. '\226\143\186 ' - end - return str -end - -local function osd_option_text(opt, info) - local str = opt.name - local col = colours.option - if info.selected then - col = info.empty and colours.selected_empty or colours.selected - elseif info.empty then - col = colours.group_empty - elseif opt.type == 'group' then - col = colours.group - end - - if opt.matches then - local buf = '' - local hl_col = info.empty and colours.search_hl_empty or - colours.search_hl - local n = 0 - - for _, match in ipairs(opt.matches) do - buf = buf .. col .. - asscape(str:sub(n + 1, match.start - 1)) .. - hl_col .. - asscape(str:sub(match.start, match.stop)) - n = match.stop - end - str = buf .. col .. asscape(str:sub(n + 1)) - else - str = col .. asscape(str) - end - - if opt.type == 'group' then - str = col .. '[' .. str .. ']' - end - - if opt.info and #opt.info > 0 then - str = str .. colours.info .. ' (' .. asscape(opt.info) .. ')' - end - - return str -end - -local function osd_option_path(opt, info) - if not opt.path or #opt.path == 0 then - return '' - end - - local str = info.empty and colours.search_path_empty or - colours.search_path - for i = #opt.path, 1, -1 do - str = str .. ' « ' .. asscape(opt.path[i].name) - end - 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 disp - if osd_img and osd_img.loaded 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 - - disp = true - else - local f = mp_utils.file_info(path) - if f then - local cmd = 'magick \'' .. path .. '\'' .. - ' -background none' .. - ' -gravity northwest' .. - ' -depth 8' .. - ' -resize ' .. w .. 'x' .. h .. - ' -extent ' .. w .. 'x' .. h .. - ' tmp.bgra' - print('exec: ' .. cmd) - os.execute(cmd) - disp = true - end - end - - osd_img = { - path = path, - cmd = { - name = 'overlay-add', - id = 0, - file = 'tmp.bgra', - w = w, - h = h, - x = x, - y = y, - fmt = 'bgra', - offset = 0, - stride = 4*w, - }, - } - - if disp then - osd_img.loaded = true - if not osd.hidden then - mp.command_native(osd_img.cmd) - end - else - mp.command_native( - {name = 'overlay-remove', id = osd_img.cmd.id}) - end -end - -local function remove_osd_image() - mp.command_native({name = 'overlay-remove', id = osd_img.cmd.id}) - osd_img = nil -end - -function update_osd() - local out = {} - - if depth > 1 then - for i = 2, depth do - out[#out+1] = osd_menu_title(menus[i]) - end - out[#out+1] = ' ' -- space character for correct line height - 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 = 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.image and opt.image ~= '' then - img = get_image_path(opt.image, true) - end - end - - -- \q2 disables line wrapping - osd.data = '{\\q2\\fs' .. font_size .. '\\pos(' .. osd_padding .. - ',' .. osd_padding .. ')}' .. table.concat(out, '\\N') - - 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 - local function set_cursor(pos, opts) local menu = menus[depth] local lines = osd_menu_lines() @@ -496,7 +228,7 @@ local function add_programme(opt, time) if opt.epg_channel_id then local prog = epg:scheduled_programme(opt.epg_channel_id, time) if prog then - opt.info = asscape(prog.title) + opt.info = prog.title end end end @@ -1202,7 +934,7 @@ end -- might eventually change this to a selective override, since some of these -- builtin keys could still be useful while the menus are open. local function set_key_bindings() - if osd.hidden then + if osd:is_hidden() then unbind_keys() mp.command_native({'enable-section', 'default'}) elseif menus[depth].search_active then @@ -1215,28 +947,12 @@ local function set_key_bindings() end local function toggle_menu() - osd.hidden = not osd.hidden - osd:update() - 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 - + osd:toggle_hidden() set_key_bindings() end mp.observe_property('osd-dimensions', 'native', function(_, val) - osd_width = val.w - osd_height = val.h - osd_scale = osd_height / 720 - + osd:resize(val.w, val.h) update_osd() end) |
