Beispiel #1
0
    def test_iter(self):
        def generator():
            yield b"1" * 8192
            yield b"2" * 4096
            yield b"3" * 2048

        fd = StreamIOIterWrapper(generator())
        self.assertEqual(fd.read(4096), b"1" * 4096)
        self.assertEqual(fd.read(2048), b"1" * 2048)
        self.assertEqual(fd.read(2048), b"1" * 2048)
        self.assertEqual(fd.read(1), b"2")
        self.assertEqual(fd.read(4095), b"2" * 4095)
        self.assertEqual(fd.read(1536), b"3" * 1536)
        self.assertEqual(fd.read(), b"3" * 512)
Beispiel #2
0
class AkamaiHDStreamIO(io.IOBase):
    Version = "2.5.8"
    FlashVersion = "LNX 11,1,102,63"

    StreamURLFormat = "{host}/{streamname}"
    ControlURLFormat = "{host}/control/{streamname}"
    ControlData = b":)"

    TokenGenerators = {"c11e59dea648d56e864fc07a19f717b9": Auth3TokenGenerator}

    StatusComplete = 3
    StatusError = 4

    Errors = {
        1: "Stream not found",
        2: "Track not found",
        3: "Seek out of bounds",
        4: "Authentication failed",
        5: "DVR disabled",
        6: "Invalid bitrate test"
    }

    def __init__(self, session, url, swf=None, seek=None):
        parsed = urlparse(url)

        self.session = session
        self.host = ("{scheme}://{netloc}").format(scheme=parsed.scheme,
                                                   netloc=parsed.netloc)
        self.streamname = parsed.path[1:]
        self.swf = swf
        self.seek = seek

    def open(self):
        self.guid = cache_bust_string(12)
        self.islive = None
        self.sessionid = None
        self.flv = None

        self.buffer = Buffer()
        self.completed_handshake = False

        url = self.StreamURLFormat.format(host=self.host,
                                          streamname=self.streamname)
        params = self._create_params(seek=self.seek)

        log.debug(f"Opening host={self.host} streamname={self.streamname}")

        try:
            res = self.session.http.get(url, stream=True, params=params)
            self.fd = StreamIOIterWrapper(res.iter_content(8192))
        except Exception as err:
            raise StreamError(str(err))

        self.handshake(self.fd)

        return self

    def handshake(self, fd):
        try:
            self.flv = FLV(fd)
        except FLVError as err:
            raise StreamError(str(err))

        self.buffer.write(self.flv.header.serialize())
        log.debug("Attempting to handshake")

        for i, tag in enumerate(self.flv):
            if i == 10:
                raise StreamError(
                    "No OnEdge metadata in FLV after 10 tags, probably not a AkamaiHD stream"
                )

            self.process_tag(tag, exception=StreamError)

            if self.completed_handshake:
                log.debug("Handshake successful")
                break

    def process_tag(self, tag, exception=IOError):
        if isinstance(tag.data, ScriptData) and tag.data.name == "onEdge":
            self._on_edge(tag.data.value, exception=exception)

        self.buffer.write(tag.serialize())

    def send_token(self, token):
        headers = {"x-Akamai-Streaming-SessionToken": token}

        log.debug("Sending new session token")
        self.send_control("sendingNewToken", headers=headers, swf=self.swf)

    def send_control(self, cmd, headers=None, **params):
        if not headers:
            headers = {}

        url = self.ControlURLFormat.format(host=self.host,
                                           streamname=self.streamname)

        headers["x-Akamai-Streaming-SessionID"] = self.sessionid

        params = self._create_params(cmd=cmd, **params)

        return self.session.http.post(url,
                                      headers=headers,
                                      params=params,
                                      data=self.ControlData,
                                      exception=StreamError)

    def read(self, size=-1):
        if not (self.flv and self.fd):
            return b""

        if self.buffer.length:
            return self.buffer.read(size)
        else:
            return self.fd.read(size)

    def _create_params(self, **extra):
        params = dict(v=self.Version,
                      fp=self.FlashVersion,
                      r=cache_bust_string(5),
                      g=self.guid)
        params.update(extra)

        return params

    def _generate_session_token(self, data64):
        swfdata = base64.decodestring(bytes(data64, "ascii"))
        md5 = hashlib.md5()
        md5.update(swfdata)
        hash = md5.hexdigest()

        if hash in self.TokenGenerators:
            generator = self.TokenGenerators[hash](self)

            return generator.generate()
        else:
            raise StreamError(
                ("No token generator available for hash '{0}'").format(hash))

    def _on_edge(self, data, exception=IOError):
        def updateattr(attr, key):
            if key in data:
                setattr(self, attr, data[key])

        log.debug("onEdge data")
        for key, val in data.items():
            if isinstance(val, str):
                val = val[:50]

            log.debug(f" {key}={val}")

        updateattr("islive", "isLive")
        updateattr("sessionid", "session")
        updateattr("status", "status")
        updateattr("streamname", "streamName")

        if self.status == self.StatusComplete:
            self.flv = None
        elif self.status == self.StatusError:
            errornum = data["errorNumber"]

            if errornum in self.Errors:
                msg = self.Errors[errornum]
            else:
                msg = "Unknown error"

            raise exception("onEdge error: " + msg)

        if not self.completed_handshake:
            if "data64" in data:
                sessiontoken = self._generate_session_token(data["data64"])
            else:
                sessiontoken = None

            self.send_token(sessiontoken)
            self.completed_handshake = True