tvheadend/support/conf_migrate.py

535 lines
12 KiB
Python
Executable file

#!/usr/bin/env python
#
# Migration version 3 configuration to version 4
#
# Files that need updating:
# autorec/* : Auto-mapped by code, but mapped here as well
# dvr/log/* : Auto-mapped by code, but mapped here as well
# channels/* : map services
# dvb/* : map network/mux but nothing else
# epggrab/otamux : remove
# epggrab/*/channels/* : update channels
#
# Create new "version" file to validate config version
#
#
# Imports
#
import os, sys, re, json, glob
import pprint
from optparse import OptionParser
#
# Utilities
#
# Generate UUID
def uuid ():
import uuid
return uuid.uuid4().hex
#
# DVB - input
#
# Adapters
def load_adapters ( path ):
adps = {}
for f in glob.glob(os.path.join(path, 'dvbadapters', '*')):
s = open(f).read()
d = json.loads(s)
t = d['type']
if t.startswith('DVB'):
t = t[4]
else:
t = 'A'
adps[os.path.basename(f)] = {
'type' : t,
'nets' : {}
}
# Done
return adps
# Muxes
def load_muxes ( path, adps ):
maps = {
'transportstreamid' : 'tsid',
'originalnetworkid' : 'onid',
'initialscan' : 'initscan',
'default_authority' : 'cridauth',
'delivery_system' : 'delsys',
'symbol_rate' : 'symbolrate'
}
muxs = {}
for f in glob.glob(os.path.join(path, 'dvbmuxes', '*', '*')):
a = os.path.basename(os.path.dirname(f))
if a not in adps: continue
t = adps[a]['type']
s = open(f).read()
d = json.loads(s)
if not 'transportstreamid' in d: continue
tsid = d['transportstreamid']
onid = d['originalnetworkid'] if 'transportstreamid' in d else None
# Build
m = {}
for k in d:
if k in maps:
m[maps[k]] = d[k]
else:
m[k] = d[k]
m['type'] = t
m['key'] = k = '%s:%04X:%04X' % (t, onid, tsid)
m['svcs'] = {}
# Fixups
if 'delsys' in m:
m['delsys'] = m['delsys'][4:]
elif t == 'A':
m['delsys'] = 'ATSC'
elif t == 'T':
m['delsys'] = 'DVBT'
elif t == 'C':
m['delsys'] = 'DVBC_ANNEX_AC'
elif t == 'S':
m['delsys'] = 'DVBS'
if 'polarisation' in m:
m['polarisation'] = m['polarisation'][0]
if 'modulation' in m and m['polarisation'] == 'PSK_8':
m['modulation'] = '8PSK'
if 'rolloff' in m:
m['rolloff'] = m['rolloff'][8:]
# Store
muxs[os.path.basename(f)] = m
# Network
n = None
if 'satconf' in d:
n = d['satconf']
if n not in adps[a]['nets']:
adps[a]['nets'][n] = {}
adps[a]['nets'][n][k] = m
# Done
return muxs
# Servies
def load_services ( path, muxs ):
svcs = {}
maps = {
'service_id' : 'sid',
'servicename' : 'svcname',
'stype' : 'dvb_servicetype',
'channel' : 'lcn',
'default_authority' : 'cridauth'
}
for f in glob.glob(os.path.join(path, 'dvbtransports', '*', '*')):
m = os.path.basename(os.path.dirname(f))
if m not in muxs: continue
# Load data
m = muxs[m]
s = open(f).read()
d = json.loads(s)
# Validate
if 'service_id' not in d: continue
if 'stream' in d: del d['stream']
# Map fields
s = {}
for k in d:
if k in maps:
s[maps[k]] = d[k]
else:
s[k] = d[k]
k = '%s:%04X:%04X:%04X' % (m['type'], m['onid'], m['tsid'], s['sid'])
m['svcs'][k] = svcs[k] = s
return svcs
#
# Channels
#
def load_channels ( path, nets ):
maps = {
'channel_number' : 'number',
'dvr_extra_time_pre' : 'dvr_pre_time',
'dvr_extra_time_pst' : 'dvr_pst_time',
}
chns = {}
# Process channels
for f in glob.glob(os.path.join(path, 'channels', '*')):
# Load data
s = open(f).read()
d = json.loads(s)
c = { 'services' : [] }
# Map fields
for k in d:
if k in maps:
c[maps[k]] = d[k]
else:
c[k] = d[k]
# Find services
for n in nets:
for m in n['muxs']:
m = n['muxs'][m]
for s in m['svcs']:
s = m['svcs'][s]
if 'uuid' not in s: continue
if 'channelname' in s and s['channelname'] == c['name']:
c['services'].append(s['uuid'])
# Store
chns[int(os.path.basename(f))] = c
# Done
return chns
#
# IPTV
#
def iptv_network ( nets, opts ):
muxes = {}
for f in glob.glob(os.path.join(path, 'iptvservices', '*')):
s = open(f).read()
d = json.loads(s)
url = '%s://%s:%d' % (opts.iptv, d['group'], d['port'])
if url not in muxes:
e = 'disabled' not in d or not d['disabled']
i = ''
if 'interface' in d: i = d['interface']
muxes[url] = m = { 'iptv_url' : url, 'enabled' : e, 'svcs' : {},
'iptv_interface': i }
else:
m = muxes[url]
# Create service entry
if 'channelname' in d:
d['svcname'] = d['channelname']
else:
d['svcname'] = ''
d['dvb_servicetype'] = d['stype']
d['sid'] = 1 # Let's hope!
d['enabled'] = e
m['svcs'] = { '1' : d }
# Remove
for f in [ 'stream', 'group', 'stype', 'interface' ]:
if f in d:
del d[f]
nets.append({ 'type' : 'iptv',
'muxs' : muxes,\
'skipinitscan' : True,\
'autodiscovery' : False })
return nets
#
# Output
#
# Build networks
def build_networks ( adps, opts ):
nets = []
# Process each adapter
for a in adps:
a = adps[a]
# Process each network
for m in a['nets']:
m = a['nets'][m]
f = False
# Look for overlap and combine
for n in nets:
if n['type'] != a['type']: continue
x = set(n['muxs'].keys())
y = set(m.keys())
i = x.intersection(x, y)
c = (2.0 * len(i)) / (len(x) + len(y))
#print 'comp %d %d %d %f' % (len(x), len(y), len(i), c)
if c > 0.5:
f = True
for k in m:
if k not in n['muxs']:
n['muxs'][k] = m[k]
else:
n['muxs'][k]['svcs'].update(m[k]['svcs'])
if not f:
n = {
'type': a['type'],
'muxs': m
}
nets.append(n)
# Set network name
i = 0
for n in nets:
i = i + 1
names = {}
for m in n['muxs']:
m = n['muxs'][m]
if 'network' not in m: continue
if m['network'] not in names:
names[m['network']] = 1
else:
names[m['network']] = 1 + names[m['network']]
name = None
for na in names:
if not name:
name = na
elif names[na] > names[name]:
name = na
if name:
n['name'] = name
else:
n['name'] = 'Network %s %d' % (n['type'], i)
# Done
return nets
# Output services
def output_services ( path, svcs, opts ):
ignore = { 'type', 'key', 'channelname', 'mapped' }
if not os.path.exists(path):
os.makedirs(path)
# Process services
for s in svcs:
s = svcs[s]
# Copy
d = {}
for k in s:
if k not in ignore:
d[k] = s[k]
# Output
u = uuid()
spath = os.path.join(path, u)
open(os.path.join(spath), 'w').write(json.dumps(d, indent=2))
s['uuid'] = u
# Output muxes
def output_muxes ( path, muxs, opts ):
ignore = [ 'type', 'key', 'svcs', 'network', 'quality', 'status' ]
# Process each
for m in muxs:
m = muxs[m]
# Copy
d = {}
for k in m:
if k not in ignore:
d[k] = m[k]
# Output
u = uuid()
mpath = os.path.join(path, u)
os.makedirs(mpath)
open(os.path.join(mpath, 'config'), 'w').write(json.dumps(d, indent=2))
# Services
output_services(os.path.join(mpath, 'services'), m['svcs'], opts)
# Output networks
def output_networks ( path, nets, opts ):
# Ensure dir exists
path = os.path.join(path, 'input', 'linuxdvb', 'networks')
if not os.path.exists(path):
os.makedirs(path)
# Write each network
for n in nets:
# Network config
if n['type'] == 'A':
c = 'linuxdvb_network_atsc'
else:
c = 'linuxdvb_network_dvb' + n['type'].lower()
d = {
'networkname' : n['name'],
'nid' : 0,
'autodiscovery' : False,
'skipinitscan' : True,
'class' : c
}
# Write
u = uuid()
npath = os.path.join(path, u)
os.mkdir(npath)
open(os.path.join(npath, 'config'), 'w').write(json.dumps(d, indent=2))
# Muxes
output_muxes(os.path.join(npath, 'muxes'), n['muxs'], opts)
# Output IPTV
def output_iptv ( path, nets, opts ):
d = None
# Find
for n in nets:
if n['type'] == 'iptv':
d = n
if not d: return
# Ensure dir exists
path = os.path.join(path, 'input', 'iptv')
if not os.path.exists(path):
os.makedirs(path)
# Write
u = uuid()
n = { 'uuid' : u,
'skipinitscan' : True,
'autodiscovery' : False }
open(os.path.join(path, 'config'), 'w').write(json.dumps(n, indent=2))
# Muxes
output_muxes(os.path.join(path, 'muxes'), d['muxs'], opts)
# Channels
def output_channels ( path, chns, opts ):
path = os.path.join(path, 'channel')
if not os.path.exists(path):
os.makedirs(path)
# Each channels
for c in chns:
c = chns[c]
u = uuid()
# Output
open(os.path.join(path, u), 'w').write(json.dumps(c, indent=2))
# Store
c['uuid'] = u
#
# DVR
#
# Find channel by name
def find_channel_by_name ( chns, name ):
for c in chns:
c = chns[c]
if 'name' in c and c['name'] == name:
return c
return None
def update_dvr ( path, chns ):
# Update all DVR log entries
for f in glob.glob(os.path.join(path, 'dvr', 'log', '*')):
# Load
s = open(f).read()
d = json.loads(s)
# Already done
if 'channelname' in d: continue
# Update all channels
if 'channel' in d:
d['channelname'] = d['channel']
c = find_channel_by_name(chns, d['channelname'])
if c and 'uuid' in c:
d['channel'] = c['uuid']
# Save
open(f, 'w').write(json.dumps(d, indent=2))
# Update autorec rules
for f in glob.glob(os.path.join(path, 'autorec', '*')):
# Load
s = open(f).read()
d = json.loads(s)
# Update all channels
if 'channel' in d:
d['channelname'] = d['channel']
c = find_channel_by_name(chns, d['channelname'])
if c and 'uuid' in c:
d['channel'] = c['uuid']
# Save
open(f, 'w').write(json.dumps(d, indent=2))
#
# EPG grab
#
def update_epg ( path, chns ):
# Remove otamux
p = os.path.join(path, 'epggrab', 'otamux')
if os.path.isfile(p):
os.unlink(p)
# Remove epgdb?
# Map grab channels
for f in glob.glob(os.path.join(path, 'epggrab', '*', 'channels', '*')):
# Load
s = open(f).read()
d = json.loads(s)
# Update all channels
t = []
if 'channels' in d:
for c in d['channels']:
if c in chns and 'uuid' in chns[c]:
t.append(chns[c]['uuid'])
d['channels'] = t
# Save
open(f, 'w').write(json.dumps(d, indent=2))
#
# Main
#
optp = OptionParser()
optp.add_option('-o', '--overlap', type='float', default=0.5,
help='Percentage overlap at which networks considered same')
optp.add_option('-i', '--iptv', type='string', default='udp',
help='IPTV input type, rtp/udp')
(opts,args) = optp.parse_args()
path = args[0]
adps = load_adapters(path)
muxs = load_muxes(path, adps)
svcs = load_services(path, muxs)
nets = build_networks(adps, opts)
output_networks(path, nets, opts)
nets = iptv_network(nets, opts)
output_iptv(path, nets, opts)
chns = load_channels(path, nets)
output_channels(path, chns, opts)
update_dvr(path, chns)
update_epg(path, chns)
sys.exit(0)