diff options
| -rw-r--r-- | downloader.lua | 4 | ||||
| -rw-r--r-- | main.lua | 143 | ||||
| -rw-r--r-- | util.lua | 86 | ||||
| -rw-r--r-- | xc.lua | 4 | 
4 files changed, 128 insertions, 109 deletions
| diff --git a/downloader.lua b/downloader.lua index 7fee596..768d726 100644 --- a/downloader.lua +++ b/downloader.lua @@ -1,6 +1,6 @@  -- Copyright 2025 David Vazgenovich Shakaryan -local utils = require('mp.utils') +local mp_utils = require('mp.utils')  local downloader = {}  local mt = {} @@ -14,7 +14,7 @@ function downloader.new()  end  function mt:exec(url, file, cb) -	if utils.file_info(file) then +	if mp_utils.file_info(file) then  		self:exec_next()  		return  	end @@ -1,9 +1,10 @@  -- Copyright 2025 David Vazgenovich Shakaryan +local util = require('util')  local _downloader = require('downloader')  local _xc = require('xc') -local utils = require('mp.utils') +local mp_utils = require('mp.utils')  -- font size is in units of osd height, which is scaled to 720  local font_size = 20 @@ -27,6 +28,7 @@ local colours = {  local script_name = mp.get_script_name()  local script_dir = mp.get_script_directory() +  local downloader = _downloader.new()  local xc = _xc.new({  	server = mp.get_opt('iptv_menu.xc_server'), @@ -59,88 +61,12 @@ local depth = 0  local menus = {}  local key_bindings = {} -local function copy_table(t) -	local u = {} -	for k, v in pairs(t) do -		u[k] = v -	end -	return u -end - -local function reverse(t) -	for i = 1, #t/2 do -		t[i], t[#t-i+1] = t[#t-i+1], t[i] -	end -end - -local function strip(str) -	return (str:gsub('^%s*(.-)%s*$', '%1')) -end - -local function utf8_seek(str, pos, n) -	local step = n > 0 and 1 or -1 -	local test = n > 0 -		and function() return pos > #str end -		or function() return pos <= 1 end - -	while n ~= 0 and not test() do -		repeat -			pos = pos + step -		until test() or bit.band(str:byte(pos), 0xc0) ~= 0x80 - -		n = n - step -	end - -	return pos -end - --- returns table of strings wrapped at width. spaces are not removed, resulting --- in width-1 visible chars; newlines and end of string are handled similarly --- for consistency. words longer than width are not broken. -local function wrap(str, width, cont_width) -	local t = {} -	local start, stop = 0, 0 -	while stop < #str do -		local i = str:find('[ \n]', stop + 1) or #str + 1 -		if i - start >= width then -			t[#t+1] = str:sub(start, stop) -			start = stop + 1 -			if cont_width then -				width = cont_width -			end -		end -		stop = i -		if str:byte(stop) == 10 or stop >= #str then -			t[#t+1] = str:sub(start, stop - 1) .. ' ' -			start = stop + 1 -		end -	end - -	return t -end - -local function read_json_file(fn) -	local f = io.open(script_dir .. '/' .. fn, 'r') -	if not f then -		return {} -	end -	local json = f:read('*all') -	f:close() -	return utils.parse_json(json) -end - -local function write_json_file(fn, data) -	local f = io.open(script_dir .. '/' .. fn, 'w') -	f:write(utils.format_json(data), '\n') -	f:close() -end -  local update_osd  local function get_image_path(url, dl)  	local path = 'img/' .. url:gsub('%W', '_') -	local f = utils.file_info(path) +	local f = mp_utils.file_info(path)  	if f then  		return path  	end @@ -198,18 +124,20 @@ local function load_section(section, name)  		name=name,  	}) -	local tmp = read_json_file(section .. '_categories.json') +	local tmp = util.read_json_file( +		mp_utils.join_path(script_dir, section .. '_categories.json'))  	for _, v in ipairs(tmp) do  		v.section = section  		v.type = 'group'  		v.group_type = 'category'  		v.id = section .. ':category:' .. v.category_id  		v.parent_id = section .. ':category:' .. v.parent_id -		v.name = strip(v.category_name) +		v.name = util.strip(v.category_name)  		add_object(v)  	end -	tmp = read_json_file(section .. '_streams.json') +	local tmp = util.read_json_file( +		mp_utils.join_path(script_dir, section .. '_streams.json'))  	for _, v in ipairs(tmp) do  		v.section = section  		if v.series_id then @@ -222,7 +150,7 @@ local function load_section(section, name)  			v.id = section .. ':stream:' .. v.stream_id  		end  		v.parent_id = section .. ':category:' .. v.category_id -		v.name = strip(v.name) +		v.name = util.strip(v.name)  		add_object(v)  	end  end @@ -243,7 +171,8 @@ local function epg_parse_time(str)  end  local function load_epg() -	local tmp = read_json_file('epg.json') +	local tmp = util.read_json_file( +		mp_utils.join_path(script_dir, 'epg.json'))  	for _, v in ipairs(tmp) do  		local ch = v.channel:lower()  		local prog = { @@ -285,7 +214,8 @@ local function load_data()  	load_section('series', 'Series')  	load_epg() -	favourites = read_json_file('favourites.json') +	favourites = util.read_json_file( +		mp_utils.join_path(script_dir, 'favourites.json'))  	-- json loading/dumping breaks when the table is empty, so we need a  	-- dummy value to prevent that  	if next(favourites) == nil then @@ -440,7 +370,7 @@ local function update_osd_image(path, menu_res)  		disp = true  	else -		local f = utils.file_info(path) +		local f = mp_utils.file_info(path)  		if f then  			local cmd = 'magick \'' .. path .. '\'' ..  				' -background none' .. @@ -638,12 +568,12 @@ local function toggle_menu_sort()  	if not menu.sorted_options then  		menu.orig_options = menu.options -		menu.sorted_options = copy_table(menu.options) +		menu.sorted_options = util.copy_table(menu.options)  		sort_options(menu.sorted_options)  		if menu.search_options then  			menu.orig_search_options = menu.search_options -			menu.sorted_search_options = copy_table( +			menu.sorted_search_options = util.copy_table(  				menu.search_options)  			sort_options(menu.sorted_search_options)  		end @@ -718,14 +648,14 @@ local function favourites_group_menu_options(group)  				path[#path+1] = curr  			end -			obj = copy_table(obj) +			obj = util.copy_table(obj)  			add_programme(obj, time)  			local c = group_count(obj)  			if c then  				obj.info = tostring(c)  			end  			if #path > 0 and curr.parent_id == 'root' then -				reverse(path) +				util.reverse(path)  				obj.path = path  			end  			options[#options+1] = obj @@ -755,7 +685,7 @@ local function series_group_menu_options(series)  		if info.episodes and info.episodes[season_num] then  			for i, episode in pairs(info.episodes[season_num]) do  				episodes[#episodes+1] = { -					name=strip(episode.title), +					name=util.strip(episode.title),  					type='stream',  					stream_type='series',  					id=series.section .. ':stream:' .. @@ -774,7 +704,7 @@ local function series_group_menu_options(series)  			group_type='season',  			id=series.section .. 'series:season:' .. season.id,  			children=episodes, -			name=strip(season.name), +			name=util.strip(season.name),  			info=count,  		}  	end @@ -794,7 +724,7 @@ local function group_menu_options(group)  	local options = {}  	local time = os.time()  	for i, v in ipairs(group.children) do -		v = copy_table(v) +		v = util.copy_table(v)  		add_programme(v, time)  		local c = group_count(v)  		if c then @@ -865,7 +795,8 @@ local function favourite_option()  		favourites[id] = true  	end -	write_json_file('favourites.json', favourites) +	util.write_json_file(mp_utils.join_path(script_dir, 'favourites.json'), +		favourites)  	update_osd()  end @@ -930,7 +861,7 @@ local function open_epg_programme(prog)  	if prog.desc then  		options[#options+1] = {name=' '} -		for _, v in ipairs(wrap(prog.desc, 80)) do +		for _, v in ipairs(util.wrap(prog.desc, 80)) do  			options[#options+1] = {name=v}  		end  	end @@ -981,7 +912,7 @@ local function add_info_field(dst, k, v, fmt)  		return  	end -	local str = strip(tostring(v)) +	local str = util.strip(tostring(v))  	if fmt then  		str = string.format(fmt, str)  	end @@ -990,7 +921,7 @@ local function add_info_field(dst, k, v, fmt)  	end  	-- continuation lines are 4 chars shorter and indented with 2 em spaces -	for i, v in ipairs(wrap(str, 80, 76)) do +	for i, v in ipairs(util.wrap(str, 80, 76)) do  		if i > 1 then  			v = '\226\128\131\226\128\131' .. v  		end @@ -1013,7 +944,7 @@ local function open_option_movie_info(opt)  	if info.description then  		options[#options+1] = {name=' '} -		for _, v in ipairs(wrap(info.description, 80)) do +		for _, v in ipairs(util.wrap(info.description, 80)) do  			options[#options+1] = {name=v}  		end  	end @@ -1112,13 +1043,13 @@ local function search_menu_options_build(options, t, path)  			t[v.type] = {}  		end -		local v = copy_table(v) +		local v = util.copy_table(v)  		v.path = path  		t[v.type][#t[v.type]+1] = v  		-- contents of lazy-loaded groups should not be searchable  		if v.type == 'group' and not v.lazy then -			local path = copy_table(path) +			local path = util.copy_table(path)  			path[#path+1] = v  			search_menu_options_build(  				group_menu_options(v), t, path) @@ -1173,7 +1104,7 @@ local function update_search_matches()  		end  		if #matches > 0 then -			local t = copy_table(v) +			local t = util.copy_table(v)  			t.matches = matches  			options[#options+1] = t  		end @@ -1201,7 +1132,7 @@ local function search_input_bs()  		return  	end -	local pos = utf8_seek(menu.search_text, menu.search_cursor, -1) +	local pos = util.utf8_seek(menu.search_text, menu.search_cursor, -1)  	menu.search_text = menu.search_text:sub(1, pos - 1) ..  		menu.search_text:sub(menu.search_cursor)  	menu.search_cursor = pos @@ -1215,8 +1146,8 @@ local function search_input_del()  	end  	menu.search_text = menu.search_text:sub(1, menu.search_cursor - 1) .. -		menu.search_text:sub( -			utf8_seek(menu.search_text, menu.search_cursor, 1)) +		menu.search_text:sub(util.utf8_seek( +			menu.search_text, menu.search_cursor, 1))  	update_search_matches()  end @@ -1233,12 +1164,14 @@ end  local function search_cursor_left()  	local menu = menus[depth] -	set_search_cursor(utf8_seek(menu.search_text, menu.search_cursor, -1)) +	set_search_cursor(util.utf8_seek( +		menu.search_text, menu.search_cursor, -1))  end  local function search_cursor_right()  	local menu = menus[depth] -	set_search_cursor(utf8_seek(menu.search_text, menu.search_cursor, 1)) +	set_search_cursor(util.utf8_seek( +		menu.search_text, menu.search_cursor, 1))  end  local function search_cursor_start() diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..da9702e --- /dev/null +++ b/util.lua @@ -0,0 +1,86 @@ +-- Copyright 2025 David Vazgenovich Shakaryan + +local mp_utils = require('mp.utils') + +local util = {} + +function util.copy_table(t) +	local u = {} +	for k, v in pairs(t) do +		u[k] = v +	end +	return u +end + +function util.reverse(t) +	for i = 1, #t/2 do +		t[i], t[#t-i+1] = t[#t-i+1], t[i] +	end +end + +function util.strip(str) +	return (str:gsub('^%s*(.-)%s*$', '%1')) +end + +-- skips over utf8 continuation bytes (10xxxxxx) +-- valid positions range from 1 to #str + 1 (*after* the last byte) +function util.utf8_seek(str, pos, n) +	local step = n > 0 and 1 or -1 +	local test = n > 0 +		and function() return pos > #str end +		or function() return pos <= 1 end + +	while n ~= 0 and not test() do +		repeat +			pos = pos + step +		until test() or bit.band(str:byte(pos), 0xc0) ~= 0x80 + +		n = n - step +	end +	return pos +end + +-- wraps string into a table of strings. spaces are not removed, resulting in +-- width-1 visible chars; newlines and end of string are handled similarly for +-- consistency. words longer than width are not broken. optional cont_width can +-- be specified to use a different width for continuation lines. +function util.wrap(str, width, cont_width) +	local t = {} +	local start, stop = 0, 0 +	while stop < #str do +		local i = str:find('[ \n]', stop + 1) or #str + 1 +		if i - start >= width then +			t[#t+1] = str:sub(start, stop) +			start = stop + 1 + +			if cont_width then +				width = cont_width +			end +		end +		stop = i + +		if str:byte(stop) == 10 or stop >= #str then +			t[#t+1] = str:sub(start, stop - 1) .. ' ' +			start = stop + 1 +		end +	end +	return t +end + +function util.read_json_file(path) +	local f = io.open(path, 'r') +	if not f then +		return {} +	end +	local json = f:read('*all') +	f:close() +	return mp_utils.parse_json(json) +end + +function util.write_json_file(path, data) +	local f = io.open(path, 'w') +	f:write(mp_utils.format_json(data), '\n') +	f:close() +end + +return util @@ -1,6 +1,6 @@  -- Copyright 2025 David Vazgenovich Shakaryan -local utils = require('mp.utils') +local mp_utils = require('mp.utils')  local xc = {}  local mt = {} @@ -26,7 +26,7 @@ function mt:get(params)  	local fd = io.popen(cmd)  	local json = fd:read('*all')  	fd:close() -	return utils.parse_json(json) +	return mp_utils.parse_json(json)  end  function mt:get_series_info(id) | 
