--[[ ZDF HBBTV Version 0.1 Copyright (C) 2021 Jacek Jendrzej 'satbaby' License: WTFPLv2 ]] function setmid(tab,mid) for k,v in pairs(tab) do if type(v) == "table" then if v.type == 'specialcovers' or v.type == 'header' or v.type == 'infotext' then if v.type then v.type = nil end if v.refid then v.refid = nil end if v.title then v.title = nil end if v.subtype then v.subtype = nil end if v.logo then v.logo = nil end if v.optional then v.optional = nil end if v.xs then v.xs = nil end if v.addDocs then v.addDocs = nil end if v.img then v.img = nil end if v.variant then v.variant = nil end else if v.play then v.play = nil end if v.view then v.view = nil end if v.pause then v.pause = nil end if v.zdfView then v.zdfView = nil end if v.click then v.click = nil end if v.co then v.co = nil end if v.structureNodePath then v.structureNodePath = nil end if v.foottxt then v.foottxt = nil end if v.elems then if #v.elems == 0 then v.elems=nil end end v.myid = mid mid = mid + 1 mid = setmid(v,mid) end end end return mid end function getmid(tab,mid) for k,v in pairs(tab) do if type(v) == "table" then if v.myid == mid then return v end v = getmid(v,mid) if v then return v end end end end function read_file(filename) local fp = io.open(filename, "r") if fp == nil then error("Error opening file '" .. filename .. "'.") end local data = fp:read("*a") fp:close() return data end function getMaxRes() local maxRes = 1280 local Nconfig = configfile.new() if Nconfig then Nconfig:loadConfig("/var/tuxbox/config/neutrino.conf") maxRes = Nconfig:getInt32("webtv_xml_quality", 1280) end return maxRes end function init() getMaxRes() lastmid = 1000 json = require "json" aktivelist = get_zdf_data('http://hbbtv.zdf.de/zdfm3/dyn/get.php') n = neutrino() vPlay = video.new() nMisc = misc.new() last_menu = {} hid = 0 Epg = nil Title = nil Info2 = nil end function getdata(Url,Postfields,outputfile,pass_headers,httpheaders) if Url == nil then return nil end if Curl == nil then Curl = curl.new() end if Url:sub(1, 2) == '//' then Url = 'https:' .. Url end local ret, data = Curl:download{ url=Url, A="Mozilla/5.0",maxRedirs=5,followRedir=false,postfields=Postfields,header=pass_headers,o=outputfile,httpheader=httpheaders } if ret == CURL.OK then if outputfile then return 1 end return data else return nil 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 hideMenu(menu) if menu then menu:hide() end end function epgInfo (xres, yres, aspectRatio, framerate) local dx = n:scale2Res(600); local dy = n:scale2Res(300); local x = ((SCREEN['END_X'] - SCREEN['OFF_X']) - dx) / 2; local y = ((SCREEN['END_Y'] - SCREEN['OFF_Y']) - dy) / 2; local wh = cwindow.new{x=x, y=y, dx=dx, dy=dy, icon="" , show_footer=false }; local ct = ctext.new{parent=wh, x=20, y=20, dx=0, dy=0, text = Epg , font_text=FONT['MENU'], mode = "ALIGN_SCROLL | ALIGN_TOP"}; wh:setCaption{title=Title, alignment=TEXT_ALIGNMENT.CENTER}; wh:paint() repeat msg, data = n:GetInput(500) if msg == RC.up or msg == RC.page_up then ct:scroll{dir="up"}; elseif msg == RC.down or msg == RC.page_down then ct:scroll{dir="down"}; end msg, data = n:GetInput(500) until msg == RC.ok or msg == RC.home or msg == RC.info ; wh:hide() end function getVideoUrlM3U8(m3u8_url) if m3u8_url == nil then return nil end local res = 0 local videoUrl = nil local audioUrl = nil local data = getdata(m3u8_url) if data then local host = m3u8_url:match('([%a]+[:]?//[_%w%-%.]+)/') local lastpos = (m3u8_url:reverse()):find("/") local hosttmp = m3u8_url:sub(1,#m3u8_url-lastpos) if hosttmp then host = hosttmp .."/" end local revision = 0 if APIVERSION ~= nil and (APIVERSION.MAJOR > 1 or ( APIVERSION.MAJOR == 1 and APIVERSION.MINOR > 82 )) then revision = nMisc:GetRevision() end local audio_url = nil if revision == 1 then -- separate audio for hd51 and co local Nconfig = configfile.new() local lang1,lang2,lang3 = nil,nil,nil Nconfig:loadConfig("/var/tuxbox/config/neutrino.conf") lang1 = Nconfig:getString("pref_lang_0", "#") lang2 = Nconfig:getString("pref_lang_1", "#") lang3 = Nconfig:getString("pref_lang_2", "#") if lang1 == "#" then lang1 = nil else lang1 = lang1:lower() lang1 = lang1:sub(1,3) end if lang2 == "#" then lang2 = nil else lang2 = lang2:lower() lang2 = lang2:sub(1,3) end if lang3 == "#" then lang3 = nil else lang3 = lang3:lower() lang3 = lang3:sub(1,3) end if lang1 == nil then lang1 = Nconfig:getString("language", "english") if lang1 == nil then lang1 = "eng" else lang1 = lang1:lower() lang1 = lang1:sub(1,3) end end local l1,l2,l3,l4,l = nil,nil,nil,nil,nil for adata in data:gmatch('TYPE%=AUDIO.GROUP%-ID=".-",(.-)\n') do local lname = adata:match('NAME="(.-)"') local lang = adata:match('LANGUAGE="(.-)"') local aurl = adata:match('URI="(.-)"') if aurl then local low_lang = lang:lower() if l1 == nil and lname and lang1 and low_lang == lang1 then l1 = aurl elseif l2 == nil and lname and lang2 and low_lang == lang2 then l2 = aurl elseif l3 == nil and lname and lang3 and low_lang == lang3 then l3 = aurl elseif l4 == nil and lname and low_lang == "deu" then l4 = aurl elseif l == nil then l = aurl end end end audio_url = l1 or l2 or l3 or l4 or l end local maxRes = getMaxRes() local allres = {} local j = 1 local minRes = 0 for band, res1, res2, url in data:gmatch('BANDWIDTH=(%d+).-RESOLUTION=(%d+)x(%d+).-\n(.-)\n') do local nr = tonumber(res1) if nr <= maxRes then minRes = nr end allres[j] = nr j=j+1 end if minRes == 0 and j>1 then maxRes = math.min(unpack(allres)) end for band, res1, res2, url in data:gmatch('BANDWIDTH=(%d+).-RESOLUTION=(%d+)x(%d+).-\n(.-)\n') do if url and res1 then local nr = tonumber(res1) if (nr <= maxRes and nr > res) then res=nr if host and url:sub(1,4) ~= "http" then url = host .. url end if audio_url and host and audio_url:sub(1,4) ~= "http" then audio_url = host .. audio_url end videoUrl = url audioUrl = audio_url end end end else return m3u8_url, nil end if videoUrl then videoUrl = videoUrl:gsub("\x0d","") end if audioUrl then audioUrl = audioUrl:gsub("\x0d","") end return videoUrl, audioUrl end function getZDFstream(tab) local url = 'https://hbbtv.zdf.de/zdfm3/dyn/get.php?id=' .. tab.link.id local jdata = getdata(url) if jdata then local jnTab = json:decode(jdata) if jnTab and jnTab.streams then local streams = jnTab.streams[1] if streams then local maxRes = getMaxRes() local mp4 = streams.h264_aac_mp4_http_na_na local m3u8 = streams.h264_aac_ts_http_m3u8_http local mpd = streams.h264_aac_mp4_http_mpd_http if maxRes > 1281 and mp4 and mp4.main and mp4.main.deu and mp4.main.deu.q3 then tab.stream = mp4.main.deu.q3 elseif maxRes < 1281 and mp4 and mp4.main and mp4.main.deu and mp4.main.deu.q1 then tab.stream = mp4.main.deu.q1 elseif m3u8 and m3u8.main and m3u8.main.deu and m3u8.main.deu.q3 then tab.stream , tab.audiostream = getVideoUrlM3U8(m3u8.main.deu.q3) elseif mpd and mpd.main and mpd.main.deu then tab.stream = mpd.main.deu end end Epg = nil Title = nil Info2 = nil if jnTab.text then Epg = jnTab.text end if jnTab.title then Title = jnTab.title end if jnTab.cpix and jnTab.cpix.nielsen and jnTab.cpix.nielsen.program then Info2 = jnTab.cpix.nielsen.program end end end end function play_video(tab) if tab.stream then hideMenu(last_menu[hid]) if Epg and Title then vPlay:setInfoFunc("epgInfo") end vPlay:PlayFile(Title, tab.stream, Info2 or "",'ZDF hbbtv',tab.audiostream or "") end end function get_zdf_data(link,data) if data == nil then data = getdata(link) end if data then local jnTab = json:decode(data) if jnTab then lastmid = setmid(jnTab,lastmid) end return jnTab end end function selPlay(id) hideMenu(last_menu[hid]) id = tonumber(id) local h = hintbox.new{caption="Please Wait ...", text="I'm Thinking."} if h then h:paint() end local vTab = getmid(aktivelist,id) if vTab then if vTab.stream == nil then getZDFstream(vTab) end end if h then h:hide() end if vTab.stream then play_video(vTab) end end function selList(id) hideMenu(last_menu[hid]) id = tonumber(id) local myTab = getmid(aktivelist,id) if myTab.elems == nil then local newTab = get_zdf_data('https://hbbtv.zdf.de/zdfm3/dyn/get.php?id=' .. myTab.link.id) myTab.elems = {} myTab.elems=newTab.elems end main_menu(myTab) end function main_menu(liste) hid = hid + 1 local tname = liste.title or liste.titletxt or liste.myid or liste.id if #tname == 0 then tname = 'NoName' end local menu = menu.new{name = tname, icon="streaming"} last_menu[hid] = menu menu:addItem{type="back"} menu:addItem{type="separatorline"} local d = 0 for i, v in ipairs(liste.elems) do if v.myid and v.hasVideo then if d == 0 then menu:addItem{type="subhead", name='Videos'} end d=d+1 local mact = "selPlay" local mname = v.titletxt or v.title or v.myid or '## error ##' if #mname == 0 then mname = 'NoName' end local vhint = v.headtxt .. ' ' .. v.infoline.text menu:addItem{type="forwarder" , name=mname, action=mact,hint=vhint ,id=v.myid ,directkey=godirectkey(d)} end end local one = true for i, v in ipairs(liste.elems) do if v.myid and (v.hasVideo==nil or v.hasVideo==false) and v.type ~= 'specialcovers' and v.type ~= 'header' then if one then menu:addItem{type="subhead", name='Untermenü'} one = false end d=d+1 local mact = "selList" local mname = v.titletxt or v.title or v.myid or '## error ##' if #mname == 0 then mname = 'NoName' end local vhint = v.headtxt menu:addItem{type="forwarder" , name=mname, action=mact,hint=vhint ,id=v.myid ,directkey=godirectkey(d)} end end menu:exec() hid = hid - 1 end function main() local h = hintbox.new{caption="Please Wait ...", text="I'm Thinking."} h:paint() init() h:hide() main_menu(aktivelist) collectgarbage() end main()