def handle(self, connection, headers_only=False): play = False # 30 minutes cache if not self.playlist or (gevent.time.time() - self.playlisttime > 30 * 60): if not self.Playlistparser(): connection.dieWithError(); return url = urlparse(connection.path) path = url.path[0:-1] if url.path.endswith('/') else url.path params = parse_qs(connection.query) if path.startswith('/%s/channel/' % connection.reqtype): if not path.endswith('.ts'): connection.dieWithError(404, 'Invalid path: %s' % unquote(path), logging.ERROR) return name = ensure_text(unquote(path[path.rfind('/')+1:])) url = self.channels.get('.'.join(name.split('.')[:-1])) if url is None: connection.dieWithError(404, 'Unknown channel: ' + name, logging.ERROR) return elif url.startswith('acestream://'): connection.path = '/content_id/%s/%s' % (url.split('/')[2], name) elif url.startswith('infohash://'): connection.path = '/infohash/%s/%s' % (url.split('/')[2], name) elif url.startswith(('http://', 'https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent')): connection.path = '/url/%s/%s' % (quote(url,''), name) connection.splittedpath = connection.path.split('/') connection.reqtype = connection.splittedpath[1].lower() name = name.split('.')[0] play = True elif self.etag == connection.headers.get('If-None-Match'): self.logger.debug('ETag matches - returning 304') connection.send_response(304) connection.send_header('Connection', 'close') connection.end_headers() return else: hostport = connection.headers['Host'] path = '' if not self.channels else '/%s/channel' % connection.reqtype add_ts = True if path.endswith('/ts') else False exported = self.playlist.exportm3u(hostport=hostport, path=path, add_ts=add_ts, header=config.m3uheadertemplate, fmt=params.get('fmt', [''])[0]) response_headers = { 'Content-Type': 'audio/mpegurl; charset=utf-8', 'Connection': 'close', 'Content-Length': len(exported), 'Access-Control-Allow-Origin': '*', 'ETag': self.etag } 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-Length'] = len(exported) response_headers['Content-Encoding'] = h except: pass connection.send_response(200) gevent.joinall([gevent.spawn(connection.send_header, k, v) for (k,v) in response_headers.items()]) connection.end_headers() if play: connection.handleRequest(headers_only, name, self.picons.get(name), fmt=params.get('fmt', [''])[0]) elif not headers_only: self.logger.debug('Exporting torrent-telik.m3u playlist') connection.wfile.write(exported)
def StreamReader(params): ''' params: dict([url=] [file_index=] [infohash= ] [ad=1 [interruptable=1]] [stream=1] [pos=position] [bitrate=] [length=]) ''' def checkBroadcast(): if not params['broadcastclients']: params['broadcast'].kill() return params['broadcast'].started def write_chunk(client, chunk, timeout=15.0): try: client.q.put(chunk, timeout=timeout) except gevent.queue.Full: client.send_error( 500, '[%s]: does not read data until %s sec' % (client.clientip, timeout), logging.ERROR) def StreamWriter(url): for chunk in s.get(url, timeout=(5, AceConfig.videotimeout), stream=True).iter_content(chunk_size=1048576): _ = params['chunkpool'].map( lambda client: write_chunk(client, chunk), params['broadcastclients']) try: params.update({ 'url': urlparse(unquote(params['url']))._replace( netloc='{aceHostIP}:{aceHTTPport}'.format( **AceConfig.ace)).geturl(), 'broadcast': gevent.getcurrent(), 'broadcastclients': AceProxy.clientcounter.getClientsList(params['infohash']), 'chunkpool': Pool(), }) with requests.session() as s: if params['url'].endswith( '.m3u8'): # AceEngine return link for HLS stream import m3u8 urls = [] while checkBroadcast(): for url in m3u8.load(params['url']).segments.uri: if url in urls: continue else: StreamWriter(url) urls.append(url) if len(urls) > 50: urls.pop(0) else: StreamWriter( params['url']) #AceStream return link for HTTP stream except (TypeError, gevent.GreenletExit): pass except Exception as err: _ = AceProxy.pool.map( lambda x: x.send_error(500, repr(err), logging.ERROR), params['broadcastclients'])
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 do_GET(self, headers_only=False): ''' GET request handler ''' # Current greenlet self.handlerGreenlet = gevent.getcurrent() # Connected client IP address self.clientip = self.headers[ 'X-Forwarded-For'] if 'X-Forwarded-For' in self.headers else self.client_address[ 0] logging.info('Accepted connection from %s path %s' % (self.clientip, unquote(self.path))) logging.debug('Client headers: %s' % dict(self.headers)) params = urlparse(self.path) self.query, self.path = params.query, params.path[: -1] if params.path.endswith( '/' ) else params.path if AceConfig.firewall and not checkFirewall(self.clientip): self.dieWithError( 401, 'Dropping connection from %s due to firewall rules' % self.clientip, logging.ERROR) return try: self.splittedpath = self.path.split('/') self.reqtype = self.splittedpath[1].lower() # backward compatibility old2newUrlParts = {'torrent': 'url', 'pid': 'content_id'} if self.reqtype in old2newUrlParts: self.reqtype = old2newUrlParts[self.reqtype] # If first parameter is 'content_id','url','infohash' .... etc or it should be handled by plugin #'direct_url', 'data', 'efile_url' if not (self.reqtype in ('content_id', 'url', 'infohash') or self.reqtype in AceProxy.pluginshandlers): self.dieWithError(400, 'Bad Request', logging.WARNING) # 400 Bad Request return except IndexError: self.dieWithError(400, 'Bad Request', logging.WARNING) # 400 Bad Request return # Handle request with plugin handler if self.reqtype in AceProxy.pluginshandlers: try: AceProxy.pluginshandlers.get(self.reqtype).handle( self, headers_only) except Exception as e: import traceback logger.error(traceback.format_exc()) self.dieWithError(500, 'Plugin exception: %s' % repr(e)) finally: return self.handleRequest(headers_only)
def do_GET(self): ''' GET request handler ''' self.handlerGreenlet = gevent.getcurrent() # Current greenlet self.clientip = self.headers.get('X-Forwarded-For', self.address_string()) # Connected client IP address logging.info(unquote('[{clientip}]: {command} {request_version} request for: {path}'.format(**self.__dict__))) logging.debug('[%s]: Request headers: %s' % (self.clientip, dict(self.headers))) if AceConfig.firewall and not checkFirewall(self.clientip): self.send_error(401, '[{clientip}]: Dropping connection due to firewall rules'.format(**self.__dict__), logging.ERROR) self.path, _, self.query = self.path.partition('?') self.path = self.path.rstrip('/') # Pretend to work fine with Fake or HEAD request. if self.command == 'HEAD' or AceConfig.isFakeRequest(self.path, self.query, self.headers): # Return 200 and exit if self.command != 'HEAD': self.command = 'FAKE' logging.debug('[{clientip}]: {command} request: send headers and close the connection'.format(**self.__dict__)) self.send_response(200) self.send_header('Content-Type', 'video/mp2t') self.send_header('Connection', 'Close') self.end_headers() return try: self.splittedpath = self.path.split('/') self.reqtype = self.splittedpath[1].lower() AceProxy.pluginshandlers[self.reqtype].handle(self) # If request should be handled by plugin raise IndexError() except (IndexError, KeyError): self.reqtype = {'torrent': 'url', 'pid': 'content_id'}.get(self.reqtype, self.reqtype) # For backward compatibility if self.reqtype in {'content_id', 'url', 'infohash', 'direct_url', 'data', 'efile_url'}: # Limit on the number of connected clients if 0 < AceConfig.maxconns <= len(AceProxy.clientcounter.getAllClientsList()): self.send_error(403, "[{clientip}]: Maximum client connections reached, can't serve request".format(**self.__dict__), logging.ERROR) # Check if third path parameter is exists /{reqtype}/{reqtype_value}/.../.../video.mpg # |_________| # And if it ends with regular video extension if not self.splittedpath[-1].endswith(('.avi', '.flv', '.m2ts', '.mkv', '.mpeg', '.mpeg4', '.mpegts', '.mpg4', '.mp4', '.mpg', '.mov', '.mpv', '.qt', '.ts', '.wmv')): self.send_error(501, '[{clientip}]: request seems like valid but no valid video extension was provided'.format(**self.__dict__), logging.ERROR) else: self.handleRequest() elif self.reqtype not in AceProxy.pluginshandlers: self.send_error(400, '[{clientip}]: Bad Request'.format(**self.__dict__), logging.WARNING) # 400 Bad Request except Exception as e: logging.error('Unexpected exception: %s' % repr(e)) logging.error(traceback.format_exc())
def _read(self, timeout=30): ''' Read telnet connection method ''' while 1: with gevent.Timeout(timeout, False): try: recvbuffer = self._socket.read_until('\r\n', None).strip().split() logging.debug('[%.20s]: <<< %s' % (self._title, unquote(' '.join(recvbuffer)))) gevent.spawn(getattr(globals()[self.__class__.__name__], '_%s_' % recvbuffer[0].lower(), '_unrecognized_'), self, recvbuffer).link_value(self._response[recvbuffer[0]]) except gevent.socket.timeout: pass # WinOS patch except gevent.Timeout: self.ShutdownAce() break except: break # Telnet connection unexpectedly closed or telnet connection error
def StreamReader(playback_url, cid): def write_chunk(client, chunk, timeout=15.0): try: client.q.put(chunk, timeout=timeout) except gevent.queue.Full: client.dieWithError( 500, 'Client %s does not read data until %s sec' % (client.clientip, timeout), logging.ERROR) def StreamWriter(url): for chunk in s.get(url, timeout=(5, AceConfig.videotimeout)).iter_content( chunk_size=1048576): _ = AceProxy.pool.map(lambda client: write_chunk(client, chunk), AceProxy.clientcounter.getClientsList(cid)) try: playback_url = urlparse(unquote(playback_url))._replace( netloc='{aceHostIP}:{aceHTTPport}'.format( **AceConfig.ace)).geturl() with requests.session() as s: s.verify = False s.stream = True if playback_url.endswith( '.m3u8'): # AceEngine return link for HLS stream used_urls = [] while 1: for url in s.get( playback_url, timeout=(5, AceConfig.videotimeout)).iter_lines(): if url.startswith(b'download not found'): return if url.startswith(b'http://') and url not in used_urls: StreamWriter(url) used_urls.append(url) if len(used_urls) > 15: used_urls.pop(0) else: StreamWriter( playback_url) #AceStream return link for HTTP stream except TypeError: pass except Exception as err: _ = AceProxy.pool.map( lambda x: x.dieWithError(500, repr(err), logging.ERROR), AceProxy.clientcounter.getClientsList(cid))
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 _recvData(self, timeout=30): ''' Data receiver method for greenlet ''' while 1: # Destroy socket connection if AceEngine STATE 0 (IDLE) and we didn't read anything from socket until Nsec with gevent.Timeout(timeout, False): try: self._recvbuffer = self._socket.read_until('\r\n', None).strip() except gevent.Timeout: self.destroy() except gevent.socket.timeout: pass except: raise else: logging.debug('<<< %s' % unquote(self._recvbuffer)) # Parsing everything only if the string is not empty # HELLOTS if self._recvbuffer.startswith('HELLOTS'): #version=engine_version version_code=version_code key=request_key http_port=http_port self._auth.set({ k: v for k, v in (x.split('=') for x in self._recvbuffer.split() if '=' in x) }) # NOTREADY elif self._recvbuffer.startswith('NOTREADY'): self._auth.set('NOTREADY') # AUTH elif self._recvbuffer.startswith('AUTH'): self._auth.set( self._recvbuffer.split()[1]) # user_auth_level # START elif self._recvbuffer.startswith('START'): # url [ad=1 [interruptable=1]] [stream=1] [pos=position] params = { k: v for k, v in (x.split('=') for x in self._recvbuffer.split() if '=' in x) } if not self._seekback or self._started_again.ready( ) or params.get('stream', '') is not '1': # If seekback is disabled, we use link in first START command. # If seekback is enabled, we wait for first START command and # ignore it, then do seekback in first EVENT position command # AceStream sends us STOP and START again with new link. # We use only second link then. self._url.set( self._recvbuffer.split()[1]) # url for play # LOADRESP elif self._recvbuffer.startswith('LOADRESP'): self._loadasync.set( json.loads( unquote(''.join( self._recvbuffer.split()[2:])))) # STATE elif self._recvbuffer.startswith('STATE'): self._state.set(self._recvbuffer.split() [1]) # STATE state_id -> STATE_NAME # STATUS elif self._recvbuffer.startswith('STATUS'): self._tempstatus = self._recvbuffer.split()[1] stat = [self._tempstatus.split(';')[0].split(':')[1] ] # main:???? if self._tempstatus.startswith('main:idle'): pass elif self._tempstatus.startswith('main:loading'): pass elif self._tempstatus.startswith('main:starting'): pass elif self._tempstatus.startswith('main:check'): pass elif self._tempstatus.startswith('main:err'): pass # err;error_id;error_message elif self._tempstatus.startswith('main:dl'): #dl; stat.extend( map(int, self._tempstatus.split(';')[1:])) elif self._tempstatus.startswith( 'main:wait'): #wait;time; stat.extend( map(int, self._tempstatus.split(';')[2:])) elif self._tempstatus.startswith( ('main:prebuf', 'main:buf')): #buf;progress;time; stat.extend( map(int, self._tempstatus.split(';')[3:])) try: self._status.set({ k: v for k, v in zip(AceConst.STATUS, stat) }) # dl, wait, buf, prebuf except: self._status.set( {'status': stat[0]}) # idle, loading, starting, check # CID elif self._recvbuffer.startswith('##'): self._cid.set(self._recvbuffer) # INFO elif self._recvbuffer.startswith('INFO'): pass # EVENT elif self._recvbuffer.startswith('EVENT'): self._tempevent = self._recvbuffer.split() if self._seekback and not self._started_again.ready( ) and 'livepos' in self._tempevent: params = { k: v for k, v in (x.split('=') for x in self._tempevent if '=' in x) } self._write( AceMessage.request.LIVESEEK( int(params['last']) - self._seekback)) self._started_again.set() elif 'getuserdata' in self._tempevent: self._write( AceMessage.request.USERDATA( self._gender, self._age)) elif 'cansave' in self._tempevent: pass elif 'showurl' in self._tempevent: pass elif 'download_stopped' in self._tempevent: pass # PAUSE elif self._recvbuffer.startswith('PAUSE'): pass #self._write(AceMessage.request.EVENT('pause')) # RESUME elif self._recvbuffer.startswith('RESUME'): pass #self._write(AceMessage.request.EVENT('play')) # STOP elif self._recvbuffer.startswith('STOP'): pass #self._write(AceMessage.request.EVENT('stop')) # SHUTDOWN elif self._recvbuffer.startswith('SHUTDOWN'): self._socket.close() break
def send(self, request, **kwargs): """ Wraps a file, described in request, in a Response object. :param request: The PreparedRequest` being "sent". :returns: a Response object containing the file """ # Check that the method makes sense. Only support GET if request.method not in ("GET", "HEAD"): raise ValueError("Invalid request method %s" % request.method) # Parse the URL url_parts = urlparse(request.url) # Reject URLs with a hostname component if url_parts.netloc and url_parts.netloc != "localhost": raise ValueError( "file: URLs with hostname components are not permitted") resp = Response() # Open the file, translate certain errors into HTTP responses # Use urllib's unquote to translate percent escapes into whatever # they actually need to be try: # Split the path on / (the URL directory separator) and decode any # % escapes in the parts path_parts = [unquote(p) for p in url_parts.path.split('/')] # Strip out the leading empty parts created from the leading /'s while path_parts and not path_parts[0]: path_parts.pop(0) # If os.sep is in any of the parts, someone fed us some shenanigans. # Treat is like a missing file. if any(os.sep in p for p in path_parts): raise IOError(errno.ENOENT, os.strerror(errno.ENOENT)) # Look for a drive component. If one is present, store it separately # so that a directory separator can correctly be added to the real # path, and remove any empty path parts between the drive and the path. # Assume that a part ending with : or | (legacy) is a drive. if path_parts and (path_parts[0].endswith('|') or path_parts[0].endswith(':')): path_drive = path_parts.pop(0) if path_drive.endswith('|'): path_drive = path_drive[:-1] + ':' while path_parts and not path_parts[0]: path_parts.pop(0) else: path_drive = '' # Try to put the path back together # Join the drive back in, and stick os.sep in front of the path to # make it absolute. path = path_drive + os.sep + os.path.join(*path_parts) # Check if the drive assumptions above were correct. If path_drive # is set, and os.path.splitdrive does not return a drive, it wasn't # reall a drive. Put the path together again treating path_drive # as a normal path component. if path_drive and not os.path.splitdrive(path): path = os.sep + os.path.join(path_drive, *path_parts) # Use io.open since we need to add a release_conn method, and # methods can't be added to file objects in python 2. resp.raw = io.open(path, "rb") resp.raw.release_conn = resp.raw.close except IOError as e: if e.errno == errno.EACCES: resp.status_code = codes.forbidden elif e.errno == errno.ENOENT: resp.status_code = codes.not_found else: resp.status_code = codes.bad_request # Wrap the error message in a file-like object # The error message will be localized, try to convert the string # representation of the exception into a byte stream resp_str = str(e).encode(locale.getpreferredencoding(False)) resp.raw = BytesIO(resp_str) resp.headers['Content-Length'] = len(resp_str) # Add release_conn to the BytesIO object resp.raw.release_conn = resp.raw.close else: resp.status_code = codes.ok resp.url = request.url # If it's a regular file, set the Content-Length resp_stat = os.fstat(resp.raw.fileno()) if stat.S_ISREG(resp_stat.st_mode): resp.headers['Content-Length'] = resp_stat.st_size return resp
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 _loadresp_(self, recvbuffer): ''' LOADRESP request_id {'status': status, 'files': [["Name", idx], [....]], 'infohash': infohash, 'checksum': checksum} ''' return json.loads(unquote(''.join(recvbuffer[2:])))
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): play = False # 30 minutes cache if not self.playlist or (int(gevent.time.time()) - self.playlisttime > 30 * 60): self.updatelogos = p2pconfig.email != 're.place@me' and p2pconfig.password != 'ReplaceMe' if not self.downloadPlaylist(): connection.dieWithError() return url = urlparse(connection.path) path = url.path[0:-1] if url.path.endswith('/') else url.path params = parse_qs(connection.query) if path.startswith('/torrenttv/channel/'): name = path.rsplit('.', 1) if not name[1]: connection.dieWithError(404, 'Invalid path: %s' % unquote(path), logging.ERROR) return name = unquote(name[0].rsplit('/', 1)[1]) if isinstance(name, bytes): name = name.decode('utf-8') # PY2 url = self.channels.get(name, None) if url is None: connection.dieWithError(404, 'Unknown channel: ' + name, logging.ERROR) return elif url.startswith('acestream://'): connection.path = '/content_id/%s/stream.ts' % url.split( '/')[2] elif url.startswith('infohash://'): connection.path = '/infohash/%s/stream.ts' % url.split('/')[2] elif url.startswith(('http://', 'https://')) and url.endswith( ('.acelive', '.acestream', '.acemedia')): connection.path = '/url/%s/stream.ts' % quote(url, '') connection.splittedpath = connection.path.split('/') connection.reqtype = connection.splittedpath[1].lower() play = True elif self.etag == connection.headers.get('If-None-Match'): self.logger.debug('ETag matches - returning 304') connection.send_response(304) connection.send_header('Connection', 'close') connection.end_headers() return else: hostport = connection.headers['Host'] path = '' if len(self.channels) == 0 else '/torrenttv/channel' add_ts = True if path.endswith('/ts') else False exported = self.playlist.exportm3u( hostport=hostport, path=path, add_ts=add_ts, header=config.m3uheadertemplate, fmt=params.get('fmt', [''])[0]).encode('utf-8') response_headers = { 'Content-Type': 'audio/mpegurl; charset=utf-8', 'Connection': 'close', 'Content-Length': len(exported), 'Access-Control-Allow-Origin': '*', 'ETag': self.etag } 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-Length'] = len(exported) response_headers['Content-Encoding'] = h except: pass connection.send_response(200) gevent.joinall([ gevent.spawn(connection.send_header, k, v) for (k, v) in response_headers.items() ]) connection.end_headers() if play: connection.handleRequest(headers_only, name, self.logomap.get(name), fmt=params.get('fmt', [''])[0]) elif not headers_only: self.logger.debug('Exporting m3u playlist') connection.wfile.write(exported)