--[[ Copyright (C) 2020 Jacek Jendrzej 'satbaby' License: GPL This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. ]] local glob = {} local version="2webTVxml Version 0.15" local n = neutrino() local conf = {} local on="ein" local off="aus" local mact = {} local loc = nil local reloadChannels = false local CONF_PATH = "/var/tuxbox/config/" if DIR and DIR.CONFIGDIR then CONF_PATH = DIR.CONFIGDIR .. '/' end locale = {} locale["deutsch"] = { file = "Datei auswählen", filehint = "Wählen Sie eine tv- oder m3u-Datei.", convert = "konvertieren in xml", converthint = "tv oder m3u in xml-format konvertieren", checkonline = "Online prüfen", checkonlinehint = "Wird geprüft , ob ein Stream online ist.", udp = "Ignorieren udp", udphint = "Überspringen url mit udp Protokoll.", rtp = "Ignorieren rtp", rtphint = "Überspringen url mit rtp Protokoll.", defpathon = "Standardpfad verwenden", defpathonhint = "Standardpfad verwenden?", defdir = "Verzeichnis: ", defdirhint = "In welchem Verzeichnis soll Datei (xml) gespeichert werden?", infohint = "Nicht unterstütztes Format", info2hint = "Liste", info3hint = "ist leer oder kein url gefunden.", dlerror = "Download fehlgeschlagen" } locale["english"] = { file = "Select file", filehint = "Select tv or m3u file", convert = "convert to xml", converthint = "convert tv or m3u to xml format", checkonline = "Check online", checkonlinehint = "Checks whether a stream is online.", udp = "Ignore udp", udphint = "Skip url with udp protokol.", rtp = "Ignore rtp", rtphint = "Skip url with rtp protokol.", defpathon = "Use default Path", defpathonhint = "Use default Path?", defdir = "Directory: ", defdirhint = "In which directory should file (xml) be saved?", infohint = "Not supported format", info2hint = "List", info3hint = "is empty or no url found.", dlerror = "Download failed" } function get_confFile() return CONF_PATH .. "2webTVxml.conf" end function get_conf_onlineFile() return CONF_PATH .. "2webTVxmlUrls.conf" end function get_conf_epgFile() return CONF_PATH .. "2webTVxmlEPG.conf" end function hideMenu(menu) if menu ~= nil then menu:hide() end end function setvar(k, v) if v and #v > 0 then conf[k]=v conf.changed = true end end function file_exists(file) local fh = filehelpers.new() if fh then return fh:exist(file, "f") else return false end end function godirectkey(d) if d == nil then return d end local _dkey = "" if d == 1 then _dkey = RC["red"] elseif d == 2 then _dkey = RC["green"] elseif d == 3 then _dkey = RC["yellow"] elseif d == 4 then _dkey = RC["blue"] elseif d < 14 then _dkey = RC[""..d - 4 ..""] elseif d == 14 then _dkey = RC["0"] else -- rest _dkey = "" end return _dkey end function saveConfig() if conf.changed then local config = configfile.new() config:setString("file", conf.file) config:setBool ("checkonline",conf.checkonline) config:setBool ("udp",conf.udp) config:setBool ("rtp",conf.rtp) config:setBool ("defpathon",conf.defpathon) config:setString("path", conf.path) config:saveConfig(get_confFile()) conf.changed = false end end function loadConfig() local config = configfile.new() config:loadConfig(get_confFile()) conf.path = config:getString("path", "/tmp") conf.checkonline = config:getBool("checkonline", false) conf.udp = config:getBool("udp", false) conf.rtp = config:getBool("rtp", false) conf.defpathon = config:getBool("defpathon", false) conf.file = config:getString("file", "/tmp/test.tv") conf.ffprobe = which("ffprobe") conf.ctimeout = 2 conf.changed = false local Nconfig = configfile.new() Nconfig:loadConfig(CONF_PATH .. "neutrino.conf") conf.lang = Nconfig:getString("language", "english") if locale[conf.lang] == nil then conf.lang = "english" end loc = locale[conf.lang] local onlineconf = get_conf_onlineFile() local havefile = file_exists(onlineconf) glob.onlineUrls = {} if havefile == true then local confdata = read_file(onlineconf) if havefile ~= nil then confdata = confdata .. '\n' for _line in confdata:gmatch('(name.-)\n') do local _name = _line:match('name="(.-)"') local _url = _line:match('url="(.-)"') local _agent = _line:match('agent="(.-)"') table.insert(glob.onlineUrls,{name=_name:lower(), url=_url,agent=_agent}) end end end local epg_conf = get_conf_epgFile() local havefile = file_exists(epg_conf) glob.epgID = {} if havefile == true then local confdata = read_file(epg_conf) if havefile ~= nil then confdata = confdata .. '\n' for _line in confdata:gmatch('(title.-)\n') do local _title = _line:match('title="(.-)"') local _epgid = _line:match('epgid="(.-)"') _title = _title:gsub("%s","") table.insert(glob.epgID,{title=_title:lower(), epgid=_epgid}) end end end end function which(bin_name) local path = os.getenv("PATH") or "/bin" for v in path:gmatch("([^:]+):?") do local file = v .. "/" .. bin_name if file_exists(file) then return true end end return false end function sleep (a) local sec = tonumber(os.clock() + a) while (os.clock() < sec) do end end function getdata(Url,outputfile) if Url == nil then return nil end if Curl == nil then Curl = curl.new() end if glob.agent == nil then agent = "Mozilla/5.0;" else agent = glob.agent end local ret, data = Curl:download{url=Url,A=agent,connectTimeout=conf.ctimeout,maxRedirs=5,followRedir=true,o=outputfile } if ret == CURL.OK then if outputfile then return 1 end return data else return nil end end function read_file(filename) if filename == nil then print("Error: FileName is empty") return nil end local fp = io.open(filename, "r") if fp == nil then print("Error opening file '" .. filename .. "'.") return nil end local data = fp:read("*a") fp:close() return data end function pop(cmd) local f = io.popen(cmd, 'r') local s = "" if f then s = f:read('*a') f:close() end return s end function hex2char(hex) return string.char(tonumber(hex, 16)) end function unescape_uri(url) return url:gsub("%%(%x%x)", hex2char) end function basename(str) if str == nil then return str end local name = string.gsub(str, "(.*/)(.*)", "%2") return name end function info(captxt,infotxt, sleep) if captxt == version and infotxt==nil then infotxt=captxt captxt="Information" end local msg, data = 0,0 local h = hintbox.new{caption=captxt, text=infotxt} h:paint() if sleep then for i=1,sleep*5,1 do msg, data = n:GetInput(500) if msg == RC.ok or msg == RC.home then break end end else repeat msg, data = n:GetInput(500) until msg == RC.ok or msg == RC.home end h:hide() end function xmlentity(str) if str == nil then return "" end local ustr = str:gsub("'","'") ustr = ustr:gsub('"',""") ustr = ustr:gsub('<',"<") ustr = ustr:gsub('>',"&qt") ustr = ustr:gsub("&","&") ustr = ustr:gsub("\r","") ustr = ustr:gsub("\n","") return ustr end function checkOnline(url) if url:find("%.m3u8") then local agent = url:match("User%-Agent=(.*)") if agent then glob.agent = agent end local data = getdata(url) glob.agent = nil if data then if data:match("#EXTM3U") then return true end return false end elseif url:match("^udp.*") or url:match("^rtp.*") or url:match("^rtmp.*") or url:match("^mms.*") or url:match("^rtsp.*") then return true elseif conf.ffprobe then local output = pop("ffprobe '" .. url .. "' 2>&1") if output:find("Stream") then return true else return false end else return true end return false end function saveXml(filename,name,xmliste,ext) if conf.defpathon then filename = conf.path .. "/" .. basename(filename) end local file = io.open(filename:sub(0,ext) .. "xml",'w+') if file then local saveUrl = true name = xmlentity(name) local pw = cprogresswindow.new{title=name} pw:paint() pw:showStatus{statusText="Start"} file:write('\n\n') for i, v in ipairs(xmliste) do v.xurl = unescape_uri(v.xurl) pw:showStatus{prog=i,max=#xmliste,statusText=tostring(i) .. "/" .. tostring(#xmliste) .. " " .. v.xtitle} if conf.checkonline then saveUrl = checkOnline(v.xurl) end if saveUrl then local script = '' if v.xurl:find("%.m3u8") then script = 'script="best_bitrate_m3u8.lua" ' end v.xurl = xmlentity(v.xurl) v.xtitle = xmlentity(v.xtitle) local epgID = '' if #glob.epgID > 0 then local epgid = checkEPG(v.xtitle) if epgid then epgID = 'epgid="' .. epgid .. '" ' end end file:write('\t\n') end end file:write("\n") file:close() reloadChannels = true sleep (1) pw:hide() end end function checkEPG(title) for i, v in ipairs(glob.epgID) do local tit = title:lower() tit = tit:gsub("%s","") if tit:find(v.title) then return v.epgid end end return nil end function m3u2xml(data,filename) if data then local urls = {} local xmliste = {} for name,url in data:gmatch('#EXTINF.-,(.-)\n(%a+://.-)\n') do if conf.udp and url:match("^udp://.*") then -- print("skip: " .. url) elseif conf.rtp and url:match("^rtp://.*") then -- print("skip: " .. url) elseif urls[url] ~= true then urls[url] = true local gen = "IPTV" local tag = "m3u" table.insert(xmliste,{xgen=gen,xtitle=name,xurl=url,xtag=tag}) end end local fname = basename(filename) fname = fname:match("(.*)%.m3u") or "" if #xmliste > 0 then saveXml(filename,"EXTM3U-" .. fname,xmliste,-4)--m3u else info(loc.info2hint .. " " .. fname, loc.info3hint,2) end end end function tv2xml(data,filename) if data then local urls = {} local xmliste = {} local saveUrl = true local name = data:match("#NAME%s+::(.-):") local fname = basename(filename) fname = fname:match("(.*)%.tv") or "" name = name or "e2tv-" .. fname for url,des in data:gmatch('#SERVICE .-:0:0:0:(%a+%%3a//.-\n)#DESCRIPTION%s+(.-)\n') do url = url:match("(.-)[:\n]") if conf.udp and url:match("^udp%%3a//.*") then -- print("skip: " .. url) elseif conf.rtp and url:match("^rtp%%3a//.*") then -- print("skip: " .. url) elseif urls[url] ~= true then urls[url] = true local gen = "IPTV" local tag = "e2tv" table.insert(xmliste,{xgen=gen,xtitle=des,xurl=url,xtag=tag}) end end if #xmliste > 0 then saveXml(filename,name,xmliste,-3)--tv else info(loc.info2hint .. " " .. fname, loc.info3hint,2) end end end function getExt(filename) local lastpos = (filename:reverse()):find("%.") if lastpos > 0 then return filename:sub(#filename - lastpos + 2,#filename) end return "" end function convert2xml(id) hideMenu(glob.main_menu) local filename = id local data = nil if id == "file" then filename = conf.file data = read_file(filename) else for i, v in ipairs(glob.onlineUrls) do if id == v.url then glob.agent = v.agent filename = v.url:match("%a+:/(/.*)") data = getdata(v.url) if data == nil then info(loc.dlerror,v.url,2) end glob.agent = nil break end end end local ext = getExt(filename) if ext == "tv" then tv2xml(data,filename) elseif ext == "m3u" then m3u2xml(data,filename) else info(" " .. ext, loc.infohint,2) end end function set_option(k, v) if v == on then conf[k]=true else conf[k]=false end if k == "defpathon" then glob.main_menu:setActive{item=m1, activ=conf[k]} for i, v in ipairs(glob.onlineUrls) do glob.main_menu:setActive{item=mact[i], activ=conf[k]} end end conf.changed = true end function bool2onoff(a) if a then return on end return off end function main_menu() glob.main_menu = menu.new{name="2WebTVxml", icon="icon_yellow"} local menu = glob.main_menu local d = 1 -- directkey menu:addKey{directkey=RC["info"], id=version, action="info"} menu:addKey{directkey=RC["help"], id=version, action="info"} menu:addItem{type="back"} menu:addItem{type="separatorline"} menu:addItem{ type="filebrowser", dir_mode="0", id="file", name=loc.file, action="setvar",enabled=true,value=conf.file,directkey=godirectkey(d), hint_icon="hint_service",hint=loc.filehint } d=d+1 menu:addItem{type="forwarder", name=loc.convert, action="convert2xml", enabled=true,id="file", directkey=godirectkey(d),hint=loc.converthint} d=d+1 menu:addItem{type="separatorline"} menu:addItem{type="chooser", action="set_option", options={ on, off }, id="checkonline", value=bool2onoff(conf.checkonline), directkey=godirectkey(d), name=loc.checkonline,hint_icon="hint_service",hint=loc.checkonlinehint} d=d+1 menu:addItem{type="chooser", action="set_option", options={ on, off }, id="udp", value=bool2onoff(conf.udp), directkey=godirectkey(d), name=loc.udp,hint_icon="hint_service",hint=loc.udphint} d=d+1 menu:addItem{type="chooser", action="set_option", options={ on, off }, id="rtp", value=bool2onoff(conf.rtp), directkey=godirectkey(d), name=loc.rtp,hint_icon="hint_service",hint=loc.rtphint} d=d+1 menu:addItem{type="chooser", action="set_option", options={ on, off }, id="defpathon", value=bool2onoff(conf.defpathon), directkey=godirectkey(d), name=loc.defpathon,hint_icon="hint_service",hint=loc.defpathonhint} d=d+1 m1 = menu:addItem{ type="filebrowser", dir_mode="1", id="path", name=loc.defdir, action="setvar",enabled=true,value=conf.path,directkey=godirectkey(d), hint_icon="hint_service",hint=loc.defdirhint } menu:setActive{item=m1, activ=conf.defpathon} menu:addItem{type="separatorline"} for i, v in ipairs(glob.onlineUrls) do d = d + 1 mact[i] = menu:addItem{type="forwarder", name=v.name , action="convert2xml", enabled=true,id=v.url, directkey=godirectkey(d),hint=loc.converthint} menu:setActive{item=mact[i], activ=conf.defpathon} end menu:exec() end function main() loadConfig() main_menu() saveConfig() if reloadChannels then getdata("http://127.0.0.1:80/control/reloadchannels") end collectgarbage() end main()