def _auth(self): """ User authentication Returns user session that can be used for API requests :param email: user email string :param password: user password string :return: unique session string """ try: self.conf.read('.aceconfig') self.session = self.conf.get('torrenttv_api', 'session') self.guid = self.conf.get('torrenttv_api', 'guid') if self.conf.get('torrenttv_api', 'email') != self.email: raise except: from uuid import getnode self.session = None self.guid = ''.join( '%02x' % ((getnode() >> 8 * i) & 0xff) for i in reversed(range(6))) # get device mac address if self.session is None or self.session == '': self.log.debug('Creating new session') url = TorrentTvApi.API_URL + 'auth.php' hooks = {'response': self._jsoncheck} params = { 'typeresult': 'json', 'username': self.email, 'password': self.password, 'application': 'tsproxy', 'guid': self.guid } try: with requests.get(url, params=params, headers=self.headers, timeout=5, hooks=hooks) as result: result.raise_for_status() self.session = result.json()['session'] self.log.debug("New session created: %s" % self.session) except requests.exceptions.RequestException as e: raise TorrentTvApiException('_auth:%s' % repr(e)) # Store session detales to config file if not self.conf.has_section('torrenttv_api'): self.conf.add_section('torrenttv_api') self.conf.set('torrenttv_api', 'email', self.email) self.conf.set('torrenttv_api', 'session', self.session) self.conf.set('torrenttv_api', 'guid', self.guid) with open('.aceconfig', 'w+') as config: self.conf.write(config) else: self.log.debug('Reusing saved session: %s' % self.session) return self.session
def handleRequest(self): ''' Main request handler path: /{reqtype}/{reqtype_value}/{file_indexes}/{developer_id}/{affiliate_id}/{zone_id}/{stream_id}/{fname}.{ext} ''' logger = logging.getLogger('handleRequest') # Make handler parameters dict self.__dict__.update({}.fromkeys( aceclient.acemessages.AceConst.START_PARAMS, '0' )) # [file_indexes, developer_id, affiliate_id, zone_id, stream_id] self.__dict__.update({ k: v for (k, v) in [(aceclient.acemessages.AceConst.START_PARAMS[i - 3], self.splittedpath[i] if self.splittedpath[i].isdigit() else '0') for i in range(3, len(self.splittedpath))] }) self.__dict__.update({ self.reqtype: unquote(self.splittedpath[2]), # {reqtype: reqtype_value} 'ace': AceConfig.ace, 'acesex': AceConfig.acesex, 'aceage': AceConfig.aceage, 'acekey': AceConfig.acekey, 'connect_timeout': AceConfig.aceconntimeout, 'result_timeout': AceConfig.aceresulttimeout, 'videoseekback': AceConfig.videoseekback, 'videotimeout': AceConfig.videotimeout, 'stream_type': ' '.join([ '{}={}'.format(k, v) for k, v in AceConfig.acestreamtype.items() ]), # request http or hls from AceEngine 'sessionID': self.handlerGreenlet.name[ self.handlerGreenlet.name.rfind('-') + 1:], # Greenlet.minimal_ident A small, unique non-negative integer 'connectionTime': gevent.time.time(), 'clientDetail': None, 'channelIcon': self.__dict__.get( 'channelIcon', 'http://static.acestream.net/sites/acestream/img/ACE-logo.png' ), }) # End parameters dict try: self.q = gevent.queue.Queue(maxsize=AceConfig.videotimeout) transcoder = gevent.event.AsyncResult() out = self.wfile gevent.spawn(wrap_errors(gevent.socket.error, self.rfile.read)).link( lambda x: self.handlerGreenlet.kill() ) # Client disconection watchdog try: if not AceProxy.clientcounter.idleAce: logger.debug( 'Create a connection with AceStream on {ace[aceHostIP]}:{ace[aceAPIport]}' .format(**self.__dict__)) AceProxy.clientcounter.idleAce = aceclient.AceClient( self.__dict__) AceProxy.clientcounter.idleAce.GetAUTH() if self.reqtype not in ('direct_url', 'efile_url'): self.__dict__.update( AceProxy.clientcounter.idleAce.GetCONTENTINFO( self.__dict__)) self.channelName = ensure_str( self.__dict__.get( 'channelName', next( iter([ x[0] for x in self.files if x[1] == int(self.file_indexes) ]), 'NoNameChannel'))) else: self.channelName = ensure_str( self.__dict__.get('channelName', 'NoNameChannel')) self.infohash = requests.auth.hashlib.sha1( ensure_binary(self.path)).hexdigest() AceProxy.clientcounter.idleAce._title = self.channelName except Exception as e: AceProxy.clientcounter.idleAce._read.kill() logging.debug('Error 404') self.send_error(404, '%s' % repr(e), logging.ERROR) self.ext = self.__dict__.get( 'ext', self.channelName[self.channelName.rfind('.') + 1:]) if self.ext == self.channelName: self.ext = query_get(self.query, 'ext', 'ts') mimetype = mimetypes.guess_type( '{channelName}.{ext}'.format(**self.__dict__))[0] try: # If &fmt transcode key present in request fmt = query_get(self.query, 'fmt') if fmt: if AceConfig.osplatform != 'Windows': if fmt in AceConfig.transcodecmd: stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL popen_params = { 'bufsize': 1048576, 'stdin': gevent.subprocess.PIPE, 'stdout': self.wfile, 'stderr': stderr, 'shell': False } try: gevent.spawn(lambda: psutil.Popen( AceConfig.transcodecmd[fmt], **popen_params )).link(transcoder) out = transcoder.get(timeout=2.0).stdin logger.info( '[{channelName}]: Transcoding to [{clientip}] started' .format(**self.__dict__)) except: logger.error( '[{channelName}]: Error starting transcoding to [{clientip}]! Is ffmpeg or VLC installed?' .format(**self.__dict__)) else: logger.error( "[{channelName}]: Can't found fmt key. Transcoding to [{clientip}] not started!" .format(**self.__dict__)) elif AceConfig.osplatform == 'Windows': logger.error( '[{channelName}]: Not applicable in Windnows OS. Transcoding to [{clientip}] not started!' .format(**self.__dict__)) if AceProxy.clientcounter.addClient(self) == 1: # Create broadcast if it does not exist yet gevent.spawn( StreamReader, self.ace.GetBroadcastStartParams(self.__dict__) ).link(lambda x: logger.debug( '[{channelName}]: Broadcast destroyed. Last client disconnected' .format(**self.__dict__))) logger.debug('[{channelName}]: Broadcast created'.format( **self.__dict__)) else: logger.debug( '[{channelName}]: Broadcast already exists'.format( **self.__dict__)) logger.info( '[{channelName}]: Streaming to [{clientip}] started'. format(**self.__dict__)) # Sending videostream headers to client response_use_chunked = False if ( transcoder.value or self.request_version == 'HTTP/1.0') else AceConfig.use_chunked drop_headers = [] proxy_headers = { 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=%s, max=100' % AceConfig.videotimeout, 'Accept-Ranges': 'none', 'Transfer-Encoding': 'chunked', 'Content-Type': 'video/MP2T' if mimetype is None else mimetype, } if not response_use_chunked: self.protocol_version = 'HTTP/1.0' proxy_headers['Connection'] = 'Close' drop_headers.extend(['Transfer-Encoding', 'Keep-Alive']) response_headers = [(k, v) for (k, v) in proxy_headers.items() if k not in drop_headers] self.send_response(200) logger.debug('[%s]: Sending HTTPAceProxy headers: %s' % (self.clientip, dict(response_headers))) gevent.joinall([ gevent.spawn(self.send_header, k, v) for (k, v) in response_headers ]) self.end_headers() # write data to client while it is alive for chunk in self.q: out.write(b'%X\r\n%s\r\n' % (len(chunk), chunk) if response_use_chunked else chunk) except aceclient.AceException as e: _ = AceProxy.pool.map( lambda x: x.send_error(500, repr(e), logging.ERROR), AceProxy.clientcounter.getClientsList(self.infohash)) except gevent.socket.error: pass # Client disconnected except gevent.GreenletExit: AceProxy.clientcounter.idleAce._read.kill() finally: AceProxy.clientcounter.deleteClient(self) logger.info( '[{channelName}]: Streaming to [{clientip}] finished'.format( **self.__dict__)) if transcoder.value: try: transcoder.value.kill() logger.info( '[{channelName}]: Transcoding to [{clientip}] stoped'. format(**self.__dict__)) except: pass
def handle(self, connection): P2pproxy.logger.debug('Handling request') hostport = connection.headers['Host'] self.params = parse_qs(connection.query) # /channels/ branch if connection.reqtype in ('channels', 'channels.m3u'): if connection.path.endswith('play'): # /channels/play?id=[id] channel_id = self.params.get('id', [''])[0] if not channel_id: # /channels/play?id=&_=[epoch timestamp] is Torrent-TV widget proxy check # P2pProxy simply closes connection on this request sending Server header, so do we if self.params.get('_', [''])[0]: P2pproxy.logger.debug('Status check') response_headers = { 'Content-Type': 'text/plain;charset=utf-8', 'Server': 'P2pProxy/1.0.4.4 HTTPAceProxy', 'Access-Control-Allow-Origin': '*', 'Connection': 'close' } connection.send_response(200) for k, v in response_headers.items(): connection.send_header(k, v) connection.wfile.write('\r\n') return else: connection.send_error(400, 'Bad request') # Bad request try: stream_type, stream, translations_list = TorrentTvApi( config.email, config.password).stream_source(channel_id) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) name = logo = '' for channel in translations_list: if channel.getAttribute('id') == channel_id: name = channel.getAttribute('name') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo break if stream_type not in ('torrent', 'contentid'): connection.send_error( 404, 'Unknown stream type: %s' % stream_type, logging.ERROR) elif stream_type == 'torrent': connection.path = '/url/%s/%s.ts' % (quote(stream, ''), name) elif stream_type == 'contentid': connection.path = '/content_id/%s/%s.ts' % (stream, name) connection.__dict__.update( {'splittedpath': connection.path.split('/')}) connection.__dict__.update({ 'channelName': name, 'channelIcon': logo, 'reqtype': connection.splittedpath[1].lower() }) return # /channels/?filter=[filter]&group=[group]&type=m3u elif connection.reqtype == 'channels.m3u' or self.params.get( 'type', [''])[0] == 'm3u': param_group = self.params.get('group', [''])[0] if param_group and 'all' in param_group: param_group = None try: translations_list = TorrentTvApi( config.email, config.password).translations( self.params.get('filter', ['all'])[0]) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) playlistgen = PlaylistGenerator( m3uchanneltemplate=config.m3uchanneltemplate) P2pproxy.logger.debug('Generating requested m3u playlist') for channel in translations_list: group_id = channel.getAttribute('group') if param_group and not group_id in param_group.split(','): continue # filter channels by &group=1,2,5... name = channel.getAttribute('name') group = TorrentTvApi.CATEGORIES[int(group_id)] cid = channel.getAttribute('id') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo fields = { 'name': name, 'id': cid, 'url': cid, 'group': group, 'logo': logo, 'tvgid': config.tvgid.format(**fields) if channel.getAttribute('epg_id') != '0' else '' } playlistgen.addItem(fields) P2pproxy.logger.debug('Exporting m3u playlist') exported = playlistgen.exportm3u( hostport=hostport, header=config.m3uheadertemplate, fmt=self.params.get('fmt', [''])[0]) connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) # /channels/?filter=[filter] else: try: translations_list = TorrentTvApi( config.email, config.password).translations( self.params.get('filter', ['all'])[0], True) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) P2pproxy.logger.debug('Exporting m3u playlist') response_headers = { 'Access-Control-Allow-Origin': '*', 'Connection': 'close', 'Content-Type': 'text/xml;charset=utf-8', 'Content-Length': len(translations_list) } try: h = connection.headers.get('Accept-Encoding').split(',')[0] translations_list = P2pproxy.compress_method[h].compress( translations_list) + P2pproxy.compress_method[h].flush( ) connection.send_header('Content-Encoding', h) except: pass response_headers['Content-Length'] = len(translations_list) connection.send_response(200) for k, v in response_headers.items(): connection.send_header(k, v) connection.end_headers() connection.wfile.write(translations_list) # same as /channels request elif connection.reqtype == 'xbmc.pvr' and connection.path.endswith( 'playlist'): connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') try: translations_list = TorrentTvApi(config.email, config.password).translations( 'all', True) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) try: h = connection.headers.get('Accept-Encoding').split(',')[0] translations_list = P2pproxy.compress_method[h].compress( translations_list) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(translations_list)) connection.end_headers() P2pproxy.logger.debug('Exporting m3u playlist') connection.wfile.write(translations_list) # /archive/ branch elif connection.reqtype == 'archive': if connection.path.endswith( ('dates', 'dates.m3u')): # /archive/dates.m3u d = datetime.now() delta = timedelta(days=1) playlistgen = PlaylistGenerator() hostport = connection.headers['Host'] days = int(self.params.get('days', ['7'])[0]) suffix = '&suffix=%s' % self.params.get( 'suffix')[0] if 'suffix' in self.params else '' for i in range(days): dfmt = d.strftime('%d-%m-%Y') url = 'http://%s/archive/playlist/?date=%s%s' % ( hostport, dfmt, suffix) playlistgen.addItem({ 'group': '', 'tvg': '', 'name': dfmt, 'url': url }) d -= delta exported = playlistgen.exportm3u(hostport=hostport, empty_header=True, parse_url=False, fmt=self.params.get( 'fmt', [''])[0]) connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) return elif connection.path.endswith( ('playlist', 'playlist.m3u')): # /archive/playlist.m3u dates = [] if 'date' in self.params: for d in self.params['date']: dates.append(self.parse_date(d).strftime('%d-%m-%Y')) else: d = datetime.now() delta = timedelta(days=1) days = int(self.params.get('days', ['7'])[0]) for i in range(days): dates.append(d.strftime('%d-%m-%Y')) d -= delta connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: channels_list = TorrentTvApi( config.email, config.password).archive_channels() except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) hostport = connection.headers['Host'] playlistgen = PlaylistGenerator() suffix = '&suffix=%s' % self.params.get( 'suffix')[0] if 'suffix' in self.params else '' for channel in channels_list: epg_id = channel.getAttribute('epg_id') name = channel.getAttribute('name') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo for d in dates: n = name + ' (' + d + ')' if len(dates) > 1 else name url = 'http://%s/archive/?type=m3u&date=%s&channel_id=%s%s' % ( hostport, d, epg_id, suffix) playlistgen.addItem({ 'group': name, 'tvg': '', 'name': n, 'url': url, 'logo': logo }) exported = playlistgen.exportm3u(hostport=hostport, empty_header=True, parse_url=False, fmt=self.params.get( 'fmt', [''])[0]) try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) return elif connection.path.endswith('channels'): # /archive/channels connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') try: archive_channels = TorrentTvApi( config.email, config.password).archive_channels(True) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) P2pproxy.logger.debug('Exporting m3u playlist') try: h = connection.headers.get('Accept-Encoding').split(',')[0] archive_channels = P2pproxy.compress_method[h].compress( archive_channels) + P2pproxy.compress_method[h].flush( ) connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(archive_channels)) connection.end_headers() connection.wfile.write(archive_channels) return if connection.path.endswith( 'play'): # /archive/play?id=[record_id] record_id = self.params.get('id', [''])[0] if not record_id: connection.send_error(400, 'Bad request') # Bad request try: stream_type, stream = TorrentTvApi( config.email, config.password).archive_stream_source(record_id) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) if stream_type not in ('torrent', 'contentid'): connection.send_error( 404, 'Unknown stream type: %s' % stream_type, logging.ERROR) elif stream_type == 'torrent': connection.path = '/url/%s/stream.ts' % quote(stream, '') elif stream_type == 'contentid': connection.path = '/content_id/%s/stream.ts' % stream connection.__dict__.update( {'splittedpath': connection.path.split('/')}) connection.__dict__.update( {'reqtype': connection.splittedpath[1].lower()}) return # /archive/?type=m3u&date=[param_date]&channel_id=[param_channel] elif self.params.get('type', [''])[0] == 'm3u': playlistgen = PlaylistGenerator() param_channel = self.params.get('channel_id', [''])[0] d = self.get_date_param() if not param_channel: try: channels_list = TorrentTvApi( config.email, config.password).archive_channels() except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) for channel in channels_list: channel_id = channel.getAttribute('epg_id') try: try: records_list = TorrentTvApi( config.email, config.password).records(channel_id, d) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) channel_name = channel.getAttribute('name') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo for record in records_list: name = record.getAttribute('name') record_id = record.getAttribute('record_id') playlistgen.addItem({ 'group': channel_name, 'tvg': '', 'name': name, 'url': record_id, 'logo': logo }) except: P2pproxy.logger.debug( 'Failed to load archive for %s' % channel_id) else: try: records_list = TorrentTvApi(config.email, config.password).records( param_channel, d) channels_list = TorrentTvApi( config.email, config.password).archive_channels() except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) P2pproxy.logger.debug('Generating archive m3u playlist') for record in records_list: record_id = record.getAttribute('record_id') channel_id = record.getAttribute('epg_id') name = record.getAttribute('name') d = datetime.fromtimestamp( float( record.getAttribute('time'))).strftime('%H:%M') n = '%s %s' % (d, name) logo = '' for channel in channels_list: if channel.getAttribute('epg_id') == channel_id: channel_name = channel.getAttribute('name') logo = channel.getAttribute('logo') if channel_name != '': name = '(' + channel_name + ') ' + name if logo != '' and config.fullpathlogo: logo = config.logobase + logo playlistgen.addItem({ 'group': channel_name, 'name': n, 'url': record_id, 'logo': logo, 'tvg': '' }) P2pproxy.logger.debug('Exporting m3u playlist') exported = playlistgen.exportm3u(hostport=hostport, empty_header=True, archive=True, fmt=self.params.get( 'fmt', [''])[0]) connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) # /archive/?date=[param_date]&channel_id=[param_channel] else: param_date = self.params.get('date', [''])[0] if not param_date: d = datetime.now() else: try: d = parse_date(param_date) except: return param_channel = self.params.get('channel_id', [''])[0] if not param_channel: connection.send_error( 500, 'Got /archive/ request but no channel_id specified!', logging.ERROR) connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') try: records_list = TorrentTvApi(config.email, config.password).records( param_channel, d.strftime('%d-%m-%Y'), True) except Exception as err: connection.send_error(404, '%s' % repr(err), logging.ERROR) P2pproxy.logger.debug('Exporting m3u playlist') try: h = connection.headers.get('Accept-Encoding').split(',')[0] records_list = P2pproxy.compress_method[h].compress( records_list) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(records_list)) connection.end_headers() connection.wfile.write(records_list) # Used to generate logomap for the torrenttv plugin elif connection.reqtype == 'logobase': logomap = {} try: import config.picons.torrenttv as picons logomap = { k: v[v.rfind('/') + 1:] for k, v in picons.logomap.items() if v is not None } except: pass #try: translations_list = TorrentTvApi(config.email, config.password).translations('all') #except Exception as err: # connection.send_error(404, '%s' % repr(err), logging.ERROR) # return #logomap.update({ channel.getAttribute('name'):channel.getAttribute('logo') for channel in translations_list }) import requests url = 'http://hmxuku36whbypzxi.onion/trash/ttv-list/ttv_logo.json' proxies = { 'http': 'socks5h://192.168.2.1:9100', 'https': 'socks5h://192.168.2.1:9100' } with requests.get(url, proxies=proxies, timeout=30) as r: logomap.update(r.json()) connection.send_response(200) if self.params.get('format', [''])[0] == 'json': from requests.compat import json exported = ensure_binary( json.dumps(logomap, ensure_ascii=False)) connection.send_header('Content-Type', 'application/json') else: exported = "logobase = '%s'\nlogomap = {\n" % config.logobase exported += ''.join(" u'%s': logobase + '%s',\n" % (name, logo) for name, logo in logomap.items()) exported += '}\n' exported = ensure_binary(exported) connection.send_header('Content-Type', 'text/plain;charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) P2pproxy.logger.debug('%s picon channels exported' % len(logomap))
def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=None): logger = logging.getLogger('HandleRequest') self.reqparams, self.path = parse_qs( self.query), self.path[:-1] if self.path.endswith( '/') else self.path self.videoextdefaults = ('.3gp', '.aac', '.ape', '.asf', '.avi', '.dv', '.divx', '.flac', '.flc', '.flv', '.m2ts', '.m4a', '.mka', '.mkv', '.mpeg', '.mpeg4', '.mpegts', '.mpg4', '.mp3', '.mp4', '.mpg', '.mov', '.m4v', '.ogg', '.ogm', '.ogv', '.oga', '.ogx', '.qt', '.rm', '.swf', '.ts', '.vob', '.wmv', '.wav', '.webm') # Limit on the number of connected clients if 0 < AceConfig.maxconns <= AceProxy.clientcounter.totalClients(): self.dieWithError( 501, "Maximum client connections reached, can't serve request from %s" % self.clientip, logging.ERROR) return # Check if third parameter exists…/self.reqtype/blablablablabla/video.mpg # And if it ends with regular video extension try: if not self.path.endswith(self.videoextdefaults): self.dieWithError( 501, 'Request seems like valid but no valid video extension was provided', logging.ERROR) return except IndexError: self.dieWithError(400, 'Bad Request', logging.WARNING) # 400 Bad Request return # Pretend to work fine with Fake or HEAD request. if headers_only or AceConfig.isFakeRequest(self.path, self.query, self.headers): # Return 200 and exit if headers_only: logger.debug('Sending headers and closing connection') else: logger.debug('Fake request - closing connection') self.send_response(200) self.send_header('Content-Type', 'video/mp2t') self.send_header('Connection', 'Close') self.end_headers() return # Check is AceEngine alive checkAce() # Make dict with parameters # [file_indexes, developer_id, affiliate_id, zone_id, stream_id] paramsdict = {}.fromkeys(aceclient.acemessages.AceConst.START_PARAMS, '0') for i in range(3, len(self.splittedpath)): paramsdict[aceclient.acemessages.AceConst.START_PARAMS[ i - 3]] = self.splittedpath[i] if self.splittedpath[i].isdigit( ) else '0' paramsdict[self.reqtype] = unquote( self.splittedpath[2]) #self.path_unquoted #End parameters dict CID = NAME = None if not AceConfig.new_api: try: if not AceProxy.clientcounter.idleAce: logger.debug('Create connection to AceEngine.....') AceProxy.clientcounter.idleAce = aceclient.AceClient( AceProxy.clientcounter, AceConfig.ace, AceConfig.aceconntimeout, AceConfig.aceresulttimeout) AceProxy.clientcounter.idleAce.aceInit( AceConfig.acesex, AceConfig.aceage, AceConfig.acekey, AceConfig.videoseekback, AceConfig.videotimeout) CID, NAME = AceProxy.clientcounter.idleAce.GETINFOHASH( self.reqtype, paramsdict[self.reqtype], paramsdict['file_indexes']) except aceclient.AceException as e: self.dieWithError(503, '%s' % repr(e), logging.ERROR) AceProxy.clientcounter.idleAce = None return else: try: with requests.session() as s: s.stream = s.verify = False url = 'http://%s:%s/ace/%s' % ( AceConfig.ace['aceHostIP'], AceConfig.ace['aceHTTPport'], 'manifest.m3u8' if AceConfig.acestreamtype['output_format'] == 'hls' else 'getstream') params = { 'id' if self.reqtype in ('cid', 'content_id') else self.reqtype: paramsdict[self.reqtype], 'format': 'json', 'pid': str(uuid4()), '_idx': paramsdict['file_indexes'] } if AceConfig.acestreamtype['output_format'] == 'hls': params.update(AceConfig.acestreamtype) del params['output_format'] self.cmd = s.get( url, params=params, timeout=(5, AceConfig.videotimeout)).json()['response'] CID = urlparse(self.cmd['playback_url']).path.split('/')[3] url = 'http://%s:%s/server/api' % ( AceConfig.ace['aceHostIP'], AceConfig.ace['aceHTTPport']) params = { 'method': 'get_media_files', self.reqtype: paramsdict[self.reqtype] } NAME = s.get(url, params=params, timeout=(5, AceConfig.aceresulttimeout)).json( )['result'][paramsdict['file_indexes']] except Exception as e: self.dieWithError(503, '%s' % repr(e), logging.ERROR) return self.connectionTime = gevent.time.time() self.channelName = NAME if not channelName else channelName self.channelIcon = 'http://static.acestream.net/sites/acestream/img/ACE-logo.png' if not channelIcon else channelIcon self.clientInfo = self.transcoder = None try: self.connectGreenlet = gevent.spawn( self.connectDetector) # client disconnection watchdog self.out = self.wfile # If &fmt transcode key present in request fmt = self.reqparams.get('fmt', [''])[0] if fmt and AceConfig.osplatform != 'Windows': if fmt in AceConfig.transcodecmd: stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL popen_params = { 'bufsize': 1048576, 'stdin': gevent.subprocess.PIPE, 'stdout': self.wfile, 'stderr': stderr, 'shell': False } try: self.transcoder = gevent.event.AsyncResult() gevent.spawn(lambda: psutil.Popen( AceConfig.transcodecmd[fmt], **popen_params)).link( self.transcoder) self.transcoder = self.transcoder.get(timeout=2.0) self.out = self.transcoder.stdin logger.info('Transcoding for %s started' % self.clientip) except: logger.error( 'Error starting transcoding! Is Ffmpeg or VLC installed?' ) self.transcoder = None self.out = self.wfile else: logger.error( "Can't found fmt key. Transcoding not started!") self.response_use_chunked = False if self.transcoder is not None else AceConfig.use_chunked if AceProxy.clientcounter.addClient(CID, self) == 1: # If there is no existing broadcast we create it playback_url = self.cmd[ 'playback_url'] if AceConfig.new_api else self.ace.START( self.reqtype, paramsdict, AceConfig.acestreamtype) if not AceProxy.ace: #Rewrite host:port for remote AceEngine playback_url = urlparse(playback_url)._replace( netloc='%s:%s' % (AceConfig.ace['aceHostIP'], AceConfig.ace['aceHTTPport'])).geturl() gevent.spawn(StreamReader, playback_url, CID) # Sending videostream headers to client logger.info('Streaming "%s" to %s started' % (self.channelName, self.clientip)) drop_headers = [] proxy_headers = { 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=15, max=100', 'Accept-Ranges': 'none', 'Transfer-Encoding': 'chunked', 'Content-Type': 'application/octet-stream', 'Cache-Control': 'max-age=0, no-cache, no-store', 'Pragma': 'no-cache' } if not self.response_use_chunked or self.request_version == 'HTTP/1.0': self.protocol_version = 'HTTP/1.0' proxy_headers['Connection'] = 'Close' drop_headers.extend( ['Transfer-Encoding', 'Keep-Alive', 'Cache-Control']) response_headers = [(k, v) for (k, v) in proxy_headers.items() if k not in drop_headers] self.send_response(200) logger.debug('Sending HTTPAceProxy headers to client: %s' % dict(response_headers)) gevent.joinall([ gevent.spawn(self.send_header, k, v) for (k, v) in response_headers ]) self.end_headers() self.connectGreenlet.join( ) # Wait until request complite or client disconnected except aceclient.AceException as e: gevent.joinall([ gevent.spawn(client.dieWithError, 503, '%s' % repr(e), logging.ERROR) for client in AceProxy.clientcounter.getClientsList(CID) ]) gevent.joinall([ gevent.spawn(client.connectGreenlet.kill) for client in AceProxy.clientcounter.getClientsList(CID) ]) except gevent.GreenletExit: pass # Client disconnected except Exception as e: self.dieWithError(500, 'Unexpected error: %s' % repr(e)) finally: logging.info('Streaming "%s" to %s finished' % (self.channelName, self.clientip)) if self.transcoder: try: self.transcoder.kill() logging.info('Transcoding for %s stoped' % self.clientip) except: pass if AceProxy.clientcounter.deleteClient(CID, self) == 0: if AceConfig.new_api: with requests.get(self.cmd['command_url'], params={'method': 'stop'}, timeout=5) as r: logging.debug('Stop broadcast: %s' % r.json()) logging.debug( 'Broadcast "%s" stoped. Last client %s disconnected' % (self.channelName, self.clientip))
def handleRequest(self, headers_only, channelName=None, channelIcon=None, fmt=None): logger = logging.getLogger('HandleRequest') self.reqparams, self.path = parse_qs( self.query), self.path[:-1] if self.path.endswith( '/') else self.path videoextdefaults = ('.avi', '.flv', '.m2ts', '.mkv', '.mpeg', '.mpeg4', '.mpegts', '.mpg4', '.mp4', '.mpg', '.mov', '.mpv', '.qt', '.ts', '.wmv') # Limit on the number of connected clients if 0 < AceConfig.maxconns <= len( AceProxy.clientcounter.getAllClientsList()): self.dieWithError( 403, "Maximum client connections reached, can't serve request from %s" % self.clientip, logging.ERROR) return # Check if third parameter exists…/pid/blablablablabla/video.mpg # |_________| # And if it ends with regular video extension try: if not self.path.endswith(videoextdefaults): self.dieWithError( 501, 'Request seems like valid but no valid video extension was provided', logging.ERROR) return except IndexError: self.dieWithError(400, 'Bad Request', logging.WARNING) # 400 Bad Request return # Pretend to work fine with Fake or HEAD request. if headers_only or AceConfig.isFakeRequest(self.path, self.query, self.headers): # Return 200 and exit if headers_only: logger.debug('Sending headers and closing connection') else: logger.debug('Fake request - closing connection') self.send_response(200) self.send_header('Content-Type', 'video/mp2t') self.send_header('Connection', 'Close') self.end_headers() self.closeConnection() return # Make dict with parameters # [file_indexes, developer_id, affiliate_id, zone_id, stream_id] paramsdict = {}.fromkeys(aceclient.acemessages.AceConst.START_PARAMS, '0') for i in range(3, len(self.splittedpath)): paramsdict[aceclient.acemessages.AceConst.START_PARAMS[ i - 3]] = self.splittedpath[i] if self.splittedpath[i].isdigit( ) else '0' paramsdict[self.reqtype] = unquote( self.splittedpath[2]) #self.path_unquoted #End parameters dict self.connectionTime = gevent.time.time() self.sessionID = str(uuid4().int)[:8] self.clientInfo = self.transcoder = None self.channelIcon = 'http://static.acestream.net/sites/acestream/img/ACE-logo.png' if channelIcon is None else channelIcon try: if not AceProxy.clientcounter.idleAce: logger.debug('Create connection to AceEngine.....') AceProxy.clientcounter.idleAce = aceclient.AceClient( AceProxy.clientcounter, AceConfig.ace, AceConfig.aceconntimeout, AceConfig.aceresulttimeout) AceProxy.clientcounter.idleAce.aceInit(AceConfig.acesex, AceConfig.aceage, AceConfig.acekey, AceConfig.videoseekback, AceConfig.videotimeout) self.CID, self.channelName = AceProxy.clientcounter.idleAce.GETINFOHASH( self.reqtype, paramsdict[self.reqtype], self.sessionID, paramsdict['file_indexes']) except aceclient.AceException as e: AceProxy.clientcounter.idleAce = None self.dieWithError(404, '%s' % repr(e), logging.ERROR) return mimetype = mimetypes.guess_type(self.channelName)[0] try: gevent.spawn(wrap_errors( gevent.socket.error, self.rfile.read)).link( lambda x: self.finish()) # Client disconection watchdog self.q = gevent.queue.Queue(maxsize=AceConfig.videotimeout) self.out = self.wfile # If &fmt transcode key present in request if fmt is None: fmt = self.reqparams.get('fmt', [''])[0] if fmt and AceConfig.osplatform != 'Windows': if fmt in AceConfig.transcodecmd: stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL popen_params = { 'bufsize': 1048576, 'stdin': gevent.subprocess.PIPE, 'stdout': self.wfile, 'stderr': stderr, 'shell': False } try: self.transcoder = gevent.event.AsyncResult() AceProxy.spawn(lambda: psutil.Popen( AceConfig.transcodecmd[fmt], **popen_params)).link( self.transcoder) self.transcoder = self.transcoder.get(timeout=2.0) self.out = self.transcoder.stdin logger.info('Transcoding for %s started' % self.clientip) except: logger.error( 'Error starting transcoding! Is Ffmpeg or VLC installed?' ) self.transcoder = None self.out = self.wfile else: logger.error( "Can't found fmt key. Transcoding not started!") # Start broadcast if it does not exist if AceProxy.clientcounter.addClient(self) == 1: playback_url = self.ace.START(self.reqtype, paramsdict, AceConfig.acestreamtype) AceProxy.pool.spawn( StreamReader, playback_url, self.CID).link(lambda x: logging.debug( 'Broadcast "%s" stoped. Last client disconnected' % self.channelName)) logger.info('Streaming "%s" to %s started' % (self.channelName, self.clientip)) # Sending videostream headers to client self.response_use_chunked = False if ( self.transcoder is not None or self.request_version == 'HTTP/1.0') else AceConfig.use_chunked drop_headers = [] proxy_headers = { 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=15, max=100', 'Accept-Ranges': 'none', 'Transfer-Encoding': 'chunked', 'Content-Type': 'video/MP2T' if mimetype is None else mimetype, 'Pragma': 'no-cache', 'Cache-Control': 'max-age=0, no-cache, no-store' } if not self.response_use_chunked: self.protocol_version = 'HTTP/1.0' proxy_headers['Connection'] = 'Close' drop_headers.extend( ['Transfer-Encoding', 'Keep-Alive', 'Cache-Control']) response_headers = [(k, v) for (k, v) in proxy_headers.items() if k not in drop_headers] self.send_response(200) logger.debug('Sending HTTPAceProxy headers to client: %s' % dict(response_headers)) gevent.joinall([ gevent.spawn(self.send_header, k, v) for (k, v) in response_headers ]) self.end_headers() # write data to client while he is alive for chunk in self.q: self.out.write(chunk) except aceclient.AceException as e: AceProxy.pool.map( lambda x: x.dieWithError(500, repr(e), logging.ERROR), AceProxy.clientcounter.getClientsList(self.CID)) except (gevent.GreenletExit, gevent.socket.error): pass # Client disconnected finally: AceProxy.clientcounter.deleteClient(self) logging.info('Streaming "%s" to %s finished' % (self.channelName, self.clientip)) if self.transcoder: try: self.transcoder.kill() logging.info('Transcoding for %s stoped' % self.clientip) except: pass self.closeConnection()
def handleRequest(self, **params): ''' :params: dict() with keys: headers_only, channelName, channelIcon ''' logger = logging.getLogger('HandleRequest') self.path = self.path[:-1] if self.path.endswith('/') else self.path # Limit on the number of connected clients if 0 < AceConfig.maxconns <= len( AceProxy.clientcounter.getAllClientsList()): self.dieWithError( 403, "Maximum client connections reached, can't serve request from %s" % self.clientip, logging.ERROR) return # Check if third parameter exists…/pid/blablablablabla/video.mpg # |_________| # And if it ends with regular video extension try: if not self.path.endswith( ('.avi', '.flv', '.m2ts', '.mkv', '.mpeg', '.mpeg4', '.mpegts', '.mpg4', '.mp4', '.mpg', '.mov', '.mpv', '.qt', '.ts', '.wmv')): self.dieWithError( 501, 'Request seems like valid but no valid video extension was provided', logging.ERROR) return except IndexError: self.dieWithError(400, 'Bad Request', logging.WARNING) # 400 Bad Request return # Pretend to work fine with Fake or HEAD request. if params.get('headers_only') or AceConfig.isFakeRequest( self.path, self.query, self.headers): # Return 200 and exit if params.get('headers_only'): logger.debug('Sending headers and closing connection') else: logger.debug('Fake request - closing connection') self.send_response(200) self.send_header('Content-Type', 'video/mp2t') self.send_header('Connection', 'Close') self.end_headers() self.closeConnection() return # Make parameters dict params.update({self.reqtype: unquote(self.splittedpath[2])}) # {command: value} params.update( {}.fromkeys(aceclient.acemessages.AceConst.START_PARAMS, '0') ) # [file_indexes, developer_id, affiliate_id, zone_id, stream_id] params.update({ k: v for (k, v) in [(aceclient.acemessages.AceConst.START_PARAMS[i - 3], self.splittedpath[i] if self.splittedpath[i].isdigit() else '0') for i in range(3, len(self.splittedpath))] }) params.update({ 'stream_type': ' '.join([ '{}={}'.format(k, v) for k, v in AceConfig.acestreamtype.items() ]) }) # request http or hls from AceEngine params['request_id'] = self.sessionID = str(uuid4().int)[:8] # End parameters dict self.connectionTime = gevent.time.time() self.clientInfo = None self.channelIcon = params.get('channelIcon') if self.channelIcon is None: self.channelIcon = 'http://static.acestream.net/sites/acestream/img/ACE-logo.png' try: if not AceProxy.clientcounter.idleAce: logger.debug( 'Create connection with AceStream on {aceHostIP}:{aceAPIport}' .format(**AceConfig.ace)) AceProxy.clientcounter.idleAce = aceclient.AceClient( AceConfig.ace, AceConfig.aceconntimeout, AceConfig.aceresulttimeout) AceProxy.clientcounter.idleAce.GetAUTH(AceConfig.acesex, AceConfig.aceage, AceConfig.acekey, AceConfig.videoseekback, AceConfig.videotimeout) if self.reqtype not in ('direct_url', 'efile_url'): self.CID, self.channelName = AceProxy.clientcounter.idleAce.GetCONTENTINFO( params) else: self.channelName = params.get('channelName') if self.channelName is None: self.channelName = 'NoNameChannel' self.CID = requests.auth.hashlib.sha1( self.channelName.encode('utf-8')).hexdigest() except aceclient.AceException as e: AceProxy.clientcounter.idleAce = None self.dieWithError(404, '%s' % repr(e), logging.ERROR) return ext = self.channelName[self.channelName.rfind('.') + 1:] if ext == self.channelName: ext = parse_qs(self.query).get('ext', ['ts'])[0] mimetype = mimetypes.guess_type('%s.%s' % (self.channelName, ext))[0] try: AceProxy.pool.spawn( wrap_errors(gevent.socket.error, self.rfile.read)).link( lambda x: self.finish()) # Client disconection watchdog self.q = gevent.queue.Queue(maxsize=AceConfig.videotimeout) out = self.wfile # If &fmt transcode key present in request fmt = parse_qs(self.query).get('fmt', [''])[0] transcoder = gevent.event.AsyncResult() if fmt: if AceConfig.osplatform != 'Windows': if fmt in AceConfig.transcodecmd: stderr = None if AceConfig.loglevel == logging.DEBUG else DEVNULL popen_params = { 'bufsize': 1048576, 'stdin': gevent.subprocess.PIPE, 'stdout': self.wfile, 'stderr': stderr, 'shell': False } try: #transcoder = gevent.event.AsyncResult() AceProxy.pool.spawn(lambda: psutil.Popen( AceConfig.transcodecmd[fmt], **popen_params) ).link(transcoder) out = transcoder.get(timeout=2.0).stdin logger.info( 'Transcoding for {clientip} started'.format( **self.__dict__)) except: logger.error( 'Error starting transcoding! Is Ffmpeg or VLC installed?' ) else: logger.error( "Can't found fmt key. Transcoding not started!") elif AceConfig.osplatform == 'Windows': logger.error( 'Not applicable in Windnows OS. Transcoding not started!' ) # Start broadcast if it does not exist if AceProxy.clientcounter.addClient(self) == 1: START = self.ace.GetBroadcastStartParams(params) self.broadcast = AceProxy.pool.spawn(StreamReader, START['url'], START['infohash']) self.broadcast.link(lambda x: logging.debug( 'Broadcast "{channelName}" stoped. Last client disconnected' .format(**self.__dict__))) logger.info( 'Streaming "{channelName}" to {clientip} started'.format( **self.__dict__)) # Sending videostream headers to client response_use_chunked = False if ( transcoder is not None or self.request_version == 'HTTP/1.0') else AceConfig.use_chunked drop_headers = [] proxy_headers = { 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=%s, max=100' % AceConfig.videotimeout, 'Accept-Ranges': 'none', 'Transfer-Encoding': 'chunked', 'Content-Type': 'video/MP2T' if mimetype is None else mimetype } if not response_use_chunked: self.protocol_version = 'HTTP/1.0' proxy_headers['Connection'] = 'Close' drop_headers.extend(['Transfer-Encoding', 'Keep-Alive']) response_headers = [(k, v) for (k, v) in proxy_headers.items() if k not in drop_headers] self.send_response(200) logger.debug('Sending HTTPAceProxy headers to client: %s' % dict(response_headers)) gevent.joinall([ gevent.spawn(self.send_header, k, v) for (k, v) in response_headers ]) self.end_headers() # write data to client while he is alive for chunk in self.q: out.write(b'%x\r\n' % len(chunk) + chunk + b'\r\n' if response_use_chunked else chunk) except aceclient.AceException as e: _ = AceProxy.pool.map( lambda x: x.dieWithError(500, repr(e), logging.ERROR), AceProxy.clientcounter.getClientsList(self.CID)) except (gevent.GreenletExit, gevent.socket.error): pass # Client disconnected finally: AceProxy.clientcounter.deleteClient(self) logging.info( 'Streaming "{channelName}" to {clientip} finished'.format( **self.__dict__)) if transcoder.value: try: transcoder.value.kill() logging.info('Transcoding for {clientip} stoped'.format( **self.__dict__)) except: pass self.closeConnection() return
def handle(self, connection, headers_only=False): P2pproxy.logger.debug('Handling request') hostport = connection.headers['Host'] self.params = parse_qs(connection.query) # /channels/ branch if connection.reqtype in ('channels', 'channels.m3u'): if connection.path.endswith('play'): # /channels/play?id=[id] channel_id = self.params.get('id', [''])[0] if not channel_id: # /channels/play?id=&_=[epoch timestamp] is Torrent-TV widget proxy check # P2pProxy simply closes connection on this request sending Server header, so do we if self.params.get('_', [''])[0]: P2pproxy.logger.debug('Status check') response_headers = { 'Content-Type': 'text/plain;charset=utf-8', 'Server': 'P2pProxy/1.0.4.4 HTTPAceProxy', 'Access-Control-Allow-Origin': '*', 'Connection': 'close' } connection.send_response(200) for k, v in response_headers.items(): connection.send_header(k, v) connection.wfile.write('\r\n') return else: connection.dieWithError(400, 'Bad request') # Bad request return if headers_only: connection.send_response(200) connection.send_header('Content-Type', 'video/mpeg') connection.end_headers() return stream_type, stream, translations_list = TorrentTvApi( config.email, config.password).stream_source(channel_id) name = logo = '' for channel in translations_list: if channel.getAttribute('id') == channel_id: name = channel.getAttribute('name') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo break if stream_type not in (b'torrent', b'contentid'): connection.dieWithError( 404, 'Unknown stream type: %s' % stream_type, logging.ERROR) return elif stream_type == b'torrent': connection.path = '/url/%s/stream.mp4' % quote(stream, '') elif stream_type == b'contentid': connection.path = '/content_id/%s/stream.mp4' % stream connection.splittedpath = connection.path.split('/') connection.reqtype = connection.splittedpath[1].lower() connection.handleRequest(headers_only, name, logo, fmt=self.params.get('fmt', [''])[0]) # /channels/?filter=[filter]&group=[group]&type=m3u elif connection.reqtype == 'channels.m3u' or self.params.get( 'type', [''])[0] == 'm3u': if headers_only: connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') connection.end_headers() return param_group = self.params.get('group', [''])[0] if param_group and 'all' in param_group: param_group = None translations_list = TorrentTvApi(config.email, config.password).translations( self.params.get( 'filter', ['all'])[0]) playlistgen = PlaylistGenerator( m3uchanneltemplate=config.m3uchanneltemplate) P2pproxy.logger.debug('Generating requested m3u playlist') for channel in translations_list: group_id = channel.getAttribute('group') if param_group and not group_id in param_group.split(','): continue # filter channels by &group=1,2,5... name = channel.getAttribute('name') group = TorrentTvApi.CATEGORIES[int(group_id)] cid = channel.getAttribute('id') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo fields = { 'name': name, 'id': cid, 'url': cid, 'group': group, 'logo': logo } if channel.getAttribute('epg_id') != '0': fields['tvgid'] = config.tvgid % fields playlistgen.addItem(fields) P2pproxy.logger.debug('Exporting m3u playlist') exported = playlistgen.exportm3u( hostport=hostport, header=config.m3uheadertemplate, fmt=self.params.get('fmt', [''])[0]).encode('utf-8') connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) # /channels/?filter=[filter] else: if headers_only: connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') connection.end_headers() return translations_list = TorrentTvApi( config.email, config.password).translations( self.params.get('filter', ['all'])[0], True) P2pproxy.logger.debug('Exporting m3u playlist') response_headers = { 'Access-Control-Allow-Origin': '*', 'Connection': 'close', 'Content-Type': 'text/xml;charset=utf-8', 'Content-Length': len(translations_list) } try: h = connection.headers.get('Accept-Encoding').split(',')[0] translations_list = P2pproxy.compress_method[h].compress( translations_list) + P2pproxy.compress_method[h].flush( ) connection.send_header('Content-Encoding', h) except: pass response_headers['Content-Length'] = len(translations_list) connection.send_response(200) for k, v in response_headers.items(): connection.send_header(k, v) connection.end_headers() connection.wfile.write(translations_list) # same as /channels request elif connection.reqtype == 'xbmc.pvr' and connection.path.endswith( 'playlist'): connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') if headers_only: connection.end_headers() return translations_list = TorrentTvApi(config.email, config.password).translations( 'all', True) try: h = connection.headers.get('Accept-Encoding').split(',')[0] translations_list = P2pproxy.compress_method[h].compress( translations_list) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(translations_list)) connection.end_headers() P2pproxy.logger.debug('Exporting m3u playlist') connection.wfile.write(translations_list) # /archive/ branch elif connection.reqtype == 'archive': if connection.path.endswith( ('dates', 'dates.m3u')): # /archive/dates.m3u d = datetime.now() delta = timedelta(days=1) playlistgen = PlaylistGenerator() hostport = connection.headers['Host'] days = int(self.params.get('days', ['7'])[0]) suffix = '&suffix=%s' % self.params.get( 'suffix')[0] if 'suffix' in self.params else '' for i in range(days): dfmt = d.strftime('%d-%m-%Y') url = 'http://%s/archive/playlist/?date=%s%s' % ( hostport, dfmt, suffix) playlistgen.addItem({ 'group': '', 'tvg': '', 'name': dfmt, 'url': url }) d -= delta exported = playlistgen.exportm3u(hostport, empty_header=True, process_url=False, fmt=self.params.get( 'fmt', [''])[0]).encode('utf-8') connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) return elif connection.path.endswith( ('playlist', 'playlist.m3u')): # /archive/playlist.m3u dates = [] if 'date' in self.params: for d in self.params['date']: dates.append(self.parse_date(d).strftime('%d-%m-%Y')) else: d = datetime.now() delta = timedelta(days=1) days = int(self.params.get('days', ['7'])[0]) for i in range(days): dates.append(d.strftime('%d-%m-%Y')) d -= delta connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') if headers_only: connection.end_headers() return channels_list = TorrentTvApi( config.email, config.password).archive_channels() hostport = connection.headers['Host'] playlistgen = PlaylistGenerator() suffix = '&suffix=%s' % self.params.get( 'suffix')[0] if 'suffix' in self.params else '' for channel in channels_list: epg_id = channel.getAttribute('epg_id') name = channel.getAttribute('name') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo for d in dates: n = name + ' (' + d + ')' if len(dates) > 1 else name url = 'http://%s/archive/?type=m3u&date=%s&channel_id=%s%s' % ( hostport, d, epg_id, suffix) playlistgen.addItem({ 'group': name, 'tvg': '', 'name': n, 'url': url, 'logo': logo }) exported = playlistgen.exportm3u(hostport, empty_header=True, process_url=False, fmt=self.params.get( 'fmt', [''])[0]).encode('utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) return elif connection.path.endswith('channels'): # /archive/channels connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') if headers_only: connection.end_headers() else: archive_channels = TorrentTvApi( config.email, config.password).archive_channels(True) P2pproxy.logger.debug('Exporting m3u playlist') try: h = connection.headers.get('Accept-Encoding').split( ',')[0] archive_channels = P2pproxy.compress_method[h].compress( archive_channels ) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(archive_channels)) connection.end_headers() connection.wfile.write(archive_channels) return if connection.path.endswith( 'play'): # /archive/play?id=[record_id] record_id = self.params.get('id', [''])[0] if not record_id: connection.dieWithError(400, 'Bad request') # Bad request return if headers_only: connection.send_response(200) connection.send_header("Content-Type", "video/mpeg") connection.end_headers() return stream_type, stream = TorrentTvApi( config.email, config.password).archive_stream_source(record_id) if stream_type not in (b'torrent', b'contentid'): connection.dieWithError( 404, 'Unknown stream type: %s' % stream_type, logging.ERROR) return elif stream_type == b'torrent': connection.path = '/url/%s/stream.mp4' % quote(stream, '') elif stream_type == b'contentid': connection.path = '/content_id/%s/stream.mp4' % stream connection.splittedpath = connection.path.split('/') connection.reqtype = connection.splittedpath[1].lower() connection.handleRequest(headers_only, fmt=self.params.get('fmt', [''])[0]) # /archive/?type=m3u&date=[param_date]&channel_id=[param_channel] elif self.params.get('type', [''])[0] == 'm3u': if headers_only: connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') connection.end_headers() return playlistgen = PlaylistGenerator() param_channel = self.params.get('channel_id', [''])[0] d = self.get_date_param() if not param_channel: channels_list = TorrentTvApi( config.email, config.password).archive_channels() for channel in channels_list: channel_id = channel.getAttribute('epg_id') try: records_list = TorrentTvApi( config.email, config.password).records(channel_id, d) channel_name = channel.getAttribute('name') logo = channel.getAttribute('logo') if logo != '' and config.fullpathlogo: logo = config.logobase + logo for record in records_list: name = record.getAttribute('name') record_id = record.getAttribute('record_id') playlistgen.addItem({ 'group': channel_name, 'tvg': '', 'name': name, 'url': record_id, 'logo': logo }) except: P2pproxy.logger.debug( 'Failed to load archive for %s' % channel_id) else: records_list = TorrentTvApi(config.email, config.password).records( param_channel, d) channels_list = TorrentTvApi( config.email, config.password).archive_channels() P2pproxy.logger.debug('Generating archive m3u playlist') for record in records_list: record_id = record.getAttribute('record_id') channel_id = record.getAttribute('epg_id') name = record.getAttribute('name') d = datetime.fromtimestamp( float( record.getAttribute('time'))).strftime('%H:%M') n = '%s %s' % (d, name) logo = '' for channel in channels_list: if channel.getAttribute('epg_id') == channel_id: channel_name = channel.getAttribute('name') logo = channel.getAttribute('logo') if channel_name != '': name = '(' + channel_name + ') ' + name if logo != '' and config.fullpathlogo: logo = config.logobase + logo playlistgen.addItem({ 'group': channel_name, 'name': n, 'url': record_id, 'logo': logo, 'tvg': '' }) P2pproxy.logger.debug('Exporting m3u playlist') exported = playlistgen.exportm3u(hostport, empty_header=True, archive=True, fmt=self.params.get( 'fmt', [''])[0]).encode('utf-8') connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') try: h = connection.headers.get('Accept-Encoding').split(',')[0] exported = P2pproxy.compress_method[h].compress( exported) + P2pproxy.compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.end_headers() connection.wfile.write(exported) # /archive/?date=[param_date]&channel_id=[param_channel] else: param_date = self.params.get('date', [''])[0] if not param_date: d = datetime.now() else: try: d = parse_date(param_date) except: return param_channel = self.params.get('channel_id', [''])[0] if not param_channel: connection.dieWithError( 500, 'Got /archive/ request but no channel_id specified!', logging.ERROR) return connection.send_response(200) connection.send_header('Access-Control-Allow-Origin', '*') connection.send_header('Connection', 'close') connection.send_header('Content-Type', 'text/xml;charset=utf-8') if headers_only: connection.end_headers() else: records_list = TorrentTvApi(config.email, config.password).records( param_channel, d.strftime('%d-%m-%Y'), True) P2pproxy.logger.debug('Exporting m3u playlist') try: h = connection.headers.get('Accept-Encoding').split( ',')[0] records_list = P2pproxy.compress_method[h].compress( records_list) + P2pproxy.compress_method[h].flush( ) connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(records_list)) connection.end_headers() connection.wfile.write(records_list) # Used to generate logomap for the torrenttv plugin elif connection.reqtype == 'logos': translations_list = TorrentTvApi( config.email, config.password).translations('all') last = translations_list[-1] connection.send_response(200) connection.send_header('Content-Type', 'text/plain;charset=utf-8') connection.end_headers() connection.wfile.write("logobase = '" + config.logobase + "'\n") connection.wfile.write('logomap = {\n') for channel in translations_list: name = channel.getAttribute('name').encode('utf-8') logo = channel.getAttribute('logo').encode('utf-8') connection.wfile.write(" u'%s': logobase + '%s'" % (name, logo)) connection.wfile.write( ',\n') if not channel == last else connection.wfile.write( '\n') connection.wfile.write('}\n')