Esempio n. 1
0
    def _get_hls_streams(self):
        url = self.HLSStreamTokenURL.format(self.channelname)

        try:
            res = urlget(url, params=dict(type="any", connection="wifi"),
                         exception=IOError)
        except IOError:
            return {}

        if not isinstance(res.json, list):
            raise PluginError("Stream info response is not JSON")

        if len(res.json) == 0:
            raise PluginError("No stream token in JSON")

        streams = {}

        token = verifyjson(res.json[0], "token")
        hashed = hmac.new(self.HLSStreamTokenKey, bytes(token, "utf8"), sha1)
        fulltoken = hashed.hexdigest() + ":" + token
        url = self.HLSSPlaylistURL.format(self.channelname)

        try:
            params = dict(token=fulltoken, hd="true")
            playlist = HLSStream.parse_variant_playlist(self.session, url,
                                                        params=params)
        except IOError as err:
            raise PluginError(err)

        return playlist
Esempio n. 2
0
    def _get_streams(self):
        channelid = self._get_channel_id(self.url)

        if not channelid:
            raise NoStreamsError(self.url)

        self.logger.debug("Fetching stream info")
        res = urlget(self.AMFURL.format(channelid))

        try:
            packet = AMF0Packet.deserialize(BytesIO(res.content))
        except (IOError, AMFError) as err:
            raise PluginError(
                ("Failed to parse AMF packet: {0}").format(str(err)))

        result = None
        for message in packet.messages:
            if message.target_uri == "/1/onResult":
                result = message.value
                break

        if not result:
            raise PluginError("No result found in AMF packet")

        streams = {}

        if "liveHttpUrl" in result:
            try:
                hlsstreams = HLSStream.parse_variant_playlist(
                    self.session, result["liveHttpUrl"])
                streams.update(hlsstreams)
            except IOError as err:
                self.logger.warning("Failed to get variant playlist: {0}", err)

        if "streamName" in result:
            if "cdnUrl" in result:
                cdn = result["cdnUrl"]
            elif "fmsUrl" in result:
                cdn = result["fmsUrl"]
            else:
                self.logger.warning("Missing cdnUrl and fmsUrl from result")
                return streams

            if "videoCodec" in result and result["videoCodec"]["height"] > 0:
                streamname = "{0}p".format(int(result["videoCodec"]["height"]))
            else:
                streamname = "live"

            streams[streamname] = self._create_stream(cdn,
                                                      result["streamName"])

        if "streamVersions" in result:
            for version, info in result["streamVersions"].items():
                if "streamVersionCdn" in info:
                    for name, cdn in info["streamVersionCdn"].items():
                        if "cdnStreamUrl" in cdn and "cdnStreamName" in cdn:
                            streams["cdn_" + name] = self._create_stream(
                                cdn["cdnStreamUrl"], cdn["cdnStreamName"])

        return streams
Esempio n. 3
0
    def _parse_smil(self, url):
        res = urlget(url)

        try:
            dom = xml.dom.minidom.parseString(res.text)
        except Exception as err:
            raise PluginError(("Unable to parse config XML: {0})").format(err))

        httpbase = None
        streams = {}

        for meta in dom.getElementsByTagName("meta"):
            if meta.getAttribute("name") == "httpBase":
                httpbase = meta.getAttribute("content")
                break

        if not httpbase:
            raise PluginError("Missing HTTP base in SMIL")

        for video in dom.getElementsByTagName("video"):
            url = "{0}/{1}".format(httpbase, video.getAttribute("src"))
            bitrate = int(video.getAttribute("system-bitrate"))
            streams[bitrate] = AkamaiHDStream(self.session, url)

        return streams
Esempio n. 4
0
    def _check_vod_key(self, nodeip, nodeid, userno, userip):
        try:
            conn = socket.create_connection((nodeip, self.KeyCheckPort),
                                            timeout=15)
        except socket.error as err:
            raise PluginError(
                ("Failed to connect to key check server: {0}").format(
                    str(err)))

        msg = "Login,0,{userno},{nodeid},{userip}\n".format(nodeid=nodeid,
                                                            userno=userno,
                                                            userip=userip)

        try:
            conn.sendall(bytes(msg, "ascii"))
            res = conn.recv(4096)
        except IOError as err:
            raise PluginError(
                ("Failed to communicate with key check server: {0}").format(
                    str(err)))

        if len(res) == 0:
            raise PluginError("Empty response from key check server")

        conn.close()

        res = str(res, "ascii").strip().split(",")

        return res[-1]
Esempio n. 5
0
    def _find_gox_url(self, data):
        url = None

        # Parsing through the live page for a link to the gox XML file.
        # Quality is simply passed as a URL parameter e.g. HQ, SQ, SQTest
        try:
            patternhtml = "[^/]+var.+(http://www.gomtv.net/gox[^;]+;)"
            url = re.search(patternhtml, data).group(1)
            url = re.sub('\" \+ playType \+ \"', "{quality}", url)
        except AttributeError:
            raise PluginError(
                "Unable to find the majority of the GOMTV.net XML URL on the Live page"
            )

        # Finding the title of the stream, probably not necessary but
        # done for completeness
        try:
            patterntitle = "this\.title[^;]+;"
            title = re.search(patterntitle, data).group(0)
            title = re.search('\"(.*)\"', title).group(0)
            title = re.sub('"', "", title)
            url = re.sub('"\+ tmpThis.title[^;]+;', title, url)
        except AttributeError:
            raise PluginError(
                "Unable to find the stream title on the Live page")

        return url
Esempio n. 6
0
    def _get_vod_streams(self):
        res = urlget(self.url)
        flashvars = re.search("FlashVars=\"(.+?)\"", res.text)

        if not flashvars:
            raise PluginError("Unable to find flashvars on page")

        flashvars = parse_qs(flashvars.group(1))
        for var in ("vjoinid", "conid", "leagueid"):
            if not var in flashvars:
                raise PluginError(
                    ("Missing key '{0}' in flashvars").format(var))

        streams = {}
        qualities = ["SQ", "HQ"]

        for quality in qualities:
            params = dict(leagueid=flashvars["leagueid"][0],
                          vjoinid=flashvars["vjoinid"][0],
                          conid=flashvars["conid"][0],
                          title="",
                          ref="",
                          tmpstamp=int(time.time()),
                          strLevel=quality)
            res = urlget(self.GOXVODURL, params=params, session=self.rsession)

            if res.text != "1002" and len(res.text) > 0:
                gox = self._parse_gox_file(res.text)
                entry = gox[0]

                nokey = False
                for var in ("NODEIP", "NODEID", "UNO", "USERIP"):
                    if not var in entry:
                        nokey = True

                if nokey:
                    self.logger.warning(
                        "Unable to fetch key, make sure that you have access to this VOD"
                    )
                    continue

                key = self._check_vod_key(entry["NODEIP"], entry["NODEID"],
                                          entry["UNO"], entry["USERIP"])

                streams[quality.lower()] = HTTPStream(
                    self.session,
                    gox[0]["REF"],
                    params=dict(key=key),
                    headers=self.StreamHeaders)

        return streams
Esempio n. 7
0
    def _get_rtmp_streams(self):
        def clean_tag(tag):
            if tag[0] == "_":
                return tag[1:]
            else:
                return tag

        chansub = self._authenticate()

        url = self.StreamInfoURL.format(self.channelname)
        params = dict(b_id="true", group="", private_code="null",
                      p=int(random.random() * 999999),
                      channel_subscription=chansub, type="any")

        self.logger.debug("Fetching stream info")
        res = urlget(url, params=params)
        data = res.text

        # fix invalid xml
        data = re.sub("<(\d+)", "<_\g<1>", data)
        data = re.sub("</(\d+)", "</_\g<1>", data)

        streams = {}

        try:
            dom = xml.dom.minidom.parseString(data)
        except Exception as err:
            raise PluginError(("Unable to parse config XML: {0})").format(err))

        nodes = dom.getElementsByTagName("nodes")[0]

        if len(nodes.childNodes) == 0:
            return streams

        swfurl = urlresolve(self.SWFURL)

        for node in nodes.childNodes:
            info = {}
            for child in node.childNodes:
                info[child.tagName] = self._get_node_text(child)

            if not ("connect" in info and "play" in info):
                continue

            stream = RTMPStream(self.session, {
                "rtmp": ("{0}/{1}").format(info["connect"], info["play"]),
                "swfVfy": swfurl,
                "live": True
            })

            sname = clean_tag(node.tagName)

            if "token" in info:
                stream.params["jtv"] = info["token"]
            else:
                self.logger.warning("No token found for stream {0}, this stream may fail to play", sname)

            streams[sname] = stream

        return streams
Esempio n. 8
0
    def _get_streams(self):
        self.logger.debug("Fetching stream info")
        res = urlget(self.url, params=dict(output="json"))

        if res.json is None:
            raise PluginError("No JSON data in stream info")

        streams = {}
        video = verifyjson(res.json, "video")
        videos = verifyjson(video, "videoReferences")

        for video in videos:
            if not ("url" in video and "playerType" in video):
                continue

            if video["playerType"] == "flash":
                if video["url"].startswith("rtmp"):
                    stream = RTMPStream(
                        self.session, {
                            "rtmp": video["url"],
                            "pageUrl": self.PageURL,
                            "swfVfy": self.SWFURL,
                            "live": True
                        })
                    streams[str(video["bitrate"]) + "k"] = stream
            elif video["playerType"] == "ios":
                try:
                    hlsstreams = HLSStream.parse_variant_playlist(
                        self.session, video["url"])
                    streams.update(hlsstreams)
                except IOError as err:
                    self.logger.warning("Failed to get variant playlist: {0}",
                                        err)

        return streams
Esempio n. 9
0
    def _parse_gox_file(self, data):
        try:
            dom = xml.dom.minidom.parseString(data)
        except Exception as err:
            raise PluginError(("Unable to parse gox file: {0})").format(err))

        entries = []

        for xentry in dom.getElementsByTagName("ENTRY"):
            entry = {}
            for child in xentry.childNodes:
                if isinstance(child, xml.dom.minidom.Element):
                    if child.tagName == "REF":
                        href = child.getAttribute("href")

                        # SQ and SQTest streams can be gomp2p links, with actual stream address passed as a parameter.
                        if href.startswith("gomp2p://"):
                            href, n = re.subn("^.*LiveAddr=", "", href)
                            href = unquote(href)

                        entry[child.tagName] = href
                    else:
                        entry[child.tagName] = self._get_node_text(child)

            entries.append(entry)

        return entries
Esempio n. 10
0
    def _get_event_url(self, prefix, data):
        match = re.search(' \"(.*)\";', data)

        if not match:
            raise PluginError("Event live page URL not found")

        return urljoin(prefix, match.group(1))
Esempio n. 11
0
    def _find_stream_urls(self, data):
        url = None

        # Parsing through the live page for a link to the gox XML file.
        # Quality is simply passed as a URL parameter e.g. HQ, SQ, SQTest
        try:
            patternhtml = "[^/]+var.+(http://www.gomtv.net/gox[^;]+;)"
            url = re.search(patternhtml, data).group(1)
            url = re.sub('\" \+ playType \+ \"', "{quality}", url)
        except AttributeError:
            raise PluginError(
                "Unable to find the majority of the GOMTV.net XML URL on the Live page"
            )

        # Finding the title of the stream, probably not necessary but
        # done for completeness
        try:
            patterntitle = "this\.title[^;]+;"
            title = re.search(patterntitle, data).group(0)
            title = re.search('\"(.*)\"', title).group(0)
            title = re.sub('"', "", title)
            url = re.sub('"\+ tmpThis.title[^;]+;', title, url)
        except AttributeError:
            raise PluginError(
                "Unable to find the stream title on the Live page")

        # Check for multiple streams going at the same time, and extract the conid and the title
        # Those streams have the class "live_now"
        patternlive = '<a\shref=\"/live/index.gom\?conid=(?P<conid>\d+)\"\sclass=\"live_now\"\stitle=\"(?P<title>[^\"]+)'
        streams = re.findall(patternlive, data)

        if len(streams) > 1:
            urls = []
            for stream in streams:
                # Modify the urlFromHTML according to the user
                singleurl = re.sub("conid=\d+", "conid=" + stream[0], url)
                singletitlehtml = "+".join(stream[0].split(" "))
                singleurl = re.sub("title=[\w|.|+]*",
                                   "title=" + singletitlehtml, singleurl)
                urls.append(singleurl)

            return urls
        else:
            if url is None:
                return []
            else:
                return [url]
Esempio n. 12
0
    def _authenticate(self, username=None, password=None, cookies=None):
        if (username is None or password is None) and cookies is None:
            raise PluginError(
                "GOMTV.net requires a username and password or a cookie")

        if cookies is not None:
            for cookie in cookies.split(";"):
                try:
                    name, value = cookie.split("=")
                except ValueError:
                    continue

                self.rsession.cookies[name.strip()] = value.strip()

            self.logger.info("Attempting to authenticate with cookies")
        else:
            form = dict(cmd="login",
                        rememberme="1",
                        mb_username=username,
                        mb_password=password)

            self.logger.info(
                "Attempting to authenticate with username and password")

            urlopen(self.LoginURL,
                    data=form,
                    headers=self.LoginHeaders,
                    session=self.rsession)

        res = urlget(self.LoginCheckURL, session=self.rsession)

        if "Please need login" in res.text:
            raise PluginError("Authentication failed")

        if "SES_USERNICK" in self.rsession.cookies:
            username = self.rsession.cookies["SES_USERNICK"]
            self.logger.info(
                ("Successfully logged in as {0}").format(username))

        if username and password:
            cookie = ""
            for v in ("SES_USERNO", "SES_STATE", "SES_MEMBERNICK",
                      "SES_USERNICK"):
                if v in self.rsession.cookies:
                    cookie += "{0}={1}; ".format(v, self.rsession.cookies[v])
            self.logger.info("Cookie for reusing this session: {0}", cookie)
Esempio n. 13
0
    def _check_channel_live(self, channelname):
        url = self.MetadataURL.format(channelname)
        res = urlget(url, params=dict(fields="mode"))

        if len(res.json) == 0:
            raise PluginError("Error retrieving stream live status")

        mode = verifyjson(res.json, "mode")

        return mode == "live"
Esempio n. 14
0
    def _get_streams(self):
        (channelid, swfurl) = self._get_channel_info(self.url)

        if not (channelid and swfurl):
            raise NoStreamsError(self.url)

        self.logger.debug("Fetching stream info")
        res = urlget(self.ConfigURL.format(channelid))

        try:
            dom = xml.dom.minidom.parseString(res.text)
        except Exception as err:
            raise PluginError(("Unable to parse config XML: {0})").format(err))

        streams = {}
        channels = dom.getElementsByTagName("channels")[0]
        clip = channels.getElementsByTagName("clip")[0]

        self.logger.debug("Verifying SWF: {0}", swfurl)
        swfhash, swfsize = swfverify(swfurl)

        for item in clip.getElementsByTagName("item"):
            base = item.getAttribute("base")
            if not base:
                continue

            if base[0] == "$":
                ref = re.match("\${(.+)}", base).group(1)
                base = self.CDN[ref]

            for streamel in item.getElementsByTagName("stream"):
                altcount = 1
                name = streamel.getAttribute("label").lower().replace(" ", "_")
                playpath = streamel.getAttribute("name")

                stream = RTMPStream(self.session, {
                    "rtmp": ("{0}/{1}").format(base, playpath),
                    "live": True,
                    "swfhash": swfhash,
                    "swfsize": swfsize,
                    "pageUrl": self.url
                })

                if not name in streams:
                    streams[name] = stream
                else:
                    if altcount == 1:
                        streams[name + "_alt"] = stream
                    else:
                        streams[name + "_alt" + str(altcount)] = stream

                    altcount += 1

        return streams
Esempio n. 15
0
    def _get_stream_info(self):
        res = urlget(self.url)

        match = re.search("var initialData = ({.+})", res.text)

        if match:
            config = match.group(1)

            try:
                parsed = json.loads(config)
            except ValueError as err:
                raise PluginError(
                    ("Unable to parse config JSON: {0})").format(err))

            return parsed
Esempio n. 16
0
    def _parse_gox_file(self, data):
        # Grabbing the gomcmd URL
        try:
            patternstream = '<REF href="([^"]*)"\s*/>'
            match = re.search(patternstream, data).group(1)
        except AttributeError:
            raise PluginError(
                "Unable to find the gomcmd URL in the GOX XML file")

        match = match.replace("&amp;", "&")
        match = unquote(match)

        # SQ and SQTest streams can be gomp2p links, with actual stream address passed as a parameter.
        if match.startswith("gomp2p://"):
            match, n = re.subn("^.*LiveAddr=", "", match)

        # Cosmetics, getting rid of the HTML entity, we don't
        # need either of the " character or &quot;
        match = match.replace("&quot;", "")

        return match
Esempio n. 17
0
    def _get_stream_info(self, url):
        res = urlget(url)
        data = res.text
        config = None

        match = re.search("'PLAYER_CONFIG': (.+)\n.+}\);", data)
        if match:
            config = match.group(1)

        match = re.search("yt.playerConfig = (.+)\;\n", data)
        if match:
            config = match.group(1)

        if config:
            try:
                parsed = json.loads(config)
            except ValueError as err:
                raise PluginError(
                    ("Unable to parse config JSON: {0})").format(err))

            return parsed
Esempio n. 18
0
    def _get_live_streams(self):
        streams = {}
        qualities = ["HQ", "SQ", "HQTest", "SQTest"]

        res = self._get_live_page(self.url)
        goxurl = self._find_gox_url(res.text)

        if not goxurl:
            raise PluginError("Unable to find GOX URL")

        for quality in qualities:
            # Grab the response of the URL listed on the Live page for a stream
            url = goxurl.format(quality=quality)
            res = urlget(url, session=self.rsession)

            # The response for the GOX XML if an incorrect stream quality is chosen is 1002.
            if res.text != "1002" and len(res.text) > 0:
                gox = self._parse_gox_file(res.text)
                streams[quality.lower()] = HTTPStream(
                    self.session, gox[0]["REF"], headers=self.StreamHeaders)

        return streams
Esempio n. 19
0
    def _get_streams(self):
        channelname = self.url.rstrip("/").rpartition("/")[2].lower()
        streams = {}

        try:
            options = dict(l="info", a="ajax_video_info", file=channelname,
                           rid=time())

            res = urlget(self.HLSStreamURL, params=options, exception=IOError)
            match = re.search("'(http://.+\.m3u8)'", res.text)

            if not match:
                raise PluginError("Failed to find HLS playlist in result")

            playlisturl = match.group(1)

            self.logger.debug("Playlist URL is {0}", playlisturl)

            streams["live"] = HLSStream(self.session, playlisturl)
        except IOError:
            raise NoStreamsError(self.url)

        return streams
Esempio n. 20
0
    def _get_metadata(self):
        url = self.MetadataURL.format(self.channelname)

        headers = {}
        cookie = self.options.get("cookie")

        if cookie:
            headers["Cookie"] = cookie

        res = urlget(url, headers=headers)

        try:
            dom = xml.dom.minidom.parseString(res.text)
        except Exception as err:
            raise PluginError(("Unable to parse config XML: {0})").format(err))

        meta = dom.getElementsByTagName("meta")[0]
        metadata = {}

        metadata["title"] = self._get_node_if_exists(dom, "title")
        metadata["access_guid"] = self._get_node_if_exists(dom, "access_guid")
        metadata["login"] = self._get_node_if_exists(dom, "login")

        return metadata
Esempio n. 21
0
    def _get_rtmp_streams(self, channelname):
        self.logger.debug("Fetching stream info")

        url = self.StreamInfoURL.format(channelname)

        self.logger.debug("JSON data url: {0}", url)

        res = urlget(url)

        if not isinstance(res.json, dict):
            raise PluginError("Stream info response is not JSON")

        if len(res.json) == 0:
            raise PluginError("JSON is empty")

        chan_info_json = res.json

        # This is ugly, not sure how to fix it.
        back_json_node = chan_info_json["sequence"][0]["layerList"][0]
        if back_json_node["name"] != "background":
            raise PluginError("JSON data has unexpected structure")

        rep_node = self._get_node_by_name(back_json_node["sequenceList"],
                                          "reporting")["layerList"]
        main_node = self._get_node_by_name(back_json_node["sequenceList"],
                                           "main")["layerList"]

        if not (rep_node and main_node):
            raise PluginError("Error parsing stream RTMP url")

        swfurl = self._get_node_by_name(
            rep_node, "reporting")["param"]["extraParams"]["videoSwfURL"]
        feeds_params = self._get_node_by_name(main_node, "video")["param"]

        if not (swfurl and feeds_params):
            raise PluginError("Error parsing stream RTMP url")

        streams = {}

        # Different feed qualities are available are a dict under "live"
        # In some cases where there's only 1 quality available,
        # it seems the "live" is absent. We use the single stream available
        # under the "customURL" key.

        if "live" in feeds_params and len(feeds_params["live"]) > 0:
            quals = feeds_params["live"]

            for key, quality in quals.items():
                info = {}

                try:
                    res = urlget(quality, exception=IOError)
                except IOError:
                    continue

                rtmpurl = res.text
                stream = RTMPStream(self.session, {
                    "rtmp": rtmpurl,
                    "swfVfy": swfurl,
                    "live": True
                })
                self.logger.debug("Adding URL: {0}", rtmpurl)

                if key in self.QualityMap:
                    sname = self.QualityMap[key]
                else:
                    sname = key

                streams[sname] = stream
        else:
            res = urlget(feeds_params["customURL"])

            rtmpurl = res.text
            stream = RTMPStream(self.session, {
                "rtmp": rtmpurl,
                "swfVfy": swfurl,
                "live": True
            })

            self.logger.debug("Adding URL: {0}", feeds_params["customURL"])
            streams["live"] = stream

        return streams