示例#1
0
def setup_plugin_args(streamlink):
    """Sets Streamlink plugin options."""

    plugin_args = PARSER.add_argument_group("Plugin options")
    for pname, plugin in streamlink.plugins.items():
        defaults = {}
        for parg in plugin.arguments:
            if not parg.is_global:
                plugin_args.add_argument(parg.argument_name(pname),
                                         **parg.options)
                defaults[parg.dest] = parg.default
            else:
                pargdest = parg.dest
                for action in PARSER._actions:
                    # find matching global argument
                    if pargdest != action.dest:
                        continue
                    defaults[pargdest] = action.default

                    # add plugin to global argument
                    plugins = getattr(action, "plugins", [])
                    plugins.append(pname)
                    setattr(action, "plugins", plugins)

        plugin.options = PluginOptions(defaults)

    return True
示例#2
0
def setup_plugin_args(session, parser):
    """Sets Streamlink plugin options."""

    plugin_args = parser.add_argument_group("Plugin options")
    for pname, plugin in session.plugins.items():
        defaults = {}
        group = plugin_args.add_argument_group(pname.capitalize())

        for parg in plugin.arguments:
            if not parg.is_global:
                group.add_argument(parg.argument_name(pname), **parg.options)
                defaults[parg.dest] = parg.default
            else:
                pargdest = parg.dest
                for action in parser._actions:
                    # find matching global argument
                    if pargdest != action.dest:
                        continue
                    defaults[pargdest] = action.default

                    # add plugin to global argument
                    plugins = getattr(action, "plugins", [])
                    plugins.append(pname)
                    setattr(action, "plugins", plugins)

        plugin.options = PluginOptions(defaults)
示例#3
0
class BTV(Plugin):
    options = PluginOptions({"username": None, "password": None})
    url_re = re.compile(r"https?://(?:www\.)?btv\.bg/live/?")

    api_url = "http://www.btv.bg/lbin/global/player_config.php"
    check_login_url = "http://www.btv.bg/lbin/userRegistration/check_user_login.php"
    login_url = "http://www.btv.bg/bin/registration2/login.php?action=login&settings=0"

    media_id_re = re.compile(r"media_id=(\d+)")
    src_re = re.compile(r"src: \"(http.*?)\"")
    api_schema = validate.Schema(
        validate.all({
            "status": "ok",
            "config": validate.text
        }, validate.get("config"),
                     validate.all(
                         validate.transform(src_re.search),
                         validate.any(None, validate.get(1), validate.url()))))

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def login(self, username, password):
        res = http.post(self.login_url,
                        data={
                            "username": username,
                            "password": password
                        })
        return res.text.startswith("success")

    def get_hls_url(self, media_id):
        res = http.get(self.api_url, params=dict(media_id=media_id))
        try:
            return parse_json(res.text, schema=self.api_schema)
        except PluginError:
            return

    def _get_streams(self):
        if not self.options.get("username") or not self.options.get(
                "username"):
            self.logger.error(
                "BTV requires registration, set the username and password"
                " with --btv-username and --btv-password")
        elif self.login(self.options.get("username"),
                        self.options.get("password")):
            res = http.get(self.url)
            media_match = self.media_id_re.search(res.text)
            media_id = media_match and media_match.group(1)
            if media_id:
                self.logger.debug("Found media id: {0}", media_id)
                stream_url = self.get_hls_url(media_id)
                if stream_url:
                    return HLSStream.parse_variant_playlist(
                        self.session, stream_url)
        else:
            self.logger.error(
                "Login failed, a valid username and password is required")
示例#4
0
def setup_plugin_args(session, parser):
    """Set Streamlink plugin options."""
    plugin_args = parser.add_argument_group("Plugin options")
    for pname, plugin in session.plugins.items():
        defaults = {}
        for parg in plugin.arguments: 
            plugin_args.add_argument(parg.argument_name(pname), **parg.options)
            defaults[parg.dest] = parg.default 
        
        plugin.options = PluginOptions(defaults)
示例#5
0
class PCYourFreeTV(Plugin):
    _login_url = 'http://pc-yourfreetv.com/home.php'
    _url_re = re.compile(
        r'http://pc-yourfreetv\.com/index_player\.php\?channel=.+?&page_id=\d+'
    )
    _video_url_re = re.compile(
        r"jwplayer\('.+?'\)\.setup\({.+?file: \"(?P<video_url>[^\"]+?)\".+?}\);",
        re.DOTALL)

    options = PluginOptions({'username': None, 'password': None})

    @classmethod
    def can_handle_url(cls, url):
        return PCYourFreeTV._url_re.match(url)

    def login(self, username, password):
        res = http.post(self._login_url,
                        data={
                            'user_name': username,
                            'user_pass': password,
                            'login': '******'
                        })

        return username in res.text

    def _get_streams(self):
        username = self.get_option('username')
        password = self.get_option('password')

        if username is None or password is None:
            self.logger.error(
                "PC-YourFreeTV requires authentication, use --pcyourfreetv-username"
                "and --pcyourfreetv-password to set your username/password combination"
            )
            return

        if self.login(username, password):
            self.logger.info("Successfully logged in as {0}", username)

        # Retrieve URL page and search for stream data
        res = http.get(self.url)
        match = self._video_url_re.search(res.text)
        if match is None:
            return
        video_url = match.group('video_url')
        if '.m3u8' in video_url:
            streams = HLSStream.parse_variant_playlist(self.session, video_url)
            if len(streams) != 0:
                for stream in streams.items():
                    yield stream
            else:
                # Not a HLS playlist
                yield 'live', HLSStream(self.session, video_url)
示例#6
0
def setup_plugin_args(streamlink):
    """Sets Streamlink plugin options."""

    plugin_args = PARSER.add_argument_group("Plugin options")
    for pname, plugin in streamlink.plugins.items():
        defaults = {}
        for parg in plugin.arguments:
            plugin_args.add_argument(parg.argument_name(pname), **parg.options)
            defaults[parg.dest] = parg.default

        plugin.options = PluginOptions(defaults)

    return True
示例#7
0
class Livestation(Plugin):
    options = PluginOptions({"email": "", "password": ""})

    @classmethod
    def can_handle_url(self, url):
        return _url_re.match(url)

    def _authenticate(self, email, password):
        csrf_token = http.get(LOGIN_PAGE_URL, schema=_csrf_token_schema)
        if not csrf_token:
            raise PluginError("Unable to find CSRF token")

        data = {
            "authenticity_token": csrf_token,
            "channel_id": "",
            "commit": "Login",
            "plan_id": "",
            "session[email]": email,
            "session[password]": password,
            "utf8": "\xE2\x9C\x93",  # Check Mark Character
        }

        res = http.post(LOGIN_POST_URL,
                        data=data,
                        acceptable_status=(200, 422))
        result = http.json(res, schema=_login_schema)

        errors = result.get("errors")
        if errors:
            errors = ", ".join(errors)
            raise PluginError("Unable to authenticate: {0}".format(errors))

        self.logger.info("Successfully logged in as {0}", result["email"])

    def _get_streams(self):
        login_email = self.options.get("email")
        login_password = self.options.get("password")
        if login_email and login_password:
            self._authenticate(login_email, login_password)

        hls_playlist = http.get(self.url, schema=_hls_playlist_schema)
        if not hls_playlist:
            return

        return HLSStream.parse_variant_playlist(self.session, hls_playlist)
示例#8
0
文件: afreeca.py 项目: Genzo45/NAP
class AfreecaTV(Plugin):

    login_url = "https://member.afreecatv.com:8111/login/LoginAction.php"

    options = PluginOptions({"username": None, "password": None})

    @classmethod
    def can_handle_url(self, url):
        return _url_re.match(url)

    @classmethod
    def stream_weight(cls, key):
        weight = QUALITY_WEIGHTS.get(key)
        if weight:
            return weight, "afreeca"

        return Plugin.stream_weight(key)

    def _get_channel_info(self, username):
        data = {"bid": username, "mode": "landing", "player_type": "html5"}

        res = http.post(CHANNEL_API_URL, data=data)
        return http.json(res, schema=_channel_schema)

    def _get_hls_key(self, broadcast, username, quality):
        headers = {"Referer": self.url}

        data = {
            "bid": username,
            "bno": broadcast,
            "pwd": "",
            "quality": quality,
            "type": "pwd"
        }
        res = http.post(CHANNEL_API_URL, data=data, headers=headers)
        return http.json(res, schema=_channel_schema)

    def _get_stream_info(self, broadcast, quality, cdn, rmd):
        params = {
            "return_type": cdn,
            "broad_key": "{broadcast}-flash-{quality}-hls".format(**locals())
        }
        res = http.get(STREAM_INFO_URLS.format(rmd=rmd), params=params)
        return http.json(res, schema=_stream_schema)

    def _get_hls_stream(self, broadcast, username, quality, cdn, rmd):
        keyjson = self._get_hls_key(broadcast, username, quality)

        if keyjson["RESULT"] != CHANNEL_RESULT_OK:
            return
        key = keyjson["AID"]

        info = self._get_stream_info(broadcast, quality, cdn, rmd)

        if "view_url" in info:
            return HLSStream(self.session,
                             info["view_url"],
                             params=dict(aid=key))

    def _login(self, username, password):
        data = {
            "szWork": "login",
            "szType": "json",
            "szUid": username,
            "szPassword": password,
            "isSaveId": "true",
            "isSavePw": "false",
            "isSaveJoin": "false"
        }

        res = http.post(self.login_url, data=data)
        res = http.json(res)
        if res["RESULT"] == 1:
            return True
        else:
            return False

    def _get_streams(self):
        if not self.session.get_option("hls-segment-ignore-names"):
            ignore_segment = ["_0", "_1", "_2"]
            self.session.set_option("hls-segment-ignore-names", ignore_segment)

        login_username = self.get_option("username")
        login_password = self.get_option("password")
        if login_username and login_password:
            self.logger.debug("Attempting login as {0}".format(login_username))
            if self._login(login_username, login_password):
                self.logger.info(
                    "Successfully logged in as {0}".format(login_username))
            else:
                self.logger.info(
                    "Failed to login as {0}".format(login_username))

        match = _url_re.match(self.url)
        username = match.group("username")

        channel = self._get_channel_info(username)
        if channel.get("BPWD") == "Y":
            self.logger.error("Stream is Password-Protected")
            return
        elif channel.get("RESULT") == -6:
            self.logger.error("Login required")
            return
        elif channel.get("RESULT") != CHANNEL_RESULT_OK:
            return

        (broadcast, rmd, cdn) = (channel["BNO"], channel["RMD"],
                                 channel["CDN"])
        if not (broadcast and rmd and cdn):
            return

        for qkey in QUALITYS:
            hls_stream = self._get_hls_stream(broadcast, username, qkey, cdn,
                                              rmd)
            if hls_stream:
                yield qkey, hls_stream
示例#9
0
class Daisuki(Plugin):
    options = PluginOptions({
        "mux_subtitles": False
    })

    @classmethod
    def can_handle_url(cls, url):
        return _url_re.match(url)

    def _get_streams(self):
        page = http.get(self.url, schema=_schema)
        if not page:
            return

        pubkey_pem = get_public_key(self.cache, urljoin(self.url, page["clientlibs"]))
        if not pubkey_pem:
            raise PluginError("Unable to get public key")

        flashvars = page["flashvars"]

        params = {
            "cashPath": int(time.time() * 1000)
        }
        res = http.get(urljoin(self.url, flashvars["country"]), params=params)
        if not res:
            return
        language = http.xml(res, schema=_language_schema)

        api_params = {}
        for key in ("ss_id", "mv_id", "device_cd", "ss1_prm", "ss2_prm", "ss3_prm"):
            if flashvars.get(key, ""):
                api_params[key] = flashvars[key]

        aeskey = number.long_to_bytes(random.getrandbits(8 * 32), 32)

        params = {
            "s": flashvars["s"],
            "c": language,
            "e": self.url,
            "d": aes_encrypt(aeskey, json.dumps(api_params)),
            "a": rsa_encrypt(pubkey_pem, aeskey)
        }
        res = http.get(urljoin(self.url, flashvars["init"]), params=params)
        if not res:
            return
        rtn = http.json(res, schema=_init_schema)
        if not rtn:
            return

        init_data = parse_json(aes_decrypt(aeskey, rtn))

        parsed = urlparse(init_data["play_url"])
        if parsed.scheme != "https" or not parsed.path.startswith("/i/") or not parsed.path.endswith("/master.m3u8"):
            return
        hlsstream_url = init_data["play_url"]

        streams = HLSStream.parse_variant_playlist(self.session, hlsstream_url)

        if "caption_url" in init_data:
            if self.get_option("mux_subtitles") and FFMPEGMuxer.is_usable(self.session):
                res = http.get(init_data["caption_url"])
                srt = http.xml(res, ignore_ns=True, schema=_xml_to_srt_schema)
                subfiles = []
                metadata = {}
                for i, lang, srt in ((i, s[0], s[1]) for i, s in enumerate(srt)):
                    subfile = tempfile.TemporaryFile()
                    subfile.write(srt.encode("utf8"))
                    subfile.seek(0)
                    subfiles.append(FileStream(self.session, fileobj=subfile))
                    metadata["s:s:{0}".format(i)] = ["language={0}".format(lang)]

                for n, s in streams.items():
                    yield n, MuxedStream(self.session, s, *subfiles,
                                         maps=list(range(0, len(metadata) + 1)),
                                         metadata=metadata)
                return
            else:
                self.logger.info("Subtitles: {0}".format(init_data["caption_url"]))

        for s in streams.items():
            yield s
示例#10
0
class Crunchyroll(Plugin):
    options = PluginOptions({
        "username": None,
        "password": None,
        "purge_credentials": None,
        "locale": API_DEFAULT_LOCALE
    })

    @classmethod
    def can_handle_url(self, url):
        return _url_re.match(url)

    @classmethod
    def stream_weight(cls, key):
        weight = STREAM_WEIGHTS.get(key)
        if weight:
            return weight, "crunchyroll"

        return Plugin.stream_weight(key)

    def _get_streams(self):
        api = self._create_api()
        match = _url_re.match(self.url)
        media_id = int(match.group("media_id"))

        try:
            info = api.get_info(media_id,
                                fields=["media.stream_data"],
                                schema=_media_schema)
        except CrunchyrollAPIError as err:
            raise PluginError(u"Media lookup error: {0}".format(err.msg))

        if not info:
            return

        streams = {}

        # The adaptive quality stream sometimes a subset of all the other streams listed, ultra is no included
        has_adaptive = any(
            [s[u"quality"] == u"adaptive" for s in info[u"streams"]])
        if has_adaptive:
            self.logger.debug(u"Loading streams from adaptive playlist")
            for stream in filter(lambda x: x[u"quality"] == u"adaptive",
                                 info[u"streams"]):
                for q, s in HLSStream.parse_variant_playlist(
                        self.session, stream[u"url"]).items():
                    # rename the bitrates to low, mid, or high. ultra doesn't seem to appear in the adaptive streams
                    name = STREAM_NAMES.get(q, q)
                    streams[name] = s

        # If there is no adaptive quality stream then parse each individual result
        for stream in info[u"streams"]:
            if stream[u"quality"] != u"adaptive":
                # the video_encode_id indicates that the stream is not a variant playlist
                if u"video_encode_id" in stream:
                    streams[stream[u"quality"]] = HLSStream(
                        self.session, stream[u"url"])
                else:
                    # otherwise the stream url is actually a list of stream qualities
                    for q, s in HLSStream.parse_variant_playlist(
                            self.session, stream[u"url"]).items():
                        # rename the bitrates to low, mid, or high. ultra doesn't seem to appear in the adaptive streams
                        name = STREAM_NAMES.get(q, q)
                        streams[name] = s

        return streams

    def _get_device_id(self):
        """Returns the saved device id or creates a new one and saves it."""
        device_id = self.cache.get("device_id")

        if not device_id:
            # Create a random device id and cache it for a year
            char_set = string.ascii_letters + string.digits
            device_id = "".join(random.sample(char_set, 32))
            self.cache.set("device_id", device_id, 365 * 24 * 60 * 60)

        return device_id

    def _create_api(self):
        """Creates a new CrunchyrollAPI object, initiates it's session and
        tries to authenticate it either by using saved credentials or the
        user's username and password.
        """
        if self.options.get("purge_credentials"):
            self.cache.set("session_id", None, 0)
            self.cache.set("auth", None, 0)

        current_time = datetime.datetime.utcnow()
        device_id = self._get_device_id()
        locale = self.options.get("locale")
        api = CrunchyrollAPI(self.cache.get("session_id"),
                             self.cache.get("auth"), locale)

        self.logger.debug("Creating session")
        try:
            api.session_id = api.start_session(device_id,
                                               schema=_session_schema)
        except CrunchyrollAPIError as err:
            if err.code == "bad_session":
                self.logger.debug(
                    "Current session has expired, creating a new one")
                api = CrunchyrollAPI(locale=locale)
                api.session_id = api.start_session(device_id,
                                                   schema=_session_schema)
            else:
                raise err

        # Save session and hope it lasts for a few hours
        self.cache.set("session_id", api.session_id, 4 * 60 * 60)
        self.logger.debug("Session created")

        if api.auth:
            self.logger.debug("Using saved credentials")
        elif self.options.get("username"):
            try:
                self.logger.info(
                    "Attempting to login using username and password")
                login = api.login(self.options.get("username"),
                                  self.options.get("password"),
                                  schema=_login_schema)
                api.auth = login["auth"]

                self.logger.info(
                    "Successfully logged in as '{0}'",
                    login["user"]["username"] or login["user"]["email"])

                expires = (login["expires"] - current_time).total_seconds()
                self.cache.set("auth", login["auth"], expires)
            except CrunchyrollAPIError as err:
                raise PluginError(u"Authentication error: {0}".format(err.msg))
        else:
            self.logger.warning(
                "No authentication provided, you won't be able to access "
                "premium restricted content")

        return api
示例#11
0
class BTSports(Plugin):
    url_re = re.compile(r"https?://sport.bt.com")
    options = PluginOptions({"username": None, "password": None})

    content_re = re.compile(r"CONTENT_(\w+)\s*=\s*'(\w+)'")
    saml_re = re.compile(r'''name="SAMLResponse" value="(.*?)"''',
                         re.M | re.DOTALL)
    api_url = "https://be.avs.bt.com/AVS/besc"
    saml_url = "https://samlfed.bt.com/sportgetfedwebhls"
    login_url = "https://signin1.bt.com/siteminderagent/forms/login.fcc"

    def __init__(self, url):
        super().__init__(url)
        http.headers = {"User-Agent": useragents.FIREFOX}

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def login(self, username, password):
        self.logger.debug("Logging in as {0}".format(username))

        redirect_to = "https://home.bt.com/ss/Satellite/secure/loginforward?redirectURL={0}".format(
            quote(self.url))
        data = {
            "cookieExpp": "30",
            "Switch": "yes",
            "SMPostLoginUrl": "/appsyouraccount/secure/postlogin",
            "loginforward":
            "https://home.bt.com/ss/Satellite/secure/loginforward",
            "smauthreason": "0",
            "TARGET": redirect_to,
            "USER": username,
            "PASSWORD": password
        }

        res = http.post(self.login_url, data=data)

        self.logger.debug("Redirected to: {0}".format(res.url))

        if url_equal(res.url, self.url, ignore_scheme=True):
            self.logger.debug("Login successful, getting SAML token")
            res = http.get(
                "https://samlfed.bt.com/sportgetfedwebhls?bt.cid={0}".format(
                    self.acid()))
            d = self.saml_re.search(res.text)
            if d:
                saml_data = d.group(1)
                self.logger.debug("BT Sports federated login...")
                res = http.post(self.api_url,
                                params={
                                    "action": "LoginBT",
                                    "channel": "WEBHLS",
                                    "bt.cid": self.acid
                                },
                                data={"SAMLResponse": saml_data})
                fed_json = http.json(res)
                success = fed_json['resultCode'] == "OK"
                if not success:
                    self.logger.error("Failed to login: {0} - {1}".format(
                        fed_json['errorDescription'], fed_json['message']))
                return success
        return False

    def device_id(self):
        device_id = self.cache.get("device_id") or str(uuid4())
        self.cache.set("device_id", device_id)
        return device_id

    def acid(self):
        acid = self.cache.get("acid") or "{cid}-B-{timestamp}".format(
            cid=self.device_id(), timestamp=int(time.time()))
        self.cache.set("acid", acid)
        return acid

    def _get_cdn(self, channel_id, channel_type="LIVE"):
        d = {
            "action": "GetCDN",
            "type": channel_type,
            "id": channel_id,
            "channel": "WEBHLS",
            "asJson": "Y",
            "bt.cid": self.acid(),
            "_": int(time.time())
        }

        res = http.get(self.api_url,
                       params=d,
                       headers={"Accept": "application/json"})
        return http.json(res)

    def _get_streams(self):
        if self.options.get("email") and self.options.get("password"):
            if self.login(self.options.get("email"),
                          self.options.get("password")):
                self.logger.debug(
                    "Logged in and authenticated with BT Sports.")

                res = http.get(self.url)
                m = self.content_re.findall(res.text)
                if m:
                    info = dict(m)
                    data = self._get_cdn(info.get("ID"), info.get("TYPE"))
                    if data['resultCode'] == 'OK':
                        return HLSStream.parse_variant_playlist(
                            self.session, data['resultObj']['src'])
                    else:
                        self.logger.error(
                            "Failed to get stream with error: {0} - {1}".
                            format(data['errorDescription'], data['message']))
        else:
            self.logger.error(
                "A username and password is required to use BT Sports")
示例#12
0
class TVPlayer(Plugin):
    api_url = "http://api.tvplayer.com/api/v2/stream/live"
    login_url = "https://tvplayer.com/account/login"
    update_url = "https://tvplayer.com/account/update-detail"
    dummy_postcode = "SE1 9LT"  # location of ITV HQ in London

    url_re = re.compile(
        r"https?://(?:www.)?tvplayer.com/(:?watch/?|watch/(.+)?)")
    stream_attrs_re = re.compile(
        r'var\s+(validate|platform|resourceId|token)\s+=\s*(.*?);', re.S)
    login_token_re = re.compile(r'input.*?name="token".*?value="(\w+)"')
    stream_schema = validate.Schema(
        {
            "tvplayer":
            validate.Schema({
                "status":
                u'200 OK',
                "response":
                validate.Schema({
                    "stream":
                    validate.url(scheme=validate.any("http")),
                    validate.optional("drmToken"):
                    validate.any(None, validate.text)
                })
            })
        }, validate.get("tvplayer"), validate.get("response"))
    options = PluginOptions({"email": None, "password": None})

    @classmethod
    def can_handle_url(cls, url):
        match = TVPlayer.url_re.match(url)
        return match is not None

    def __init__(self, url):
        super(TVPlayer, self).__init__(url)
        http.headers.update({"User-Agent": useragents.CHROME})

    def authenticate(self, username, password):
        res = http.get(self.login_url)
        match = self.login_token_re.search(res.text)
        token = match and match.group(1)
        res2 = http.post(self.login_url,
                         data=dict(email=username,
                                   password=password,
                                   token=token),
                         allow_redirects=False)
        # there is a 302 redirect on a successful login
        return res2.status_code == 302

    def _get_streams(self):
        if self.get_option("email") and self.get_option("password"):
            self.authenticate(self.get_option("email"),
                              self.get_option("password"))

        # find the list of channels from the html in the page
        self.url = self.url.replace("https", "http")  # https redirects to http
        res = http.get(self.url)

        if "enter your postcode" in res.text:
            self.logger.info(
                "Setting your postcode to: {0}. "
                "This can be changed in the settings on tvplayer.com",
                self.dummy_postcode)
            res = http.post(self.update_url,
                            data=dict(postcode=self.dummy_postcode),
                            params=dict(return_url=self.url))

        stream_attrs = dict((k, v.strip('"'))
                            for k, v in self.stream_attrs_re.findall(res.text))

        if "resourceId" in stream_attrs and "validate" in stream_attrs and "platform" in stream_attrs:
            # get the stream urls
            res = http.post(self.api_url,
                            data=dict(service=1,
                                      id=stream_attrs["resourceId"],
                                      validate=stream_attrs["validate"],
                                      platform=stream_attrs["platform"],
                                      token=stream_attrs.get("token")))

            stream_data = http.json(res, schema=self.stream_schema)

            if stream_data.get("drmToken"):
                self.logger.error(
                    "This stream is protected by DRM can cannot be played")
                return
            else:
                return HLSStream.parse_variant_playlist(
                    self.session, stream_data["stream"])
        else:
            if "need to login" in res.text:
                self.logger.error(
                    "You need to login using --tvplayer-email/--tvplayer-password to view this stream"
                )
示例#13
0
class BBCiPlayer(Plugin):
    url_re = re.compile(
        r"""https?://(?:www\.)?bbc.co.uk/iplayer/
        (
            episode/(?P<episode_id>\w+)|
            live/(?P<channel_name>\w+)
        )
    """, re.VERBOSE)
    mediator_re = re.compile(
        r'window\.mediatorDefer\s*=\s*page\([^,]*,\s*(\{.*?})\);', re.DOTALL)
    tvip_re = re.compile(r'event_master_brand=(\w+?)&')
    account_locals_re = re.compile(r'window.bbcAccount.locals\s*=\s*(\{.*?});')
    swf_url = "http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf"
    hash = base64.b64decode(
        b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==")
    api_url = (
        "http://open.live.bbc.co.uk/mediaselector/6/select/"
        "version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/"
    )
    platforms = ("pc", "iptv-all")
    config_url = "http://www.bbc.co.uk/idcta/config"
    auth_url = "https://account.bbc.com/signin"

    config_schema = validate.Schema(
        validate.transform(parse_json), {
            "signin_url": validate.url(),
            "identity": {
                "cookieAgeDays": int,
                "accessTokenCookieName": validate.text,
                "idSignedInCookieName": validate.text
            }
        })
    mediator_schema = validate.Schema(
        {"episode": {
            "versions": [{
                "id": validate.text
            }]
        }}, validate.get("episode"), validate.get("versions"), validate.get(0),
        validate.get("id"))
    mediaselector_schema = validate.Schema(
        validate.transform(parse_json), {
            "media": [{
                "connection":
                [{
                    validate.optional("href"): validate.url(),
                    validate.optional("transferFormat"): validate.text
                }],
                "kind":
                validate.text
            }]
        }, validate.get("media"),
        validate.filter(lambda x: x["kind"] == "video"))
    options = PluginOptions({"password": None, "username": None})

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    @classmethod
    def _hash_vpid(cls, vpid):
        return sha1(cls.hash + str(vpid).encode("utf8")).hexdigest()

    def find_vpid(self, url, res=None):
        self.logger.debug("Looking for vpid on {0}", url)
        # Use pre-fetched page if available
        res = res or http.get(url)
        m = self.mediator_re.search(res.text)
        vpid = m and parse_json(m.group(1), schema=self.mediator_schema)
        return vpid

    def find_tvip(self, url):
        self.logger.debug("Looking for tvip on {0}", url)
        res = http.get(url)
        m = self.tvip_re.search(res.text)
        return m and m.group(1)

    def mediaselector(self, vpid):
        for platform in self.platforms:
            url = self.api_url.format(vpid=vpid,
                                      vpid_hash=self._hash_vpid(vpid),
                                      platform=platform)
            self.logger.debug("Info API request: {0}", url)
            stream_urls = http.get(url, schema=self.mediaselector_schema)
            for media in stream_urls:
                for connection in media["connection"]:
                    if connection.get("transferFormat") == "hds":
                        for s in HDSStream.parse_manifest(
                                self.session, connection["href"]).items():
                            yield s
                    if connection.get("transferFormat") == "hls":
                        for s in HLSStream.parse_variant_playlist(
                                self.session, connection["href"]).items():
                            yield s

    def login(self, ptrt_url, context="tvandiplayer"):
        # get the site config, to find the signin url
        config = http.get(self.config_url,
                          params=dict(ptrt=ptrt_url),
                          schema=self.config_schema)

        res = http.get(config["signin_url"],
                       params=dict(userOrigin=context, context=context),
                       headers={"Referer": self.url})
        m = self.account_locals_re.search(res.text)
        if m:
            auth_data = parse_json(m.group(1))
            res = http.post(self.auth_url,
                            params=dict(context=auth_data["userOrigin"],
                                        ptrt=auth_data["ptrt"]["value"],
                                        userOrigin=auth_data["userOrigin"],
                                        nonce=auth_data["nonce"]),
                            data=dict(jsEnabled="false",
                                      attempts=0,
                                      username=self.get_option("username"),
                                      password=self.get_option("password")))
            # redirects to ptrt_url on successful login
            if res.url == ptrt_url:
                return res
        else:
            self.logger.error(
                "Could not authenticate, could not find the authentication nonce"
            )

    def _get_streams(self):
        if not self.get_option("username"):
            self.logger.error(
                "BBC iPlayer requires an account you must login using "
                "--bbciplayer-username and --bbciplayer-password")
            return
        self.logger.info(
            "A TV License is required to watch BBC iPlayer streams, see the BBC website for more "
            "information: https://www.bbc.co.uk/iplayer/help/tvlicence")
        page_res = self.login(self.url)
        if not page_res:
            self.logger.error(
                "Could not authenticate, check your username and password")
            return

        m = self.url_re.match(self.url)
        episode_id = m.group("episode_id")
        channel_name = m.group("channel_name")

        if episode_id:
            self.logger.debug("Loading streams for episode: {0}", episode_id)
            vpid = self.find_vpid(self.url, res=page_res)
            if vpid:
                self.logger.debug("Found VPID: {0}", vpid)
                for s in self.mediaselector(vpid):
                    yield s
            else:
                self.logger.error("Could not find VPID for episode {0}",
                                  episode_id)
        elif channel_name:
            self.logger.debug("Loading stream for live channel: {0}",
                              channel_name)
            tvip = self.find_tvip(self.url)
            if tvip:
                self.logger.debug("Found TVIP: {0}", tvip)
                for s in self.mediaselector(tvip):
                    yield s
示例#14
0
class AnimeLab(Plugin):
    url_re = re.compile(r"https?://(?:www\.)?animelab\.com/player/")
    login_url = "https://www.animelab.com/login"
    video_collection_re = re.compile(r"VideoCollection\((\[.*?\])\);")
    playlist_position_re = re.compile(r"playlistPosition\s*=\s*(\d+);")
    video_collection_schema = validate.Schema(
        validate.union({
            "position": validate.all(
                validate.transform(playlist_position_re.search),
                validate.any(
                    None,
                    validate.all(validate.get(1), validate.transform(int))
                )
            ),
            "playlist": validate.all(
                validate.transform(video_collection_re.search),
                validate.any(
                    None,
                    validate.all(
                        validate.get(1),
                        validate.transform(parse_json)
                    )
                )
            )
        })
    )
    options = PluginOptions({
        "email": None,
        "password": None
    })

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def login(self, email, password):
        self.logger.debug("Attempting to log in as {0}", email)
        res = http.post(self.login_url,
                        data=dict(email=email, password=password),
                        allow_redirects=False,
                        raise_for_status=False)
        loc = res.headers.get("Location", "")
        if "geoblocked" in loc.lower():
            self.logger.error("AnimeLab is not available in your territory")
        elif res.status_code >= 400:
            self.logger.error("Failed to login to AnimeLab, check your email/password combination")
        else:
            return True

        return False

    def _get_streams(self):
        email, password = self.get_option("email"), self.get_option("password")
        if not email or not password:
            self.logger.error("AnimeLab requires authentication, use --animelab-email "
                              "and --animelab-password to set your email/password combination")
            return

        if self.login(email, password):
            self.logger.info("Successfully logged in as {0}", email)
            video_collection = http.get(self.url, schema=self.video_collection_schema)
            if video_collection["playlist"] is None or video_collection["position"] is None:
                return

            data = video_collection["playlist"][video_collection["position"]]

            self.logger.debug("Found {0} version {1} hard-subs",
                              data["language"]["name"],
                              "with" if data["hardSubbed"] else "without")

            for video in data["videoInstances"]:
                if video["httpUrl"]:
                    q = video["videoQuality"]["description"]
                    s = HTTPStream(self.session, video["httpUrl"])
                    yield q, s
示例#15
0
class Zattoo(Plugin):
    API_HELLO = '{0}/zapi/session/hello'
    API_LOGIN = '******'
    API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False'
    API_WATCH = '{0}/zapi/watch'
    API_WATCH_VOD = '{0}/zapi/avod/videos/{1}/watch'

    _url_re = re.compile(
        r'''
        https?://
        (?P<base_url>
        zattoo\.com
        |
        tvonline\.ewe\.de
        |
        nettv\.netcologne\.de
        )/(?:watch/(?P<channel>[^/\s]+)
        |
        ondemand/watch/(?P<vod_id>[^-]+)-)
        ''', re.VERBOSE)

    _app_token_re = re.compile(r"""window\.appToken\s+=\s+'([^']+)'""")

    _channels_schema = validate.Schema(
        {
            'success':
            int,
            'channel_groups': [{
                'channels': [
                    {
                        'display_alias': validate.text,
                        'cid': validate.text
                    },
                ]
            }]
        },
        validate.get('channel_groups'),
    )

    options = PluginOptions({
        'email': None,
        'password': None,
        'purge_credentials': None
    })

    def __init__(self, url):
        super(Zattoo, self).__init__(url)
        self._session_attributes = Cache(filename='plugin-cache.json',
                                         key_prefix='zattoo:attributes')
        self._authed = self._session_attributes.get(
            'beaker.session.id') and self._session_attributes.get(
                'pzuid') and self._session_attributes.get('power_guide_hash')
        self._uuid = self._session_attributes.get('uuid')
        self._expires = self._session_attributes.get('expires')

        self.base_url = 'https://{0}'.format(
            Zattoo._url_re.match(url).group('base_url'))
        self.headers = {
            'User-Agent': useragents.CHROME,
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'X-Requested-With': 'XMLHttpRequest',
            'Referer': self.base_url
        }

    @classmethod
    def can_handle_url(cls, url):
        return Zattoo._url_re.match(url)

    def _hello(self):
        self.logger.debug('_hello ...')
        res = http.get(self.base_url)
        match = self._app_token_re.search(res.text)

        app_token = match.group(1)
        hello_url = self.API_HELLO.format(self.base_url)

        if self._uuid:
            __uuid = self._uuid
        else:
            __uuid = str(uuid.uuid4())
            self._session_attributes.set('uuid', __uuid, expires=3600 * 24)

        params = {
            'client_app_token': app_token,
            'uuid': __uuid,
            'lang': 'en',
            'format': 'json'
        }
        res = http.post(hello_url, headers=self.headers, data=params)
        return res

    def _login(self, email, password, _hello):
        self.logger.debug('_login ... Attempting login as {0}'.format(email))

        login_url = self.API_LOGIN.format(self.base_url)

        params = {'login': email, 'password': password, 'remember': 'true'}

        res = http.post(login_url,
                        headers=self.headers,
                        data=params,
                        cookies=_hello.cookies)
        data = http.json(res)

        self._authed = data['success']
        if self._authed:
            self.logger.debug('New Session Data')
            self._session_attributes.set('beaker.session.id',
                                         res.cookies.get('beaker.session.id'),
                                         expires=3600 * 24)
            self._session_attributes.set('pzuid',
                                         res.cookies.get('pzuid'),
                                         expires=3600 * 24)
            self._session_attributes.set('power_guide_hash',
                                         data['session']['power_guide_hash'],
                                         expires=3600 * 24)
            return self._authed
        else:
            return None

    def _watch(self):
        self.logger.debug('_watch ...')
        match = self._url_re.match(self.url)
        if not match:
            return
        channel = match.group('channel')
        vod_id = match.group('vod_id')

        cookies = {
            'beaker.session.id':
            self._session_attributes.get('beaker.session.id'),
            'pzuid': self._session_attributes.get('pzuid')
        }

        watch_url = []
        if channel:
            params, watch_url = self._watch_live(channel, cookies)
        elif vod_id:
            params, watch_url = self._watch_vod(vod_id)

        if not watch_url:
            return

        res = []
        try:
            res = http.post(watch_url,
                            headers=self.headers,
                            data=params,
                            cookies=cookies)
        except Exception as e:
            if '404 Client Error' in str(e):
                self.logger.error(
                    'Unfortunately streaming is not permitted in this country or this channel does not exist.'
                )
            elif '402 Client Error: Payment Required' in str(e):
                self.logger.error(
                    'Paid subscription required for this channel.')
                self.logger.info(
                    'If paid subscription exist, use --zattoo-purge-credentials to start a new session.'
                )
            else:
                self.logger.error(str(e))
            return

        data = http.json(res)

        if data['success']:
            for hls_url in data['stream']['watch_urls']:
                for s in HLSStream.parse_variant_playlist(
                        self.session, hls_url['url']).items():
                    yield s

    def _watch_live(self, channel, cookies):
        self.logger.debug('_watch_live ... Channel: {0}'.format(channel))
        watch_url = self.API_WATCH.format(self.base_url)

        channels_url = self.API_CHANNELS.format(
            self.base_url, self._session_attributes.get('power_guide_hash'))
        res = http.get(channels_url, headers=self.headers, cookies=cookies)
        data = http.json(res, schema=self._channels_schema)

        c_list = []
        for d in data:
            for c in d['channels']:
                c_list.append(c)

        cid = []
        zattoo_list = []
        for c in c_list:
            zattoo_list.append(c['display_alias'])
            if c['display_alias'] == channel:
                cid = c['cid']

        self.logger.debug(
            'Available zattoo channels in this country: {0}'.format(', '.join(
                sorted(zattoo_list))))

        if not cid:
            cid = channel

        self.logger.debug('CHANNEL ID: {0}'.format(cid))

        params = {'cid': cid, 'https_watch_urls': True, 'stream_type': 'hls'}
        return params, watch_url

    def _watch_vod(self, vod_id):
        self.logger.debug('_watch_vod ...')
        watch_url = self.API_WATCH_VOD.format(self.base_url, vod_id)
        params = {'https_watch_urls': True, 'stream_type': 'hls'}
        return params, watch_url

    def _get_streams(self):
        email = self.get_option('email')
        password = self.get_option('password')

        if self.options.get('purge_credentials'):
            self._session_attributes.set('beaker.session.id', None, expires=0)
            self._session_attributes.set('expires', None, expires=0)
            self._session_attributes.set('power_guide_hash', None, expires=0)
            self._session_attributes.set('pzuid', None, expires=0)
            self._session_attributes.set('uuid', None, expires=0)
            self._authed = False
            self.logger.info('All credentials were successfully removed.')

        if not self._authed and (not email and not password):
            self.logger.error(
                'A login for Zattoo is required, use --zattoo-email EMAIL --zattoo-password PASSWORD to set them'
            )
            return

        if self._authed:
            if self._expires < time.time():
                # login after 24h
                expires = time.time() + 3600 * 24
                self._session_attributes.set('expires',
                                             expires,
                                             expires=3600 * 24)
                self._authed = False

        if not self._authed:
            __hello = self._hello()
            if not self._login(email, password, __hello):
                self.logger.error(
                    'Failed to login, check your username/password')
                return

        return self._watch()
示例#16
0
class BBCiPlayer(Plugin):
    """
    Allows streaming of live channels from bbc.co.uk/iplayer/live/* and of iPlayer programmes from
    bbc.co.uk/iplayer/episode/*
    """
    url_re = re.compile(r"""https?://(?:www\.)?bbc.co.uk/iplayer/
        (
            episode/(?P<episode_id>\w+)|
            live/(?P<channel_name>\w+)
        )
    """, re.VERBOSE)
    mediator_re = re.compile(r'window\.mediatorDefer\s*=\s*page\([^,]*,\s*({.*?})\);', re.DOTALL)
    tvip_re = re.compile(r'event_master_brand=(\w+?)&')
    account_locals_re = re.compile(r'window.bbcAccount.locals\s*=\s*({.*?});')
    swf_url = "http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf"
    hash = base64.b64decode(b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==")
    api_url = ("http://open.live.bbc.co.uk/mediaselector/6/select/"
               "version/2.0/mediaset/{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/")
    platforms = ("pc", "iptv-all")
    session_url = "https://session.bbc.com/session"
    auth_url = "https://account.bbc.com/signin"

    mediator_schema = validate.Schema(
        {
            "episode": {
                "versions": [{"id": validate.text}]
            }
        },
        validate.get("episode"), validate.get("versions"), validate.get(0), validate.get("id")
    )
    mediaselector_schema = validate.Schema(
        validate.transform(parse_json),
        {"media": [
            {"connection": [{
                validate.optional("href"): validate.url(),
                validate.optional("transferFormat"): validate.text
            }],
                "kind": validate.text}
        ]},
        validate.get("media"),
        validate.filter(lambda x: x["kind"] == "video")
    )
    options = PluginOptions({
        "password": None,
        "username": None
    })

    @classmethod
    def can_handle_url(cls, url):
        """ Confirm plugin can handle URL """
        return cls.url_re.match(url) is not None

    @classmethod
    def _hash_vpid(cls, vpid):
        return sha1(cls.hash + str(vpid).encode("utf8")).hexdigest()

    @classmethod
    def _extract_nonce(cls, http_result):
        """
        Given an HTTP response from the sessino endpoint, extract the nonce, so we can "sign" requests with it.
        We don't really sign the requests in the traditional sense of a nonce, we just incude them in the auth requests.

        :param http_result: HTTP response from the bbc session endpoint.
        :type http_result: requests.Response
        :return: nonce to "sign" url requests with
        :rtype: string
        """

        # Extract the redirect URL from the last call
        last_redirect_url = urlparse(http_result.history[-1].request.url)
        last_redirect_query = dict(parse_qsl(last_redirect_url.query))
        # Extract the nonce from the query string in the redirect URL
        final_url = urlparse(last_redirect_query['goto'])
        goto_url = dict(parse_qsl(final_url.query))
        goto_url_query = parse_json(goto_url['state'])

        # Return the nonce we can use for future queries
        return goto_url_query['nonce']

    def find_vpid(self, url, res=None):
        """
        Find the Video Packet ID in the HTML for the provided URL

        :param url: URL to download, if res is not provided.
        :param res: Provide a cached version of the HTTP response to search
        :type url: string
        :type res: requests.Response
        :return: Video Packet ID for a Programme in iPlayer
        :rtype: string
        """
        self.logger.debug("Looking for vpid on {0}", url)
        # Use pre-fetched page if available
        res = res or http.get(url)
        m = self.mediator_re.search(res.text)
        vpid = m and parse_json(m.group(1), schema=self.mediator_schema)
        return vpid

    def find_tvip(self, url):
        self.logger.debug("Looking for tvip on {0}", url)
        res = http.get(url)
        m = self.tvip_re.search(res.text)
        return m and m.group(1)

    def mediaselector(self, vpid):
        for platform in self.platforms:
            url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid), platform=platform)
            self.logger.debug("Info API request: {0}", url)
            stream_urls = http.get(url, schema=self.mediaselector_schema)
            for media in stream_urls:
                for connection in media["connection"]:
                    if connection.get("transferFormat") == "hds":
                        for s in HDSStream.parse_manifest(self.session, connection["href"]).items():
                            yield s
                    if connection.get("transferFormat") == "hls":
                        for s in HLSStream.parse_variant_playlist(self.session, connection["href"]).items():
                            yield s

    def login(self, ptrt_url):
        """
        Create session using BBC ID. See https://www.bbc.co.uk/usingthebbc/account/

        :param ptrt_url: The snapback URL to redirect to after successful authentication
        :type ptrt_url: string
        :return: Whether authentication was successful
        :rtype: bool
        """
        session_res = http.get(
            self.session_url,
            params=dict(ptrt=ptrt_url)
        )

        http_nonce = self._extract_nonce(session_res)

        res = http.post(
            self.auth_url,
            params=dict(
                ptrt=ptrt_url,
                nonce=http_nonce
            ),
            data=dict(
                jsEnabled=True,
                username=self.get_option("username"),
                password=self.get_option('password'),
                attempts=0
            ),
            headers={"Referer": self.url})

        return len(res.history) != 0

    def _get_streams(self):
        if not self.get_option("username"):
            self.logger.error("BBC iPlayer requires an account you must login using "
                              "--bbciplayer-username and --bbciplayer-password")
            return
        self.logger.info("A TV License is required to watch BBC iPlayer streams, see the BBC website for more "
                         "information: https://www.bbc.co.uk/iplayer/help/tvlicence")
        if not self.login(self.url):
            self.logger.error("Could not authenticate, check your username and password")
            return

        m = self.url_re.match(self.url)
        episode_id = m.group("episode_id")
        channel_name = m.group("channel_name")

        if episode_id:
            self.logger.debug("Loading streams for episode: {0}", episode_id)
            vpid = self.find_vpid(self.url)
            if vpid:
                self.logger.debug("Found VPID: {0}", vpid)
                for s in self.mediaselector(vpid):
                    yield s
            else:
                self.logger.error("Could not find VPID for episode {0}", episode_id)
        elif channel_name:
            self.logger.debug("Loading stream for live channel: {0}", channel_name)
            tvip = self.find_tvip(self.url)
            if tvip:
                self.logger.debug("Found TVIP: {0}", tvip)
                for s in self.mediaselector(tvip):
                    yield s
示例#17
0
class WWENetwork(Plugin):
    url_re = re.compile(r"https?://network.wwe.com")
    content_id_re = re.compile(r'''"content_id" : "(\d+)"''')
    playback_scenario = "HTTP_CLOUD_WIRED"
    login_url = "https://secure.net.wwe.com/workflow.do"
    login_page_url = "https://secure.net.wwe.com/enterworkflow.do?flowId=account.login&forwardUrl=http%3A%2F%2Fnetwork.wwe.com"
    api_url = "https://ws.media.net.wwe.com/ws/media/mf/op-findUserVerifiedEvent/v-2.3"
    _info_schema = validate.Schema(
        validate.union({
            "status":
            validate.union({
                "code":
                validate.all(validate.xml_findtext(".//status-code"),
                             validate.transform(int)),
                "message":
                validate.xml_findtext(".//status-message"),
            }),
            "urls":
            validate.all(validate.xml_findall(".//url"),
                         [validate.getattr("text")]),
            validate.optional("fingerprint"):
            validate.xml_findtext(".//updated-fingerprint"),
            validate.optional("session_key"):
            validate.xml_findtext(".//session-key"),
            "session_attributes":
            validate.all(validate.xml_findall(".//session-attribute"), [
                validate.getattr("attrib"),
                validate.union({
                    "name": validate.get("name"),
                    "value": validate.get("value")
                })
            ])
        }))
    options = PluginOptions({
        "email": None,
        "password": None,
    })

    def __init__(self, url):
        super(WWENetwork, self).__init__(url)
        http.headers.update({"User-Agent": useragents.CHROME})
        self._session_attributes = Cache(filename="plugin-cache.json",
                                         key_prefix="wwenetwork:attributes")
        self._session_key = self.cache.get("session_key")
        self._authed = self._session_attributes.get(
            "ipid") and self._session_attributes.get("fprt")

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def login(self, email, password):
        self.logger.debug("Attempting login as {0}", email)
        # sets some required cookies to login
        http.get(self.login_page_url)
        # login
        res = http.post(self.login_url,
                        data=dict(registrationAction='identify',
                                  emailAddress=email,
                                  password=password,
                                  submitButton=""),
                        headers={"Referer": self.login_page_url},
                        allow_redirects=False)

        self._authed = "Authentication Error" not in res.text
        if self._authed:
            self._session_attributes.set("ipid",
                                         res.cookies.get("ipid"),
                                         expires=3600 * 1.5)
            self._session_attributes.set("fprt",
                                         res.cookies.get("fprt"),
                                         expires=3600 * 1.5)

        return self._authed

    def _update_session_attribute(self, key, value):
        if value:
            self._session_attributes.set(key, value,
                                         expires=3600 * 1.5)  # 1h30m expiry
            http.cookies.set(key, value)

    @property
    def session_key(self):
        return self._session_key

    @session_key.setter
    def session_key(self, value):
        self.cache.set("session_key", value)
        self._session_key = value

    def _get_media_info(self, content_id):
        """
        Get the info about the content, based on the ID
        :param content_id:
        :return:
        """
        params = {
            "identityPointId": self._session_attributes.get("ipid"),
            "fingerprint": self._session_attributes.get("fprt"),
            "contentId": content_id,
            "playbackScenario": self.playback_scenario,
            "platform": "WEB_MEDIAPLAYER_5",
            "subject": "LIVE_EVENT_COVERAGE",
            "frameworkURL": "https://ws.media.net.wwe.com",
            "_": int(time.time())
        }
        if self.session_key:
            params["sessionKey"] = self.session_key
        url = self.api_url.format(id=content_id)
        res = http.get(url, params=params)
        return http.xml(res, ignore_ns=True, schema=self._info_schema)

    def _get_content_id(self):
        #  check the page to find the contentId
        res = http.get(self.url)
        m = self.content_id_re.search(res.text)
        if m:
            return m.group(1)

    def _get_streams(self):
        email = self.get_option("email")
        password = self.get_option("password")

        if not self._authed and (not email and not password):
            self.logger.error(
                "A login for WWE Network is required, use --wwenetwork-email/"
                "--wwenetwork-password to set them")
            return

        if not self._authed:
            if not self.login(email, password):
                self.logger.error(
                    "Failed to login, check your username/password")
                return

        content_id = self._get_content_id()
        if content_id:
            self.logger.debug("Found content ID: {0}", content_id)
            info = self._get_media_info(content_id)
            if info["status"]["code"] == 1:
                # update the session attributes
                self._update_session_attribute("fprt", info.get("fingerprint"))
                for attr in info["session_attributes"]:
                    self._update_session_attribute(attr["name"], attr["value"])

                if info.get("session_key"):
                    self.session_key = info.get("session_key")
                for url in info["urls"]:
                    for s in HLSStream.parse_variant_playlist(
                            self.session, url,
                            name_fmt="{pixels}_{bitrate}").items():
                        yield s
            else:
                raise PluginError(
                    "Could not load streams: {message} ({code})".format(
                        **info["status"]))
示例#18
0
class UFCTV(Plugin):
    url_re = re.compile(r"https?://(?:www\.)?ufc\.tv/(channel|video)/.+")
    video_info_re = re.compile(r"""program\s*=\s*(\{.*?});""", re.DOTALL)
    channel_info_re = re.compile(r"""g_channel\s*=\s(\{.*?});""", re.DOTALL)

    stream_api_url = "https://www.ufc.tv/service/publishpoint"
    auth_url = "https://www.ufc.tv/secure/authenticate"
    auth_schema = validate.Schema(validate.xml_findtext("code"))

    options = PluginOptions({"username": None, "password": None})

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def _get_stream_url(self, video_id, vtype="video"):
        res = http.post(self.stream_api_url,
                        data={
                            "id": video_id,
                            "type": vtype,
                            "format": "json"
                        },
                        headers={"User-Agent": useragents.IPHONE_6})
        data = http.json(res)
        return data.get("path")

    def _get_info(self, url):
        res = http.get(url)
        # try to find video info first
        m = self.video_info_re.search(res.text)
        if not m:
            # and channel info if that fails
            m = self.channel_info_re.search(res.text)
        return m and js_to_json(m.group(1))

    def _login(self, username, password):
        res = http.post(self.auth_url,
                        data={
                            "username": username,
                            "password": password,
                            "cookielink": False
                        })
        login_status = http.xml(res, schema=self.auth_schema)
        self.logger.debug("Login status for {0}: {1}", username, login_status)
        if login_status == "loginlocked":
            self.logger.error(
                "The account {0} has been locked, the password needs to be reset"
            )
        return login_status == "loginsuccess"

    def _get_streams(self):
        if self.get_option("username") and self.get_option("password"):
            self.logger.debug("Attempting login as {0}",
                              self.get_option("username"))
            if self._login(self.get_option("username"),
                           self.get_option("password")):
                self.logger.info("Successfully logged in as {0}",
                                 self.get_option("username"))
            else:
                self.logger.info("Failed to login as {0}",
                                 self.get_option("username"))

        video = self._get_info(self.url)
        if video:
            self.logger.debug("Found {type}: {name}", **video)
            surl = self._get_stream_url(video['id'],
                                        video.get('type', "video"))
            surl = surl.replace("_iphone", "")
            if surl:
                return HLSStream.parse_variant_playlist(self.session, surl)
            else:
                self.logger.error(
                    "Could not get stream URL for video: {name} ({id})",
                    **video)
        else:
            self.logger.error("Could not find any video info on the page")
示例#19
0
class Crunchyroll(Plugin):
    options = PluginOptions({
        "username": None,
        "password": None,
        "purge_credentials": None,
        "locale": API_DEFAULT_LOCALE
    })

    @classmethod
    def can_handle_url(self, url):
        return _url_re.match(url)

    @classmethod
    def stream_weight(cls, key):
        weight = STREAM_WEIGHTS.get(key)
        if weight:
            return weight, "crunchyroll"

        return Plugin.stream_weight(key)

    def _get_streams(self):
        api = self._create_api()
        match = _url_re.match(self.url)
        media_id = int(match.group("media_id"))

        try:
            info = api.get_info(media_id,
                                fields=["media.stream_data"],
                                schema=_media_schema)
        except CrunchyrollAPIError as err:
            raise PluginError(u"Media lookup error: {0}".format(err.msg))

        if not info:
            return

        # TODO: Use dict comprehension here after dropping Python 2.6 support.
        return dict((stream["quality"], HLSStream(self.session, stream["url"]))
                    for stream in info["streams"])

    def _get_device_id(self):
        """Returns the saved device id or creates a new one and saves it."""
        device_id = self.cache.get("device_id")

        if not device_id:
            # Create a random device id and cache it for a year
            char_set = string.ascii_letters + string.digits
            device_id = "".join(random.sample(char_set, 32))
            self.cache.set("device_id", device_id, 365 * 24 * 60 * 60)

        return device_id

    def _create_api(self):
        """Creates a new CrunchyrollAPI object, initiates it's session and
        tries to authenticate it either by using saved credentials or the
        user's username and password.
        """
        if self.options.get("purge_credentials"):
            self.cache.set("session_id", None, 0)
            self.cache.set("auth", None, 0)

        current_time = datetime.datetime.utcnow()
        device_id = self._get_device_id()
        locale = self.options.get("locale")
        api = CrunchyrollAPI(self.cache.get("session_id"),
                             self.cache.get("auth"), locale)

        self.logger.debug("Creating session")
        try:
            api.session_id = api.start_session(device_id,
                                               schema=_session_schema)
        except CrunchyrollAPIError as err:
            if err.code == "bad_session":
                self.logger.debug(
                    "Current session has expired, creating a new one")
                api = CrunchyrollAPI(locale=locale)
                api.session_id = api.start_session(device_id,
                                                   schema=_session_schema)
            else:
                raise err

        # Save session and hope it lasts for a few hours
        self.cache.set("session_id", api.session_id, 4 * 60 * 60)
        self.logger.debug("Session created")

        if api.auth:
            self.logger.debug("Using saved credentials")
        elif self.options.get("username"):
            try:
                self.logger.info(
                    "Attempting to login using username and password")
                login = api.login(self.options.get("username"),
                                  self.options.get("password"),
                                  schema=_login_schema)
                api.auth = login["auth"]

                self.logger.info("Successfully logged in as '{0}'",
                                 login["user"]["username"])

                expires = (login["expires"] - current_time).total_seconds()
                self.cache.set("auth", login["auth"], expires)
            except CrunchyrollAPIError as err:
                raise PluginError(u"Authentication error: {0}".format(err.msg))
        else:
            self.logger.warning(
                "No authentication provided, you won't be able to access "
                "premium restricted content")

        return api
示例#20
0
class Rtve(Plugin):
    secret_key = base64.b64decode("eWVMJmRhRDM=")
    content_id_re = re.compile(r'data-id\s*=\s*"(\d+)"')
    url_re = re.compile(
        r"""
        https?://(?:www\.)?rtve\.es/(?:directo|noticias|television|deportes|alacarta|drmn)/.*?/?
    """, re.VERBOSE)
    cdn_schema = validate.Schema(
        validate.transform(partial(parse_xml, invalid_char_entities=True)),
        validate.xml_findall(".//preset"), [
            validate.union({
                "quality":
                validate.all(validate.getattr("attrib"), validate.get("type")),
                "urls":
                validate.all(validate.xml_findall(".//url"),
                             [validate.getattr("text")])
            })
        ])
    subtitles_api = "http://www.rtve.es/api/videos/{id}/subtitulos.json"
    subtitles_schema = validate.Schema(
        {"page": {
            "items": [{
                "src": validate.url(),
                "lang": validate.text
            }]
        }}, validate.get("page"), validate.get("items"))
    video_api = "http://www.rtve.es/api/videos/{id}.json"
    video_schema = validate.Schema(
        {
            "page": {
                "items": [{
                    "qualities": [{
                        "preset": validate.text,
                        "height": int
                    }]
                }]
            }
        }, validate.get("page"), validate.get("items"), validate.get(0))
    options = PluginOptions({"mux_subtitles": False})

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def __init__(self, url):
        Plugin.__init__(self, url)
        self.zclient = ZTNRClient(self.secret_key)
        http.headers = {"User-Agent": useragents.SAFARI_8}

    def _get_content_id(self):
        res = http.get(self.url)
        m = self.content_id_re.search(res.text)
        return m and int(m.group(1))

    def _get_subtitles(self, content_id):
        res = http.get(self.subtitles_api.format(id=content_id))
        return http.json(res, schema=self.subtitles_schema)

    def _get_quality_map(self, content_id):
        res = http.get(self.video_api.format(id=content_id))
        data = http.json(res, schema=self.video_schema)
        qmap = {}
        for item in data["qualities"]:
            qname = {
                "MED": "Media",
                "HIGH": "Alta",
                "ORIGINAL": "Original"
            }.get(item["preset"], item["preset"])
            qmap[qname] = u"{0}p".format(item["height"])
        return qmap

    def _get_streams(self):
        streams = []
        content_id = self._get_content_id()
        if content_id:
            self.logger.debug("Found content with id: {0}", content_id)
            stream_data = self.zclient.get_cdn_list(content_id,
                                                    schema=self.cdn_schema)
            quality_map = None

            for stream in stream_data:
                for url in stream["urls"]:
                    if url.endswith("m3u8"):
                        try:
                            streams.extend(
                                HLSStream.parse_variant_playlist(
                                    self.session, url).items())
                        except (IOError, OSError):
                            self.logger.debug("Failed to load m3u8 url: {0}",
                                              url)
                    elif ((url.endswith("mp4") or url.endswith("mov")
                           or url.endswith("avi"))
                          and http.head(
                              url, raise_for_status=False).status_code == 200):
                        if quality_map is None:  # only make the request when it is necessary
                            quality_map = self._get_quality_map(content_id)
                        # rename the HTTP sources to match the HLS sources
                        quality = quality_map.get(stream["quality"],
                                                  stream["quality"])
                        streams.append((quality, HTTPStream(self.session,
                                                            url)))

            subtitles = None
            if self.get_option("mux_subtitles"):
                subtitles = self._get_subtitles(content_id)
            if subtitles:
                substreams = {}
                for i, subtitle in enumerate(subtitles):
                    substreams[subtitle["lang"]] = HTTPStream(
                        self.session, subtitle["src"])

                for q, s in streams:
                    yield q, MuxedStream(self.session, s, subtitles=substreams)
            else:
                for s in streams:
                    yield s
示例#21
0
class NPO(Plugin):
    api_url = "http://ida.omroep.nl/app.php/{endpoint}"
    url_re = re.compile(r"https?://(\w+\.)?(npo\.nl|zapp\.nl|zappelin\.nl)/")
    media_id_re = re.compile(r'''<npo-player\smedia-id=["'](?P<media_id>[^"']+)["']''')
    prid_re = re.compile(r'''(?:data(-alt)?-)?prid\s*[=:]\s*(?P<q>["'])(\w+)(?P=q)''')
    react_re = re.compile(r'''data-react-props\s*=\s*(?P<q>["'])(?P<data>.*?)(?P=q)''')

    auth_schema = validate.Schema({"token": validate.text}, validate.get("token"))
    streams_schema = validate.Schema({
        "items": [
            [{
                "label": validate.text,
                "contentType": validate.text,
                "url": validate.url(),
                "format": validate.text
            }]
        ]
    }, validate.get("items"), validate.get(0))
    stream_info_schema = validate.Schema(validate.any(
        validate.url(),
        validate.all({"errorcode": 0, "url": validate.url()},
                     validate.get("url"))
    ))
    options = PluginOptions({
        "subtitles": False
    })

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def __init__(self, url):
        super(NPO, self).__init__(url)
        self._token = None
        http.headers.update({"User-Agent": useragents.CHROME})

    def api_call(self, endpoint, schema=None, params=None):
        url = self.api_url.format(endpoint=endpoint)
        res = http.get(url, params=params)
        return http.json(res, schema=schema)

    @property
    def token(self):
        if not self._token:
            self._token = self.api_call("auth", schema=self.auth_schema)
        return self._token

    def _get_prid(self, subtitles=False):
        res = http.get(self.url)
        bprid = None

        # Locate the asset id for the content on the page
        for alt, _, prid in self.prid_re.findall(res.text):
            if alt and subtitles:
                bprid = prid
            elif bprid is None:
                bprid = prid

        if bprid is None:
            m = self.react_re.search(res.text)
            if m:
                data = parse_json(m.group("data").replace("&quot;", '"'))
                bprid = data.get("mid")

        if bprid is None:
            m = self.media_id_re.search(res.text)
            if m:
                bprid = m.group('media_id')

        return bprid

    def _get_streams(self):
        asset_id = self._get_prid(self.get_option("subtitles"))

        if asset_id:
            self.logger.debug("Found asset id: {0}", asset_id)
            streams = self.api_call(asset_id,
                                    params=dict(adaptive="yes",
                                                token=self.token),
                                    schema=self.streams_schema)

            for stream in streams:
                if stream["format"] in ("adaptive", "hls", "mp4"):
                    if stream["contentType"] == "url":
                        stream_url = stream["url"]
                    else:
                        # using type=json removes the javascript function wrapper
                        info_url = stream["url"].replace("type=jsonp", "type=json")

                        # find the actual stream URL
                        stream_url = http.json(http.get(info_url),
                                               schema=self.stream_info_schema)

                    if stream["format"] in ("adaptive", "hls"):
                        for s in HLSStream.parse_variant_playlist(self.session, stream_url).items():
                            yield s
                    elif stream["format"] in ("mp3", "mp4"):
                        yield "vod", HTTPStream(self.session, stream_url)
示例#22
0
class TVPlayer(Plugin):
    context_url = "http://tvplayer.com/watch/context"
    api_url = "http://api.tvplayer.com/api/v2/stream/live"
    login_url = "https://tvplayer.com/account/login"
    update_url = "https://tvplayer.com/account/update-detail"
    dummy_postcode = "SE1 9LT"  # location of ITV HQ in London

    url_re = re.compile(
        r"https?://(?:www.)?tvplayer.com/(:?watch/?|watch/(.+)?)")
    stream_attrs_re = re.compile(
        r'data-(resource|token|channel-id)\s*=\s*"(.*?)"', re.S)
    data_id_re = re.compile(r'data-id\s*=\s*"(.*?)"', re.S)
    login_token_re = re.compile(r'input.*?name="token".*?value="(\w+)"')
    stream_schema = validate.Schema(
        {
            "tvplayer":
            validate.Schema({
                "status":
                u'200 OK',
                "response":
                validate.Schema({
                    "stream":
                    validate.url(scheme=validate.any("http", "https")),
                    validate.optional("drmToken"):
                    validate.any(None, validate.text)
                })
            })
        }, validate.get("tvplayer"), validate.get("response"))
    context_schema = validate.Schema({
        "validate": validate.text,
        validate.optional("token"): validate.text,
        "platform": {
            "key": validate.text
        }
    })
    options = PluginOptions({"email": None, "password": None})

    @classmethod
    def can_handle_url(cls, url):
        match = TVPlayer.url_re.match(url)
        return match is not None

    def __init__(self, url):
        super(TVPlayer, self).__init__(url)
        http.headers.update({"User-Agent": useragents.CHROME})

    def authenticate(self, username, password):
        res = http.get(self.login_url)
        match = self.login_token_re.search(res.text)
        token = match and match.group(1)
        res2 = http.post(self.login_url,
                         data=dict(email=username,
                                   password=password,
                                   token=token),
                         allow_redirects=False)
        # there is a 302 redirect on a successful login
        return res2.status_code == 302

    def _get_stream_data(self, resource, channel_id, token, service=1):
        # Get the context info (validation token and platform)
        self.logger.debug(
            "Getting stream information for resource={0}".format(resource))
        context_res = http.get(self.context_url,
                               params={
                                   "resource": resource,
                                   "gen": token
                               })
        context_data = http.json(context_res, schema=self.context_schema)
        self.logger.debug("Context data: {0}", str(context_data))

        # get the stream urls
        res = http.post(self.api_url,
                        data=dict(service=service,
                                  id=channel_id,
                                  validate=context_data["validate"],
                                  token=context_data.get("token"),
                                  platform=context_data["platform"]["key"]),
                        raise_for_status=False)

        return http.json(res, schema=self.stream_schema)

    def _get_stream_attrs(self, page):
        stream_attrs = dict(
            (k.replace("-", "_"), v.strip('"'))
            for k, v in self.stream_attrs_re.findall(page.text))

        if not stream_attrs.get("channel_id"):
            m = self.data_id_re.search(page.text)
            stream_attrs["channel_id"] = m and m.group(1)

        self.logger.debug("Got stream attributes: {0}", str(stream_attrs))
        valid = True
        for a in ("channel_id", "resource", "token"):
            if a not in stream_attrs:
                self.logger.debug("Missing '{0}' from stream attributes", a)
                valid = False

        return stream_attrs if valid else {}

    def _get_streams(self):
        if self.get_option("email") and self.get_option("password"):
            if not self.authenticate(self.get_option("email"),
                                     self.get_option("password")):
                self.logger.warning("Failed to login as {0}".format(
                    self.get_option("email")))

        # find the list of channels from the html in the page
        self.url = self.url.replace("https", "http")  # https redirects to http
        res = http.get(self.url)

        if "enter your postcode" in res.text:
            self.logger.info(
                "Setting your postcode to: {0}. "
                "This can be changed in the settings on tvplayer.com",
                self.dummy_postcode)
            res = http.post(self.update_url,
                            data=dict(postcode=self.dummy_postcode),
                            params=dict(return_url=self.url))

        stream_attrs = self._get_stream_attrs(res)
        if stream_attrs:
            stream_data = self._get_stream_data(**stream_attrs)

            if stream_data:
                if stream_data.get("drmToken"):
                    self.logger.error(
                        "This stream is protected by DRM can cannot be played")
                    return
                else:
                    return HLSStream.parse_variant_playlist(
                        self.session, stream_data["stream"])
        else:
            if "need to login" in res.text:
                self.logger.error(
                    "You need to login using --tvplayer-email/--tvplayer-password to view this stream"
                )
示例#23
0
class UStreamTV(Plugin):
    url_re = re.compile(
        r"""
    https?://(www\.)?ustream\.tv
        (?:
            (/embed/|/channel/id/)(?P<channel_id>\d+)
        )?
        (?:
            (/embed)?/recorded/(?P<video_id>\d+)
        )?
    """, re.VERBOSE)
    media_id_re = re.compile(r'"ustream:channel_id"\s+content\s*=\s*"(\d+)"')
    options = PluginOptions({"password": None})

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def _api_get_streams(self,
                         media_id,
                         application,
                         cluster="live",
                         referrer=None,
                         retries=3):
        if retries > 0:
            app_id = 11
            app_ver = 2
            referrer = referrer or self.url
            self.api = UHSClient(self.session,
                                 media_id,
                                 application,
                                 referrer=referrer,
                                 cluster=cluster,
                                 app_id=app_id,
                                 app_version=app_ver,
                                 password=self.get_option("password"))
            self.logger.debug(
                "Connecting to UStream API: media_id={0}, application={1}, referrer={2}, cluster={3}, "
                "app_id={4}, app_ver={5}", media_id, application, referrer,
                cluster, app_id, app_ver)
            if self.api.connect():
                for i in range(
                        5):  # make at most five requests to get the moduleInfo
                    try:
                        for s in self._do_poll(media_id, application, cluster,
                                               referrer, retries):
                            yield s
                    except ModuleInfoNoStreams:
                        self.logger.debug("Retrying moduleInfo request")
                        time.sleep(1)
                    else:
                        break

    def _do_poll(self,
                 media_id,
                 application,
                 cluster="live",
                 referrer=None,
                 retries=3):
        res = self.api.poll()
        if res:
            for result in res:
                if result["cmd"] == "moduleInfo":
                    for s in self.handle_module_info(result["args"], media_id,
                                                     application, cluster,
                                                     referrer, retries):
                        yield s
                elif result["cmd"] == "reject":
                    for s in self.handle_reject(result["args"], media_id,
                                                application, cluster, referrer,
                                                retries):
                        yield s
                else:
                    self.logger.debug("Unknown command: {0}({1})",
                                      result["cmd"], result["args"])

    def handle_module_info(self,
                           args,
                           media_id,
                           application,
                           cluster="live",
                           referrer=None,
                           retries=3):
        has_results = False
        for streams in UHSClient.module_info_schema.validate(args):
            has_results = True
            if isinstance(streams, list):
                for stream in streams:
                    for q, s in HLSStream.parse_variant_playlist(
                            self.session, stream["url"]).items():
                        yield q, UStreamHLSStream(self.session, s.url,
                                                  self.api)
            elif isinstance(streams, dict):
                for stream in streams.get("streams", []):
                    name = "{0}k".format(stream["bitrate"])
                    for surl in stream["streamName"]:
                        yield name, HTTPStream(self.session, surl)
            elif streams == "offline":
                self.logger.warning("This stream is currently offline")

        if not has_results:
            raise ModuleInfoNoStreams

    def handle_reject(self,
                      args,
                      media_id,
                      application,
                      cluster="live",
                      referrer=None,
                      retries=3):
        for arg in args:
            if "cluster" in arg:
                self.logger.debug("Switching cluster to {0}",
                                  arg["cluster"]["name"])
                cluster = arg["cluster"]["name"]
            if "referrerLock" in arg:
                referrer = arg["referrerLock"]["redirectUrl"]

        return self._api_get_streams(media_id,
                                     application,
                                     cluster=cluster,
                                     referrer=referrer,
                                     retries=retries - 1)

    def _get_streams(self):
        # establish a mobile non-websockets api connection
        umatch = self.url_re.match(self.url)
        application = "channel"

        channel_id = umatch.group("channel_id")
        video_id = umatch.group("video_id")
        if channel_id:
            application = "channel"
            media_id = channel_id
        elif video_id:
            application = "recorded"
            media_id = video_id
        else:
            media_id = self._find_media_id()

        if media_id:
            for s in self._api_get_streams(media_id, application):
                yield s
        else:
            self.logger.error("Cannot find a media_id on this page")

    def _find_media_id(self):
        self.logger.debug("Searching for media ID on the page")
        res = http.get(self.url, headers={"User-Agent": useragents.CHROME})
        m = self.media_id_re.search(res.text)
        return m and m.group(1)
示例#24
0
class ABweb(Plugin):
    '''BIS Livestreams of french AB Groupe
       http://www.abweb.com/BIS-TV-Online/
    '''

    login_url = 'http://www.abweb.com/BIS-TV-Online/Default.aspx'

    _url_re = re.compile(
        r'https?://(?:www\.)?abweb\.com/BIS-TV-Online/bistvo-tele-universal.aspx',
        re.IGNORECASE)
    _hls_re = re.compile(
        r'''["']file["']:\s?["'](?P<url>[^"']+\.m3u8[^"']+)["']''')
    _iframe_re = re.compile(r'''<iframe[^>]+src=["'](?P<url>[^"']+)["']''')

    _input_re = re.compile(r'''(<input[^>]+>)''')
    _name_re = re.compile(r'''name=["']([^"']*)["']''')
    _value_re = re.compile(r'''value=["']([^"']*)["']''')

    expires_time = 3600 * 24

    options = PluginOptions({
        'username': None,
        'password': None,
        'purge_credentials': None
    })

    def __init__(self, url):
        super(ABweb, self).__init__(url)
        self._session_attributes = Cache(filename='plugin-cache.json',
                                         key_prefix='abweb:attributes')
        self._authed = self._session_attributes.get(
            'ASP.NET_SessionId') and self._session_attributes.get(
                '.abportail1')
        self._expires = self._session_attributes.get(
            'expires',
            time.time() + self.expires_time)

    @classmethod
    def can_handle_url(cls, url):
        return cls._url_re.match(url) is not None

    def set_expires_time_cache(self):
        expires = time.time() + self.expires_time
        self._session_attributes.set('expires',
                                     expires,
                                     expires=self.expires_time)

    def get_iframe_url(self):
        self.logger.debug('search for an iframe')
        res = http.get(self.url)
        m = self._iframe_re.search(res.text)
        if not m:
            raise PluginError('No iframe found.')

        iframe_url = m.group('url')
        iframe_url = update_scheme('http://', iframe_url)
        self.logger.debug('IFRAME URL={0}'.format(iframe_url))
        return iframe_url

    def get_hls_url(self, iframe_url):
        self.logger.debug('search for hls url')
        res = http.get(iframe_url)
        m = self._hls_re.search(res.text)
        if not m:
            raise PluginError('No playlist found.')

        return m and m.group('url')

    def _login(self, username, password):
        '''login and update cached cookies'''
        self.logger.debug('login ...')

        res = http.get(self.login_url)
        input_list = self._input_re.findall(res.text)
        if not input_list:
            raise PluginError('Missing input data on login website.')

        data = {}
        for _input_data in input_list:
            try:
                _input_name = self._name_re.search(_input_data).group(1)
            except AttributeError:
                continue

            try:
                _input_value = self._value_re.search(_input_data).group(1)
            except AttributeError:
                _input_value = ''

            data[_input_name] = _input_value

        login_data = {
            'ctl00$Login1$UserName': username,
            'ctl00$Login1$Password': password,
            'ctl00$Login1$LoginButton.x': '0',
            'ctl00$Login1$LoginButton.y': '0'
        }
        data.update(login_data)

        res = http.post(self.login_url, data=data)

        for cookie in http.cookies:
            self._session_attributes.set(cookie.name,
                                         cookie.value,
                                         expires=3600 * 24)

        if self._session_attributes.get(
                'ASP.NET_SessionId') and self._session_attributes.get(
                    '.abportail1'):
            self.logger.debug('New session data')
            self.set_expires_time_cache()
            return True
        else:
            self.logger.error('Failed to login, check your username/password')
            return False

    def _get_streams(self):
        http.headers.update({
            'User-Agent':
            useragents.CHROME,
            'Referer':
            'http://www.abweb.com/BIS-TV-Online/bistvo-tele-universal.aspx'
        })

        login_username = self.get_option('username')
        login_password = self.get_option('password')

        if self.options.get('purge_credentials'):
            self._session_attributes.set('ASP.NET_SessionId', None, expires=0)
            self._session_attributes.set('.abportail1', None, expires=0)
            self._authed = False
            self.logger.info('All credentials were successfully removed.')

        if not self._authed and not (login_username and login_password):
            self.logger.error(
                'A login for ABweb is required, use --abweb-username USERNAME --abweb-password PASSWORD'
            )
            return

        if self._authed:
            if self._expires < time.time():
                self.logger.debug('get new cached cookies')
                # login after 24h
                self.set_expires_time_cache()
                self._authed = False
            else:
                self.logger.info(
                    'Attempting to authenticate using cached cookies')
                http.cookies.set(
                    'ASP.NET_SessionId',
                    self._session_attributes.get('ASP.NET_SessionId'))
                http.cookies.set('.abportail1',
                                 self._session_attributes.get('.abportail1'))

        if not self._authed and not self._login(login_username,
                                                login_password):
            return

        iframe_url = self.get_iframe_url()
        http.headers.update({'Referer': iframe_url})

        hls_url = self.get_hls_url(iframe_url)
        hls_url = update_scheme(self.url, hls_url)

        self.logger.debug('URL={0}'.format(hls_url))
        variant = HLSStream.parse_variant_playlist(self.session, hls_url)
        if variant:
            for q, s in variant.items():
                yield q, s
        else:
            yield 'live', HLSStream(self.session, hls_url)
示例#25
0
class Pluzz(Plugin):
    GEO_URL = 'http://geo.francetv.fr/ws/edgescape.json'
    API_URL = 'http://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion={0}'
    PLAYER_GENERATOR_URL = 'https://sivideo.webservices.francetelevisions.fr/assets/staticmd5/getUrl?id=jquery.player.7.js'
    TOKEN_URL = 'http://hdfauthftv-a.akamaihd.net/esi/TA?url={0}'

    _url_re = re.compile(
        r'https?://((?:www)\.france\.tv/.+\.html|www\.(ludo|zouzous)\.fr/heros/[\w-]+|(sport|france3-regions)\.francetvinfo\.fr/.+?/(tv/direct)?)'
    )
    _pluzz_video_id_re = re.compile(r'data-main-video="(?P<video_id>.+?)"')
    _jeunesse_video_id_re = re.compile(
        r'playlist: \[{.*?,"identity":"(?P<video_id>.+?)@(?P<catalogue>Ludo|Zouzous)"'
    )
    _f3_regions_video_id_re = re.compile(
        r'"http://videos\.francetv\.fr/video/(?P<video_id>.+)@Regions"')
    _sport_video_id_re = re.compile(r'data-video="(?P<video_id>.+?)"')
    _player_re = re.compile(
        r'src="(?P<player>//staticftv-a\.akamaihd\.net/player/jquery\.player.+?-[0-9a-f]+?\.js)"></script>'
    )
    _swf_re = re.compile(
        r'//staticftv-a\.akamaihd\.net/player/bower_components/player_flash/dist/FranceTVNVPVFlashPlayer\.akamai-[0-9a-f]+\.swf'
    )
    _hds_pv_data_re = re.compile(r"~data=.+?!")
    _mp4_bitrate_re = re.compile(r'.*-(?P<bitrate>[0-9]+k)\.mp4')

    _geo_schema = validate.Schema(
        {'reponse': {
            'geo_info': {
                'country_code': validate.text
            }
        }})

    _api_schema = validate.Schema({
        'videos':
        validate.all([{
            'format':
            validate.any(None, validate.text),
            'url':
            validate.any(
                None,
                validate.url(),
            ),
            'statut':
            validate.text,
            'drm':
            bool,
            'geoblocage':
            validate.any(None, [validate.all(validate.text)]),
            'plages_ouverture':
            validate.all([{
                'debut': validate.any(None, int),
                'fin': validate.any(None, int)
            }])
        }]),
        'subtitles':
        validate.any([],
                     validate.all([{
                         'type': validate.text,
                         'url': validate.url(),
                         'format': validate.text
                     }]))
    })

    _player_schema = validate.Schema({'result': validate.url()})

    options = PluginOptions({"mux_subtitles": False})

    @classmethod
    def can_handle_url(cls, url):
        return Pluzz._url_re.match(url)

    def _get_streams(self):
        # Retrieve geolocation data
        res = http.get(self.GEO_URL)
        geo = http.json(res, schema=self._geo_schema)
        country_code = geo['reponse']['geo_info']['country_code']

        # Retrieve URL page and search for video ID
        res = http.get(self.url)
        if 'france.tv' in self.url:
            match = self._pluzz_video_id_re.search(res.text)
        elif 'ludo.fr' in self.url or 'zouzous.fr' in self.url:
            match = self._jeunesse_video_id_re.search(res.text)
        elif 'france3-regions.francetvinfo.fr' in self.url:
            match = self._f3_regions_video_id_re.search(res.text)
        elif 'sport.francetvinfo.fr' in self.url:
            match = self._sport_video_id_re.search(res.text)
        if match is None:
            return
        video_id = match.group('video_id')

        # Retrieve SWF player URL
        swf_url = None
        res = http.get(self.PLAYER_GENERATOR_URL)
        player_url = update_scheme(
            self.url,
            http.json(res, schema=self._player_schema)['result'])
        res = http.get(player_url)
        match = self._swf_re.search(res.text)
        if match is not None:
            swf_url = update_scheme(self.url, match.group(0))

        res = http.get(self.API_URL.format(video_id))
        videos = http.json(res, schema=self._api_schema)
        now = time.time()

        offline = False
        geolocked = False
        drm = False
        expired = False

        streams = []
        for video in videos['videos']:
            video_url = video['url']

            # Check whether video format is available
            if video['statut'] != 'ONLINE':
                offline = offline or True
                continue

            # Check whether video format is geo-locked
            if video['geoblocage'] is not None and country_code not in video[
                    'geoblocage']:
                geolocked = geolocked or True
                continue

            # Check whether video is DRM-protected
            if video['drm']:
                drm = drm or True
                continue

            # Check whether video format is expired
            available = False
            for interval in video['plages_ouverture']:
                available = (interval['debut'] or 0) <= now <= (interval['fin']
                                                                or sys.maxsize)
                if available:
                    break
            if not available:
                expired = expired or True
                continue

            # TODO: add DASH streams once supported
            if '.mpd' in video_url:
                continue

            if '.f4m' in video_url or 'france.tv' in self.url:
                res = http.get(self.TOKEN_URL.format(video_url))
                video_url = res.text

            if '.f4m' in video_url and swf_url is not None:
                for bitrate, stream in HDSStream.parse_manifest(
                        self.session, video_url, is_akamai=True,
                        pvswf=swf_url).items():
                    # HDS videos with data in their manifest fragment token
                    # doesn't seem to be supported by HDSStream. Ignore such
                    # stream (but HDS stream having only the hdntl parameter in
                    # their manifest token will be provided)
                    pvtoken = stream.request_params['params'].get(
                        'pvtoken', '')
                    match = self._hds_pv_data_re.search(pvtoken)
                    if match is None:
                        streams.append((bitrate, stream))
            elif '.m3u8' in video_url:
                for stream in HLSStream.parse_variant_playlist(
                        self.session, video_url).items():
                    streams.append(stream)
            # HBB TV streams are not provided anymore by France Televisions
            elif '.mp4' in video_url and '/hbbtv/' not in video_url:
                match = self._mp4_bitrate_re.match(video_url)
                if match is not None:
                    bitrate = match.group('bitrate')
                else:
                    # Fallback bitrate (seems all France Televisions MP4 videos
                    # seem have such bitrate)
                    bitrate = '1500k'
                streams.append((bitrate, HTTPStream(self.session, video_url)))

        if self.get_option("mux_subtitles") and videos['subtitles'] != []:
            substreams = {}
            for subtitle in videos['subtitles']:
                # TTML subtitles are available but not supported by FFmpeg
                if subtitle['format'] == 'ttml':
                    continue
                substreams[subtitle['type']] = HTTPStream(
                    self.session, subtitle['url'])

            for quality, stream in streams:
                yield quality, MuxedStream(self.session,
                                           stream,
                                           subtitles=substreams)
        else:
            for stream in streams:
                yield stream

        if offline:
            self.logger.error(
                'Failed to access stream, may be due to offline content')
        if geolocked:
            self.logger.error(
                'Failed to access stream, may be due to geo-restricted content'
            )
        if drm:
            self.logger.error(
                'Failed to access stream, may be due to DRM-protected content')
        if expired:
            self.logger.error(
                'Failed to access stream, may be due to expired content')
示例#26
0
class Schoolism(Plugin):
    url_re = re.compile(r"https?://(?:www\.)?schoolism\.com/watchLesson.php")
    login_url = "https://www.schoolism.com/index.php"
    key_time_url = "https://www.schoolism.com/video-html/key-time.php"
    playlist_re = re.compile(r"var allVideos=(\[\{.*\}]);", re.DOTALL)
    js_to_json = partial(re.compile(r'(?!<")(\w+):(?!/)').sub, r'"\1":')
    playlist_schema = validate.Schema(
        validate.transform(playlist_re.search),
        validate.any(
            None,
            validate.all(
                validate.get(1),
                validate.transform(js_to_json),
                validate.transform(lambda x: x.replace(",}", "}")),  # remove invalid ,
                validate.transform(parse_json),
                [{
                    "sources": validate.all([{
                        "playlistTitle": validate.text,
                        "title": validate.text,
                        "src": validate.text,
                        "type": validate.text,
                    }],
                        # only include HLS streams
                        validate.filter(lambda s: s["type"] == "application/x-mpegurl")
                    )
                }]
            )
        )
    )

    options = PluginOptions({
        "email": None,
        "password": None,
        "part": 1
    })

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    def login(self, email, password):
        """
        Login to the schoolism account and return the users account
        :param email: (str) email for account
        :param password: (str) password for account
        :return: (str) users email
        """
        if self.options.get("email") and self.options.get("password"):
            res = http.post(self.login_url, data={"email": email,
                                                  "password": password,
                                                  "redirect": None,
                                                  "submit": "Login"})

            if res.cookies.get("password") and res.cookies.get("email"):
                return res.cookies.get("email")
            else:
                self.logger.error("Failed to login to Schoolism, incorrect email/password combination")
        else:
            self.logger.error("An email and password are required to access Schoolism streams")

    def _get_streams(self):
        user = self.login(self.options.get("email"), self.options.get("password"))
        if user:
            self.logger.debug("Logged in to Schoolism as {0}", user)
            res = http.get(self.url, headers={"User-Agent": useragents.SAFARI_8})
            lesson_playlist = self.playlist_schema.validate(res.text)

            part = self.options.get("part")

            self.logger.info("Attempting to play lesson Part {0}", part)
            found = False

            # make request to key-time api, to get key specific headers
            res = http.get(self.key_time_url, headers={"User-Agent": useragents.SAFARI_8})

            for i, video in enumerate(lesson_playlist, 1):
                if video["sources"] and i == part:
                    found = True
                    for source in video["sources"]:
                        for s in HLSStream.parse_variant_playlist(self.session,
                                                                  source["src"],
                                                                  headers={"User-Agent": useragents.SAFARI_8,
                                                                           "Referer": self.url}).items():
                            yield s

            if not found:
                self.logger.error("Could not find lesson Part {0}", part)
示例#27
0
class Twitch(Plugin):
    options = PluginOptions({
        "cookie": None,
        "oauth_token": None,
        "disable_hosting": False,
    })

    @classmethod
    def stream_weight(cls, key):
        weight = QUALITY_WEIGHTS.get(key)
        if weight:
            return weight, "twitch"

        return Plugin.stream_weight(key)

    @classmethod
    def can_handle_url(cls, url):
        return _url_re.match(url)

    def __init__(self, url):
        Plugin.__init__(self, url)
        match = _url_re.match(url).groupdict()
        self._channel = match.get("channel") and match.get("channel").lower()
        self._channel_id = None
        self.subdomain = match.get("subdomain")
        self.video_type = match.get("video_type")
        if match.get("videos_id"):
            self.video_type = "v"
        self.video_id = match.get("video_id") or match.get("videos_id")
        self.clip_name = match.get("clip_name")
        self._hosted_chain = []

        parsed = urlparse(url)
        self.params = parse_query(parsed.query)

        self.api = TwitchAPI(beta=self.subdomain == "beta", version=5)
        self.usher = UsherService()

    @property
    def channel(self):
        if not self._channel:
            if self.video_id:
                cdata = self._channel_from_video_id(self.video_id)
                self._channel = cdata["name"].lower()
                self._channel_id = cdata["_id"]
        return self._channel

    @channel.setter
    def channel(self, channel):
        self._channel = channel
        # channel id becomes unknown
        self._channel_id = None

    @property
    def channel_id(self):
        if not self._channel_id:
            # If the channel name is set, use that to look up the ID
            if self._channel:
                cdata = self._channel_from_login(self._channel)
                self._channel_id = cdata["_id"]

            # If the channel name is not set but the video ID is,
            # use that to look up both ID and name
            elif self.video_id:
                cdata = self._channel_from_video_id(self.video_id)
                self._channel = cdata["name"].lower()
                self._channel_id = cdata["_id"]
        return self._channel_id

    def _channel_from_video_id(self, video_id):
        vdata = self.api.videos(video_id)
        if "channel" not in vdata:
            raise PluginError("Unable to find video: {0}".format(video_id))
        return vdata["channel"]

    def _channel_from_login(self, channel):
        cdata = self.api.users(login=channel)
        if len(cdata["users"]):
            return cdata["users"][0]
        else:
            raise PluginError("Unable to find channel: {0}".format(channel))

    def _authenticate(self):
        if self.api.oauth_token:
            return

        oauth_token = self.options.get("oauth_token")
        cookies = self.options.get("cookie")

        if oauth_token:
            self.logger.info("Attempting to authenticate using OAuth token")
            self.api.oauth_token = oauth_token
            user = self.api.user(schema=_user_schema)

            if user:
                self.logger.info("Successfully logged in as {0}", user)
            else:
                self.logger.error("Failed to authenticate, the access token "
                                  "is invalid or missing required scope")
        elif cookies:
            self.logger.info("Attempting to authenticate using cookies")

            self.api.add_cookies(cookies)
            self.api.oauth_token = self.api.token(schema=_viewer_token_schema)
            login = self.api.viewer_info(schema=_viewer_info_schema)

            if login:
                self.logger.info("Successfully logged in as {0}", login)
            else:
                self.logger.error("Failed to authenticate, your cookies "
                                  "may have expired")

    def _create_playlist_streams(self, videos):
        start_offset = int(videos.get("start_offset", 0))
        stop_offset = int(videos.get("end_offset", 0))
        streams = {}

        for quality, chunks in videos.get("chunks").items():
            if not chunks:
                if videos.get("restrictions", {}).get(quality) == "chansub":
                    self.logger.warning("The quality '{0}' is not available "
                                        "since it requires a subscription.",
                                        quality)
                continue

            # Rename 'live' to 'source'
            if quality == "live":
                quality = "source"

            chunks_filtered = list(filter(lambda c: c["url"], chunks))
            if len(chunks) != len(chunks_filtered):
                self.logger.warning("The video '{0}' contains invalid chunks. "
                                    "There will be missing data.", quality)
                chunks = chunks_filtered

            chunks_duration = sum(c.get("length") for c in chunks)

            # If it's a full broadcast we just use all the chunks
            if start_offset == 0 and chunks_duration == stop_offset:
                # No need to use the FLV concat if it's just one chunk
                if len(chunks) == 1:
                    url = chunks[0].get("url")
                    stream = HTTPStream(self.session, url)
                else:
                    chunks = [HTTPStream(self.session, c.get("url")) for c in chunks]
                    stream = FLVPlaylist(self.session, chunks,
                                         duration=chunks_duration)
            else:
                try:
                    stream = self._create_video_clip(chunks,
                                                     start_offset,
                                                     stop_offset)
                except StreamError as err:
                    self.logger.error("Error while creating video '{0}': {1}",
                                      quality, err)
                    continue

            streams[quality] = stream

        return streams

    def _create_video_clip(self, chunks, start_offset, stop_offset):
        playlist_duration = stop_offset - start_offset
        playlist_offset = 0
        playlist_streams = []
        playlist_tags = []

        for chunk in chunks:
            chunk_url = chunk["url"]
            chunk_length = chunk["length"]
            chunk_start = playlist_offset
            chunk_stop = chunk_start + chunk_length
            chunk_stream = HTTPStream(self.session, chunk_url)

            if chunk_start <= start_offset <= chunk_stop:
                try:
                    headers = extract_flv_header_tags(chunk_stream)
                except IOError as err:
                    raise StreamError("Error while parsing FLV: {0}", err)

                if not headers.metadata:
                    raise StreamError("Missing metadata tag in the first chunk")

                metadata = headers.metadata.data.value
                keyframes = metadata.get("keyframes")

                if not keyframes:
                    if chunk["upkeep"] == "fail":
                        raise StreamError("Unable to seek into muted chunk, try another timestamp")
                    else:
                        raise StreamError("Missing keyframes info in the first chunk")

                keyframe_offset = None
                keyframe_offsets = keyframes.get("filepositions")
                keyframe_times = [playlist_offset + t for t in keyframes.get("times")]
                for time, offset in zip(keyframe_times, keyframe_offsets):
                    if time > start_offset:
                        break

                    keyframe_offset = offset

                if keyframe_offset is None:
                    raise StreamError("Unable to find a keyframe to seek to "
                                      "in the first chunk")

                chunk_headers = dict(Range="bytes={0}-".format(int(keyframe_offset)))
                chunk_stream = HTTPStream(self.session, chunk_url,
                                          headers=chunk_headers)
                playlist_streams.append(chunk_stream)
                for tag in headers:
                    playlist_tags.append(tag)
            elif start_offset <= chunk_start < stop_offset:
                playlist_streams.append(chunk_stream)

            playlist_offset += chunk_length

        return FLVPlaylist(self.session, playlist_streams,
                           tags=playlist_tags, duration=playlist_duration)

    def _get_video_streams(self):
        self.logger.debug("Getting video steams for {} (type={})".format(self.video_id, self.video_type))
        self._authenticate()

        if self.video_type == "b":
            self.video_type = "a"

        try:
            videos = self.api.videos(self.video_type + self.video_id,
                                     schema=_video_schema)
        except PluginError as err:
            if "HTTP/1.1 0 ERROR" in str(err):
                raise NoStreamsError(self.url)
            else:
                raise

        # Parse the "t" query parameter on broadcasts and adjust
        # start offset if needed.
        time_offset = self.params.get("t")
        if time_offset:
            videos["start_offset"] += time_to_offset(self.params.get("t"))

        return self._create_playlist_streams(videos)

    def _access_token(self, type="live"):
        try:
            if type == "live":
                endpoint = "channels"
                value = self.channel
            elif type == "video":
                endpoint = "vods"
                value = self.video_id

            sig, token = self.api.access_token(endpoint, value,
                                               schema=_access_token_schema)
        except PluginError as err:
            if "404 Client Error" in str(err):
                raise NoStreamsError(self.url)
            else:
                raise

        return sig, token

    def _check_for_host(self):
        host_info = self.api.hosted_channel(include_logins=1, host=self.channel_id).json()["hosts"][0]
        if "target_login" in host_info and host_info["target_login"].lower() != self.channel.lower():
            self.logger.info("{0} is hosting {1}".format(self.channel, host_info["target_login"]))
            return host_info["target_login"]

    def _get_hls_streams(self, stream_type="live"):
        self.logger.debug("Getting {} HLS streams for {}".format(stream_type, self.channel))
        self._authenticate()
        self._hosted_chain.append(self.channel)

        if stream_type == "live":
            hosted_channel = self._check_for_host()
            if hosted_channel and self.options.get("disable_hosting"):
                self.logger.info("hosting was disabled by command line option")
            elif hosted_channel:
                self.logger.info("switching to {}", hosted_channel)
                if hosted_channel in self._hosted_chain:
                    self.logger.error(u"A loop of hosted channels has been detected, "
                                      "cannot find a playable stream. ({})".format(u" -> ".join(self._hosted_chain + [hosted_channel])))
                    return {}
                self.channel = hosted_channel
                return self._get_hls_streams(stream_type)

            # only get the token once the channel has been resolved
            sig, token = self._access_token(stream_type)
            url = self.usher.channel(self.channel, sig=sig, token=token)
        elif stream_type == "video":
            sig, token = self._access_token(stream_type)
            url = self.usher.video(self.video_id, nauthsig=sig, nauth=token)
        else:
            self.logger.debug("Unknown HLS stream type: {}".format(stream_type))
            return {}

        try:
            # If the stream is a VOD that is still being recorded the stream should start at the
            # beginning of the recording
            streams = HLSStream.parse_variant_playlist(self.session, url,
                                                       force_restart=not stream_type == "live")
        except IOError as err:
            err = str(err)
            if "404 Client Error" in err or "Failed to parse playlist" in err:
                return
            else:
                raise PluginError(err)

        try:
            token = parse_json(token, schema=_token_schema)
            for name in token["restricted_bitrates"]:
                if name not in streams:
                    self.logger.warning("The quality '{0}' is not available "
                                        "since it requires a subscription.",
                                        name)
        except PluginError:
            pass

        return streams

    def _get_clips(self):
        quality_options = self.api.clip_status(self.channel, self.clip_name, schema=_quality_options_schema)
        streams = {}
        for quality_option in quality_options:
            streams[quality_option["quality"]] = HTTPStream(self.session, quality_option["source"])
        return streams

    def _get_streams(self):
        if self.video_id:
            if self.video_type == "v":
                return self._get_hls_streams("video")
            else:
                return self._get_video_streams()
        elif self.clip_name:
            return self._get_clips()
        else:
            return self._get_hls_streams("live")
示例#28
0
class Pixiv(Plugin):
    """Plugin for https://sketch.pixiv.net/lives"""

    _url_re = re.compile(
        r"https?://sketch\.pixiv\.net/[^/]+(?P<videopage>/lives/\d+)?")

    _videopage_re = re.compile(
        r"""["']live-button["']><a\shref=["'](?P<path>[^"']+)["']""")
    _data_re = re.compile(
        r"""<script\sid=["']state["']>[^><{]+(?P<data>{[^><]+})</script>""")
    _post_key_re = re.compile(
        r"""name=["']post_key["']\svalue=["'](?P<data>[^"']+)["']""")

    _data_schema = validate.Schema(
        validate.all(
            validate.transform(_data_re.search),
            validate.any(
                None,
                validate.all(
                    validate.get("data"),
                    validate.transform(parse_json),
                    validate.get("context"),
                    validate.get("dispatcher"),
                    validate.get("stores"),
                ))))

    login_url_get = "https://accounts.pixiv.net/login"
    login_url_post = "https://accounts.pixiv.net/api/login"

    options = PluginOptions({"username": None, "password": None})

    @classmethod
    def can_handle_url(cls, url):
        return cls._url_re.match(url) is not None

    def find_videopage(self):
        self.logger.debug("Not a videopage")
        res = http.get(self.url)

        m = self._videopage_re.search(res.text)
        if not m:
            self.logger.debug(
                "No stream path, stream might be offline or invalid url.")
            raise NoStreamsError(self.url)

        path = m.group("path")
        self.logger.debug("Found new path: {0}".format(path))
        return urljoin(self.url, path)

    def _login(self, username, password):
        res = http.get(self.login_url_get)
        m = self._post_key_re.search(res.text)
        if not m:
            raise PluginError("Missing post_key, no login posible.")

        post_key = m.group("data")
        data = {
            "lang": "en",
            "source": "sketch",
            "post_key": post_key,
            "pixiv_id": username,
            "password": password,
        }

        res = http.post(self.login_url_post, data=data)
        res = http.json(res)

        if res["body"].get("success"):
            return True
        else:
            return False

    def _get_streams(self):
        http.headers = {"User-Agent": useragents.FIREFOX}

        login_username = self.get_option("username")
        login_password = self.get_option("password")
        if login_username and login_password:
            self.logger.debug("Attempting login as {0}".format(login_username))
            if self._login(login_username, login_password):
                self.logger.info(
                    "Successfully logged in as {0}".format(login_username))
            else:
                self.logger.info(
                    "Failed to login as {0}".format(login_username))

        videopage = self._url_re.match(self.url).group("videopage")
        if not videopage:
            self.url = self.find_videopage()

        data = http.get(self.url, schema=self._data_schema)

        if not data.get("LiveStore"):
            self.logger.debug("No video url found, stream might be offline.")
            return

        data = data["LiveStore"]["lives"]

        # get the unknown user-id
        for _key in data.keys():
            video_data = data.get(_key)

        owner = video_data["owner"]
        self.logger.info("Owner ID: {0}".format(owner["user_id"]))
        self.logger.debug("HLS URL: {0}".format(owner["hls_movie"]))
        for n, s in HLSStream.parse_variant_playlist(
                self.session, owner["hls_movie"]).items():
            yield n, s

        performers = video_data.get("performers")
        if performers:
            for p in performers:
                self.logger.info("CO-HOST ID: {0}".format(p["user_id"]))
                hls_url = p["hls_movie"]
                self.logger.debug("HLS URL: {0}".format(hls_url))
                for n, s in HLSStream.parse_variant_playlist(
                        self.session, hls_url).items():
                    _n = "{0}_{1}".format(n, p["user_id"])
                    yield _n, s
示例#29
0
class Neulion(Plugin):
    """Streamlink Plugin for websites based on Neulion
       Example urls can be found in tests/test_plugin_neulion.py
    """

    url_re = re.compile(
        r"""https?://
        (?P<domain>
            www\.(?:
                ufc\.tv
                |
                elevensports\.(?:be|lu|pl|sg|tw)
                |
                tennischanneleverywhere\.com
                )
            |
            watch\.(?:
                nba\.com
                |
                rugbypass\.com
                )
            |
            fanpass\.co\.nz
        )
        /(?P<vtype>channel|game|video)/.+""", re.VERBOSE)
    video_info_re = re.compile(r"""program\s*=\s*(\{.*?});""", re.DOTALL)
    channel_info_re = re.compile(r"""g_channel\s*=\s(\{.*?});""", re.DOTALL)
    current_video_re = re.compile(
        r"""(?:currentVideo|video)\s*=\s*(\{[^;]+});""", re.DOTALL)
    info_fallback_re = re.compile(
        r"""
        var\s?
        (?:
            currentGameId
        |
            programId
        )
        \s?=\s?["']?(?P<id>\d+)["']?;
        """, re.VERBOSE)

    stream_api_url = "https://{0}/service/publishpoint"
    auth_url = "https://{0}/secure/authenticate"
    auth_schema = validate.Schema(validate.xml_findtext("code"))

    options = PluginOptions({"username": None, "password": None})

    @classmethod
    def can_handle_url(cls, url):
        return cls.url_re.match(url) is not None

    @property
    def _domain(self):
        match = self.url_re.match(self.url)
        return match.group("domain")

    @property
    def _vtype(self):
        match = self.url_re.match(self.url)
        return match.group("vtype")

    def _get_stream_url(self, video_id, vtype):
        try:
            res = http.post(self.stream_api_url.format(self._domain),
                            data={
                                "id": video_id,
                                "type": vtype,
                                "format": "json"
                            },
                            headers={"User-Agent": useragents.IPHONE_6})
        except Exception as e:
            if "400 Client Error" in str(e):
                self.logger.error("Login required")
                return
            else:
                raise e

        data = http.json(res)
        return data.get("path")

    def _get_info(self, text):
        # try to find video info first
        m = self.video_info_re.search(text)
        if not m:
            m = self.current_video_re.search(text)
        if not m:
            # and channel info if that fails
            m = self.channel_info_re.search(text)
        if m:
            js_data = m.group(1)
            try:
                return_data = js_to_json(js_data)
                self.logger.debug("js_to_json")
            except Exception as e:
                self.logger.debug("js_to_json_regex_fallback")
                return_data = js_to_json_regex_fallback(js_data)
            finally:
                return return_data

    def _get_info_fallback(self, text):
        info_id = self.info_fallback_re.search(text)
        if info_id:
            self.logger.debug("Found id from _get_info_fallback")
            return {"id": info_id.group("id")}

    def _login(self, username, password):
        res = http.post(self.auth_url.format(self._domain),
                        data={
                            "username": username,
                            "password": password,
                            "cookielink": False
                        })
        login_status = http.xml(res, schema=self.auth_schema)
        self.logger.debug("Login status for {0}: {1}", username, login_status)
        if login_status == "loginlocked":
            self.logger.error(
                "The account {0} has been locked, the password needs to be reset"
            )
        return login_status == "loginsuccess"

    def _get_streams(self):
        login_username = self.get_option("username")
        login_password = self.get_option("password")
        if login_username and login_password:
            self.logger.debug("Attempting login as {0}", login_username)
            if self._login(login_username, login_password):
                self.logger.info("Successfully logged in as {0}",
                                 login_username)
            else:
                self.logger.info("Failed to login as {0}", login_username)

        res = http.get(self.url)
        video = self._get_info(res.text)
        if not video:
            video = self._get_info_fallback(res.text)

        if video:
            self.logger.debug("Found {type}: {name}".format(
                type=video.get("type", self._vtype),
                name=video.get("name", "???")))
            surl = self._get_stream_url(video["id"],
                                        video.get("type", self._vtype))
            if surl:
                surl = surl.replace("_iphone", "")
                return HLSStream.parse_variant_playlist(self.session, surl)
            else:
                self.logger.error(
                    "Could not get stream URL for video: {name} ({id})".format(
                        id=video.get("id", "???"),
                        name=video.get("name", "???"),
                    ))
        else:
            self.logger.error("Could not find any video info on the page")
示例#30
0
class UStreamTV(Plugin):
    options = PluginOptions({"password": ""})

    @classmethod
    def can_handle_url(cls, url):
        return _url_re.match(url)

    @classmethod
    def stream_weight(cls, stream):
        match = re.match("mobile_(\w+)", stream)
        if match:
            weight, group = Plugin.stream_weight(match.group(1))
            weight -= 1
            group = "mobile_ustream"
        elif stream == "recorded":
            weight, group = 720, "ustream"
        else:
            weight, group = Plugin.stream_weight(stream)

        return weight, group

    def _get_channel_id(self):
        res = http.get(self.url)
        match = _channel_id_re.search(res.text)
        if match:
            return int(match.group(1))

    def _get_hls_streams(self, channel_id, wait_for_transcode=False):
        # HLS streams are created on demand, so we may have to wait
        # for a transcode to be started.
        attempts = wait_for_transcode and 10 or 1
        playlist_url = HLS_PLAYLIST_URL.format(channel_id)
        streams = {}
        while attempts and not streams:
            try:
                streams = HLSStream.parse_variant_playlist(
                    self.session, playlist_url, nameprefix="mobile_")
            except IOError:
                # Channel is probably offline
                break

            attempts -= 1
            sleep(3)

        return streams

    def _create_rtmp_stream(self, cdn, stream_name):
        parsed = urlparse(cdn)
        params = {
            "rtmp": cdn,
            "app": parsed.path[1:],
            "playpath": stream_name,
            "pageUrl": self.url,
            "swfUrl": SWF_URL,
            "live": True
        }

        return RTMPStream(self.session, params)

    def _get_module_info(self, app, media_id, password="", schema=None):
        self.logger.debug("Waiting for moduleInfo invoke")
        conn = create_ums_connection(app, media_id, self.url, password)

        attempts = 3
        while conn.connected and attempts:
            try:
                result = conn.process_packets(invoked_method="moduleInfo",
                                              timeout=10)
            except (IOError, librtmp.RTMPError) as err:
                raise PluginError("Failed to get stream info: {0}".format(err))

            try:
                result = _module_info_schema.validate(result)
                break
            except PluginError:
                attempts -= 1

        conn.close()

        if schema:
            result = schema.validate(result)

        return result

    def _get_desktop_streams(self, channel_id):
        password = self.options.get("password")
        channel = self._get_module_info("channel",
                                        channel_id,
                                        password,
                                        schema=_channel_schema)

        if not isinstance(channel.get("stream"), list):
            raise NoStreamsError(self.url)

        streams = {}
        for provider in channel["stream"]:
            try:
                provider_url = provider["url"]
                provider_name = provider["name"]
            except KeyError:
                continue
            for stream_index, stream_info in enumerate(provider["streams"]):
                if not isinstance(stream_info, dict):
                    continue
                stream = None
                stream_height = int(stream_info.get("height", 0))
                stream_name = stream_info.get("description")
                if not stream_name:
                    if stream_height > 0:
                        if not stream_info.get("isTranscoded"):
                            stream_name = "{0}p+".format(stream_height)
                        else:
                            stream_name = "{0}p".format(stream_height)
                    else:
                        stream_name = "live"

                if stream_name in streams:
                    provider_name_clean = provider_name.replace("uhs_", "")
                    stream_name += "_alt_{0}".format(provider_name_clean)

                if provider_name.startswith("uhs_"):
                    stream = UHSStream(self.session, channel_id, self.url,
                                       provider_name, stream_index, password)
                elif provider_url.startswith("rtmp"):
                    playpath = stream_info["streamName"]
                    stream = self._create_rtmp_stream(provider_url, playpath)

                if stream:
                    streams[stream_name] = stream

        return streams

    def _get_live_streams(self, channel_id):
        has_desktop_streams = False
        if HAS_LIBRTMP:
            try:
                streams = self._get_desktop_streams(channel_id)
                # TODO: Replace with "yield from" when dropping Python 2.
                for stream in streams.items():
                    has_desktop_streams = True
                    yield stream
            except PluginError as err:
                self.logger.error("Unable to fetch desktop streams: {0}", err)
            except NoStreamsError:
                pass
        else:
            self.logger.warning(
                "python-librtmp is not installed, but is needed to access "
                "the desktop streams")

        try:
            streams = self._get_hls_streams(
                channel_id, wait_for_transcode=not has_desktop_streams)

            # TODO: Replace with "yield from" when dropping Python 2.
            for stream in streams.items():
                yield stream
        except PluginError as err:
            self.logger.error("Unable to fetch mobile streams: {0}", err)
        except NoStreamsError:
            pass

    def _get_recorded_streams(self, video_id):
        if HAS_LIBRTMP:
            recording = self._get_module_info("recorded",
                                              video_id,
                                              schema=_recorded_schema)

            if not isinstance(recording.get("stream"), list):
                return

            for provider in recording["stream"]:
                base_url = provider.get("url")
                for stream_info in provider["streams"]:
                    bitrate = int(stream_info.get("bitrate", 0))
                    stream_name = (bitrate > 0 and "{0}k".format(bitrate)
                                   or "recorded")

                    url = stream_info["streamName"]
                    if base_url:
                        url = base_url + url

                    if url.startswith("http"):
                        yield stream_name, HTTPStream(self.session, url)
                    elif url.startswith("rtmp"):
                        params = dict(rtmp=url, pageUrl=self.url)
                        yield stream_name, RTMPStream(self.session, params)

        else:
            self.logger.warning(
                "The proper API could not be used without python-librtmp "
                "installed. Stream URL is not guaranteed to be valid")

            url = RECORDED_URL.format(video_id)
            random_hash = "{0:02x}{1:02x}".format(randint(0, 255),
                                                  randint(0, 255))
            params = dict(hash=random_hash)
            stream = HTTPStream(self.session, url, params=params)
            yield "recorded", stream

    def _get_streams(self):
        match = _url_re.match(self.url)

        video_id = match.group("video_id")
        if video_id:
            return self._get_recorded_streams(video_id)

        channel_id = match.group("channel_id") or self._get_channel_id()
        if channel_id:
            return self._get_live_streams(channel_id)