|
--
|
|
-- VDR Streamdev Client
|
|
--
|
|
-- A script which turns mpv into a client for VDR with the Streamdev-Plugin
|
|
--
|
|
-- Copyright 2017 Martin Wache
|
|
-- VDR Streamdev Client 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 Version 2 of the License.
|
|
-- You can obtain a copy of the License from
|
|
-- https://www.gnu.org/licenses/gpl2.html
|
|
--
|
|
-- Short instructions:
|
|
-- 1. Enable the streamdev-server-plugin in vdr
|
|
-- 2. Modify streamdevhosts.conf to contain the clients IP
|
|
-- 3. If you want to have channel names and epg info,
|
|
-- modify svdrphosts.conf to contain the clients IP.
|
|
-- Also netcat ('nc') needs to be installed and in the path.
|
|
-- 4. Place this file in one of mpvs script folders
|
|
-- ( ~/.config/mpv/scripts/) or call mpv with the --script
|
|
-- command line option
|
|
-- 5. start mpv
|
|
-- mpv vdrstream://[vdr-host][:streamdev-port]
|
|
--
|
|
-- When mpv is running in Streamdev client mode, you can use
|
|
-- the keys UP,DOWN, 0-9 to select channels.
|
|
-- ENTER will bring up the channel info display.
|
|
-- The key 'i' will show the epg info for the current runing
|
|
-- event.
|
|
--
|
|
|
|
local options = {
|
|
host="192.168.55.4",
|
|
svdrp_port="6419",
|
|
streamdev_port="3000",
|
|
previous_channel_time=10,
|
|
epg_update_time=300,
|
|
}
|
|
require 'mp.options'
|
|
read_options(options,'vdr-streamdev-client')
|
|
|
|
local channels = { }
|
|
local startup = 1
|
|
local vdruri
|
|
local utils = require 'mp.utils'
|
|
local channel_idx=1
|
|
local next_channel=0
|
|
local last_channel=1
|
|
local epgnow = {}
|
|
local epgnext = {}
|
|
local epg_timer -- refreshes the epg info regulary
|
|
|
|
local osd_state="off"
|
|
|
|
function toArray(i)
|
|
local array={}
|
|
for v in i do
|
|
array[#array+1]=v
|
|
end
|
|
return array
|
|
end
|
|
|
|
local function send_svdrp(command)
|
|
ret = utils.subprocess({
|
|
args= {'/bin/bash', '-c', 'echo "'..command
|
|
..'" |nc '..options.host..' '..options.svdrp_port},
|
|
-- args= {'/bin/bash', '-c', 'echo "'..command
|
|
-- ..'" >/dev/tcp/'..options.host..'/'..options.svdrp_port},
|
|
cancellable=false,
|
|
})
|
|
return ret.stdout
|
|
end
|
|
|
|
local function parse_lstc(stdout)
|
|
mp.log("info","Getting channel list")
|
|
for i in string.gmatch(stdout,"[^\r\n]+") do
|
|
local code = i:sub(1,4)
|
|
if (code ~= "250-") then
|
|
mp.log("info","Unknown code '"..code.."'")
|
|
else
|
|
local channel_end=i:find(";")
|
|
if (channel_end ~=nil) then
|
|
local channel=i:sub(5,channel_end-1)
|
|
local sp=channel:find(" ")
|
|
if (sp ~= nil) then
|
|
local c =channel:sub(0,sp-1)
|
|
if (channels[c] == nil) then
|
|
channels[c]={}
|
|
end
|
|
channels[c]['name']=channel:sub(sp+1)
|
|
-- S19.2E-1-1079-28011 ZDFinfo (S19.2E)
|
|
-- ZDFinfo;ZDFvision:11953:HC34M2S0:S19.2E:27500:610=2:620=deu@3,621=mis@3,622=mul@3;625=deu@106:630;631=deu:0:28011:1:1079:0
|
|
local para=toArray(i:sub(channel_end+1):gmatch("[^:]+"))
|
|
if (#para>12) then
|
|
local cid=para[4].."-"..para[11].."-"..para[12].."-"..para[10]
|
|
--mp.log("info",cid)
|
|
channels[c]['id']=cid
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function get_channels()
|
|
parse_lstc(send_svdrp('LSTC'))
|
|
end
|
|
|
|
local function parse_lste(stdout)
|
|
local epginfo={}
|
|
local cid
|
|
mp.log("info","Updating epg")
|
|
for i in string.gmatch(stdout,"[^\r\n]+") do
|
|
local code = i:sub(1,5)
|
|
if (code == "215-C") then
|
|
cid=i:sub(7)
|
|
cid=cid:sub(1,cid:find(" ")-1)
|
|
-- mp.log("info","Channel '"..cid.."'")
|
|
if (epginfo[cid] == nil) then
|
|
epginfo[cid] = {}
|
|
end
|
|
elseif (code == "215-T") then
|
|
--mp.log("info","cid '"..cid.."' Titel '"..i:sub(7).."'")
|
|
if (cid ~= nil) then
|
|
epginfo[cid]['title']=i:sub(7)
|
|
end
|
|
elseif (code == "215-E") then
|
|
if (cid ~= nil) then
|
|
local p=toArray(i:sub(7):gmatch("[^ ]+"))
|
|
epginfo[cid]['start']=p[2]
|
|
epginfo[cid]['duration']=p[3]
|
|
end
|
|
elseif (code == "215-S") then
|
|
if (cid ~= nil) then
|
|
epginfo[cid]['subtitle']=i:sub(7)
|
|
end
|
|
elseif (code == "215-D") then
|
|
if (cid ~= nil) then
|
|
epginfo[cid]['description']=i:sub(7)
|
|
end
|
|
elseif (code =="215-c") then
|
|
cid = nil
|
|
end
|
|
end
|
|
return epginfo
|
|
end
|
|
|
|
local function get_epg_now()
|
|
epgnow=parse_lste(send_svdrp("LSTE now"))
|
|
end
|
|
|
|
local function get_epg_next()
|
|
epgnext=parse_lste(send_svdrp("LSTE next"))
|
|
end
|
|
|
|
|
|
|
|
local function print_time(t)
|
|
if (t == nil) then
|
|
return " "
|
|
end
|
|
return os.date('%H:%M',t)
|
|
end
|
|
|
|
|
|
local function format_epg(epg_info)
|
|
local msg=print_time(epg_info['start'])
|
|
if (epg_info['title'] ~= nil) then
|
|
msg = msg.." "..epg_info['title']
|
|
end
|
|
return msg
|
|
end
|
|
|
|
local function show_description()
|
|
local cinfo=channels[tostring(channel_idx)]
|
|
local msg=""
|
|
local cid=cinfo['id']
|
|
local einfo=epgnow[cid]
|
|
if (cinfo and einfo) then
|
|
msg = msg .. format_epg(einfo)
|
|
if (einfo['subtitle'] ~= nil) then
|
|
msg = msg .."\n\n" .. einfo['subtitle']
|
|
end
|
|
msg = msg .."\n\n" .. einfo['description']:gsub("|","\n")
|
|
end
|
|
mp.osd_message( msg, 30)
|
|
end
|
|
|
|
local function format_progress(part,length)
|
|
mp.log("info","part "..part.." length "..length)
|
|
ret = ""
|
|
for i = 1,length do
|
|
if (i/length>part) then
|
|
ret = ret .. "--"
|
|
else
|
|
ret = ret .. "+"
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
local function show_channel_info()
|
|
msg=os.date('%H:%M').." "..channel_idx
|
|
local cinfo=channels[tostring(channel_idx)]
|
|
if (cinfo) then
|
|
local cid=cinfo['id']
|
|
msg=msg.." "..cinfo['name'].."\n"
|
|
if (epgnow[cid] ~= nil) then
|
|
local einfo=epgnow[cid]
|
|
if (einfo['start'] ~= nil and einfo['duration'] ~= nil) then
|
|
local part = (os.time()-tonumber(epgnow[cid]['start']))
|
|
/tonumber(epgnow[cid]['duration'])
|
|
msg = msg .. "\n" .. format_progress(part,30)
|
|
end
|
|
msg = msg .. "\n" .. format_epg(epgnow[cid])
|
|
end
|
|
if (epgnext[cid] ~= nil) then
|
|
msg = msg .. "\n" ..format_epg(epgnext[cid])
|
|
end
|
|
end
|
|
mp.osd_message(msg,5)
|
|
end
|
|
|
|
local function switch_channel(no)
|
|
mp.log("info","switch_channel "..no)
|
|
local sav_channel=channel_idx
|
|
mp.add_timeout(options.previous_channel_time,function()
|
|
last_channel=sav_channel
|
|
end)
|
|
channel_idx=no
|
|
mp.commandv("loadfile",vdruri .. channel_idx)
|
|
show_channel_info()
|
|
next_channel=0
|
|
end
|
|
|
|
local function channel_next()
|
|
mp.log("info","next channel called " .. channel_idx .. " len channels " .. #channels)
|
|
channel_idx = channel_idx + 1
|
|
--#if (channel_idx > #channels) then
|
|
--# channel_idx = 1
|
|
--#end
|
|
--mp.commandv("loadfile",channels[channel_idx])
|
|
--mp.commandv("loadfile",vdruri .. channel_idx)
|
|
switch_channel(channel_idx);
|
|
-- mp.set_property("stream-open-filename",channels[channel_idx])
|
|
end
|
|
local function channel_prev()
|
|
mp.log("info","next channel called " .. channel_idx .. " len channels " .. #channels)
|
|
channel_idx = channel_idx - 1
|
|
--#if (channel_idx > #channels) then
|
|
--# channel_idx = 1
|
|
--#end
|
|
--mp.commandv("loadfile",channels[channel_idx])
|
|
switch_channel(channel_idx);
|
|
-- mp.set_property("stream-open-filename",channels[channel_idx])
|
|
end
|
|
local channel_timer=mp.add_periodic_timer(2,function()
|
|
if ( next_channel ~= 0 ) then
|
|
switch_channel(next_channel)
|
|
next_channel = 0
|
|
end
|
|
if ( channel_timer ~= nil ) then
|
|
channel_timer:kill()
|
|
end
|
|
end)
|
|
local function key(key)
|
|
if (key == 0 and next_channel == 0) then
|
|
-- immediatly update last_channel
|
|
local sav_channel=channel_idx
|
|
switch_channel(last_channel);
|
|
last_channel=sav_channel
|
|
return
|
|
end
|
|
next_channel=next_channel*10+key
|
|
mp.osd_message(next_channel)
|
|
channel_timer:resume()
|
|
end
|
|
|
|
local function keypress(k)
|
|
return function()
|
|
key(k)
|
|
end
|
|
end
|
|
|
|
local function on_start()
|
|
local url = mp.get_property("stream-open-filename")
|
|
mp.log("info","channels length "..#channels)
|
|
|
|
if (url:find("vdrstream://") == 1) then
|
|
if ( startup == 1) then
|
|
local host_port = url:sub(13)
|
|
if (host_port:len()>0) then
|
|
local has_port=host_port:find(":")
|
|
if (has_port) then
|
|
options.host = host_port:sub(1,has_port-1)
|
|
options.streamdev_port=host_port:sub(has_port+1)
|
|
else
|
|
options.host = host_port
|
|
end
|
|
end
|
|
mp.log("info","VDR host:"..options.host)
|
|
mp.log("info","VDR svdrp port:"..options.svdrp_port)
|
|
mp.log("info","VDR streamdev port:"..options.streamdev_port)
|
|
vdruri="http://"..options.host..":"..options.streamdev_port.."/TS/"
|
|
|
|
-- set parameters to optimize channel switch time
|
|
mp.set_property("cache-secs",1)
|
|
mp.set_property("demuxer-lavf-analyzeduration",1)
|
|
mp.set_property("ytdl","no")
|
|
|
|
get_channels()
|
|
-- load epg in background
|
|
mp.add_timeout(1,function()
|
|
get_epg_now()
|
|
get_epg_next()
|
|
mp.log("info","finished epg")
|
|
end)
|
|
-- periodically update epg
|
|
epg_timer = mp.add_periodic_timer(options.epg_update_time,function()
|
|
get_epg_now()
|
|
get_epg_next()
|
|
end)
|
|
|
|
startup = 0
|
|
end
|
|
-- mp.set_property("stream-open-filename",channels[channel_idx])
|
|
mp.set_property("cache-size",1024)
|
|
switch_channel(1)
|
|
end
|
|
mp.log("info","Lua version " .. _VERSION)
|
|
end
|
|
|
|
mp.add_forced_key_binding("UP",'next_channel',channel_next)
|
|
mp.add_forced_key_binding("DOWN",'prev_channel',channel_prev)
|
|
mp.add_forced_key_binding("0",'key0',keypress(0))
|
|
mp.add_forced_key_binding("1",'key1',keypress(1))
|
|
mp.add_forced_key_binding("2",'key2',keypress(2))
|
|
mp.add_forced_key_binding("3",'key3',keypress(3))
|
|
mp.add_forced_key_binding("4",'key4',keypress(4))
|
|
mp.add_forced_key_binding("5",'key5',keypress(5))
|
|
mp.add_forced_key_binding("6",'key6',keypress(6))
|
|
mp.add_forced_key_binding("7",'key7',keypress(7))
|
|
mp.add_forced_key_binding("8",'key8',keypress(8))
|
|
mp.add_forced_key_binding("9",'key9',keypress(9))
|
|
mp.add_forced_key_binding("ENTER",'show_channel_info',show_channel_info)
|
|
mp.add_forced_key_binding("i",'show_description',show_description)
|
|
mp.add_hook("on_load", 50, on_start)
|