summaryrefslogtreecommitdiff
path: root/main.lua
diff options
context:
space:
mode:
Diffstat (limited to 'main.lua')
-rw-r--r--main.lua324
1 files changed, 20 insertions, 304 deletions
diff --git a/main.lua b/main.lua
index 5d08ba9..e0f1f48 100644
--- a/main.lua
+++ b/main.lua
@@ -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)