def _createM3uFile(self,channelsList,userDataPath): try: if not os.path.exists(userDataPath): os.makedirs(userDataPath) if not channelsList: raise FreeboxHandlerError('channel list is empty channelsList:') with io.open(userDataPath+'freebox.m3u', 'w', encoding='utf-8') as the_file: m3uhead = u'#EXTM3U\r\n' the_file.write(m3uhead) for dChannel in channelsList: m3uFormat = "#EXTINF:-1 channel-id=\"%d\" tvg-id=\"%s\" tvg-name=\"%s\" tvg-logo=\"%s\" group-title=\"%s\",%s\r\n%s\r\n" m3uline = m3uFormat % ( dChannel['number'], dChannel['channelId'], dChannel['shortname'].replace(" ", "_"), dChannel['logo'], dChannel['group'], dChannel['name'], dChannel['stream'] ) the_file.write(m3uline) return True except Exception, e: raise FreeboxHandlerError(str(e))
def _requestJsonData(self, url): apiResponse = requests.get(self.apiUrl + url , headers={'X-Fbx-App-Auth': self.session} , verify=self.certPath) if apiResponse.ok: jData = json.loads(apiResponse.content) if jData['success'] == True: return jData['result'] raise FreeboxHandlerError('API '+url+' Not answered as intended - HTTP Status:'+str(apiResponse.status_code)+'-'+str(jData['msg'].encode('utf-8'))) raise FreeboxHandlerError('API '+url+' Not answered as intended - HTTP Status:'+str(apiResponse.status_code))
def _checkBouquetId(self): try: jData = self._requestJsonData('/tv/bouquets/') for bouquet in jData: if bouquet['name'] == self.bouquetName: self.bouquetId = bouquet['id'] # if no ID returned, it must because they changed the name, so please report issue to update code if not self.bouquetId: raise FreeboxHandlerError('Bouquet Name seems to have changed report the issue this need to update code') except Exception as e: raise FreeboxHandlerError('jData:'+str(jData)+' bouquet:'+str(bouquet))
def _checkApiVersion(self): apiResponse = requests.get(self.apiUrl + '/api_version', verify=self.certPath) jData = json.loads(apiResponse.content) if apiResponse.ok and format(len(jData)) > 0: jData = json.loads(apiResponse.content) if LooseVersion(jData['api_version']) >= '3.0': return True raise FreeboxHandlerError('FreeBox API not compatible') return False else: raise FreeboxHandlerError('FreeBox API not answered - HTTP Status:'+apiResponse.status_code) return False
def _getSession(self): if not (self.appToken and self.challenge): raise FreeboxHandlerError('missing value appToken:'+self.appToken+" challenge:"+self.challenge) hashed = hmac.new(self.appToken, self.challenge, hashlib.sha1) payload = { 'app_id': self.appId, 'password': hashed.hexdigest() } data = json.dumps(payload) apiResponse = requests.post(self.apiUrl + '/login/session/', data, verify=self.certPath) jData = json.loads(apiResponse.content) if jData['success'] == True: self.session = jData['result']['session_token'] return True raise FreeboxHandlerError('Get Session - ' + str(jData['msg'].encode('utf-8') + "appToken:"+self.appToken+" challenge:"+self.challenge.encode('utf-8'))) return False
def _filterAndSortChannels(self,channelsList, streamsList): lChannels = [] for channelId in channelsList: # we filter channel and don't process unavailable channels if channelsList[channelId]['available'] == True: # we get the stream uri for the channel in choosed quality and the channel number in same time channelNumber = '' rtspUrl = '' try: streamChannel = (item for item in streamsList if item["uuid"] == channelsList[channelId]['uuid']).next() except StopIteration as e: xbmc.log('[FREEBOXTV] no stream found for '+str(channelsList[channelId]['uuid'])+' - '+channelsList[channelId]['name'], xbmc.LOGWARNING) # if the channel is not a pub_service, the rtsp is always missing if streamChannel['pub_service'] == True: channelNumber = streamChannel['number'] # we prepare a dict of stream uri & quality available for the channel lStream = {} for stream in streamChannel['streams']: lStream[stream['quality']] = stream['rtsp'] #we search for the nearest of choosed one quality available tmpQuality = self.quality while tmpQuality not in lStream: tmpQuality = self._getLowerQuality(tmpQuality) if not tmpQuality: raise FreeboxHandlerError("Quality unexpected with uuid:"+uuid) rtspUrl = lStream[tmpQuality] # we process the channel only if a stream uri is returned if rtspUrl: dChannel = { 'channelId':channelsList[channelId]['uuid'].replace('-','.'), 'number':channelNumber, 'name':channelsList[channelId]['name'], 'shortname':channelsList[channelId]['short_name'], 'logo':self.apiUrl.split('/api')[0]+channelsList[channelId]['logo_url'], 'group':self.bouquetName, 'stream':rtspUrl, #'quality':quality } lChannels.append(dChannel) if len(lChannels)<=0: raise FreeboxHandlerError("List is empty") # we sort the channels list by their official number in the bouquet finalChannelsList = sorted(lChannels, key=lambda lChannels: lChannels['number']) # We give some feedback to the user xbmc.executebuiltin('Notification(FreeboxTV, filtered %d channels on total of %d, %d )' % ( len(finalChannelsList), len(channelsList), 5000)) return finalChannelsList
def _checkPairing(self, trackId): apiResponse = requests.get(self.apiUrl + '/login/authorize/' + trackId, verify=self.certPath) jData = json.loads(apiResponse.content) if not apiResponse.ok and jData['success'] == True: raise FreeboxHandlerError(jData['msg']) # get the challenge token needed for getting login session if jData['result']['status'] == 'granted': self.challenge = jData['result']['challenge'] return True if jData['result']['status'] == 'unknown' or jData['result']['status'] == 'denied': #TODO: return false to display a popup saying to user to authorize pairing on their freebox raise FreeboxHandlerError('Pairing Revoked') if jData['result']['status'] == 'pending' or jData['result']['status'] == 'timeout': #TODO: return false to display a popup saying to user to authorize pairing on their freebox raise FreeboxHandlerError('User not confirmed authorization') return False
def _pairingWithFreebox(self): import socket payload = {'app_id': self.appId, 'app_name': self.appName, 'app_version': self.appVersion, 'device_name': self.deviceName } payload = json.dumps(payload) apiResponse = requests.post(self.apiUrl + '/login/authorize/', data=payload, verify=self.certPath) jData = json.loads(apiResponse.content) if not apiResponse.ok and jData['success'] == True: raise FreeboxHandlerError('ERROR: ' + jData['msg']) self.appToken = jData['result']['app_token'] self.trackId = jData['result']['track_id'] return (self.appToken, self.trackId)
def createXmlTvFile(self,channelsList,userDataPath): from jsonmerge import merge if not channelsList: raise FreeboxHandlerError('channel list is empty channelsList:') xmltvLine = ( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" "<!DOCTYPE tv SYSTEM \"xmltv.dtd\">\r\n\r\n" "<tv source-info-url=\"http://www.schedulesdirect.org/\" source-info-name=\"Schedules Direct\"" " generator-info-name=\"XMLTV/$Id: tv_grab_na_dd.in,v 1.70 2008/03/03 15:21:41 rmeden Exp" " $\" generator-info-url=\"http://www.xmltv.org/\">\r\n" ) channelNumber = 0 for channel in channelsList: channelNumber = channelNumber+1 xmltvLine += ( " <channel id=\"%s\">\r\n" " <display-name>%s</display-name>\r\n" " <icon src=\"%s\" />\r\n" " </channel>\r\n" ) % (channel['channelId'], channel['name'], channel['logo']) addTime = 0 programsList = {} os.environ['TZ'] = 'Europe/Paris' for i in range(0,24): # on interroge toute la journée addTime = 0 nowHour = int(time.strftime("%H")) deltaHour = i - nowHour if deltaHour >= 0: # enfin sauf les heures passés, osef if i > 0: addTime = deltaHour*3600 epoch = int(time.mktime(time.localtime())) + addTime xbmc.log('epoch:'+str(epoch)+' meaning:'+time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(epoch)),xbmc.LOGWARNING) programsList = merge( programsList, self._requestJsonData('/tv/epg/by_time/'+str(epoch)) ) programmNumber = 0 channelProgramNumber = 0 for channelId,lChannel in programsList.iteritems(): channelProgramNumber = channelProgramNumber +1 for programId,lProgram in lChannel.iteritems(): programmNumber = programmNumber+1 programStartTS = int(lProgram['date']) programEndTS = int(programStartTS) + int(lProgram['duration']) # We want to write program only for channel we display not the unavailable channel if any(d['channelId'] == channelId.replace('-','.') for d in channelsList): xmltvLine += ( " <programme start=\"%s\" stop=\"%s\" channel=\"%s\">\r\n" ) % ( #Freebox give local timestamp, need to convert it to gmt timestamp for xmltv # and forget timezone offset it mess up all time.strftime('%Y%m%d%H%M%S -0100', time.localtime( programStartTS - 14400) ), time.strftime('%Y%m%d%H%M%S -0100', time.localtime( programEndTS - 14400 ) ), channelId.replace('-','.') ) if 'title' in lProgram: xmltvLine += " <title>%s</title>\r\n" % lProgram['title'] if 'category_name' in lProgram: xmltvLine += " <category>%s</category>\r\n" % lProgram['category_name'] if 'sub_title' in lProgram: xmltvLine += " <sub-title>%s</sub-title>\r\n" % lProgram['sub_title'] if 'episode_number' in lProgram: if not 'season_number' in lProgram: xmltvLine += " <episode-num system=\"xmltv_ns\">%d</episode-num>\r\n" % lProgram['episode_number'] else: xmltvLine += " <episode-num system=\"xmltv_ns\">%d.%d</episode-num>\r\n" % (lProgram['season_number'], lProgram['episode_number']) xmltvLine += ' </programme>\r\n' xmltvLine += '</tv>' with io.open(userDataPath+'freebox.xml', 'w', encoding='utf-8') as the_file: the_file.write(xmltvLine) xbmc.executebuiltin('Notification(FreeboxTV, M3U & XMLTV Wrote sucessfully, all is ready!,5000 )') xbmc.log('[FREEBOXTV]XMLTV wrote- channels:'+str(channelNumber)+' channels For Program:'+str(channelProgramNumber)+' program:'+str(programmNumber),xbmc.LOGWARNING) return True