def exportm3u(self, hostport, path='', add_ts=False, empty_header=False, archive=False, process_url=True, header=None, fmt=None, _bytearray=bytearray): ''' Exports m3u playlist ''' if add_ts: hostport = 'ts://%s' % hostport # Adding ts:// after http:// for some players if header is None: itemlist = self.m3uheader if not empty_header else self.m3uemptyheader else: itemlist = header items = self.sort(self.itemlist) if self.sort else self.itemlist for i in items: item = i.copy() # {'group': XXX, 'tvg': XXX, 'logo': XXX, 'name': XXX, 'tvgid': XXX, 'url': XXX} name = quote(ensure_str(item.get('name').replace('"', "'").replace(',', '.')), '') url = item['url'] if process_url: if url.startswith(('http://', 'https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent')): # For .acelive and .torrent item['url'] = 'http://%s/url/%s/%s.ts' % (hostport, quote(url,''), name) elif url.startswith('infohash://'): # For INFOHASHes item['url'] = 'http://%s/infohash/%s/%s.ts' % (hostport, url.split('/')[2], name) elif url.startswith('acestream://'): # For PIDs item['url'] = 'http://%s/content_id/%s/%s.ts' % (hostport, url.split('/')[2], name) elif archive and url.isdigit(): # For archive channel id's item['url'] = 'http://%s/archive/play?id=%s' % (hostport, url) elif not archive and url.isdigit(): # For channel id's item['url'] = 'http://%s/channels/play?id=%s' % (hostport, url) elif path.endswith('channel'): # For plugins channel name maping item['url'] = 'http://%s%s/%s' % (hostport, path, url) if fmt: item['url'] += '&fmt=%s' % fmt if '?' in item['url'] else '/?fmt=%s' % fmt itemlist += self.m3uchanneltemplate.format(**item) #Generates EXTINF line with url return _bytearray(itemlist, 'utf-8')
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)
def Playlistparser(self): try: s = requests.Session() s.mount('file://', FileAdapter()) with s.get(config.url, headers=self.headers, proxies=config.proxies, stream=False, timeout=30) as playlist: if playlist.status_code != 304: if playlist.encoding is None: playlist.encoding = 'utf-8' playlist = playlist.json() self.headers['If-Modified-Since'] = gevent.time.strftime( '%a, %d %b %Y %H:%M:%S %Z', gevent.time.gmtime(self.playlisttime)) self.playlist = PlaylistGenerator( m3uchanneltemplate=config.m3uchanneltemplate) self.picons = picons.logomap self.channels = {} m = requests.auth.hashlib.md5() logging.info('[%s]: playlist %s downloaded' % (self.__class__.__name__, config.url)) try: urlpattern = requests.utils.re.compile( r'^(acestream|infohash)://[0-9a-f]{40}$|^(http|https)://.*.(acelive|acestream|acemedia|torrent)$' ) for channel in playlist['channels']: name = channel['name'] url = 'infohash://{url}'.format(**channel) channel['group'] = channel.pop('cat') channel['logo'] = self.picons[name] = channel.get( 'logo', picons.logomap.get(name)) channel['tvgid'] = channel.pop('program') if requests.utils.re.search(urlpattern, url): self.channels[name] = url channel['url'] = quote(ensure_str(name), '') self.playlist.addItem(channel) m.update(ensure_binary(name)) except Exception as e: logging.error("[%s]: can't parse JSON! %s" % (self.__class__.__name__, repr(e))) return False self.etag = '"' + m.hexdigest() + '"' logging.debug('[%s]: plugin playlist generated' % self.__class__.__name__) self.playlisttime = gevent.time.time() except requests.exceptions.RequestException: logging.error("[%s]: can't download %s playlist!" % (self.__class__.__name__, config.url)) return False except: logging.error(traceback.format_exc()) return False
def Playlistparser(self): try: s = requests.Session() s.mount('file://', FileAdapter()) with s.get(config.url, headers=self.headers, proxies=config.proxies, stream=False, timeout=30) as r: if r.status_code != 304: if r.encoding is None: r.encoding = 'utf-8' self.headers['If-Modified-Since'] = gevent.time.strftime( '%a, %d %b %Y %H:%M:%S %Z', gevent.time.gmtime(self.playlisttime)) self.playlist = PlaylistGenerator( m3uchanneltemplate=config.m3uchanneltemplate) self.picons = picons.logomap self.channels = {} m = requests.auth.hashlib.md5() logging.info('[%s]: playlist %s downloaded' % (self.__class__.__name__, config.url)) pattern = requests.utils.re.compile( r',(?P<name>.+) \((?P<group>.+)\)[\r\n]+(?P<url>[^\r\n]+)?' ) urlpattern = requests.utils.re.compile( r'^(acestream|infohash)://[0-9a-f]{40}$|^(http|https)://.*.(acelive|acestream|acemedia|torrent)$' ) for match in pattern.finditer(r.text, requests.auth.re.MULTILINE): itemdict = match.groupdict() name = itemdict.get('name', '') itemdict['logo'] = self.picons[name] = itemdict.get( 'logo', picons.logomap.get(name)) url = itemdict['url'] if requests.utils.re.search(urlpattern, url): self.channels[name] = url itemdict['url'] = quote(ensure_str(name), '') self.playlist.addItem(itemdict) m.update(ensure_binary(name)) self.etag = '"' + m.hexdigest() + '"' logging.debug('[%s]: plugin playlist generated' % self.__class__.__name__) self.playlisttime = gevent.time.time() except requests.exceptions.RequestException: logging.error("[%s]: can't download %s playlist!" % (self.__class__.__name__, config.url)) return False except: logging.error(traceback.format_exc()) return False return True
def Playlistparser(self): try: with requests.get(config.url, headers=self.headers, proxies=config.proxies, stream=False, timeout=30) as playlist: if playlist.status_code != 304: if playlist.encoding is None: playlist.encoding = 'utf-8' self.headers['If-Modified-Since'] = gevent.time.strftime( '%a, %d %b %Y %H:%M:%S %Z', gevent.time.gmtime(self.playlisttime)) self.playlist = PlaylistGenerator( m3uchanneltemplate=config.m3uchanneltemplate) self.picons = picons.logomap self.channels = {} m = requests.auth.hashlib.md5() self.logger.info('Playlist %s downloaded' % config.url) try: for channel in playlist.json()['channels']: channel['name'] = name = channel.get('name', '') channel[ 'url'] = url = 'acestream://%s' % channel.get( 'url') channel['group'] = channel.get('cat') if not 'logo' in channel: channel['logo'] = picons.logomap.get(name) self.picons[name] = channel['logo'] if url.startswith(('acestream://', 'infohash://')) \ or (url.startswith(('http://','https://')) and url.endswith(('.acelive','.acestream','.acemedia'))): self.channels[name] = url channel['url'] = quote( ensure_str(name) + '.ts', '') self.playlist.addItem(channel) m.update(name.encode('utf-8')) except Exception as e: self.logger.error("Can't parse JSON! %s" % repr(e)) return self.etag = '"' + m.hexdigest() + '"' self.logger.debug('torrent-telik.m3u playlist generated') self.playlisttime = gevent.time.time() except requests.exceptions.RequestException: self.logger.error("Can't download %s playlist!" % config.url) return False except: self.logger.error(traceback.format_exc()) return False return True
def Playlistparser(self): try: s = requests.Session() s.mount('file://', FileAdapter()) with s.get(config.url, headers=self.headers, proxies=config.proxies, stream=False, timeout=30) as r: if r.status_code != 304: if r.encoding is None: r.encoding = 'utf-8' self.headers['If-Modified-Since'] = gevent.time.strftime( '%a, %d %b %Y %H:%M:%S %Z', gevent.time.gmtime(self.playlisttime)) self.playlist = PlaylistGenerator( m3uchanneltemplate=config.m3uchanneltemplate) self.picons = picons.logomap self.channels = {} m = requests.auth.hashlib.md5() self.logger.info('Playlist %s downloaded' % config.url) pattern = requests.auth.re.compile( r',(?P<name>.+) \((?P<group>.+)\)[\r\n]+(?P<url>[^\r\n]+)?' ) for match in pattern.finditer(r.text, requests.auth.re.MULTILINE): itemdict = match.groupdict() name = itemdict.get('name', '') if not 'logo' in itemdict: itemdict['logo'] = picons.logomap.get(name) self.picons[name] = itemdict['logo'] url = itemdict['url'] if url.startswith(('acestream://', 'infohash://')) \ or (url.startswith(('http://','https://')) and url.endswith(('.acelive', '.acestream', '.acemedia', '.torrent'))): self.channels[name] = url itemdict['url'] = quote(ensure_str('%s.ts' % name), '') self.playlist.addItem(itemdict) m.update(name.encode('utf-8')) self.etag = '"' + m.hexdigest() + '"' self.logger.debug('torrenttv.m3u playlist generated') self.playlisttime = gevent.time.time() except requests.exceptions.RequestException: self.logger.error("Can't download %s playlist!" % config.url) return False except: self.logger.error(traceback.format_exc()) return False return True
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 echo_params(self, request): params = sorted([(ensure_str(k), ensure_str(v)) for k, v in request.params.items()]) return Response(repr(params))
async def urlopen(self, method, url, body=None, headers=None, retries=None, timeout=_Default, pool_timeout=None, body_pos=None, **response_kw): """ Get a connection from the pool and perform an HTTP request. This is the lowest level call for making a request, so you'll need to specify all the raw details. .. note:: More commonly, it's appropriate to use a convenience method provided by :class:`.RequestMethods`, such as :meth:`request`. :param method: HTTP request method (such as GET, POST, PUT, etc.) :param body: Data to send in the request body (useful for creating POST requests, see HTTPConnectionPool.post_url for more convenience). :param headers: Dictionary of custom headers to send, such as User-Agent, If-None-Match, etc. If None, pool headers are used. If provided, these headers completely replace any pool-specific headers. :param retries: Configure the number of retries to allow before raising a :class:`~urllib3.exceptions.MaxRetryError` exception. Pass ``None`` to retry until you receive a response. Pass a :class:`~urllib3.util.retry.Retry` object for fine-grained control over different types of retries. Pass an integer number to retry connection errors that many times, but no other types of errors. Pass zero to never retry. If ``False``, then retries are disabled and any exception is raised immediately. Also, instead of raising a MaxRetryError on redirects, the redirect response will be returned. :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. :param timeout: If specified, overrides the default timeout for this one request. It may be a float (in seconds) or an instance of :class:`urllib3.util.Timeout`. :param pool_timeout: If set and the pool is set to block=True, then this method will block for ``pool_timeout`` seconds and raise EmptyPoolError if no connection is available within the time period. :param int body_pos: Position to seek to in file-like body in the event of a retry or redirect. Typically this won't need to be set because urllib3 will auto-populate the value when needed. :param \\**response_kw: Additional parameters are passed to :meth:`urllib3.response.HTTPResponse.from_base` """ if headers is None: headers = self.headers if not isinstance(retries, Retry): retries = Retry.from_int(retries, default=self.retries, redirect=False) # Ensure that the URL we're connecting to is properly encoded if url.startswith("/"): url = six.ensure_str(_encode_target(url)) else: url = six.ensure_str(parse_url(url).url) conn = None # Track whether `conn` needs to be released before # returning/raising/recursing. release_this_conn = False # Merge the proxy headers. Only do this in HTTP. We have to copy the # headers dict so we can safely change it without those changes being # reflected in anyone else's copy. if self.scheme == "http": headers = headers.copy() headers.update(self.proxy_headers) # Must keep the exception bound to a separate variable or else Python 3 # complains about UnboundLocalError. err = None # Keep track of whether we cleanly exited the except block. This # ensures we do proper cleanup in finally. clean_exit = False # Rewind body position, if needed. Record current position # for future rewinds in the event of a redirect/retry. body_pos = set_file_position(body, body_pos) if body is not None: _add_transport_headers(headers) try: # Request a connection from the queue. timeout_obj = self._get_timeout(timeout) conn = await self._get_conn(timeout=pool_timeout) conn.timeout = timeout_obj.connect_timeout # Make the request on the base connection object. base_response = await self._make_request(conn, method, url, timeout=timeout_obj, body=body, headers=headers) # Pass method to Response for length checking response_kw["request_method"] = method # Import httplib's response into our own wrapper object response = self.ResponseCls.from_base(base_response, pool=self, retries=retries, **response_kw) # Everything went great! clean_exit = True except queue.Empty: # Timed out by queue. raise EmptyPoolError(self, "No pool connections are available.") except ( TimeoutError, SocketError, ProtocolError, h11.ProtocolError, BaseSSLError, SSLError, CertificateError, ) as e: # Discard the connection for these exceptions. It will be # replaced during the next _get_conn() call. clean_exit = False if isinstance(e, (BaseSSLError, CertificateError)): e = SSLError(e) elif isinstance(e, (SocketError, NewConnectionError)) and self.proxy: e = ProxyError("Cannot connect to proxy.", e) elif isinstance(e, (SocketError, h11.ProtocolError)): e = ProtocolError("Connection aborted.", e) retries = retries.increment(method, url, error=e, _pool=self, _stacktrace=sys.exc_info()[2]) retries.sleep() # Keep track of the error for the retry warning. err = e finally: if not clean_exit: # We hit some kind of exception, handled or otherwise. We need # to throw the connection away unless explicitly told not to. # Close the connection, set the variable to None, and make sure # we put the None back in the pool to avoid leaking it. conn = conn and conn.close() release_this_conn = True if release_this_conn: # Put the connection back to be reused. If the connection is # expired then it will be None, which will get replaced with a # fresh connection during _get_conn. self._put_conn(conn) if not conn: # Try again log.warning("Retrying (%r) after connection broken by '%r': %s", retries, err, url) return await self.urlopen(method, url, body, headers, retries, timeout=timeout, pool_timeout=pool_timeout, body_pos=body_pos, **response_kw) async def drain_and_release_conn(response): try: # discard any remaining response body, the connection will be # released back to the pool once the entire response is read await response.read() except (TimeoutError, SocketError, ProtocolError, BaseSSLError, SSLError): pass # Check if we should retry the HTTP response. has_retry_after = bool(response.getheader("Retry-After")) if retries.is_retry(method, response.status, has_retry_after): try: retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: if retries.raise_on_status: # Drain and release the connection for this response, since # we're not returning it to be released manually. await drain_and_release_conn(response) raise return response # drain and return the connection to the pool before recursing await drain_and_release_conn(response) retries.sleep(response) log.debug("Retry: %s", url) return await self.urlopen(method, url, body, headers, retries=retries, timeout=timeout, pool_timeout=pool_timeout, body_pos=body_pos, **response_kw) return response