Project

General

Profile

RE: EPG for NA OTA/zap2it » zap2epg.py

zap2epg program - edit4ever !, 2018-01-04 21:13

 
1
# zap2epg tv schedule grabber for kodi
2
################################################################################
3
#   This program is free software: you can redistribute it and/or modify
4
#    it under the terms of the GNU General Public License as published by
5
#    the Free Software Foundation, either version 3 of the License, or
6
#    (at your option) any later version.
7
#
8
#    This program is distributed in the hope that it will be useful,
9
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
10
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
#    GNU General Public License for more details.
12
#
13
#    You should have received a copy of the GNU General Public License
14
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
################################################################################
16

    
17
import urllib2
18
import base64
19
import codecs
20
import time
21
import datetime
22
import _strptime
23
import calendar
24
import gzip
25
import os
26
import logging
27
import re
28
import json
29
import sys
30
from os.path import dirname
31
import xml.etree.ElementTree as ET
32
from collections import OrderedDict
33
import hashlib
34

    
35

    
36
def mainRun(userdata):
37
    settingsFile = os.path.join(userdata, 'settings.xml')
38
    settings = ET.parse(settingsFile)
39
    root = settings.getroot()
40
    settingsDict = {}
41
    xdescOrderDict = {}
42
    kodiVersion = root.attrib.get('version')
43
    logging.info('Kodi settings version is: %s', kodiVersion)
44
    for setting in root.findall('setting'):
45
        if kodiVersion == '2':
46
            settingStr = setting.text
47
        else:
48
            settingStr = setting.get('value')
49
            if settingStr == '':
50
                settingStr = None
51
        settingID = setting.get('id')
52
        settingsDict[settingID] = settingStr
53
    for setting in settingsDict:
54
        if setting == 'slist':
55
            stationList = settingsDict[setting]
56
        if setting == 'zipcode':
57
            zipcode = settingsDict[setting]
58
        if setting == 'lineup':
59
            lineup = settingsDict[setting]
60
        if setting == 'lineupcode':
61
            lineupcode = settingsDict[setting]
62
        if setting == 'days':
63
            days = settingsDict[setting]
64
        if setting == 'xdetails':
65
            xdetails = settingsDict[setting]
66
        if setting == 'xdesc':
67
            xdesc = settingsDict[setting]
68
        if setting == 'epicon':
69
            epicon = settingsDict[setting]
70
        if setting == 'epgenre':
71
            epgenre = settingsDict[setting]
72
        if setting == 'tvhurl':
73
            tvhurl = settingsDict[setting]
74
        if setting == 'tvhport':
75
            tvhport = settingsDict[setting]
76
        if setting == 'usern':
77
            usern = settingsDict[setting]
78
        if setting == 'passw':
79
            passw = settingsDict[setting]
80
        if setting == 'chmatch':
81
            chmatch = settingsDict[setting]
82
        if setting == 'tvhmatch':
83
            tvhmatch = settingsDict[setting]
84
        if setting.startswith('desc'):
85
            xdescOrderDict[setting] = (settingsDict[setting])
86
    xdescOrder = [value for (key, value) in sorted(xdescOrderDict.items())]
87
    if lineupcode != 'lineupId':
88
        chmatch = 'false'
89
        tvhmatch = 'false'
90
    if zipcode.isdigit():
91
        country = 'USA'
92
    else:
93
        country = 'CAN'
94
    logging.info('Running zap2epg-0.6.3 for zipcode: %s and lineup: %s', zipcode, lineup)
95
    pythonStartTime = time.time()
96
    cacheDir = os.path.join(userdata, 'cache')
97
    dayHours = int(days) * 8 # set back to 8 when done testing
98
    gridtimeStart = (int(time.mktime(time.strptime(str(datetime.datetime.now().replace(microsecond=0,second=0,minute=0)), '%Y-%m-%d %H:%M:%S'))))
99
    schedule = {}
100
    tvhMatchDict = {}
101

    
102
    def tvhMatchGet():
103
        tvhUrlBase = 'http://' + tvhurl + ":" + tvhport
104
        channels_url = tvhUrlBase + '/api/channel/grid?all=1&limit=999999999&sort=name&filter=[{"type":"boolean","value":true,"field":"enabled"}]'
105
        if usern is not None and passw is not None:
106
            logging.info('Adding Tvheadend username and password to request url...')
107
            request = urllib2.Request(channels_url)
108
            request.add_header('Authorization', b'Basic ' + base64.b64encode(usern + b':' + passw))
109
            response = urllib2.urlopen(request)
110
        else:
111
            response = urllib2.urlopen(channels_url)
112
        try:
113
            logging.info('Accessing Tvheadend channel list from: %s', tvhUrlBase)
114
            channels = json.load(response)
115
            for ch in channels['entries']:
116
                channelName = ch['name']
117
                channelNum = ch['number']
118
                tvhMatchDict[channelNum] = channelName
119
            logging.info('%s Tvheadend channels found...', str(len(tvhMatchDict)))
120
        except urllib2.HTTPError as e:
121
            logging.exception('Exception: tvhMatch - %s', e.strerror)
122
            pass
123

    
124
    def deleteOldCache(gridtimeStart, showList):
125
        logging.info('Checking for old cache files...')
126
        try:
127
            if os.path.exists(cacheDir):
128
                entries = os.listdir(cacheDir)
129
                for entry in entries:
130
                    oldfile = entry.split('.')[0]
131
                    if oldfile.isdigit():
132
                        fn = os.path.join(cacheDir, entry)
133
                        if (int(oldfile) + 10800) < gridtimeStart:
134
                            try:
135
                                os.remove(fn)
136
                                logging.info('Deleting old cache: %s', entry)
137
                            except OSError, e:
138
                                logging.warn('Error Deleting: %s - %s.' % (e.filename, e.strerror))
139
                    elif not oldfile.isdigit():
140
                        fn = os.path.join(cacheDir, entry)
141
                        if oldfile not in showList:
142
                            try:
143
                                os.remove(fn)
144
                                logging.info('Deleting old cache: %s', entry)
145
                            except OSError, e:
146
                                logging.warn('Error Deleting: %s - %s.' % (e.filename, e.strerror))
147
        except Exception as e:
148
            logging.exception('Exception: deleteOldCache - %s', e.strerror)
149

    
150
    def convTime(t):
151
        return time.strftime("%Y%m%d%H%M%S",time.localtime(int(t)))
152

    
153
    def savepage(fn, data):
154
        if not os.path.exists(cacheDir):
155
            os.mkdir(cacheDir)
156
        fileDir = os.path.join(cacheDir, fn)
157
        with gzip.open(fileDir,"wb+") as f:
158
            f.write(data)
159
            f.close()
160

    
161
    def genreSort(EPfilter, EPgenre):
162
        genreList = []
163
        if epgenre == '2':
164
            # for f in EPfilter:
165
            #     fClean = re.sub('filter-','',f)
166
            #     genreList.append(fClean)
167
            for g in EPgenre:
168
                if g != "Comedy":
169
                    genreList.append(g)
170
            if 'Movie' in genreList or 'movie' in genreList or 'Movies' in genreList:
171
                genreList.insert(0, "Movie / Drama")
172
            if 'News' in genreList:
173
                genreList.insert(0, "News / Current affairs")
174
            if 'Game show' in genreList:
175
                genreList.insert(0, "Game show / Quiz / Contest")
176
            if 'Law' in genreList:
177
                genreList.insert(0, "Show / Game show")
178
            if 'Art' in genreList or 'Culture' in genreList:
179
                genreList.insert(0, "Arts / Culture (without music)")
180
            if 'Entertainment' in genreList:
181
                genreList.insert(0, "Popular culture / Traditional Arts")
182
            if 'Politics' in genreList or 'Social' in genreList or 'Public affairs' in genreList:
183
                genreList.insert(0, "Social / Political issues / Economics")
184
            if 'Education' in genreList or 'Science' in genreList:
185
                genreList.insert(0, "Education / Science / Factual topics")
186
            if 'How-to' in genreList:
187
                genreList.insert(0, "Leisure hobbies")
188
            if 'Travel' in genreList:
189
                genreList.insert(0, "Tourism / Travel")
190
            if 'Sitcom' in genreList:
191
                genreList.insert(0, "Variety show")
192
            if 'Talk' in genreList:
193
                genreList.insert(0, "Talk show")
194
            if 'Children' in genreList:
195
                genreList.insert(0, "Children's / Youth programs")
196
            if 'Animated' in genreList:
197
                genreList.insert(0, "Cartoons / Puppets")
198
            if 'Music' in genreList:
199
                genreList.insert(0, "Music / Ballet / Dance")
200
        if epgenre == '1':
201
            # for f in EPfilter:
202
            #     fClean = re.sub('filter-','',f)
203
            #     genreList.append(fClean)
204
            for g in EPgenre:
205
                genreList.append(g)
206
            if 'Movie' in genreList or 'movie' in genreList or 'Movies' in genreList:
207
                genreList = ["Movie / Drama"]
208
            elif 'News' in genreList:
209
                genreList = ["News / Current affairs"]
210
            elif 'News magazine' in genreList:
211
                genreList = ["News magazine"]
212
            elif 'Public affairs' in genreList:
213
                genreList = ["News / Current affairs"]
214
            elif 'Interview' in genreList:
215
                genreList = ["Discussion / Interview / Debate"]
216
            elif 'Game show' in genreList:
217
                genreList = ["Game show / Quiz / Contest"]
218
            elif 'Talk' in genreList:
219
                genreList = ["Talk show"]
220
            elif 'Sports' in genreList:
221
                genreList = ["Sports"]
222
            elif 'Sitcom' in genreList:
223
                genreList = ["Variety show"]
224
            elif 'Children' in genreList:
225
                genreList = ["Children's / Youth programs"]
226
            else:
227
                genreList = ["Variety show"]
228
        if epgenre == '3':
229
            # for f in EPfilter:
230
            #     fClean = re.sub('filter-','',f)
231
            #     genreList.append(fClean)
232
            for g in EPgenre:
233
                genreList.append(g)
234
        if 'Movie' in genreList:
235
            genreList.remove('Movie')
236
            genreList.insert(0, 'Movie')
237
        return genreList
238

    
239
    def printHeader(fh, enc):
240
        logging.info('Creating xmltv.xml file...')
241
        fh.write("<?xml version=\"1.0\" encoding=\""+ enc + "\"?>\n")
242
        fh.write("<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\n\n")
243
        fh.write("<tv source-info-url=\"http://tvschedule.zap2it.com/\" source-info-name=\"zap2it.com\">\n")
244

    
245
    def printFooter(fh):
246
        fh.write("</tv>\n")
247

    
248
    def printStations(fh):
249
        global stationCount
250
        stationCount = 0
251
        try:
252
            logging.info('Writing Stations to xmltv.xml file...')
253
            try:
254
                scheduleSort = OrderedDict(sorted(schedule.iteritems(), key=lambda x: int(x[1]['chnum'])))
255
            except:
256
                scheduleSort = OrderedDict(sorted(schedule.iteritems(), key=lambda x: x[1]['chfcc']))
257
            for station in scheduleSort:
258
                fh.write('\t<channel id=\"' + station + '.zap2epg\">\n')
259
                if 'chtvh' in scheduleSort[station] and scheduleSort[station]['chtvh'] is not None:
260
                    xchtvh = re.sub('&','&amp;',scheduleSort[station]['chtvh'])
261
                    fh.write('\t\t<display-name>' + xchtvh + '</display-name>\n')
262
                if 'chnum' in scheduleSort[station] and 'chfcc' in scheduleSort[station]:
263
                    xchnum = scheduleSort[station]['chnum']
264
                    xchfcc = scheduleSort[station]['chfcc']
265
                    fh.write('\t\t<display-name>' + xchnum + ' ' + re.sub('&','&amp;',xchfcc) + '</display-name>\n')
266
                    fh.write('\t\t<display-name>' + re.sub('&','&amp;',xchfcc) + '</display-name>\n')
267
                    fh.write('\t\t<display-name>' + xchnum + '</display-name>\n')
268
                elif 'chfcc' in scheduleSort[station]:
269
                    xchnum = scheduleSort[station]['chfcc']
270
                    fh.write('\t\t<display-name>' + re.sub('&','&amp;',xcfcc) + '</display-name>\n')
271
                elif 'chnum' in scheduleSort[station]:
272
                    xchnum = scheduleSort[station]['chnum']
273
                    fh.write('\t\t<display-name>' + xchnum + '</display-name>\n')
274
                if 'chicon' in scheduleSort[station]:
275
                    fh.write("\t\t<icon src=\"http:" + scheduleSort[station]['chicon'] + "\" />\n")
276
                fh.write("\t</channel>\n")
277
                stationCount += 1
278
        except Exception as e:
279
            logging.exception('Exception: printStations')
280

    
281
    def printEpisodes(fh):
282
        global episodeCount
283
        episodeCount = 0
284
        try:
285
            logging.info('Writing Episodes to xmltv.xml file...')
286
            if xdesc is True:
287
                logging.info('Appending Xdetails to description for xmltv.xml file...')
288
            for station in schedule:
289
                lang = 'en'
290
                sdict = schedule[station]
291
                for episode in sdict:
292
                    if not episode.startswith("ch"):
293
                        try:
294
                            edict = sdict[episode]
295
                            if 'epstart' in edict:
296
                                startTime = convTime(edict['epstart'])
297
                                is_dst = time.daylight and time.localtime().tm_isdst > 0
298
                                TZoffset = "%.2d%.2d" %(- (time.altzone if is_dst else time.timezone)/3600, 0)
299
                                stopTime = convTime(edict['epend'])
300
                                fh.write('\t<programme start=\"' + startTime + ' ' + TZoffset + '\" stop=\"' + stopTime + ' ' + TZoffset + '\" channel=\"' + station + '.zap2epg' + '\">\n')
301
                                fh.write('\t\t<episode-num system=\"dd_progid\">' + edict['epid'] + '</episode-num>\n')
302
                                if edict['epshow'] is not None:
303
                                    fh.write('\t\t<title lang=\"' + lang + '\">' + re.sub('&','&amp;',edict['epshow']) + '</title>\n')
304
                                if edict['eptitle'] is not None:
305
                                    fh.write('\t\t<sub-title lang=\"'+ lang + '\">' + re.sub('&','&amp;', edict['eptitle']) + '</sub-title>\n')
306
                                if xdesc == 'true':
307
                                    xdescSort = addXDetails(edict)
308
                                    fh.write('\t\t<desc lang=\"' + lang + '\">' + re.sub('&','&amp;', xdescSort) + '</desc>\n')
309
                                if xdesc == 'false':
310
                                    if edict['epdesc'] is not None:
311
                                        fh.write('\t\t<desc lang=\"' + lang + '\">' + re.sub('&','&amp;', edict['epdesc']) + '</desc>\n')
312
                                if edict['epsn'] is not None and edict['epen'] is not None:
313
                                    fh.write("\t\t<episode-num system=\"onscreen\">" + 'S' + edict['epsn'].zfill(2) + 'E' + edict['epen'].zfill(2) + "</episode-num>\n")
314
                                    fh.write("\t\t<episode-num system=\"xmltv_ns\">" + str(int(edict['epsn'])-1) +  "." + str(int(edict['epen'])-1) + ".</episode-num>\n")
315
                                if edict['epyear'] is not None:
316
                                    fh.write('\t\t<date>' + edict['epyear'] + '</date>\n')
317
                                if not episode.startswith("MV"):
318
                                    if epicon == '1':
319
                                        if edict['epimage'] is not None and edict['epimage'] != '':
320
                                            fh.write('\t\t<icon src="https://zap2it.tmsimg.com/assets/' + edict['epimage'] + '.jpg" />\n')
321
                                        else:
322
                                            if edict['epthumb'] is not None and edict['epthumb'] != '':
323
                                                fh.write('\t\t<icon src="https://zap2it.tmsimg.com/assets/' + edict['epthumb'] + '.jpg" />\n')
324
                                    if epicon == '2':
325
                                        if edict['epthumb'] is not None and edict['epthumb'] != '':
326
                                            fh.write('\t\t<icon src="https://zap2it.tmsimg.com/assets/' + edict['epthumb'] + '.jpg" />\n')
327
                                if episode.startswith("MV"):
328
                                    if edict['epthumb'] is not None and edict['epthumb'] != '':
329
                                        fh.write('\t\t<icon src="https://zap2it.tmsimg.com/assets/' + edict['epthumb'] + '.jpg" />\n')
330
                                if not any(i in ['New', 'Live'] for i in edict['epflag']):
331
                                    fh.write("\t\t<previously-shown ")
332
                                    if edict['epoad'] is not None and int(edict['epoad']) > 0:
333
                                        fh.write("start=\"" + convTime(edict['epoad']) + " " + TZoffset + "\"")
334
                                    fh.write(" />\n")
335
                                if edict['epflag'] is not None:
336
                                    if 'New' in edict['epflag']:
337
                                        fh.write("\t\t<new />\n")
338
                                    if 'Live' in edict['epflag']:
339
                                        fh.write("\t\t<live />\n")
340
                                if edict['eprating'] is not None:
341
                                    fh.write('\t\t<rating>\n\t\t\t<value>' + edict['eprating'] + '</value>\n\t\t</rating>\n')
342
                                if edict['epstar'] is not None:
343
                                    fh.write('\t\t<star-rating>\n\t\t\t<value>' + edict['epstar'] + '/4</value>\n\t\t</star-rating>\n')
344
                                if epgenre != '0':
345
                                    if edict['epfilter'] is not None and edict['epgenres'] is not None:
346
                                        genreNewList = genreSort(edict['epfilter'], edict['epgenres'])
347
                                        for genre in genreNewList:
348
                                            fh.write("\t\t<category lang=\"" + lang + "\">" + genre + "</category>\n")
349
                                fh.write("\t</programme>\n")
350
                                episodeCount += 1
351
                        except Exception as e:
352
                            logging.exception('No data for episode %s:', episode)
353
                            #fn = os.path.join(cacheDir, episode + '.json')
354
                            #os.remove(fn)
355
                            #logging.info('Deleting episode %s:', episode)
356
        except Exception as e:
357
            logging.exception('Exception: printEpisodes')
358

    
359
    def xmltv():
360
        try:
361
            enc = 'utf-8'
362
            outFile = os.path.join(userdata, 'xmltv.xml')
363
            fh = codecs.open(outFile, 'w+b', encoding=enc)
364
            printHeader(fh, enc)
365
            printStations(fh)
366
            printEpisodes(fh)
367
            printFooter(fh)
368
            fh.close()
369
        except Exception as e:
370
            logging.exception('Exception: xmltv')
371

    
372
    def parseStations(content):
373
        try:
374
            ch_guide = json.loads(content)
375
            for station in ch_guide['channels']:
376
                skey = station.get('channelId')
377
                if stationList is not None:
378
                    if skey in stationList:
379
                        schedule[skey] = {}
380
                        chName = station.get('callSign')
381
                        schedule[skey]['chfcc'] = chName
382
                        schedule[skey]['chicon'] = station.get('thumbnail').split('?')[0]
383
                        chnumStart = station.get('channelNo')
384
                        if '.' not in chnumStart and chmatch == 'true' and chName is not None:
385
                            chsub = re.search('(\d+)$', chName)
386
                            if chsub is not None:
387
                                chnumUpdate = chnumStart + '.' + chsub.group(0)
388
                            else:
389
                                chnumUpdate = chnumStart + '.1'
390
                        else:
391
                            chnumUpdate = chnumStart
392
                        schedule[skey]['chnum'] = chnumUpdate
393
                        if tvhmatch == 'true' and '.' in chnumUpdate:
394
                            if chnumUpdate in tvhMatchDict:
395
                                schedule[skey]['chtvh'] = tvhMatchDict[chnumUpdate]
396
                            else:
397
                                schedule[skey]['chtvh'] = None
398
                else:
399
                    schedule[skey] = {}
400
                    chName = station.get('callSign')
401
                    schedule[skey]['chfcc'] = chName
402
                    schedule[skey]['chicon'] = station.get('thumbnail').split('?')[0]
403
                    chnumStart = station.get('channelNo')
404
                    if '.' not in chnumStart and chmatch == 'true' and chName is not None:
405
                        chsub = re.search('(\d+)$', chName)
406
                        if chsub is not None:
407
                            chnumUpdate = chnumStart + '.' + chsub.group(0)
408
                        else:
409
                            chnumUpdate = chnumStart + '.1'
410
                    else:
411
                        chnumUpdate = chnumStart
412
                    schedule[skey]['chnum'] = chnumUpdate
413
                    if tvhmatch == 'true' and '.' in chnumUpdate:
414
                        if chnumUpdate in tvhMatchDict:
415
                            schedule[skey]['chtvh'] = tvhMatchDict[chnumUpdate]
416
                        else:
417
                            schedule[skey]['chtvh'] = None
418
        except Exception as e:
419
            logging.exception('Exception: parseStations')
420

    
421
    def parseEpisodes(content):
422
        try:
423
            ch_guide = json.loads(content)
424
            for station in ch_guide['channels']:
425
                skey = station.get('channelId')
426
                if stationList is not None:
427
                    if skey in stationList:
428
                        episodes = station.get('events')
429
                        for episode in episodes:
430
                            epkey = str(calendar.timegm(time.strptime(episode.get('startTime'), '%Y-%m-%dT%H:%M:%SZ')))
431
                            schedule[skey][epkey] = {}
432
                            schedule[skey][epkey]['epid'] = episode['program'].get('tmsId')
433
                            schedule[skey][epkey]['epstart'] = str(calendar.timegm(time.strptime(episode.get('startTime'), '%Y-%m-%dT%H:%M:%SZ')))
434
                            schedule[skey][epkey]['epend'] = str(calendar.timegm(time.strptime(episode.get('endTime'), '%Y-%m-%dT%H:%M:%SZ')))
435
                            schedule[skey][epkey]['eplength'] = episode.get('duration')
436
                            schedule[skey][epkey]['epshow'] = episode['program'].get('title')
437
                            schedule[skey][epkey]['eptitle'] = episode['program'].get('episodeTitle')
438
                            schedule[skey][epkey]['epdesc'] = episode['program'].get('shortDesc')
439
                            schedule[skey][epkey]['epyear'] = episode['program'].get('releaseYear')
440
                            schedule[skey][epkey]['eprating'] = episode.get('rating')
441
                            schedule[skey][epkey]['epflag'] = episode.get('flag')
442
                            schedule[skey][epkey]['eptags'] = episode.get('tags')
443
                            schedule[skey][epkey]['epsn'] = episode['program'].get('season')
444
                            schedule[skey][epkey]['epen'] = episode['program'].get('episode')
445
                            schedule[skey][epkey]['epthumb'] = episode.get('thumbnail')
446
                            schedule[skey][epkey]['epoad'] = None
447
                            schedule[skey][epkey]['epstar'] = None
448
                            schedule[skey][epkey]['epfilter'] = episode.get('filter')
449
                            schedule[skey][epkey]['epgenres'] = None
450
                            schedule[skey][epkey]['epcredits'] = None
451
                            schedule[skey][epkey]['epxdesc'] = None
452
                            schedule[skey][epkey]['epseries'] = episode.get('seriesId')
453
                            schedule[skey][epkey]['epimage'] = None
454
                            schedule[skey][epkey]['epfan'] = None
455
                else:
456
                    episodes = station.get('events')
457
                    for episode in episodes:
458
                        epkey = str(calendar.timegm(time.strptime(episode.get('startTime'), '%Y-%m-%dT%H:%M:%SZ')))
459
                        schedule[skey][epkey] = {}
460
                        schedule[skey][epkey]['epid'] = episode['program'].get('tmsId')
461
                        schedule[skey][epkey]['epstart'] = str(calendar.timegm(time.strptime(episode.get('startTime'), '%Y-%m-%dT%H:%M:%SZ')))
462
                        schedule[skey][epkey]['epend'] = str(calendar.timegm(time.strptime(episode.get('endTime'), '%Y-%m-%dT%H:%M:%SZ')))
463
                        schedule[skey][epkey]['eplength'] = episode.get('duration')
464
                        schedule[skey][epkey]['epshow'] = episode['program'].get('title')
465
                        schedule[skey][epkey]['eptitle'] = episode['program'].get('episodeTitle')
466
                        schedule[skey][epkey]['epdesc'] = episode['program'].get('shortDesc')
467
                        schedule[skey][epkey]['epyear'] = episode['program'].get('releaseYear')
468
                        schedule[skey][epkey]['eprating'] = episode.get('rating')
469
                        schedule[skey][epkey]['epflag'] = episode.get('flag')
470
                        schedule[skey][epkey]['eptags'] = episode.get('tags')
471
                        schedule[skey][epkey]['epsn'] = episode['program'].get('season')
472
                        schedule[skey][epkey]['epen'] = episode['program'].get('episode')
473
                        schedule[skey][epkey]['epthumb'] = episode.get('thumbnail')
474
                        schedule[skey][epkey]['epoad'] = None
475
                        schedule[skey][epkey]['epstar'] = None
476
                        schedule[skey][epkey]['epfilter'] = episode.get('filter')
477
                        schedule[skey][epkey]['epgenres'] = None
478
                        schedule[skey][epkey]['epcredits'] = None
479
                        schedule[skey][epkey]['epxdesc'] = None
480
                        schedule[skey][epkey]['epseries'] = episode.get('seriesId')
481
                        schedule[skey][epkey]['epimage'] = None
482
                        schedule[skey][epkey]['epfan'] = None
483
        except Exception as e:
484
            logging.exception('Exception: parseEpisodes')
485

    
486
    def parseXdetails():
487
        showList = []
488
        failList = []
489
        try:
490
            for station in schedule:
491
                sdict = schedule[station]
492
                for episode in sdict:
493
                    if not episode.startswith("ch"):
494
                        edict = sdict[episode]
495
                        EPseries = edict['epseries']
496
                        showList.append(edict['epseries'])
497
                        filename = EPseries + '.json'
498
                        fileDir = os.path.join(cacheDir, filename)
499
                        try:
500
                            if not os.path.exists(fileDir) and EPseries not in failList:
501
                                retry = 3
502
                                while retry > 0:
503
                                    logging.info('Downloading details data for: %s', EPseries)
504
                                    url = 'https://tvlistings.gracenote.com/api/program/overviewDetails'
505
                                    data = 'programSeriesID=' + EPseries
506
                                    try:
507
                                        URLcontent = urllib2.Request(url, data=data)
508
                                        JSONcontent = urllib2.urlopen(URLcontent).read()
509
                                        if JSONcontent:
510
                                            with open(fileDir,"wb+") as f:
511
                                                f.write(JSONcontent)
512
                                                f.close()
513
                                            retry = 0
514
                                        else:
515
                                            time.sleep(1)
516
                                            retry -= 1
517
                                            logging.warn('Retry downloading missing details data for: %s', EPseries)
518
                                    except urllib2.URLError, e:
519
                                        time.sleep(1)
520
                                        retry -= 1
521
                                        logging.warn('Retry downloading details data for: %s  -  %s', EPseries, e)
522
                            if os.path.exists(fileDir):
523
                                fileSize = os.path.getsize(fileDir)
524
                                if fileSize > 0:
525
                                    with open(fileDir, 'rb') as f:
526
                                        EPdetails = json.loads(f.read())
527
                                        f.close()
528
                                    logging.info('Parsing %s', filename)
529
                                    edict['epimage'] = EPdetails.get('seriesImage')
530
                                    edict['epfan'] = EPdetails.get('backgroundImage')
531
                                    EPgenres = EPdetails.get('seriesGenres')
532
                                    if filename.startswith("MV"):
533
                                        edict['epcredits'] = EPdetails['overviewTab'].get('cast')
534
                                        EPgenres = 'Movie|' + EPgenres
535
                                    edict['epgenres'] = EPgenres.split('|')
536
                                    #edict['epstar'] = EPdetails.get('starRating')
537
                                    EPlist = EPdetails['upcomingEpisodeTab']
538
                                    EPid = edict['epid']
539
                                    for airing in EPlist:
540
                                        if EPid.lower() == airing['tmsID'].lower():
541
                                            if not episode.startswith("MV"):
542
                                                try:
543
                                                    origDate = airing.get('originalAirDate')
544
                                                    if origDate != '':
545
                                                        EPoad = re.sub('Z', ':00Z', airing.get('originalAirDate'))
546
                                                        edict['epoad'] = str(calendar.timegm(time.strptime(EPoad, '%Y-%m-%dT%H:%M:%SZ')))
547
                                                except Exception as e:
548
                                                    logging.exception('Could not parse oad for: %s - %s', episode, e)
549

    
550
                                else:
551
                                    logging.warn('Could not parse data for: %s - deleting file', filename)
552
                                    os.remove(fileDir)
553
                            else:
554
                                logging.warn('Could not download details data for: %s - skipping episode', episode)
555
                                failList.append(EPseries)
556
                        except Exception as e:
557
                            logging.exception('Could not parse data for: %s - deleting file  -  %s', episode, e)
558
                            #os.remove(fileDir)
559
        except Exception as e:
560
            logging.exception('Exception: parseXdetails')
561
        return showList
562

    
563
    def addXDetails(edict):
564
        try:
565
            ratings = ""
566
            date = ""
567
            myear = ""
568
            new = ""
569
            live = ""
570
            hd = ""
571
            cc = ""
572
            cast = ""
573
            season = ""
574
            epis = ""
575
            episqts = ""
576
            prog = ""
577
            plot= ""
578
            descsort = ""
579
            bullet = u"\u2022 "
580
            hyphen = u"\u2013 "
581
            newLine = "\n"
582
            space = " "
583
            colon = u"\u003A "
584
            vbar = u"\u007C "
585
            slash = u"\u2215 "
586
            comma = u"\u002C "
587

    
588
            def getSortName(opt):
589
                return {
590
                    1: bullet,
591
                    2: newLine,
592
                    3: hyphen,
593
                    4: space,
594
                    5: colon,
595
                    6: vbar,
596
                    7: slash,
597
                    8: comma,
598
                    9: plot,
599
                    10: new,
600
                    11: hd,
601
                    12: cc,
602
                    13: season,
603
                    14: ratings,
604
                    15: date,
605
                    16: prog,
606
                    17: epis,
607
                    18: episqts,
608
                    19: cast,
609
                    20: myear,
610
                }.get(opt, None)
611

    
612
            def cleanSortList(optList):
613
                cleanList=[]
614
                optLen = len(optList)
615
                for opt in optList:
616
                    thisOption = getSortName(int(opt))
617
                    if thisOption:
618
                        cleanList.append(int(opt))
619
                for item in reversed(cleanList):
620
                    if cleanList[-1] <= 8:
621
                        del cleanList[-1]
622
                return cleanList
623

    
624
            def makeDescsortList(optList):
625
                sortOrderList =[]
626
                lastOption = 1
627
                cleanedList = cleanSortList(optList)
628
                for opt in cleanedList:
629
                    thisOption = getSortName(int(opt))
630
                    if int(opt) <= 8 and lastOption <= 8:
631
                        if int(opt) == 2 and len(sortOrderList) > 1:
632
                            del sortOrderList[-1]
633
                            sortOrderList.append(thisOption)
634
                        lastOption = int(opt)
635
                    elif thisOption and lastOption:
636
                        sortOrderList.append(thisOption)
637
                        lastOption = int(opt)
638
                    elif thisOption:
639
                        lastOption = int(opt)
640
                return sortOrderList
641

    
642
            if edict['epoad'] is not None and int(edict['epoad']) > 0:
643
                is_dst = time.daylight and time.localtime().tm_isdst > 0
644
                TZoffset = (time.altzone if is_dst else time.timezone)
645
                origDate = int(edict['epoad']) + TZoffset
646
                finalDate = datetime.datetime.fromtimestamp(origDate).strftime('%B %d%% %Y')
647
                finalDate = re.sub('%', ',', finalDate)
648
                date = "First aired: " + finalDate + space
649
            if edict['epyear'] is not None:
650
                myear = "Released: " + edict['epyear'] + space
651
            if edict['eprating'] is not None:
652
                ratings = edict['eprating'] + space
653
            if edict['epflag'] != []:
654
                flagList = edict['epflag']
655
                new = ' - '.join(flagList).upper() + space
656
            #if edict['epnew'] is not None:
657
                #new = edict['epnew'] + space
658
            #if edict['eplive'] is not None:
659
                #new = edict['eplive'] + space
660
            #if edict['epprem'] is not None:
661
                #new = edict['epprem'] + space
662
            #if edict['epfin'] is not None:
663
                #new = edict['epfin'] + space
664
            if edict['eptags'] != []:
665
                tagsList = edict['eptags']
666
                cc = ' | '.join(tagsList).upper() + space
667
            #if edict['ephd'] is not None:
668
                #hd = edict['ephd'] + space
669
            if edict['epsn'] is not None and edict['epen'] is not None:
670
                s = re.sub('S', '', edict['epsn'])
671
                sf = "Season " + str(int(s))
672
                e = re.sub('E', '', edict['epen'])
673
                ef = "Episode " + str(int(e))
674
                season = sf + " - " + ef + space
675
            if edict['epcredits'] is not None:
676
                cast = "Cast: "
677
                castlist = ""
678
                prev = None
679
                EPcastList = []
680
                for c in edict['epcredits']:
681
                    EPcastList.append(c['name'])
682
                for g in EPcastList:
683
                    if prev is None:
684
                        castlist = g
685
                        prev = g
686
                    else:
687
                        castlist = castlist + ", " + g
688
                cast = cast + castlist + space
689
            if edict['epshow'] is not None:
690
                prog = edict['epshow'] + space
691
            if edict['eptitle'] is not None:
692
                epis = edict['eptitle'] + space
693
                episqts = '\"' + edict['eptitle'] + '\"' + space
694
            if edict['epdesc'] is not None:
695
                plot = edict['epdesc'] + space
696

    
697
        # todo - handle star ratings
698

    
699
            descsort = "".join(makeDescsortList(xdescOrder))
700
            return descsort
701
        except Exception as e:
702
            logging.exception('Exception: addXdetails to description')
703

    
704

    
705
    try:
706
        if not os.path.exists(cacheDir):
707
            os.mkdir(cacheDir)
708
        count = 0
709
        gridtime = gridtimeStart
710
        if stationList is None:
711
            logging.info('No channel list found - adding all stations!')
712
        if tvhmatch == 'true':
713
            tvhMatchGet()
714
        while count < dayHours:
715
            filename = str(gridtime) + '.json.gz'
716
            fileDir = os.path.join(cacheDir, filename)
717
            if not os.path.exists(fileDir):
718
                try:
719
                    logging.info('Downloading guide data for: %s', str(gridtime))
720
                    url = 'http://tvlistings.gracenote.com/api/grid?lineupId=&timespan=3&headendId=' + lineupcode + '&country=' + country + '&device=-&postalCode=' + zipcode + '&time=' + str(gridtime) + '&pref=-&userId=-'
721
                    saveContent = urllib2.urlopen(url).read()
722
                    savepage(fileDir, saveContent)
723
                except:
724
                    logging.warn('Could not download guide data for: %s', str(gridtime))
725
                    logging.warn('URL: %s', url)
726
            if os.path.exists(fileDir):
727
                try:
728
                    with gzip.open(fileDir, 'rb') as f:
729
                        content = f.read()
730
                        f.close()
731
                    logging.info('Parsing %s', filename)
732
                    if count == 0:
733
                        parseStations(content)
734
                    parseEpisodes(content)
735
                except:
736
                    logging.warn('JSON file error for: %s - deleting file', filename)
737
                    os.remove(fileDir)
738
            count += 1
739
            gridtime = gridtime + 10800
740
        if xdetails == 'true':
741
            showList = parseXdetails()
742
        else:
743
            showList = []
744
        xmltv()
745
        deleteOldCache(gridtimeStart, showList)
746
        timeRun = round((time.time() - pythonStartTime),2)
747
        logging.info('zap2epg completed in %s seconds. ', timeRun)
748
        logging.info('%s Stations and %s Episodes written to xmltv.xml file.', str(stationCount), str(episodeCount))
749
####### remove this block after testing
750
        # dictFileName = 'REdict.json'
751
        # DictFileDir = os.path.join(userdata, dictFileName)
752
        # with open(DictFileDir, 'w') as fp:
753
        #     json.dump(REdict, fp)
754
        # dictFileNameTxt = 'schedule.txt'
755
        # DictFileTxtDir = os.path.join(userdata, dictFileNameTxt)
756
        # f = open(DictFileTxtDir,"w")
757
        # f.write( str(schedule) )
758
        # f.close()
759
####### remove this block after testing
760
        return timeRun, stationCount, episodeCount
761
    except Exception as e:
762
        logging.exception('Exception: main')
763

    
764
if __name__ == '__main__':
765
    userdata = os.getcwd()
766
    log = os.path.join(userdata, 'zap2epg.log')
767
    logging.basicConfig(filename=log, filemode='w', format='%(asctime)s %(message)s', datefmt='%Y/%m/%d %H:%M:%S', level=logging.DEBUG)
768
    mainRun(userdata)
(2-2/2)