-
Notifications
You must be signed in to change notification settings - Fork 3
/
MythUtil-Channel-HDHR-channelCheck
executable file
·233 lines (214 loc) · 10.2 KB
/
MythUtil-Channel-HDHR-channelCheck
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#!/usr/bin/python3
import argparse
import sys
import json
import re
import natsort
import requests
import requests.auth
class MythTVServices():
def __init__(self, host=None, port=None, username=None, password=None):
if host is None:
host = 'localhost'
self.host = host
if port is None:
port = 6544
self.port = port
self.session = requests.Session()
if username and password:
self.session.auth = requests.auth.HTTPDigestAuth(username, password)
self.request(service='Myth', api='version')
def request(self, service=None, api=None, data={}, method=None, stream=False):
version = '0.28'
headers = {'User-Agent':'{} Python Services API Client'.format(version),
'Accept':'application/json',
'Accept-Encoding':'gzip,deflate'}
if api is None:
raise ValueError('api must be specified')
url = 'http://{}:{}/{}/{}'.format(self.host, self.port, service, api)
if method is None:
if bool(data):
method = 'post'
else:
method = 'get'
if method == 'get':
response = self.session.get(url, headers=headers, params=data, stream=stream)
elif method == 'post':
response = self.session.post(url, headers=headers, data=data, stream=stream)
else:
raise ValueError('method is not post or get: {}'.format(method))
response.raise_for_status()
if stream:
response.raw.decode_content = True
return response.raw
else:
return response.json()
def Capture(self, api=None, data={}, method=None, stream=False):
return self.request(service='Capture', api=api, data=data, method=method, stream=stream)
def Channel(self, api=None, data={}, method=None, stream=False):
return self.request(service='Channel', api=api, data=data, method=method, stream=stream)
def Content(self, api=None, data={}, method=None, stream=False):
return self.request(service='Content', api=api, data=data, method=method, stream=stream)
def Dvr(self, api=None, data={}, method=None, stream=False):
return self.request(service='Dvr', api=api, data=data, method=method, stream=stream)
def Frontend(self, api=None, data={}, method=None, stream=False):
return self.request(service='Frontend', api=api, data=data, method=method, stream=stream)
def Guide(self, api=None, data={}, method=None, stream=False):
return self.request(service='Guide', api=api, data=data, method=method, stream=stream)
def Myth(self, api=None, data={}, method=None, stream=False):
return self.request(service='Myth', api=api, data=data, method=method, stream=stream)
def Video(self, api=None, data={}, method=None, stream=False):
return self.request(service='Video', api=api, data=data, method=method, stream=stream)
def channelNormalize(channel):
m0 = re.match(r'^(\d+)$', channel)
m1 = re.match(r'^(\d+)\.(\d+)$', channel)
m2 = re.match(r'^(\d+)_(\d+)$', channel)
m3 = re.match(r'^(\d+)-(\d+)$', channel)
if m0:
return '{}'.format(int(m0.group(1)))
elif m1:
return '{}.{}'.format(int(m1.group(1)), int(m1.group(2)))
elif m2:
return '{}.{}'.format(int(m2.group(1)), int(m2.group(2)))
elif m3:
return '{}.{}'.format(int(m3.group(1)), int(m3.group(2)))
raise TypeError('Invalid channel: {}'.format(channel))
def channelCheck(channel):
try:
return channelNormalize(channel)
except Exception:
raise argparse.ArgumentTypeError('{} is not a valid channel'.format(channel))
def unsignedInt(v):
try:
v = int(v)
except ValueError:
raise argparse.ArgumentTypeError("{} is not a valid positive integer".format(v))
if v < 0:
raise argparse.ArgumentTypeError("{} is not a valid positive integer".format(v))
return v
def versionTuple(v):
return tuple(map(int, (v.split("."))))
def transformAPIElementNames(APIname, existingDict):
transforms = \
{
'VideoSource':
{
'Id': 'SourceId',
},
'Channel':
{
'SourceId': 'SourceID',
'MplexId': 'MplexID',
'ChanId': 'ChannelID',
'ChanNum': 'ChannelNumber',
'ServiceId': 'ServiceID',
'ATSCMajorChan': 'ATSCMajorChannel',
'ATSCMinorChan': 'ATSCMinorChannel',
'Visible': 'visible',
'FrequencyId': 'FrequencyID',
'DefaultAuth': 'DefaultAuthority'
}
}
transform = transforms.get(APIname, {})
newDict = {}
for key, value in existingDict.items():
newKey = transform.get(key, key)
if newKey is not None:
newDict[newKey] = value
return newDict
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--hdhr', action='store', type=str, required=True,
help='the HDHomeRun from which to retrieve the lineup')
parser.add_argument('--backend', '--host', action='store', type=str, default='localhost',
help='the host (backend) to access. The default is localhost.')
parser.add_argument('--port', action='store', type=int, default=6544,
help='the port to connect to on on the host. The default is 6544')
parser.add_argument('--username', action='store', type=str, default=None,
help='the username to use for host authentication')
parser.add_argument('--password', action='store', type=str, default=None,
help='the password to use for host authentication')
sourcegroup = parser.add_mutually_exclusive_group(required=True)
sourcegroup.add_argument('--videosource-name', action='store', type=str, dest='sourceName',
help='the video source name')
sourcegroup.add_argument('--videosource-id', action='store', type=unsignedInt, dest='sourceId',
help='the video source id')
parser.add_argument('--channel', '--channels', '--include-channel', '--include-channels',
nargs='+', type=channelCheck, dest='channelInclude',
help='list of channels to consider. The default is all')
parser.add_argument('--exclude-channel', '--exclude-channels', '--no-channel', '--no-channels',
nargs='+', type=channelCheck, dest='channelExclude',
help='list of channels to exclude. The default is none')
parser.add_argument('--include-demo-channels', action='store_true', default=False, dest='includeDemo',
help='include demo channels on HDHR')
args = parser.parse_args()
s = MythTVServices(args.backend, args.port, args.username, args.password)
try:
hostname = s.Myth('GetHostName')['String']
except Exception:
print('Unable to obtain hostname from host {}:{}'.format(args.backend, args.port))
sys.exit(1)
ChannelServiceVersion = versionTuple(s.Channel('version')['String'])
# Validate sourceid/name
mythsl = s.Channel('GetVideoSourceList')['VideoSourceList']['VideoSources']
sourceId = None
sourceName = None
for source in mythsl:
if int(source['Id']) == args.sourceId or source['SourceName'] == args.sourceName:
sourceId = int(source['Id'])
sourceName = source['SourceName']
break
if sourceId is None:
print('Video source not found')
sys.exit(1)
# Get channel list from hdhr
try:
hdhrLineup = requests.get('http://{}/lineup.json?show=all&tuning'.format(args.hdhr)).json()
except Exception:
print('Unable to obtain lineup from {}'.format(args.hdhr))
sys.exit(1)
hdhrChannelList = {}
for hdhrChannel in hdhrLineup:
if 'GuideNumber' not in hdhrChannel:
continue
if 'Demo' in hdhrChannel:
if bool(hdhrChannel['Demo']) and not args.includeDemo:
continue
guidenumber = channelNormalize(hdhrChannel['GuideNumber'])
if args.channelInclude is not None:
if guidenumber not in args.channelInclude:
continue
if args.channelExclude is not None:
if guidenumber in args.channelExclude:
continue
hdhrChannelList[guidenumber] = hdhrChannel
# Get channel list for source
mythChannelInfo = s.Channel('GetChannelInfoList', {'SourceID': sourceId, 'Details': True})['ChannelInfoList']['ChannelInfos']
mythChannelList = []
mythChannelFreqList = []
for mythChannel in mythChannelInfo:
if 'ChanNum' not in mythChannel:
continue
c = channelNormalize(mythChannel['ChanNum'])
if args.channelInclude is not None:
if c not in args.channelInclude:
continue
if args.channelExclude is not None:
if c in args.channelExclude:
continue
mythChannelList.append(mythChannel)
f = channelNormalize(mythChannel['FrequencyId'])
mythChannelFreqList.append(f)
# Report on channels that (likely) should be added to MythTV
for guidenumber in natsort.natsorted(hdhrChannelList.keys()):
if guidenumber not in mythChannelFreqList:
hdhrChannel = hdhrChannelList[guidenumber]
guidename = ''
if 'GuideName' in hdhrChannel:
guidename = hdhrChannel['GuideName']
print('Tuner channel {} ({}) is not in the MythTV channel database'.format(guidenumber, guidename))
# Report on channels that (likely) should be deleted from MythTV
mythChannelList = natsort.natsorted(mythChannelList, key=lambda c: c['ChanNum'])
for mythChannel in mythChannelList:
if mythChannel['FrequencyId'] not in hdhrChannelList:
print('MythTV channel {} ({}) specifies a tuner channel {} which is not available'.format(mythChannel['ChanNum'], mythChannel['CallSign'], mythChannel['FrequencyId']))