forked from garybuhrmaster/MythUtil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
MythUtil-Channel-HDHR-visibilityCheck
executable file
·319 lines (296 loc) · 14.5 KB
/
MythUtil-Channel-HDHR-visibilityCheck
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#!/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
def extendedVisibility(arg):
ua = str(arg).lower()
if ua in ['true'[:len(ua)], 'yes'[:len(ua)], '1', 'visible'[:len(ua)]]:
return 'Visible'
elif ua in ['false'[:len(ua)], 'no'[:len(ua)], '0', 'invisible'[:len(ua)], 'notvisible'[:len(ua)], 'not visible'[:len(ua)]]:
return 'Not Visible'
elif ua in ['always'[:len(ua)], '2', 'always visible'[:len(ua)]]:
return 'Always Visible'
elif ua in ['never'[:len(ua)], '-1', 'never visible'[:len(ua)]]:
return 'Never Visible'
raise argparse.ArgumentTypeError('{} is not a valid visible/true, not visible/false, always visible, never visible value'.format(arg))
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')
parser.add_argument('--respect-channel-disabled', action='store_true', default=False, dest='respectdisabled',
help='mark channels as not visible if disabled in device')
parser.add_argument('--ignore-copy-protection', action='store_true', default=False, dest='ignorecopyprotect',
help='ignore copy-protection settings for visibility determination')
visiblegroup = parser.add_mutually_exclusive_group(required=False)
visiblegroup.add_argument('--force-visible', action='store_true', default=False, dest='forcevisible',
help='force the channel(s) to be set to visible')
visiblegroup.add_argument('--force-invisible', action='store_true', default=False, dest='forceinvisible',
help='force the channel(s) to be set to invisible')
visiblegroup.add_argument('--visibility', action='store', type=extendedVisibility, dest='extendedvisible',
help='specify the channel(s) visibility')
parser.add_argument('--update', action='store_true', default=False,
help='update the channels to correct any detected problem. The default is just report')
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'])
if (ChannelServiceVersion < versionTuple("1.9")) and (args.extendedvisible in ['Never Visible', 'Always Visible']):
print('Warning: backend does not supported extended visibility settings')
# 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 = []
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)
# Report visibility changes
mythChannelList = natsort.natsorted(mythChannelList, key=lambda c: c['ChanNum'])
for mythChannel in mythChannelList:
chanNum = mythChannel['ChanNum']
freqId = mythChannel['FrequencyId']
if freqId not in hdhrChannelList:
print('Channel {} is not available on the tuner, visibility not available'.format(chanNum))
continue
else:
hdhrChannel = hdhrChannelList[freqId]
if 'Subscribed' not in hdhrChannel:
subscribed = True
else:
subscribed = bool(hdhrChannel['Subscribed'])
if 'DRM' not in hdhrChannel:
drm = False
else:
drm = bool(hdhrChannel['DRM'])
if 'Enabled' not in hdhrChannel:
enabled = True
else:
enabled = bool(hdhrChannel['Enabled'])
HDHRvisible = subscribed
if drm and not args.ignorecopyprotect:
HDHRvisible = False
if not enabled and args.respectdisabled:
HDHRvisible = False
if ChannelServiceVersion >= versionTuple("1.9"):
mvisible = mythChannel['ExtendedVisible']
desiredvisible = mvisible
if args.extendedvisible:
desiredvisible = args.extendedvisible
elif args.forcevisible or HDHRvisible:
if mvisible in ['Visible', 'Not Visible']:
desiredvisible = 'Visible'
elif mvisible == 'Never Visible':
print('Channel {} visibility is should be: Visible, but locked to Never Visible'.format(chanNum))
continue
elif args.forceinvisible or not HDHRvisible:
if mvisible in ['Visible', 'Not Visible']:
desiredvisible = 'Not Visible'
elif mvisible == 'Always Visible':
print('Channel {} visibility is should be: Not Visible, but locked to Always Visible'.format(chanNum))
continue
else:
mvisible = bool(mythChannel['Visible'] == 'true')
desiredvisible = HDHRvisible
if args.extendedvisible:
desiredvisible = bool(args.extendedvisible in ['Visible', 'Always Visible'])
elif args.forcevisible:
desiredvisible = True
elif args.forceinvisible:
desiredvisible = False
if mvisible != desiredvisible:
print('Channel {} visibility should be: {}'.format(chanNum, desiredvisible))
if ChannelServiceVersion >= versionTuple("1.9"):
data = {}
data['ChanId'] = mythChannel['ChanId']
data['ExtendedVisible'] = desiredvisible
else:
data = mythChannel.copy()
data['Visible'] = desiredvisible
if args.update:
if bool(s.Channel('UpdateDBChannel', transformAPIElementNames('Channel', data))['bool']):
print(' Channel {} updated'.format(chanNum))
else:
print(' Channel {} update failed'.format(chanNum))
if args.update:
try:
s.Dvr('RescheduleRecordings')
except Exception:
pass