Example #1
0
def createAce(): # Create telnet connection to the AceEngine API port
    logger.debug('Create connection to AceEngine.....')
    try: ace = aceclient.AceClient(AceConfig.ace, AceConfig.aceconntimeout, AceConfig.aceresulttimeout)
    except:
         logger.error('Ace Stream telnet connection failed'); raise
    else:
         ace.aceInit(AceConfig.acesex, AceConfig.aceage, AceConfig.acekey, AceConfig.videoseekback, AceConfig.videotimeout)
         return ace
Example #2
0
 def createAce(self):
     logger = logging.getLogger('CreateAce')
     ace = aceclient.AceClient(AceConfig.acehost, AceConfig.aceAPIport, AceConfig.aceHTTPport, AceConfig.acehostslist,
                               connect_timeout=AceConfig.aceconntimeout,result_timeout=AceConfig.aceresulttimeout)
     logger.debug("AceClient created")
     ace.aceInit(gender=AceConfig.acesex, age=AceConfig.aceage,product_key=AceConfig.acekey, seekback=AceConfig.videoseekback)
     logger.debug("AceClient inited")
     return ace
Example #3
0
    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
Example #4
0
    def handleRequest(self, headers_only):
        # Check if third parameter exists
        # …/pid/blablablablabla/video.mpg
        #                      |_________|
        # And if it ends with regular video extension
        try:
            if not self.path.endswith(
                ('.3gp', '.avi', '.flv', '.mkv', '.mov', '.mp4', '.mpeg',
                 '.mpg', '.ogv', '.ts')):
                logger.error(
                    "Request seems like valid but no valid video extension was provided"
                )
                self.dieWithError(400)
                return
        except IndexError:
            self.dieWithError(400)  # 400 Bad Request
            return

        # Limit concurrent connections
        if 0 < AceConfig.maxconns <= AceStuff.clientcounter.total:
            logger.debug("Maximum connections reached, can't serve this")
            self.dieWithError(503)  # 503 Service Unavailable
            return

        # Pretend to work fine with Fake UAs or HEAD request.
        useragent = self.headers.get('User-Agent')
        fakeua = useragent and useragent in AceConfig.fakeuas
        if headers_only or fakeua:
            if fakeua:
                logger.debug("Got fake UA: " + self.headers.get('User-Agent'))
            # Return 200 and exit
            self.send_response(200)
            self.send_header("Content-Type", "video/mpeg")
            self.end_headers()
            self.closeConnection()
            return

        self.path_unquoted = urllib2.unquote(self.splittedpath[2])
        # Make list with parameters
        self.params = list()
        for i in xrange(3, 8):
            try:
                self.params.append(int(self.splittedpath[i]))
            except (IndexError, ValueError):
                self.params.append('0')

        # Adding client to clientcounter
        clients = AceStuff.clientcounter.add(self.path_unquoted, self.clientip)
        # If we are the one client, but sucessfully got ace from clientcounter,
        # then somebody is waiting in the videodestroydelay state
        self.ace = AceStuff.clientcounter.getAce(self.path_unquoted)
        if not self.ace:
            shouldcreateace = True
        else:
            shouldcreateace = False

        # Use PID as VLC ID if PID requested
        # Or torrent url MD5 hash if torrent requested
        if self.reqtype == 'pid':
            self.vlcid = self.path_unquoted
        else:
            self.vlcid = hashlib.md5(self.path_unquoted).hexdigest()

        # If we don't use VLC and we're not the first client
        if clients != 1 and not AceConfig.vlcuse:
            AceStuff.clientcounter.delete(self.path_unquoted, self.clientip)
            logger.error(
                "Not the first client, cannot continue in non-VLC mode")
            self.dieWithError(503)  # 503 Service Unavailable
            return

        if shouldcreateace:
            # If we are the only client, create AceClient
            try:
                self.ace = aceclient.AceClient(
                    AceConfig.acehost,
                    AceConfig.aceport,
                    connect_timeout=AceConfig.aceconntimeout,
                    result_timeout=AceConfig.aceresulttimeout)
                # Adding AceClient instance to pool
                AceStuff.clientcounter.addAce(self.path_unquoted, self.ace)
                logger.debug("AceClient created")
            except aceclient.AceException as e:
                logger.error("AceClient create exception: " + repr(e))
                AceStuff.clientcounter.delete(self.path_unquoted,
                                              self.clientip)
                self.dieWithError(502)  # 502 Bad Gateway
                return

        # Send fake headers if this User-Agent is in fakeheaderuas tuple
        if fakeua:
            logger.debug("Sending fake headers for " + useragent)
            self.send_response(200)
            self.send_header("Content-Type", "video/mpeg")
            self.end_headers()
            # Do not send real headers at all
            self.headerssent = True

        try:
            self.hanggreenlet = gevent.spawn(self.hangDetector)
            logger.debug("hangDetector spawned")
            gevent.sleep()

            # Initializing AceClient
            if shouldcreateace:
                self.ace.aceInit(gender=AceConfig.acesex,
                                 age=AceConfig.aceage,
                                 product_key=AceConfig.acekey,
                                 pause_delay=AceConfig.videopausedelay,
                                 seekback=AceConfig.videoseekback)
                logger.debug("AceClient inited")
                if self.reqtype == 'pid':
                    contentinfo = self.ace.START(
                        self.reqtype, {
                            'content_id': self.path_unquoted,
                            'file_indexes': self.params[0]
                        })
                elif self.reqtype == 'torrent':
                    paramsdict = dict(
                        zip(aceclient.acemessages.AceConst.START_TORRENT,
                            self.params))
                    paramsdict['url'] = self.path_unquoted
                    contentinfo = self.ace.START(self.reqtype, paramsdict)
                logger.debug("START done")

            # Getting URL
            self.url = self.ace.getUrl(AceConfig.videotimeout)
            # Rewriting host for remote Ace Stream Engine
            self.url = self.url.replace('127.0.0.1', AceConfig.acehost)
            self.errorhappened = False

            if shouldcreateace:
                logger.debug("Got url " + self.url)
                # If using VLC, add this url to VLC
                if AceConfig.vlcuse:
                    # Force ffmpeg demuxing if set in config
                    if AceConfig.vlcforceffmpeg:
                        self.vlcprefix = 'http/ffmpeg://'
                    else:
                        self.vlcprefix = ''

                    self.ace.pause()
                    # Sleeping videodelay
                    gevent.sleep(AceConfig.videodelay)
                    self.ace.play()

                    AceStuff.vlcclient.startBroadcast(
                        self.vlcid, self.vlcprefix + self.url,
                        AceConfig.vlcmux, AceConfig.vlcpreaccess)
                    # Sleep a bit, because sometimes VLC doesn't open port in
                    # time
                    gevent.sleep(0.5)

            # Building new VLC url
            if AceConfig.vlcuse:
                self.url = 'http://' + AceConfig.vlchost + \
                    ':' + str(AceConfig.vlcoutport) + '/' + self.vlcid
                logger.debug("VLC url " + self.url)

            # Sending client headers to videostream
            self.video = urllib2.Request(self.url)
            for key in self.headers.dict:
                self.video.add_header(key, self.headers.dict[key])

            self.video = urllib2.urlopen(self.video)

            # Sending videostream headers to client
            if not self.headerssent:
                self.send_response(self.video.getcode())
                if self.video.info().dict.has_key('connection'):
                    del self.video.info().dict['connection']
                if self.video.info().dict.has_key('server'):
                    del self.video.info().dict['server']
                if self.video.info().dict.has_key('transfer-encoding'):
                    del self.video.info().dict['transfer-encoding']
                if self.video.info().dict.has_key('keep-alive'):
                    del self.video.info().dict['keep-alive']

                for key in self.video.info().dict:
                    self.send_header(key, self.video.info().dict[key])
                # End headers. Next goes video data
                self.end_headers()
                logger.debug("Headers sent")

            if not AceConfig.vlcuse:
                self.ace.pause()
                # Sleeping videodelay
                gevent.sleep(AceConfig.videodelay)
                self.ace.play()

            # Run proxyReadWrite
            self.proxyReadWrite()

            # Waiting until hangDetector is joined
            self.hanggreenlet.join()
            logger.debug("Request handler finished")

        except (aceclient.AceException, vlcclient.VlcException,
                urllib2.URLError) as e:
            logger.error("Exception: " + repr(e))
            self.errorhappened = True
            self.dieWithError()
        except gevent.GreenletExit:
            # hangDetector told us about client disconnection
            pass
        except Exception:
            # Unknown exception
            logger.error(traceback.format_exc())
            self.errorhappened = True
            self.dieWithError()
        finally:
            logger.debug("END REQUEST")
            AceStuff.clientcounter.delete(self.path_unquoted, self.clientip)
            if not self.errorhappened and not AceStuff.clientcounter.get(
                    self.path_unquoted):
                # If no error happened and we are the only client
                logger.debug("Sleeping for " +
                             str(AceConfig.videodestroydelay) + " seconds")
                gevent.sleep(AceConfig.videodestroydelay)
            if not AceStuff.clientcounter.get(self.path_unquoted):
                logger.debug("That was the last client, destroying AceClient")
                if AceConfig.vlcuse:
                    try:
                        AceStuff.vlcclient.stopBroadcast(self.vlcid)
                    except:
                        pass
                self.ace.destroy()
                AceStuff.clientcounter.deleteAce(self.path_unquoted)
Example #5
0
    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))
Example #6
0
    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()
Example #7
0
    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

        # Limit on the number of clients connected to broadcasts
        if 0 < AceConfig.maxconns <= AceStuff.clientcounter.totalClients():
            self.dieWithError(503, "Maximum client connections reached, can't serve request from %s" % self.clientip, logging.ERROR)  # 503 Service Unavailable
            return

        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')
        # 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(400, 'Request seems like valid but no valid video extension was provided', logging.ERROR)
                return
        except IndexError: self.dieWithError(400, 'Bad Request', logging.WARNING); return  # 400 Bad Request
        # 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/mpeg')
            self.send_header('Connection', 'Close')
            self.end_headers()
            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] = requests.compat.unquote(self.splittedpath[2]) #self.path_unquoted
        #End parameters dict
        self.connectionTime = gevent.time.time()
        CID = NAME = None
        try:
            if not AceStuff.clientcounter.idleAce:
               logger.debug('Create connection to AceEngine.....')
               AceStuff.clientcounter.idleAce = aceclient.AceClient(AceStuff.clientcounter, AceConfig.ace, AceConfig.aceconntimeout, AceConfig.aceresulttimeout)
               AceStuff.clientcounter.idleAce.aceInit(AceConfig.acesex, AceConfig.aceage, AceConfig.acekey, AceConfig.videoseekback, AceConfig.videotimeout)
            if self.reqtype not in ('direct_url', 'efile_url'):
               CID, NAME = AceStuff.clientcounter.idleAce.GETINFOHASH(self.reqtype, paramsdict[self.reqtype], paramsdict['file_indexes'])
            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
            # If there is no existing broadcast we create it
            if AceStuff.clientcounter.addClient(CID, self) == 1:
                logger.warning('Create a broadcast "%s"' % self.channelName)
                # Send START commands to AceEngine and Getting URL from engine
                url = self.ace.START(self.reqtype, paramsdict, AceConfig.acestreamtype)
                # Rewriting host:port for remote Ace Stream Engine
                if not AceStuff.ace:
                  url = requests.compat.urlparse(url)._replace(netloc='%s:%s' % (AceConfig.ace['aceHostIP'], AceConfig.ace['aceHTTPport'])).geturl()
                # Start streamreader for broadcast
                gevent.spawn(self.ace.AceStreamReader, url, CID)
                #except: pass
                logger.warning('Broadcast "%s" created' % self.channelName)

        except aceclient.AceException as e: self.dieWithError(500, 'AceClient exception: %s' % repr(e))
        except Exception as e: self.dieWithError(500, 'Unkonwn exception: %s' % repr(e))
        else:
            # streaming to client
            self.transcoder = None
            self.out = self.wfile
            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 }
                    self.transcoder = gevent.subprocess.Popen(AceConfig.transcodecmd[fmt], **popen_params)
                    self.out = self.transcoder.stdin
                    logger.warning('Ffmpeg transcoding started')
                else:
                    logger.error("Can't found fmt key. Ffmpeg transcoding not started!")

            logger.info('Streaming "%s" to %s started' % (self.channelName, self.clientip))
            # Sending videostream headers to client
            headers = { 'Connection': 'Keep-Alive', 'Keep-Alive': 'timeout=15, max=100', 'Accept-Ranges': 'none',
                        'Content-Type': 'application/octet-stream', 'Transfer-Encoding': 'chunked' }
            drop_headers = []

            if self.transcoder: drop_headers.extend(['Transfer-Encoding'])

            response_headers = [ (k,v) for (k,v) in headers.items() if k not in drop_headers ]
            self.send_response(200)
            logger.debug('Sending HTTPAceProxy headers to client: %s' % dict(response_headers))
            for (k,v) in response_headers: self.send_header(k,v)
            self.end_headers()

            while self.connection: gevent.sleep(0.5) # Stream data to client from AceStreamReader

            if self.transcoder is not None:
                try: self.transcoder.kill(); logger.warning('Ffmpeg transcoding stoped')
                except: pass
            logger.info('Streaming "%s" to %s finished' % (self.channelName, self.clientip))

        finally:
            if CID and AceStuff.clientcounter.deleteClient(CID, self) == 0:
                logger.warning('Broadcast "%s" stoped. Last client disconnected' % self.channelName)
        return
Example #8
0
    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