def handle(self, connection): path_file_ext = connection.path[connection.path.rfind('.') + 1:] if connection.splittedpath[ 1] == 'stat' and connection.splittedpath.__len__() == 2: if query_get(connection.query, 'action') == 'get_status': Stat.SendResponse( 200, 'json', ensure_binary( json.dumps(self.getStatusJSON(), ensure_ascii=True)), connection) else: try: Stat.SendResponse(200, 'html', Stat.getReqFileContent('index.html'), connection) except: connection.send_error(404, 'Not Found') elif path_file_ext: try: Stat.SendResponse( 200, path_file_ext, Stat.getReqFileContent( connection.path.replace(r'/stat', '')), connection) except: connection.send_error(404, 'Not Found') else: connection.send_error(404, 'Not Found')
def handle(self, connection): exported = self.createPlaylist(connection.headers['Host'], connection.reqtype, query_get(connection.query, 'fmt')).encode('utf-8') connection.send_response(200) connection.send_header('Content-Type', 'audio/mpegurl; charset=utf-8') connection.send_header('Access-Control-Allow-Origin', '*') try: h = connection.headers.get('Accept-Encoding').split(',')[0] compress_method = { 'zlib': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS), 'deflate': zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS), 'gzip': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) } exported = compress_method[h].compress( exported) + compress_method[h].flush() connection.send_header('Content-Encoding', h) except: pass connection.send_header('Content-Length', len(exported)) connection.send_header('Connection', 'close') connection.end_headers() connection.wfile.write(exported)
def exportm3u(self, **params): ''' Exports m3u playlist params: hostport= '', path='', empty_header=False, archive=False, parse_url=True, header=None, query=None ''' def line_generator(item): ''' Generates EXTINF line with url ''' item = item.copy() # {'group': XXX, 'tvg': XXX, 'logo': XXX, 'name': XXX, 'tvgid': XXX, 'url': XXX} params.update({'name': quote(ensure_str(item.get('name').replace('"', "'").replace(',', '.')), '')}) url = item['url'] if not params.get('parse_url'): if params.get('path') and params.get('path').endswith('channel'): # For plugins channel name maping params.update({'value': url}) item['url'] = urlunparse(u'{schema};{netloc};{path}/{value}.{ext};;{query};'.format(**params).split(';')) elif url.startswith(('http://', 'https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent')): # For .acelive and .torrent params.update({'value': quote(url,'')}) item['url'] = urlunparse(u'{schema};{netloc};/url/{value}/{name}.{ext};;{query};'.format(**params).split(';')) elif url.startswith('infohash://'): # For INFOHASHes params.update({'value': url.split('/')[2]}) item['url'] = urlunparse(u'{schema};{netloc};/infohash/{value}/{name}.{ext};;{query};'.format(**params).split(';')) elif url.startswith('acestream://'): # For PIDs params.update({'value': url.split('/')[2]}) item['url'] = urlunparse(u'{schema};{netloc};/content_id/{value}/{name}.{ext};;{query};'.format(**params).split(';')) elif params.get('archive') and url.isdigit(): # For archive channel id's params.update({'value': url}) item['url'] = urlunparse(u'{schema};{netloc};/archive/play;;id={value};'.format(**params).split(';')) elif not params.get('archive') and url.isdigit(): # For channel id's params.update({'value': url}) item['url'] = urlunparse(u'{schema};{netloc};/channels/play;;id={value};'.format(**params).split(';')) return self.m3uchanneltemplate.format(**item) params.update({'schema': 'http', 'netloc': params.get('hostport'), 'ext': query_get(params.get('query',''), 'ext', 'ts')}) return ensure_binary(params.get('header', self.m3uemptyheader if params.get('empty_header') else self.m3uheader) + ''.join(map(line_generator, self.sort(self.itemlist))))
def handle(self, connection): # 30 minutes cache if not self.playlist or (gevent.time.time() - self.playlisttime > 30 * 60): if not self.Playlistparser(): logging.info('Parser failed to parse') connection.send_error() connection.ext = query_get(connection.query, 'ext', 'ts') if connection.path.startswith('/{reqtype}/channel/'.format(**connection.__dict__)): if not connection.path.endswith(connection.ext): logging.info('Invalid path') connection.send_error(404, 'Invalid path: {path}'.format(**connection.__dict__), logging.ERROR) name = ensure_text(unquote(os.path.splitext(os.path.basename(connection.path))[0])) url = self.channels.get(name) if url is None: logging.info('Unknown channel') connection.send_error(404, '[%s]: unknown channel: %s' % (self.__class__.__name__, name), logging.ERROR) connection.__dict__.update({'channelName': name, 'channelIcon': self.picons.get(name), 'path': {'acestream': '/content_id/%s/%s.%s' % (urlparse(url).netloc, name, connection.ext), 'infohash' : '/infohash/%s/%s.%s' % (urlparse(url).netloc, name, connection.ext), 'http' : '/url/%s/%s.%s' % (quote(url,''), name, connection.ext), 'https' : '/url/%s/%s.%s' % (quote(url,''), name, connection.ext), }[urlparse(url).scheme]}) connection.__dict__.update({'splittedpath': connection.path.split('/')}) connection.__dict__.update({'reqtype': connection.splittedpath[1].lower()}) return elif self.etag == connection.headers.get('If-None-Match'): logging.debug('[%s]: ETag matches. Return 304 to [%s]' % (self.__class__.__name__, connection.clientip)) connection.send_response(304) connection.send_header('Connection', 'close') connection.end_headers() return else: host_port = connection.headers['Host'] exported = self.playlist.exportm3u( hostport=host_port, path='' if not self.channels else '/{reqtype}/channel'.format(**connection.__dict__), header=config.m3uheadertemplate.format(get_epg_url(host_port, config, config.tvgurl), config.tvgshift), query=connection.query ) response_headers = {'Content-Type': 'audio/mpegurl; charset=utf-8', 'Connection': 'close', 'Access-Control-Allow-Origin': '*'} try: h = connection.headers.get('Accept-Encoding').split(',')[0] compress_method = { 'zlib': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS), 'deflate': zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS), 'gzip': zlib.compressobj(9, zlib.DEFLATED, zlib.MAX_WBITS | 16) } exported = compress_method[h].compress(exported) + compress_method[h].flush() response_headers['Content-Encoding'] = h except: pass response_headers['Content-Length'] = len(exported) if connection.request_version == 'HTTP/1.1': response_headers['ETag'] = self.etag connection.send_response(200) gevent.joinall([gevent.spawn(connection.send_header, k, v) for (k,v) in response_headers.items()]) connection.end_headers() connection.wfile.write(exported) logging.debug('[%s]: plugin sent playlist to [%s]' % (self.__class__.__name__, connection.clientip))
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