int(ts[-6:-5] + "1")) _url_re = re.compile( r""" http(s)?://(\w+\.)?crunchyroll\. (?: com|de|es|fr|co.jp ) (?:/[^/&?]+)? /[^/&?]+-(?P<media_id>\d+) """, re.VERBOSE) _api_schema = validate.Schema({ "error": bool, validate.optional("code"): validate.text, validate.optional("message"): validate.text, validate.optional("data"): object, }) _media_schema = validate.Schema( { "stream_data": validate.any( None, { "streams": validate.all([{ "quality": validate.any(validate.text, None), "url": validate.url(scheme="http", path=validate.endswith(".m3u8")),
def parse_fmt_list(formatsmap): formats = {} if not formatsmap: return formats for format in formatsmap.split(","): s = format.split("/") (w, h) = s[1].split("x") formats[int(s[0])] = "{0}p".format(h) return formats _config_schema = validate.Schema( { validate.optional("fmt_list"): validate.all( validate.text, validate.transform(parse_fmt_list) ), validate.optional("url_encoded_fmt_stream_map"): validate.all( validate.text, validate.transform(parse_stream_map), [{ "itag": validate.all( validate.text, validate.transform(int) ), "quality": validate.text, "url": validate.url(scheme="http"), validate.optional("s"): validate.text, validate.optional("stereo3d"): validate.all(
from streamlink.plugin import Plugin from streamlink.plugin.api import http, validate from streamlink.stream import FLVPlaylist, HTTPStream API_URL = "http://veetle.com/index.php/stream/ajaxStreamLocation/{0}/flash" _url_re = re.compile(""" http(s)?://(\w+\.)?veetle.com (:? /.*(v|view)/ (?P<channel>[^/]+/[^/&?]+) )? """, re.VERBOSE) _schema = validate.Schema({ validate.optional("isLive"): bool, "payload": validate.any(int, validate.url(scheme="http")), "success": bool }) class Veetle(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_streams(self): self.url = http.resolve_url(self.url) match = _url_re.match(self.url) parsed = urlparse(self.url) if parsed.fragment:
_channel_schema = validate.Schema( { "CHANNEL": { "RESULT": validate.transform(int), "BROAD_INFOS": [{ "list": [{ "nBroadNo": validate.text }] }] } }, validate.get("CHANNEL") ) _stream_schema = validate.Schema( { validate.optional("view_url"): validate.url( scheme=validate.any("rtmp", "http") ) } ) class AfreecaTV(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_channel_info(self, username): headers = { "Referer": self.url }
validate.get("Streams") ) ], "Server": validate.text }], validate.filter(lambda s: s["LinkType"] in STREAMING_TYPES) ) }] }, validate.get("Data", {}) ) _video_schema = validate.Schema( { "Data": [{ "Assets": validate.all( [{ validate.optional("Links"): validate.all( [{ "Target": validate.text, "Uri": validate.text }], validate.filter(lambda l: l["Target"] in STREAMING_TYPES) )}], validate.filter(lambda a: "Links" in a) ) }]}, validate.get("Data", {}), validate.get(0, {}), validate.get("Assets", {}), validate.get(0, {}), validate.get("Links", []), )
def parse_fmt_list(formatsmap): formats = {} if not formatsmap: return formats for format in formatsmap.split(","): s = format.split("/") (w, h) = s[1].split("x") formats[int(s[0])] = "{0}p".format(h) return formats _config_schema = validate.Schema( { validate.optional("fmt_list"): validate.all( validate.text, validate.transform(parse_fmt_list) ), validate.optional("url_encoded_fmt_stream_map"): validate.all( validate.text, validate.transform(parse_stream_map), [{ "itag": validate.all( validate.text, validate.transform(int) ), "quality": validate.text, "url": validate.url(scheme="http"), validate.optional("s"): validate.text, validate.optional("stereo3d"): validate.all(
validate.all( validate.get(2), validate.url(scheme="http") ) ) ) _video_schema = validate.Schema({ "videoJsonPlayer": { "VSR": validate.any( [], { validate.text: { "height": int, "mediaType": validate.text, "url": validate.text, validate.optional("streamer"): validate.text }, }, ), "VTY": validate.text } }) class ArteTV(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _create_stream(self, stream, is_live): stream_name = "{0}p".format(stream["height"])
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"]))
class BBCiPlayer(Plugin): """ Allows streaming of live channels from bbc.co.uk/iplayer/live/* and of iPlayer programmes from bbc.co.uk/iplayer/episode/* """ mediator_re = re.compile(r'window\.__IPLAYER_REDUX_STATE__\s*=\s*({.*?});', re.DOTALL) state_re = re.compile( r'window.__IPLAYER_REDUX_STATE__\s*=\s*({.*?});</script>') account_locals_re = re.compile(r'window.bbcAccount.locals\s*=\s*({.*?});') hash = base64.b64decode( b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==") api_url = "https://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({"versions": [{ "id": validate.text }]}, validate.get("versions"), validate.get(0), validate.get("id")) mediaselector_schema = validate.Schema( validate.transform(parse_json), { "media": [{ "connection": validate.all( [{ validate.optional("href"): validate.url(), validate.optional("transferFormat"): validate.text }], validate.filter(lambda c: c.get("href"))), "kind": validate.text }] }, validate.get("media"), validate.filter(lambda x: x["kind"] == "video")) arguments = PluginArguments( PluginArgument("username", requires=["password"], metavar="USERNAME", help="The username used to register with bbc.co.uk."), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help= "A bbc.co.uk account password to use with --bbciplayer-username.", prompt="Enter bbc.co.uk account password"), PluginArgument("hd", action="store_true", help=""" Prefer HD streams over local SD streams, some live programmes may not be broadcast in HD. """), ) @classmethod def _hash_vpid(cls, vpid): return sha1(cls.hash + str(vpid).encode("utf8")).hexdigest() 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 """ log.debug(f"Looking for vpid on {url}") # Use pre-fetched page if available res = res or self.session.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, master=False): log.debug("Looking for {0} tvip on {1}".format( "master" if master else "", url)) res = self.session.http.get(url) m = self.state_re.search(res.text) data = m and parse_json(m.group(1)) if data: channel = data.get("channel") if master: return channel.get("masterBrand") return channel.get("id") def mediaselector(self, vpid): urls = defaultdict(set) for platform in self.platforms: url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid), platform=platform) log.debug(f"Info API request: {url}") medias = self.session.http.get(url, schema=self.mediaselector_schema) for media in medias: for connection in media["connection"]: urls[connection.get("transferFormat")].add( connection["href"]) for stream_type, urls in urls.items(): log.debug(f"{len(urls)} {stream_type} streams") for url in list(urls): try: if stream_type == "hds": yield from HDSStream.parse_manifest(self.session, url).items() if stream_type == "hls": yield from HLSStream.parse_variant_playlist( self.session, url).items() if stream_type == "dash": yield from DASHStream.parse_manifest( self.session, url).items() log.debug(f" OK: {url}") except Exception: log.debug(f" FAIL: {url}") 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 """ def auth_check(res): return ptrt_url in ([h.url for h in res.history] + [res.url]) # make the session request to get the correct cookies session_res = self.session.http.get(self.session_url, params=dict(ptrt=ptrt_url)) if auth_check(session_res): log.debug("Already authenticated, skipping authentication") return True res = self.session.http.post(self.auth_url, params=urlparse(session_res.url).query, data=dict( jsEnabled=True, username=self.get_option("username"), password=self.get_option('password'), attempts=0), headers={"Referer": self.url}) return auth_check(res) def _get_streams(self): if not self.get_option("username"): log.error("BBC iPlayer requires an account you must login using " "--bbciplayer-username and --bbciplayer-password") return log.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): log.error( "Could not authenticate, check your username and password") return episode_id = self.match.group("episode_id") channel_name = self.match.group("channel_name") if episode_id: log.debug(f"Loading streams for episode: {episode_id}") vpid = self.find_vpid(self.url) if vpid: log.debug(f"Found VPID: {vpid}") yield from self.mediaselector(vpid) else: log.error(f"Could not find VPID for episode {episode_id}") elif channel_name: log.debug(f"Loading stream for live channel: {channel_name}") if self.get_option("hd"): tvip = self.find_tvip(self.url, master=True) + "_hd" if tvip: log.debug(f"Trying HD stream {tvip}...") try: yield from self.mediaselector(tvip) except PluginError: log.error( "Failed to get HD streams, falling back to SD") else: return tvip = self.find_tvip(self.url) if tvip: log.debug(f"Found TVIP: {tvip}") yield from self.mediaselector(tvip)
class OPENRECtv(Plugin): _url_re = re.compile(r"https?://(?:www\.)?openrec.tv/(?:live|movie)/(?P<id>[^/]+)") _stores_re = re.compile(r"window.stores\s*=\s*({.*?});", re.DOTALL | re.MULTILINE) _config_re = re.compile(r"window.sharedConfig\s*=\s*({.*?});", re.DOTALL | re.MULTILINE) api_url = "https://apiv5.openrec.tv/api/v5/movies/{id}/detail" login_url = "https://www.openrec.tv/viewapp/v4/mobile/user/login" _config_schema = validate.Schema({ "urls": { "apiv5Authorized": validate.url() } }) _stores_schema = validate.Schema({ "moviePageStore": { "movieStore": { "id": validate.text, "title": validate.text, "media": { "url": validate.any(None, '', validate.url()) } } } }, validate.get("moviePageStore"), validate.get("movieStore")) _detail_schema = validate.Schema({ validate.optional("error_message"): validate.text, "status": int, validate.optional("data"): { "type": validate.text, "items": [{ "media": { "url": validate.any(None, validate.url()), "url_dvr": validate.any(None, validate.url()) } }] } }) _login_schema = validate.Schema({ validate.optional("error_message"): validate.text, "status": int, validate.optional("data"): object }) arguments = PluginArguments( PluginArgument( "email", requires=["password"], metavar="EMAIL", help=""" The email associated with your openrectv account, required to access any openrectv stream. """), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help=""" An openrectv account password to use with --openrectv-email. """) ) def __init__(self, url): super().__init__(url) self._pdata = None self._pres = None self._pconfig = None @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def login(self, email, password): res = self.session.http.post(self.login_url, data={"mail": email, "password": password}) data = self.session.http.json(res, self._login_schema) if data["status"] == 0: log.debug("Logged in as {0}".format(data["data"]["user_name"])) else: log.error("Failed to login: {0}".format(data["error_message"])) return data["status"] == 0 def _get_page(self): if not self._pres: self._pres = self.session.http.get(self.url) return self._pres def _get_movie_data(self): pres = self._get_page() match = self._stores_re.search(pres.text) if match: self._pdata = parse_json(match.group(1), schema=self._stores_schema) return self._pdata def _get_page_config(self): pres = self._get_page() match = self._config_re.search(pres.text) if match: self._pconfig = parse_json(match.group(1)) return self._pconfig def _get_details(self, id): config = self._get_page_config() api_url = config["urls"]["apiv5Authorized"] url = "{base}/movies/{id}/detail".format(base=api_url, id=id) res = self.session.http.get(url, headers={ "access-token": self.session.http.cookies.get("access_token"), "uuid": self.session.http.cookies.get("uuid") }) data = self.session.http.json(res, schema=self._detail_schema) if data["status"] == 0: log.debug("Got valid detail response") return data["data"] else: log.error("Failed to get video stream: {0}".format(data["error_message"])) def get_title(self): mdata = self._get_movie_data() if mdata: return mdata["title"] def _get_streams(self): mdata = self._get_movie_data() if mdata: log.debug("Found video: {0} ({1})".format(mdata["title"], mdata["id"])) if mdata["media"]["url"]: yield from HLSStream.parse_variant_playlist(self.session, mdata["media"]["url"]).items() elif self.get_option("email") and self.get_option("password"): if self.login(self.get_option("email"), self.get_option("password")): details = self._get_details(mdata["id"]) if details: for item in details["items"]: yield from HLSStream.parse_variant_playlist(self.session, item["media"]["url"]).items() else: log.error("You must login to access this stream")
class LivespottingTV(Plugin): _player_re = re.compile(r"player_id:\s*'(\w+)',\s*livesource_id:\s*'(\w+)'") _URL_PLAYER_CONFIG = "https://player.livespotting.com/v1/config/{player_id}.json" _URL_PLAYER_SHOWROOM = "https://player.livespotting.com/v2/livesource/{livesource_id}?type=showroom" _playlist_schema = validate.Schema({ "id": validate.text, "playlist": validate.url(scheme="http"), "playlist_mode": validate.text, "weather_live_enable": bool, }) _sources_schema = validate.Schema([{ validate.optional("mediaid"): validate.text, validate.optional("title"): validate.text, validate.optional("livestream"): validate.all(validate.url(scheme="http"), validate.contains(".m3u8")), "sources": [{"file": validate.all(validate.url(scheme="http"), validate.contains(".m3u8"))}], }]) _livesource_schema = validate.Schema({ "id": validate.text, "source": validate.all(validate.url(scheme="http"), validate.contains(".m3u8")), }) def _get_streams(self): source_id = self.match.group(1) res = self.session.http.get(self.url) m = self._player_re.search(res.text) if m: _player_id, _source_id = m.groups() if _source_id == source_id: config_url = self._URL_PLAYER_CONFIG.format(player_id=_player_id) log.debug("config_url: {0}".format(config_url)) res = self.session.http.get(config_url) res = self.session.http.json(res, schema=self._playlist_schema) log.debug("playlist_mode: {0}".format(res["playlist_mode"])) log.debug("weather_live_enable: {0}".format(res["weather_live_enable"])) if res["playlist_mode"] == "showroom": playlist_url = self._URL_PLAYER_SHOWROOM.format(livesource_id=_source_id) _schema = self._livesource_schema else: playlist_url = res["playlist"] _schema = self._sources_schema log.debug("playlist_url: {0}".format(playlist_url)) res = self.session.http.get(playlist_url) res = self.session.http.json(res, schema=_schema) log.trace("sources: {0!r}".format(res)) for source in res if isinstance(res, list) else [res]: _id = source.get("mediaid") or _source_id if _id == _source_id: title = source.get("title", "N/A") log.debug("title: {0}".format(title)) if source.get("livestream"): for s in HLSStream.parse_variant_playlist(self.session, source["livestream"]).items(): yield s else: log.debug("No 'livestream' source found, trying alt. sources") sources = source.get("sources") or [{"file": source["source"]}] for file in sources: for s in HLSStream.parse_variant_playlist(self.session, file["file"]).items(): yield s
class Ceskatelevize(Plugin): ajax_url = 'https://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist' _url_re = re.compile(r'http(s)?://([^.]*.)?ceskatelevize.cz') _player_re = re.compile(r'ivysilani/embed/iFramePlayer[^"]+') _hash_re = re.compile(r'hash:"([0-9a-z]+)"') _playlist_info_re = re.compile(r'{"type":"([a-z]+)","id":"([0-9]+)"') _playlist_url_schema = validate.Schema({ validate.optional("streamingProtocol"): validate.text, "url": validate.any(validate.url(), "Error", "error_region") }) _playlist_schema = validate.Schema({ "playlist": [{ validate.optional("type"): validate.text, "streamUrls": { "main": validate.url(), } }] }) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) def _get_streams(self): self.session.http.headers.update({'User-Agent': useragents.IPAD}) self.session.http.verify = False log.warning('SSL certificate verification is disabled.') # fetch requested url and find playlist info response = self.session.http.get(self.url) info = self._find_playlist_info(response) if not info: # do next try with new API def _fallback_api(*args, **kwargs): self.api2 = CeskatelevizeAPI2(self.session, self.url, *args, **kwargs) return self.api2._get_streams() # playlist info not found, let's try to find player url player_url = self._find_player_url(response) if not player_url: log.debug( 'Cannot find playlist info or player url, do next try with new API' ) return _fallback_api(res=response) # get player url and try to find playlist info in it response = self.session.http.get(player_url) info = self._find_playlist_info(response) if not info: log.debug( 'Cannot find playlist info in the player url, do next try with new API' ) return _fallback_api() log.trace('{0!r}'.format(info)) data = { 'playlist[0][type]': info['type'], 'playlist[0][id]': info['id'], 'requestUrl': '/ivysilani/embed/iFramePlayer.php', 'requestSource': 'iVysilani', 'type': 'html' } headers = { 'x-addr': '127.0.0.1', } # fetch playlist url response = self.session.http.post(self.ajax_url, data=data, headers=headers) json_data = self.session.http.json(response, schema=self._playlist_url_schema) log.trace('{0!r}'.format(json_data)) if json_data['url'] in ['Error', 'error_region']: log.error('This stream is not available') return # fetch playlist response = self.session.http.post(json_data['url']) json_data = self.session.http.json(response, schema=self._playlist_schema) log.trace('{0!r}'.format(json_data)) playlist = json_data['playlist'][0]['streamUrls']['main'] return HLSStream.parse_variant_playlist(self.session, playlist) @classmethod def _find_playlist_info(cls, response): """ Finds playlist info (type, id) in HTTP response. :param response: Response object. :returns: Dictionary with type and id. """ values = {} matches = cls._playlist_info_re.search(response.text) if matches: values['type'] = matches.group(1) values['id'] = matches.group(2) return values @classmethod def _find_player_url(cls, response): """ Finds embedded player url in HTTP response. :param response: Response object. :returns: Player url (str). """ url = '' matches = cls._player_re.search(response.text) if matches: tmp_url = matches.group(0).replace('&', '&') if 'hash' not in tmp_url: # there's no hash in the URL, try to find it matches = cls._hash_re.search(response.text) if matches: url = tmp_url + '&hash=' + matches.group(1) else: url = tmp_url return 'http://ceskatelevize.cz/' + url
class CeskatelevizeAPI2: _player_api = 'https://playlist.ceskatelevize.cz/' _url_re = re.compile(r'http(s)?://([^.]*.)?ceskatelevize.cz') _playlist_info_re = re.compile( r'{\s*"type":\s*"([a-z]+)",\s*"id":\s*"(\w+)"') _playlist_schema = validate.Schema({ "CODE": validate.contains("OK"), "RESULT": { "playlist": [{ "streamUrls": { "main": validate.url(), } }] } }) _ctcomp_re = re.compile( r'data-ctcomp="Video"\sdata-video-id="(?P<val1>[^"]*)"\sdata-ctcomp-data="(?P<val2>[^"]+)">' ) _ctcomp_schema = validate.Schema( validate.text, validate.transform(_ctcomp_re.findall), validate.transform( lambda vl: [{ "video-id": v[0], "ctcomp-data": json.loads(html_unescape(v[1])) } for v in vl])) _playlist_info_schema = validate.Schema({ "type": validate.text, "id": validate.any(validate.text, int), "key": validate.text, "date": validate.text, "requestSource": validate.text, "drm": int, validate.optional("canBePlay"): int, validate.optional("assetId"): validate.text, "quality": validate.text, validate.optional("region"): int }) def __init__(self, session, url, res=None): self.session = session self.url = url self.response = res def _get_streams(self): if self.response is None: infos = self.session.http.get(self.url, schema=self._ctcomp_schema) else: infos = self.session.http.json(self.response, schema=self._ctcomp_schema) if not infos: # playlist infos not found raise PluginError('Cannot find playlist infos!') vod_prio = len(infos) == 2 for info in infos: try: pl = info['ctcomp-data']['source']['playlist'][0] except KeyError: raise PluginError('Cannot find playlist info!') pl = self._playlist_info_schema.validate(pl) if vod_prio and pl['type'] != 'VOD': continue log.trace('{0!r}'.format(info)) if pl['type'] == 'LIVE': data = { "contentType": "live", "items": [{ "id": pl["id"], "assetId": pl["assetId"], "key": pl["key"], "playerType": "dash", "date": pl["date"], "requestSource": pl["requestSource"], "drm": pl["drm"], "quality": pl["quality"], }] } elif pl['type'] == 'VOD': data = { "contentType": "vod", "items": [{ "id": pl["id"], "key": pl["key"], "playerType": "dash", "date": pl["date"], "requestSource": pl["requestSource"], "drm": pl["drm"], "canBePlay": pl["canBePlay"], "quality": pl["quality"], "region": pl["region"] }] } headers = { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", } data = json.dumps(data) response = self.session.http.post(self._player_api, data="data={}".format(quote(data)), headers=headers) json_data = self.session.http.json(response, schema=self._playlist_schema) log.trace('{0!r}'.format(json_data)) playlist = json_data['RESULT']['playlist'][0]['streamUrls']['main'] yield from DASHStream.parse_manifest(self.session, playlist).items()
class IDF1(Plugin): DACAST_API_URL = 'https://json.dacast.com/b/{}/{}/{}' DACAST_TOKEN_URL = 'https://services.dacast.com/token/i/b/{}/{}/{}' _url_re = re.compile(r'http://www\.idf1\.fr/(videos/[^/]+/[^/]+\.html|live\b)') _video_id_re = re.compile(r"dacast\('(?P<broadcaster_id>\d+)_(?P<video_type>[a-z]+)_(?P<video_id>\d+)', 'replay_content', data\);") _video_id_alt_re = re.compile(r'<script src="https://player.dacast.com/js/player.js" id="(?P<broadcaster_id>\d+)_(?P<video_type>[cf])_(?P<video_id>\d+)"') _player_url = 'http://ssl.p.jwpcdn.com/player/v/7.12.6/jwplayer.flash.swf' _api_schema = validate.Schema( validate.transform(parse_json), { validate.optional('html5'): validate.all( [ { 'src': validate.url() }, ], ), 'hls': validate.url(), 'hds': validate.url() }, validate.transform(lambda x: [update_scheme(IDF1.DACAST_API_URL, x['hls']), x['hds']] + [y['src'] for y in x.get('html5', [])]) ) _token_schema = validate.Schema( validate.transform(parse_json), {'token': validate.text}, validate.get('token') ) _user_agent = useragents.IE_11 @classmethod def can_handle_url(cls, url): return IDF1._url_re.match(url) def _get_streams(self): res = self.session.http.get(self.url) match = self._video_id_re.search(res.text) or self._video_id_alt_re.search(res.text) if match is None: return broadcaster_id = match.group('broadcaster_id') video_type = match.group('video_type') video_id = match.group('video_id') videos = self.session.http.get(self.DACAST_API_URL.format(broadcaster_id, video_type, video_id), schema=self._api_schema) token = self.session.http.get( self.DACAST_TOKEN_URL.format(broadcaster_id, video_type, video_id), schema=self._token_schema, headers={'referer': self.url} ) parsed = [] for video_url in videos: video_url += token # Ignore duplicate video URLs if video_url in parsed: continue parsed.append(video_url) # Ignore HDS streams (broken) if '.m3u8' in video_url: for s in HLSStream.parse_variant_playlist(self.session, video_url).items(): yield s
validate.get("data") ) _plu_schema = validate.Schema( { "urls": [{ "securityUrl": validate.url(scheme=validate.any("rtmp", "http")), "resolution": validate.text, "ext": validate.text }] } ) _qq_schema = validate.Schema( { validate.optional("playurl"): validate.url(scheme="http") }, validate.get("playurl") ) STREAM_WEIGHTS = { "middle": 540, "source": 1080 } class Tga(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) @classmethod
class SteamBroadcastPlugin(Plugin): _url_re = re.compile(r"https?://steamcommunity.com/broadcast/watch/(\d+)") _steamtv_url_re = re.compile(r"https?://steam.tv/(\w+)") _watch_broadcast_url = "https://steamcommunity.com/broadcast/watch/" _get_broadcast_url = "https://steamcommunity.com/broadcast/getbroadcastmpd/" _user_agent = "streamlink/{}".format(streamlink.__version__) _broadcast_schema = Schema({ "success": validate.any("ready", "unavailable", "waiting", "waiting_to_start", "waiting_for_start"), "retry": int, "broadcastid": validate.any(validate.text, int), validate.optional("url"): validate.url(), validate.optional("viewertoken"): validate.text }) _get_rsa_key_url = "https://steamcommunity.com/login/getrsakey/" _rsa_key_schema = validate.Schema({ "publickey_exp": validate.all(validate.text, validate.transform(lambda x: int(x, 16))), "publickey_mod": validate.all(validate.text, validate.transform(lambda x: int(x, 16))), "success": True, "timestamp": validate.text, "token_gid": validate.text }) _dologin_url = "https://steamcommunity.com/login/dologin/" _dologin_schema = validate.Schema({ "success": bool, "requires_twofactor": bool, validate.optional("message"): validate.text, validate.optional("emailauth_needed"): bool, validate.optional("emaildomain"): validate.text, validate.optional("emailsteamid"): validate.text, validate.optional("login_complete"): bool, validate.optional("captcha_needed"): bool, validate.optional("captcha_gid"): validate.any(validate.text, int) }) _captcha_url = "https://steamcommunity.com/public/captcha.php?gid={}" arguments = PluginArguments( PluginArgument( "email", metavar="EMAIL", requires=["password"], help=""" A Steam account email address to access friends/private streams """ ), PluginArgument( "password", metavar="PASSWORD", sensitive=True, help=""" A Steam account password to use with --steam-email. """ )) def __init__(self, url): super(SteamBroadcastPlugin, self).__init__(url) self.session.http.headers["User-Agent"] = self._user_agent @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None or cls._steamtv_url_re.match(url) is not None @property def donotcache(self): return str(int(time.time() * 1000)) def encrypt_password(self, email, password): """ Get the RSA key for the user and encrypt the users password :param email: steam account :param password: password for account :return: encrypted password """ res = self.session.http.get(self._get_rsa_key_url, params=dict(username=email, donotcache=self.donotcache)) rsadata = self.session.http.json(res, schema=self._rsa_key_schema) rsa = RSA.construct((rsadata["publickey_mod"], rsadata["publickey_exp"])) cipher = PKCS1_v1_5.new(rsa) return base64.b64encode(cipher.encrypt(password.encode("utf8"))), rsadata["timestamp"] def dologin(self, email, password, emailauth="", emailsteamid="", captchagid="-1", captcha_text="", twofactorcode=""): """ Logs in to Steam """ epassword, rsatimestamp = self.encrypt_password(email, password) login_data = { 'username': email, "password": epassword, "emailauth": emailauth, "loginfriendlyname": "Streamlink", "captchagid": captchagid, "captcha_text": captcha_text, "emailsteamid": emailsteamid, "rsatimestamp": rsatimestamp, "remember_login": True, "donotcache": self.donotcache, "twofactorcode": twofactorcode } res = self.session.http.post(self._dologin_url, data=login_data) resp = self.session.http.json(res, schema=self._dologin_schema) if not resp[u"success"]: if resp.get(u"captcha_needed"): # special case for captcha captchagid = resp[u"captcha_gid"] log.error("Captcha result required, open this URL to see the captcha: {}".format( self._captcha_url.format(captchagid))) try: captcha_text = self.input_ask("Captcha text") except FatalPluginError: captcha_text = None if not captcha_text: return False else: # If the user must enter the code that was emailed to them if resp.get(u"emailauth_needed"): if not emailauth: try: emailauth = self.input_ask("Email auth code required") except FatalPluginError: emailauth = None if not emailauth: return False else: raise SteamLoginFailed("Email auth key error") # If the user must enter a two factor auth code if resp.get(u"requires_twofactor"): try: twofactorcode = self.input_ask("Two factor auth code required") except FatalPluginError: twofactorcode = None if not twofactorcode: return False if resp.get(u"message"): raise SteamLoginFailed(resp[u"message"]) return self.dologin(email, password, emailauth=emailauth, emailsteamid=resp.get(u"emailsteamid", u""), captcha_text=captcha_text, captchagid=captchagid, twofactorcode=twofactorcode) elif resp.get("login_complete"): return True else: log.error("Something when wrong when logging in to Steam") return False def login(self, email, password): log.info("Attempting to login to Steam as {}".format(email)) return self.dologin(email, password) def _get_broadcast_stream(self, steamid, viewertoken=0, sessionid=None): log.debug("Getting broadcast stream: sessionid={0}".format(sessionid)) res = self.session.http.get(self._get_broadcast_url, params=dict(broadcastid=0, steamid=steamid, viewertoken=viewertoken, sessionid=sessionid)) return self.session.http.json(res, schema=self._broadcast_schema) def _get_streams(self): streamdata = None if self.get_option("email"): if self.login(self.get_option("email"), self.get_option("password")): log.info("Logged in as {0}".format(self.get_option("email"))) self.save_cookies(lambda c: "steamMachineAuth" in c.name) # Handle steam.tv URLs if self._steamtv_url_re.match(self.url) is not None: # extract the steam ID from the page res = self.session.http.get(self.url) for div in itertags(res.text, 'div'): if div.attributes.get("id") == "webui_config": broadcast_data = html_unescape(div.attributes.get("data-broadcast")) steamid = parse_json(broadcast_data).get("steamid") self.url = self._watch_broadcast_url + steamid # extract the steam ID from the URL steamid = self._url_re.match(self.url).group(1) res = self.session.http.get(self.url) # get the page to set some cookies sessionid = res.cookies.get('sessionid') while streamdata is None or streamdata[u"success"] in ("waiting", "waiting_for_start"): streamdata = self._get_broadcast_stream(steamid, sessionid=sessionid) if streamdata[u"success"] == "ready": return DASHStream.parse_manifest(self.session, streamdata["url"]) elif streamdata[u"success"] == "unavailable": log.error("This stream is currently unavailable") return else: r = streamdata[u"retry"] / 1000.0 log.info("Waiting for stream, will retry again in {} seconds...".format(r)) time.sleep(r)
from streamlink.exceptions import PluginError _url_re = re.compile( r'http(s)?://([^.]*.)?ceskatelevize.cz' ) _player_re = re.compile( r'ivysilani/embed/iFramePlayer[^"]+' ) _hash_re = re.compile( r'hash:"([0-9a-z]+)"' ) _playlist_info_re = re.compile( r'{"type":"([a-z]+)","id":"([0-9]+)"' ) _playlist_url_schema = validate.Schema({ validate.optional("streamingProtocol"): validate.text, "url": validate.any( validate.url(), "Error", "error_region" ) }) _playlist_schema = validate.Schema({ "playlist": [{ validate.optional("type"): validate.text, "streamUrls": { "main": validate.url(), } }] })
_room_id_re = re.compile(r'"roomId":(?P<room_id>\d+),') _room_id_alt_re = re.compile( r'content="showroom:///room\?room_id=(?P<room_id>\d+)"') _room_id_lookup_failure_log = 'Failed to find room_id for {0} using {1} regex' _api_status_url = 'https://www.showroom-live.com/room/is_live?room_id={room_id}' _api_stream_url = 'https://www.showroom-live.com/api/live/streaming_url?room_id={room_id}' _api_stream_schema = validate.Schema( validate.any( { "streaming_url_list": validate.all([{ "url": validate.text, validate.optional("stream_name"): validate.text, "id": int, "label": validate.text, "is_default": int, "type": validate.text, "quality": int, }]) }, {})) # the "low latency" streams are rtmp, the others are hls _rtmp_quality_lookup = { "オリジナル画質": "high", "オリジナル画質(低遅延)": "high", "original spec(low latency)": "high", "original spec": "high", "低画質": "low",
"720": 3, "480": 2, "medium": 2, "360": 1, "low": 1 } _url_re = re.compile(""" http(s)?://(\w+\.)?gaminglive\.tv /(?P<type>channels|videos)/(?P<name>[^/]+) """, re.VERBOSE) _quality_re = re.compile("[^/]+-(?P<quality>[^/]+)") _channel_schema = validate.Schema( { validate.optional("state"): { "stream": { "qualities": [validate.text], "rootUrl": validate.url(scheme="rtmp") } } }, validate.get("state") ) _vod_schema = validate.Schema( { "name": validate.text, "channel_slug": validate.text, "title": validate.text, "created_at": validate.transform(int)
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'channel"\s*:\s*{\s*"id"\s*:\s*"(\w+?)"') tvip_master_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( { "appStoreState": { "versions": [{"id": validate.text}] } }, validate.get("appStoreState"), 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") ) arguments = PluginArguments( PluginArgument( "username", requires=["password"], metavar="USERNAME", help="The username used to register with bbc.co.uk." ), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help="A bbc.co.uk account password to use with --bbciplayer-username.", prompt="Enter bbc.co.uk account password" ), PluginArgument( "hd", action="store_true", help=""" Prefer HD streams over local SD streams, some live programmes may not be broadcast in HD. """ ), ) @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 """ log.debug("Looking for vpid on {0}", url) # Use pre-fetched page if available res = res or self.session.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, master=False): log.debug("Looking for {0} tvip on {1}", "master" if master else "", url) res = self.session.http.get(url) if master: m = self.tvip_master_re.search(res.text) else: m = self.tvip_re.search(res.text) return m and m.group(1) def mediaselector(self, vpid): urls = defaultdict(set) for platform in self.platforms: url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid), platform=platform) log.debug("Info API request: {0}", url) medias = self.session.http.get(url, schema=self.mediaselector_schema) for media in medias: for connection in media["connection"]: urls[connection.get("transferFormat")].add(connection["href"]) for stream_type, urls in urls.items(): log.debug("{0} {1} streams", len(urls), stream_type) for url in list(urls): try: if stream_type == "hds": for s in HDSStream.parse_manifest(self.session, url).items(): yield s if stream_type == "hls": for s in HLSStream.parse_variant_playlist(self.session, url).items(): yield s if stream_type == "dash": for s in DASHStream.parse_manifest(self.session, url).items(): yield s log.debug(" OK: {0}", url) except: log.debug(" FAIL: {0}", url) 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 """ def auth_check(res): return ptrt_url in ([h.url for h in res.history] + [res.url]) # make the session request to get the correct cookies session_res = self.session.http.get( self.session_url, params=dict(ptrt=ptrt_url) ) if auth_check(session_res): log.debug("Already authenticated, skipping authentication") return True http_nonce = self._extract_nonce(session_res) res = self.session.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 auth_check(res) def _get_streams(self): if not self.get_option("username"): log.error( "BBC iPlayer requires an account you must login using " "--bbciplayer-username and --bbciplayer-password") return log.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): log.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: log.debug("Loading streams for episode: {0}", episode_id) vpid = self.find_vpid(self.url) if vpid: log.debug("Found VPID: {0}", vpid) for s in self.mediaselector(vpid): yield s else: log.error("Could not find VPID for episode {0}", episode_id) elif channel_name: log.debug("Loading stream for live channel: {0}", channel_name) if self.get_option("hd"): tvip = self.find_tvip(self.url, master=True) + "_hd" if tvip: log.debug("Trying HD stream {0}...", tvip) try: for s in self.mediaselector(tvip): yield s except PluginError: log.error( "Failed to get HD streams, falling back to SD") else: return tvip = self.find_tvip(self.url) if tvip: log.debug("Found TVIP: {0}", tvip) for s in self.mediaselector(tvip): yield s
) ''', re.VERBOSE) _room_id_re = re.compile(r'"roomId":(?P<room_id>\d+),') _room_id_alt_re = re.compile(r'content="showroom:///room\?room_id=(?P<room_id>\d+)"') _room_id_lookup_failure_log = 'Failed to find room_id for {0} using {1} regex' _api_status_url = 'https://www.showroom-live.com/room/is_live?room_id={room_id}' _api_stream_url = 'https://www.showroom-live.com/api/live/streaming_url?room_id={room_id}' _api_stream_schema = validate.Schema( validate.any({ "streaming_url_list": validate.all([ { "url": validate.text, validate.optional("stream_name"): validate.text, "id": int, "label": validate.text, "is_default": int, "type": validate.text, "quality": int, } ]) }, {} ) ) # the "low latency" streams are rtmp, the others are hls _rtmp_quality_lookup = { "オリジナル画質": "high",
class SBScokr(Plugin): api_channel = 'http://apis.sbs.co.kr/play-api/1.0/onair/channel/{0}' api_channels = 'http://static.apis.sbs.co.kr/play-api/1.0/onair/channels' _channels_schema = validate.Schema( { 'list': [{ 'channelname': validate.all(validate.text, ), 'channelid': validate.text, validate.optional('type'): validate.text, }] }, validate.get('list'), ) _channel_schema = validate.Schema( { 'onair': { 'info': { 'onair_yn': validate.text, 'overseas_yn': validate.text, 'overseas_text': validate.text, }, 'source': { 'mediasourcelist': validate.any([{ validate.optional('default'): validate.text, 'mediaurl': validate.text, }], []) }, } }, validate.get('onair'), ) arguments = PluginArguments( PluginArgument('id', metavar='CHANNELID', type=str.upper, help=''' Channel ID to play. Example: %(prog)s http://play.sbs.co.kr/onair/pc/index.html best --sbscokr-id S01 ''')) def _get_streams(self): user_channel_id = self.get_option('id') res = self.session.http.get(self.api_channels) res = self.session.http.json(res, schema=self._channels_schema) channels = {} for channel in sorted(res, key=lambda x: x['channelid']): if channel.get('type') in ('TV', 'Radio'): channels[channel['channelid']] = channel['channelname'] log.info('Available IDs: {0}'.format(', '.join( '{0} ({1})'.format(key, value) for key, value in channels.items()))) if not user_channel_id: log.error('No channel selected, use --sbscokr-id CHANNELID') return elif user_channel_id and user_channel_id not in channels.keys(): log.error( 'Channel ID "{0}" is not available.'.format(user_channel_id)) return params = { 'v_type': '2', 'platform': 'pcweb', 'protocol': 'hls', 'jwt-token': '', 'rnd': random.randint(50, 300) } res = self.session.http.get(self.api_channel.format(user_channel_id), params=params) res = self.session.http.json(res, schema=self._channel_schema) for media in res['source']['mediasourcelist']: if media['mediaurl']: for s in HLSStream.parse_variant_playlist( self.session, media['mediaurl']).items(): yield s else: if res['info']['onair_yn'] != 'Y': log.error('This channel is currently unavailable') elif res['info']['overseas_yn'] != 'Y': log.error(res['info']['overseas_text'])
validate.length(1), validate.get(0) ) _player_schema = validate.Schema( { "clip": { "baseUrl": validate.any(None, validate.text), "bitrates": validate.all( validate.filter(lambda b: b.get("url") and b.get("label")), [{ "label": validate.text, "url": validate.text, }], ) }, validate.optional("playlist"): [{ validate.optional("connectionProvider"): validate.text, validate.optional("netConnectionUrl"): validate.text, validate.optional("bitrates"): [{ "label": validate.text, "url": validate.text, "provider": validate.text }] }], "plugins": validate.all( dict, validate.filter(lambda k, v: k in ["rtmp", "rtmpHitbox", "hls"]), { validate.text: { validate.optional("netConnectionUrl"): validate.text, "url": validate.text
def parse_fmt_list(formatsmap): formats = {} if not formatsmap: return formats for format in formatsmap.split(","): s = format.split("/") (w, h) = s[1].split("x") formats[int(s[0])] = "{0}p".format(h) return formats _config_schema = validate.Schema({ validate.optional("fmt_list"): validate.all(validate.text, validate.transform(parse_fmt_list)), validate.optional("url_encoded_fmt_stream_map"): validate.all(validate.text, validate.transform(parse_stream_map), [{ "itag": validate.all(validate.text, validate.transform(int)), "quality": validate.text, "url": validate.url(scheme="http"), validate.optional("s"): validate.text, validate.optional("stereo3d"): validate.all(validate.text, validate.transform(int), validate.transform(bool)), }]),
int(ts[-6:-5] + "1") ) _url_re = re.compile(r""" http(s)?://(\w+\.)?crunchyroll\. (?: com|de|es|fr|co.jp ) (?:/[^/&?]+)? /[^/&?]+-(?P<media_id>\d+) """, re.VERBOSE) _api_schema = validate.Schema({ "error": bool, validate.optional("code"): validate.text, validate.optional("message"): validate.text, validate.optional("data"): object, }) _media_schema = validate.Schema( { "stream_data": validate.any( None, { "streams": validate.all( [{ "quality": validate.any(validate.text, None), "url": validate.url( scheme="http", path=validate.endswith(".m3u8") ),
class LiveEdu(Plugin): login_url = "https://www.liveedu.tv/accounts/login/" url_re = re.compile(r"https?://(?:\w+\.)?(?:livecoding|liveedu)\.tv/") config_re = re.compile( r"""\Wconfig.(?P<key>\w+)\s*=\s*(?P<q>['"])(?P<value>.*?)(?P=q);""") csrf_re = re.compile(r'''"csrfToken"\s*:\s*"(\w+)"''') api_schema = validate.Schema({ "viewing_urls": { validate.optional("error"): validate.text, validate.optional("urls"): [{ "src": validate.url(), "type": validate.text, validate.optional("res"): int, validate.optional("label"): validate.text, }] } }) config_schema = validate.Schema({ "selectedVideoHID": validate.text, "livestreamURL": validate.text, "videosURL": validate.text }) 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): """ Attempt a login to LiveEdu.tv """ email = self.get_option("email") password = self.get_option("password") if email and password: res = http.get(self.login_url) csrf_match = self.csrf_re.search(res.text) token = csrf_match and csrf_match.group(1) self.logger.debug("Attempting login as {0} (token={1})", email, token) res = http.post(self.login_url, data=dict(login=email, password=password, csrfmiddlewaretoken=token), allow_redirects=False, raise_for_status=False, headers={"Referer": self.login_url}) if res.status_code != 302: self.logger.error("Failed to login to LiveEdu account: {0}", email) def _get_streams(self): """ Get the config object from the page source and call the API to get the list of streams :return: """ # attempt a login self.login() res = http.get(self.url) # decode the config for the page matches = self.config_re.finditer(res.text) try: config = self.config_schema.validate( dict([m.group("key", "value") for m in matches])) except PluginError: return if config["selectedVideoHID"]: self.logger.debug("Found video hash ID: {0}", config["selectedVideoHID"]) api_url = urljoin( self.url, urljoin(config["videosURL"], config["selectedVideoHID"])) elif config["livestreamURL"]: self.logger.debug("Found live stream URL: {0}", config["livestreamURL"]) api_url = urljoin(self.url, config["livestreamURL"]) else: return ares = http.get(api_url) data = http.json(ares, schema=self.api_schema) viewing_urls = data["viewing_urls"] if "error" in viewing_urls: self.logger.error("Failed to load streams: {0}", viewing_urls["error"]) else: for url in viewing_urls["urls"]: try: label = "{0}p".format(url.get("res", url["label"])) except KeyError: label = "live" if url["type"] == "rtmp/mp4" and RTMPStream.is_usable( self.session): params = { "rtmp": url["src"], "pageUrl": self.url, "live": True, } yield label, RTMPStream(self.session, params) elif url["type"] == "application/x-mpegURL": for s in HLSStream.parse_variant_playlist( self.session, url["src"]).items(): yield s
_url_re = re.compile(r"http(s)?://(\w+\.)?weeb.tv/(channel|online)/(?P<channel>[^/&?]+)") _schema = validate.Schema( dict, validate.map(lambda k, v: (PARAMS_KEY_MAP.get(k, k), v)), validate.any( { "status": validate.transform(int), "rtmp": validate.url(scheme="rtmp"), "playpath": validate.text, "multibitrate": validate.all( validate.transform(int), validate.transform(bool) ), "block_type": validate.transform(int), validate.optional("token"): validate.text, validate.optional("block_time"): validate.text, validate.optional("reconnect_time"): validate.text, }, { "status": validate.transform(int), }, ) ) class Weeb(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url)
_flashvar_re = re.compile(r"""(['"])(.*?)\1\s*:\s*(['"])(.*?)\3""") _clientlibs_re = re.compile(r"""<script.*?src=(['"])(.*?/clientlibs_anime_watch.*?\.js)\1""") _schema = validate.Schema( validate.union({ "flashvars": validate.all( validate.transform(_flashvars_re.search), validate.get(1), validate.transform(_flashvar_re.findall), validate.map(lambda v: (v[1], v[3])), validate.transform(dict), { "s": validate.text, "country": validate.text, "init": validate.text, validate.optional("ss_id"): validate.text, validate.optional("mv_id"): validate.text, validate.optional("device_cd"): validate.text, validate.optional("ss1_prm"): validate.text, validate.optional("ss2_prm"): validate.text, validate.optional("ss3_prm"): validate.text } ), "clientlibs": validate.all( validate.transform(_clientlibs_re.search), validate.get(2), validate.text ) }) )
validate.transform(_csrf_token_re.search), validate.any(None, validate.get(1)) ) _hls_playlist_schema = validate.Schema( validate.transform(_hls_playlist_re.search), validate.any( None, validate.all( validate.get(1), validate.url(scheme="http", path=validate.endswith(".m3u8")) ) ) ) _login_schema = validate.Schema({ "email": validate.text, validate.optional("errors"): validate.all( { "base": [validate.text] }, validate.get("base"), ) }) class Livestation(Plugin): options = PluginOptions({ "email": "", "password": "" }) @classmethod
class Vimeo(Plugin): _config_url_re = re.compile( r'(?:"config_url"|\bdata-config-url)\s*[:=]\s*(".+?")') _config_re = re.compile(r"var\s+config\s*=\s*({.+?})\s*;") _config_url_schema = validate.Schema( validate.transform(_config_url_re.search), validate.any( None, validate.Schema( validate.get(1), validate.parse_json(), validate.transform(html_unescape), validate.url(), ), ), ) _config_schema = validate.Schema( validate.parse_json(), { "request": { "files": { validate.optional("dash"): { "cdns": { validate.text: { "url": validate.url() } } }, validate.optional("hls"): { "cdns": { validate.text: { "url": validate.url() } } }, validate.optional("progressive"): validate.all([{ "url": validate.url(), "quality": validate.text }]), }, validate.optional("text_tracks"): validate.all([{ "url": validate.text, "lang": validate.text }]), } }, ) _player_schema = validate.Schema( validate.transform(_config_re.search), validate.any(None, validate.Schema(validate.get(1), _config_schema)), ) arguments = PluginArguments(PluginArgument("mux-subtitles", is_global=True)) def _get_streams(self): if "player.vimeo.com" in self.url: data = self.session.http.get(self.url, schema=self._player_schema) else: api_url = self.session.http.get(self.url, schema=self._config_url_schema) if not api_url: return data = self.session.http.get(api_url, schema=self._config_schema) videos = data["request"]["files"] streams = [] for stream_type in ("hls", "dash"): if stream_type not in videos: continue for _, video_data in videos[stream_type]["cdns"].items(): log.trace("{0!r}".format(video_data)) url = video_data.get("url") if stream_type == "hls": for stream in HLSStream.parse_variant_playlist( self.session, url).items(): streams.append(stream) elif stream_type == "dash": p = urlparse(url) if p.path.endswith("dash.mpd"): # LIVE url = self.session.http.get(url).json()["url"] elif p.path.endswith("master.json"): # VOD url = url.replace("master.json", "master.mpd") else: log.error("Unsupported DASH path: {0}".format(p.path)) continue for stream in DASHStream.parse_manifest(self.session, url).items(): streams.append(stream) for stream in videos.get("progressive", []): streams.append( (stream["quality"], HTTPStream(self.session, stream["url"]))) if self.get_option("mux_subtitles") and data["request"].get( "text_tracks"): substreams = { s["lang"]: HTTPStream(self.session, "https://vimeo.com" + s["url"]) for s in data["request"]["text_tracks"] } for quality, stream in streams: yield quality, MuxedStream(self.session, stream, subtitles=substreams) else: for stream in streams: yield stream
from streamlink.plugin.api import validate from streamlink.plugin.api.utils import parse_json __all__ = ["parse_playlist"] _playlist_re = re.compile("\(?\{.*playlist: (\[.*\]),.*?\}\)?;", re.DOTALL) _js_to_json = partial(re.compile("(\w+):\s").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(parse_json), [{ "sources": [{ "file": validate.text, validate.optional("label"): validate.text }] }] ) ) ) def parse_playlist(res): """Attempts to parse a JWPlayer playlist in a HTTP response body.""" return _playlist_schema.validate(res.text)
_plu_schema = validate.Schema({ "playLines": [{ "urls": [{ "securityUrl": validate.url(scheme=validate.any("rtmp", "http")), "resolution": validate.text, "ext": validate.text }] }] }) _qq_schema = validate.Schema( {validate.optional("playurl"): validate.url(scheme="http")}, validate.get("playurl")) STREAM_WEIGHTS = {"middle": 540, "source": 1080} class Tga(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) @classmethod def stream_weight(cls, stream): if stream in STREAM_WEIGHTS: return STREAM_WEIGHTS[stream], "tga"
import re from random import random from streamlink.plugin import Plugin, PluginError from streamlink.plugin.api import http, validate from streamlink.stream import HTTPStream, RTMPStream API_CLIENT_NAME = "Bambuser AS2" API_CONTEXT = "b_broadcastpage" API_KEY = "005f64509e19a868399060af746a00aa" API_URL_VIDEO = "http://player-c.api.bambuser.com/getVideo.json" _url_re = re.compile("http(s)?://(\w+.)?bambuser.com/v/(?P<video_id>\d+)") _video_schema = validate.Schema({ validate.optional("error"): validate.text, validate.optional("result"): { "id": validate.text, "size": validate.text, "url": validate.url( scheme=validate.any("rtmp", "http") ) } }) class Bambuser(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url)
def parse_fmt_list(formatsmap): formats = {} if not formatsmap: return formats for format in formatsmap.split(","): s = format.split("/") (w, h) = s[1].split("x") formats[int(s[0])] = "{0}p".format(h) return formats _config_schema = validate.Schema( { validate.optional("fmt_list"): validate.all( validate.text, validate.transform(parse_fmt_list) ), validate.optional("url_encoded_fmt_stream_map"): validate.all( validate.text, validate.transform(parse_stream_map), [{ "itag": validate.all( validate.text, validate.transform(int) ), "quality": validate.text, "url": validate.url(scheme="http"), validate.optional("s"): validate.text, validate.optional("stereo3d"): validate.all(
def test_dict_optional_keys(self): assert validate({"a": 1, optional("b"): 2}, {"a": 1}) == {"a": 1} assert validate({"a": 1, optional("b"): 2}, {"a": 1, "b": 2}) == {"a": 1, "b": 2}
QUALITY_WEIGHTS = { "aws_original": 1080, "aws_hd": 720, "aws_sd": 480, } CDN_SITES = {"aws": "aws_cf"} _url_re = re.compile( r"http(s)?://(?P<cdn>\w+\.)?afreeca(tv)?.com/(?P<username>\w+)(/\d+)?") _channel_schema = validate.Schema( { "CHANNEL": { "RESULT": validate.transform(int), validate.optional("BNO"): validate.text, validate.optional("RMD"): validate.text, validate.optional("AID"): validate.text } }, validate.get("CHANNEL")) _stream_schema = validate.Schema({ validate.optional("view_url"): validate.url(scheme=validate.any("rtmp", "http")), "stream_status": validate.text }) class AfreecaTV(Plugin): @classmethod
int(ts[-6:-5] + "1") ) _url_re = re.compile(""" http(s)?://(\w+\.)?crunchyroll\. (?: com|de|es|fr|co.jp ) /[^/&?]+ /[^/&?]+-(?P<media_id>\d+) """, re.VERBOSE) _api_schema = validate.Schema({ "error": bool, validate.optional("code"): validate.text, validate.optional("message"): validate.text, validate.optional("data"): object, }) _media_schema = validate.Schema( { "stream_data": validate.any( None, { "streams": validate.all( [{ "quality": validate.any(validate.text, None), "url": validate.url( scheme="http", path=validate.endswith(".m3u8") )
class Schoolism(Plugin): url_re = re.compile( r"https?://(?:www\.)?schoolism\.com/(viewAssignment|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\s*=\s*(\[.*\]);", re.DOTALL) js_to_json = partial(re.compile(r'(?!<")(\w+):(?!/)').sub, r'"\1":') fix_brackets = partial(re.compile(r',\s*\}').sub, r'}') playlist_schema = validate.Schema( validate.transform(playlist_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(js_to_json), validate.transform(fix_brackets), # remove invalid , validate.transform(parse_json), [{ "sources": validate.all( [{ validate.optional("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") ) }]))) arguments = PluginArguments( PluginArgument("email", required=True, requires=["password"], metavar="EMAIL", help=""" The email associated with your Schoolism account, required to access any Schoolism stream. """), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help="A Schoolism account password to use with --schoolism-email." ), PluginArgument("part", type=int, default=1, metavar="PART", help=""" Play part number PART of the lesson, or assignment feedback video. Defaults is 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 = self.session.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: log.error( "Failed to login to Schoolism, incorrect email/password combination" ) else: log.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: log.debug(f"Logged in to Schoolism as {user}") res = self.session.http.get( self.url, headers={"User-Agent": useragents.SAFARI_8}) lesson_playlist = self.playlist_schema.validate(res.text) part = self.options.get("part") video_type = "Lesson" if "lesson" in self.url_re.match( self.url).group(1).lower() else "Assignment Feedback" log.info(f"Attempting to play {video_type} Part {part}") found = False # make request to key-time api, to get key specific headers _ = self.session.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"]: if source['type'] == "video/mp4": yield "live", HTTPStream(self.session, source["src"], headers={ "User-Agent": useragents.SAFARI_8, "Referer": self.url }) elif source['type'] == "application/x-mpegurl": yield from HLSStream.parse_variant_playlist( self.session, source["src"], headers={ "User-Agent": useragents.SAFARI_8, "Referer": self.url }).items() if not found: log.error(f"Could not find {video_type} Part {part}")
SWF_URL = "http://www.ardmediathek.de/ard/static/player/base/flash/PluginFlash.swf" HDCORE_PARAMETER = "?hdcore=3.3.0" QUALITY_MAP = { "auto": "auto", 3: "544p", 2: "360p", 1: "288p", 0: "144p" } _url_re = re.compile(r"http(s)?://(?:(\w+\.)?ardmediathek.de/tv|mediathek.daserste.de/)") _media_id_re = re.compile(r"/play/(?:media|config)/(\d+)") _media_schema = validate.Schema({ "_mediaArray": [{ "_mediaStreamArray": [{ validate.optional("_server"): validate.text, "_stream": validate.any(validate.text, [validate.text]), "_quality": validate.any(int, validate.text) }] }] }) _smil_schema = validate.Schema( validate.union({ "base": validate.all( validate.xml_find("head/meta"), validate.get("base"), validate.url(scheme="http") ), "cdn": validate.all( validate.xml_find("head/meta"), validate.get("cdn")
class UStreamTVWsClient(WebsocketClient): API_URL = "wss://r{0}-1-{1}-{2}-ws-{3}.ums.ustream.tv:1935/1/ustream" APP_ID = 3 APP_VERSION = 2 STREAM_OPENED_TIMEOUT = 6 _schema_cmd = validate.Schema({ "cmd": str, "args": [{str: object}], }) _schema_stream_formats = validate.Schema({ "streams": [validate.any( validate.all( { "contentType": "video/mp4", "sourceStreamVersion": int, "initUrl": str, "segmentUrl": str, "bitrate": int, "height": int, }, validate.transform(lambda obj: StreamFormatVideo(**obj)) ), validate.all( { "contentType": "audio/mp4", "sourceStreamVersion": int, "initUrl": str, "segmentUrl": str, "bitrate": int, validate.optional("language"): str, }, validate.transform(lambda obj: StreamFormatAudio(**obj)) ), object )] }) _schema_stream_segments = validate.Schema({ "chunkId": int, "chunkTime": int, "contentAccess": validate.all( { "accessList": [{ "data": { "path": str } }] }, validate.get(("accessList", 0, "data", "path")) ), "hashes": {validate.transform(int): str} }) stream_cdn: str = None stream_formats_video: List[StreamFormatVideo] = None stream_formats_audio: List[StreamFormatAudio] = None stream_initial_id: int = None def __init__( self, session, media_id, application, referrer=None, cluster="live", password=None, app_id=APP_ID, app_version=APP_VERSION ): self.opened = Event() self.ready = Event() self.stream_error = None # a list of deques subscribed by worker threads which independently need to read segments self.stream_segments_subscribers: List[Deque[Segment]] = [] self.stream_segments_initial: Deque[Segment] = deque() self.stream_segments_lock = RLock() self.media_id = media_id self.application = application self.referrer = referrer self.cluster = cluster self.password = password self.app_id = app_id self.app_version = app_version super().__init__(session, self._get_url(), origin="https://www.ustream.tv") def _get_url(self): return self.API_URL.format(randint(0, 0xffffff), self.media_id, self.application, self.cluster) def _set_error(self, error: Any): self.stream_error = error self.ready.set() def _set_ready(self): if not self.ready.is_set() and self.stream_cdn and self.stream_initial_id is not None: self.ready.set() if self.opened.wait(self.STREAM_OPENED_TIMEOUT): log.debug("Stream opened, keeping websocket connection alive") else: log.info("Closing websocket connection") self.ws.close() def segments_subscribe(self) -> Deque[Segment]: with self.stream_segments_lock: # copy the initial segments deque (segments arrive early) new_deque = self.stream_segments_initial.copy() self.stream_segments_subscribers.append(new_deque) return new_deque def _segments_append(self, segment: Segment): # if there are no subscribers yet, add segment(s) to the initial deque if not self.stream_segments_subscribers: self.stream_segments_initial.append(segment) else: for subscriber_deque in self.stream_segments_subscribers: subscriber_deque.append(segment) def on_open(self, wsapp): args = { "type": "viewer", "appId": self.app_id, "appVersion": self.app_version, "rsid": f"{randint(0, 10_000_000_000):x}:{randint(0, 10_000_000_000):x}", "rpin": f"_rpin.{randint(0, 1_000_000_000_000_000)}", "referrer": self.referrer, "clusterHost": "r%rnd%-1-%mediaId%-%mediaType%-%protocolPrefix%-%cluster%.ums.ustream.tv", "media": self.media_id, "application": self.application } if self.password: args["password"] = self.password self.send_json({ "cmd": "connect", "args": [args] }) def on_message(self, wsapp, data: str): try: parsed = parse_json(data, schema=self._schema_cmd) except PluginError: log.error(f"Could not parse message: {data[:50]}") return cmd: str = parsed["cmd"] args: List[Dict] = parsed["args"] log.trace(f"Received '{cmd}' command") log.trace(f"{args!r}") handlers = self._MESSAGE_HANDLERS.get(cmd) if handlers is not None: for arg in args: for name, handler in handlers.items(): argdata = arg.get(name) if argdata is not None: log.debug(f"Processing '{cmd}' - '{name}'") handler(self, argdata) # noinspection PyMethodMayBeStatic def _handle_warning(self, data: Dict): log.warning(f"{data['code']}: {str(data['message'])[:50]}") # noinspection PyUnusedLocal def _handle_reject_nonexistent(self, *args): self._set_error("This channel does not exist") # noinspection PyUnusedLocal def _handle_reject_geo_lock(self, *args): self._set_error("This content is not available in your area") def _handle_reject_cluster(self, arg: Dict): self.cluster = arg["name"] log.info(f"Switching cluster to: {self.cluster}") self.reconnect(url=self._get_url()) def _handle_reject_referrer_lock(self, arg: Dict): self.referrer = arg["redirectUrl"] log.info(f"Updating referrer to: {self.referrer}") self.reconnect(url=self._get_url()) def _handle_module_info_cdn_config(self, data: Dict): self.stream_cdn = urlunparse(( data["protocol"], data["data"][0]["data"][0]["sites"][0]["host"], data["data"][0]["data"][0]["sites"][0]["path"], "", "", "" )) self._set_ready() def _handle_module_info_stream(self, data: Dict): if data.get("contentAvailable") is False: return self._set_error("This stream is currently offline") mp4_segmented = data.get("streamFormats", {}).get("mp4/segmented") if not mp4_segmented: return # parse the stream formats once if self.stream_initial_id is None: try: formats = self._schema_stream_formats.validate(mp4_segmented) formats = formats["streams"] except PluginError as err: return self._set_error(err) self.stream_formats_video = list(filter(lambda f: type(f) is StreamFormatVideo, formats)) self.stream_formats_audio = list(filter(lambda f: type(f) is StreamFormatAudio, formats)) # parse segment duration and hashes, and queue new segments try: segmentdata: Dict = self._schema_stream_segments.validate(mp4_segmented) except PluginError: log.error("Failed parsing hashes") return current_id: int = segmentdata["chunkId"] duration: int = segmentdata["chunkTime"] path: str = segmentdata["contentAccess"] hashes: Dict[int, str] = segmentdata["hashes"] sorted_ids = sorted(hashes.keys()) count = len(sorted_ids) if count == 0: return # initial segment ID (needed by the workers to filter queued segments) if self.stream_initial_id is None: self.stream_initial_id = current_id current_time = datetime.now() # lock the stream segments deques for the worker threads with self.stream_segments_lock: # interpolate and extrapolate segments from the provided id->hash data diff = 10 - sorted_ids[0] % 10 # if there's only one id->hash item, extrapolate until the next decimal for idx, segment_id in enumerate(sorted_ids): idx_next = idx + 1 if idx_next < count: # calculate the difference between IDs and use that to interpolate segment IDs # the last id->hash item will use the previous diff to extrapolate segment IDs diff = sorted_ids[idx_next] - segment_id for num in range(segment_id, segment_id + diff): self._segments_append(Segment( num=num, duration=duration, available_at=current_time + timedelta(seconds=(num - current_id - 1) * duration / 1000), hash=hashes[segment_id], path=path )) self._set_ready() # ---- _MESSAGE_HANDLERS: Dict[str, Dict[str, Callable[["UStreamTVWsClient", Any], None]]] = { "warning": { "code": _handle_warning, }, "reject": { "cluster": _handle_reject_cluster, "referrerLock": _handle_reject_referrer_lock, "nonexistent": _handle_reject_nonexistent, "geoLock": _handle_reject_geo_lock, }, "moduleInfo": { "cdnConfig": _handle_module_info_cdn_config, "stream": _handle_module_info_stream, } }
import re from streamlink.compat import urlparse from streamlink.plugin import Plugin from streamlink.plugin.api import http, validate from streamlink.stream import RTMPStream, HTTPStream, HLSStream from streamlink.utils import parse_json, rtmpparse, swfdecompress _url_re = re.compile("http(s)?://api.dmcloud.net/player/embed/[^/]+/[^/]+") _rtmp_re = re.compile(b"customURL[^h]+(https://.*?)\\\\") _info_re = re.compile("var info = (.*);") _schema = validate.Schema( { "mode": validate.text, validate.optional("mp4_url"): validate.url(scheme="http"), validate.optional("ios_url"): validate.url(scheme="http"), validate.optional("swf_url"): validate.url(scheme="http"), } ) class DMCloud(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_rtmp_stream(self, swfurl): res = http.get(swfurl) swf = swfdecompress(res.content) match = _rtmp_re.search(swf) if not match:
_rtmp_re = re.compile(r""" (?P<host>rtmp://[^/]+) /(?P<app>[^/]+) /(?P<playpath>.+) """, re.VERBOSE) _url_re = re.compile(r""" http(s)?://(\w+\.)? dailymotion.com (/embed)?/(video|live) /(?P<media_id>[^_?/]+) """, re.VERBOSE) _media_inner_schema = validate.Schema([{ "layerList": [{ "name": validate.text, validate.optional("sequenceList"): [{ "layerList": validate.all( [{ "name": validate.text, validate.optional("param"): dict }], validate.filter(lambda l: l["name"] in ("video", "reporting")) ) }] }] }]) _media_schema = validate.Schema( validate.any( _media_inner_schema, validate.all( {"sequence": _media_inner_schema},
from streamlink.stream import AkamaiHDStream, HLSStream HTTP_HEADERS = { "User-Agent": ("Mozilla5.0") } _url_re = re.compile("http(s)?://(www\.)?livestream.com/") _stream_config_schema = validate.Schema({ "event": { "stream_info": validate.any({ "is_live": bool, "qualities": [{ "bitrate": int, "height": int }], validate.optional("play_url"): validate.url(scheme="http"), validate.optional("m3u8_url"): validate.url( scheme="http", path=validate.endswith(".m3u8") ), }, None) }, validate.optional("playerUri"): validate.text }) _smil_schema = validate.Schema(validate.union({ "http_base": validate.all( validate.xml_find("{http://www.w3.org/2001/SMIL20/Language}head/" "{http://www.w3.org/2001/SMIL20/Language}meta" "[@name='httpBase']"), validate.xml_element(attrib={ "content": validate.text
_quality_re = re.compile(r"(\d+p)$") _url_re = re.compile( r""" http(s)?://(www\.)?(hitbox|smashcast).tv /(?P<channel>[^/]+) (?: (?:/videos)?/(?P<media_id>[^/]+) )? """, re.VERBOSE) _live_schema = validate.Schema( { "livestream": [{ "media_user_name": validate.text, validate.optional("media_hosted_media"): object, "media_is_live": validate.all(validate.text, validate.transform(int), validate.transform(bool)), "media_id": validate.text }], }, validate.get("livestream"), validate.length(1), validate.get(0)) _player_schema = validate.Schema({ "clip": { "baseUrl": validate.any(None, validate.text), "bitrates": validate.all( validate.filter(lambda b: b.get("url") and b.get("label")),
import random import re from streamlink.plugin import Plugin from streamlink.plugin.api import http from streamlink.plugin.api import validate from streamlink.plugin.api import useragents from streamlink.stream import HLSStream _url_re = re.compile(r"http(s)?://(www\.)?camsoda\.com/(?P<username>[^\"\']+)") _api_user_schema = validate.Schema( { "status": validate.any(int, validate.text), validate.optional("user"): { "online": validate.any(int, validate.text), "chatstatus": validate.text, } } ) _api_video_schema = validate.Schema( { "token": validate.text, "app": validate.text, "edge_servers": [validate.text], "stream_name": validate.text } )
_access_token_schema = validate.Schema( { "token": validate.text, "sig": validate.text }, validate.union((validate.get("sig"), validate.get("token")))) _token_schema = validate.Schema( { "chansub": { "restricted_bitrates": validate.all([validate.text], validate.filter(lambda n: not re.match( r"(.+_)?archives|live|chunked", n))) } }, validate.get("chansub")) _user_schema = validate.Schema( {validate.optional("display_name"): validate.text}, validate.get("display_name")) _video_schema = validate.Schema({ "chunks": { validate.text: [{ "length": int, "url": validate.any(None, validate.url(scheme="http")), "upkeep": validate.any("pass", "fail", None) }] }, "restrictions": { validate.text: validate.text }, "start_offset": int, "end_offset": int, })
_token_schema = validate.Schema( { "chansub": { "restricted_bitrates": validate.all( [validate.text], validate.filter( lambda n: not re.match(r"(.+_)?archives|live|chunked", n) ) ) } }, validate.get("chansub") ) _user_schema = validate.Schema( { validate.optional("display_name"): validate.text }, validate.get("display_name") ) _video_schema = validate.Schema( { "chunks": { validate.text: [{ "length": int, "url": validate.any(None, validate.url(scheme="http")), "upkeep": validate.any("pass", "fail", None) }] }, "restrictions": {validate.text: validate.text}, "start_offset": int, "end_offset": int,
class AbemaTV(Plugin): ''' Abema.tv https://abema.tv/ Note: Streams are geo-restricted to Japan ''' _url_re = re.compile( r"""https://abema\.tv/( now-on-air/(?P<onair>[^\?]+) | video/episode/(?P<episode>[^\?]+) | channels/.+?/slots/(?P<slots>[^\?]+) )""", re.VERBOSE) _CHANNEL = "https://api.abema.io/v1/channels" _USER_API = "https://api.abema.io/v1/users" _PRGM_API = "https://api.abema.io/v1/video/programs/{0}" _SLOTS_API = "https://api.abema.io/v1/media/slots/{0}" _PRGM3U8 = "https://vod-abematv.akamaized.net/program/{0}/playlist.m3u8" _SLOTM3U8 = "https://vod-abematv.akamaized.net/slot/{0}/playlist.m3u8" SECRETKEY = (b"v+Gjs=25Aw5erR!J8ZuvRrCx*rGswhB&qdHd_SYerEWdU&a?3DzN9B" b"Rbp5KwY4hEmcj5#fykMjJ=AuWz5GSMY-d@H7DMEh3M@9n2G552Us$$" b"k9cD=3TxwWe86!x#Zyhe") _USER_SCHEMA = validate.Schema({ u"profile": { u"userId": validate.text }, u"token": validate.text }) _CHANNEL_SCHEMA = validate.Schema({ u"channels": [{ u"id": validate.text, "name": validate.text, "playback": { validate.optional(u"dash"): validate.text, u"hls": validate.text } }] }) _PRGM_SCHEMA = validate.Schema( {u"label": { validate.optional(u"free"): bool }}) _SLOT_SCHEMA = validate.Schema( {u"slot": { u"flags": { validate.optional("timeshiftFree"): bool } }}) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def __init__(self, url): super().__init__(url) self.session.http.headers.update({'User-Agent': useragents.CHROME}) def _generate_applicationkeysecret(self, deviceid): deviceid = deviceid.encode("utf-8") # for python3 # plus 1 hour and drop minute and secs # for python3 : floor division ts_1hour = (int(time.time()) + 60 * 60) // 3600 * 3600 time_struct = time.gmtime(ts_1hour) ts_1hour_str = str(ts_1hour).encode("utf-8") h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(self.SECRETKEY) tmp = h.digest() for i in range(time_struct.tm_mon): h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(tmp) tmp = h.digest() h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(urlsafe_b64encode(tmp).rstrip(b"=") + deviceid) tmp = h.digest() for i in range(time_struct.tm_mday % 5): h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(tmp) tmp = h.digest() h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(urlsafe_b64encode(tmp).rstrip(b"=") + ts_1hour_str) tmp = h.digest() for i in range(time_struct.tm_hour % 5): # utc hour h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(tmp) tmp = h.digest() return urlsafe_b64encode(tmp).rstrip(b"=").decode("utf-8") def _is_playable(self, vtype, vid): auth_header = {"Authorization": "Bearer " + self.usertoken} if vtype == "episode": res = self.session.http.get(self._PRGM_API.format(vid), headers=auth_header) jsonres = self.session.http.json(res, schema=self._PRGM_SCHEMA) return jsonres["label"].get("free", False) is True elif vtype == "slots": res = self.session.http.get(self._SLOTS_API.format(vid), headers=auth_header) jsonres = self.session.http.json(res, schema=self._SLOT_SCHEMA) return jsonres["slot"]["flags"].get("timeshiftFree", False) is True def _get_streams(self): deviceid = str(uuid.uuid4()) appkeysecret = self._generate_applicationkeysecret(deviceid) json_data = { "deviceId": deviceid, "applicationKeySecret": appkeysecret } res = self.session.http.post(self._USER_API, json=json_data) jsonres = self.session.http.json(res, schema=self._USER_SCHEMA) self.usertoken = jsonres['token'] # for authorzation matchresult = self._url_re.match(self.url) if matchresult.group("onair"): onair = matchresult.group("onair") if onair == "news-global": self._CHANNEL = update_qsd(self._CHANNEL, {"division": "1"}) res = self.session.http.get(self._CHANNEL) jsonres = self.session.http.json(res, schema=self._CHANNEL_SCHEMA) channels = jsonres["channels"] for channel in channels: if onair == channel["id"]: break else: raise NoStreamsError(self.url) playlisturl = channel["playback"]["hls"] elif matchresult.group("episode"): episode = matchresult.group("episode") if not self._is_playable("episode", episode): log.error("Premium stream is not playable") return {} playlisturl = self._PRGM3U8.format(episode) elif matchresult.group("slots"): slots = matchresult.group("slots") if not self._is_playable("slots", slots): log.error("Premium stream is not playable") return {} playlisturl = self._SLOTM3U8.format(slots) log.debug("URL={0}".format(playlisturl)) # hook abematv private protocol self.session.http.mount( "abematv-license://", AbemaTVLicenseAdapter(self.session, deviceid, self.usertoken)) streams = HLSStream.parse_variant_playlist(self.session, playlisturl) if not streams: return {"live": HLSStream(self.session, playlisturl)} else: return streams
_flashvar_re = re.compile(r"""(['"])(.*?)\1\s*:\s*(['"])(.*?)\3""") _clientlibs_re = re.compile(r"""<script.*?src=(['"])(.*?/clientlibs_anime_watch.*?\.js)\1""") _schema = validate.Schema( validate.union({ "flashvars": validate.all( validate.transform(_flashvars_re.search), validate.get(1), validate.transform(_flashvar_re.findall), validate.map(lambda v: (v[1], v[3])), validate.transform(dict), { "s": validate.text, "country": validate.text, "init": validate.text, validate.optional("ss_id"): validate.text, validate.optional("mv_id"): validate.text, validate.optional("device_cd"): validate.text, validate.optional("ss1_prm"): validate.text, validate.optional("ss2_prm"): validate.text, validate.optional("ss3_prm"): validate.text } ), "clientlibs": validate.all( validate.transform(_clientlibs_re.search), validate.get(2), validate.text ) }) )
def parse_fmt_list(formatsmap): formats = {} if not formatsmap: return formats for format in formatsmap.split(","): s = format.split("/") (w, h) = s[1].split("x") formats[int(s[0])] = "{0}p".format(h) return formats _config_schema = validate.Schema( { validate.optional("fmt_list"): validate.all( validate.text, validate.transform(parse_fmt_list) ), validate.optional("url_encoded_fmt_stream_map"): validate.all( validate.text, validate.transform(parse_stream_map), [{ "itag": validate.all( validate.text, validate.transform(int) ), "quality": validate.text, "url": validate.url(scheme="http"), validate.optional("s"): validate.text, validate.optional("stereo3d"): validate.all(
_rtmp_re = re.compile(""" (?P<host>rtmp://[^/]+) /(?P<app>[^/]+) /(?P<playpath>.+) """, re.VERBOSE) _url_re = re.compile(""" http(s)?://(\w+\.)? dailymotion.com (/embed)?/(video|live) /(?P<media_id>[^_?/]+) """, re.VERBOSE) _media_inner_schema = validate.Schema([{ "layerList": [{ "name": validate.text, validate.optional("sequenceList"): [{ "layerList": validate.all( [{ "name": validate.text, validate.optional("param"): dict }], validate.filter(lambda l: l["name"] in ("video", "reporting")) ) }] }] }]) _media_schema = validate.Schema( validate.any( _media_inner_schema, validate.all( {"sequence": _media_inner_schema},
_token_schema = validate.Schema( { "chansub": { "restricted_bitrates": validate.all( [validate.text], validate.filter( lambda n: not re.match(r"(.+_)?archives|live|chunked", n) ) ) } }, validate.get("chansub") ) _user_schema = validate.Schema( { validate.optional("display_name"): validate.text }, validate.get("display_name") ) _video_schema = validate.Schema( { "chunks": { validate.text: [{ "length": int, "url": validate.any(None, validate.url(scheme="http")), "upkeep": validate.any("pass", "fail", None) }] }, "restrictions": {validate.text: validate.text}, "start_offset": int, "end_offset": int,
_url_re = re.compile(r"http(s)?://(\w+\.)?be-at.tv/") _schema = validate.Schema( validate.any( None, { "status": int, "media": [{ "duration": validate.any(float, int), "offset": validate.any(float, int), "id": int, "parts": [{ "duration": validate.any(float, int), "id": int, "offset": validate.any(float, int), validate.optional("recording"): int, validate.optional("start"): validate.any(float, int) }] }] } ) ) Chunk = namedtuple("Chunk", "recording quality sequence extension") class BeatFLVTagConcat(FLVTagConcat): def __init__(self, *args, **kwargs): FLVTagConcat.__init__(self, *args, **kwargs) def decrypt_data(self, key, iv, data):
class Dogan(Plugin): """ Support for the live streams from Doğan Media Group channels """ url_re = re.compile( r""" https?://(?:www.)? (?:teve2.com.tr/(?:canli-yayin|filmler/.*|programlar/.*)| kanald.com.tr/.*| cnnturk.com/canli-yayin| dreamtv.com.tr/canli-yayin| dreamturk.com.tr/canli) """, re.VERBOSE) playerctrl_re = re.compile( r'''<div[^>]*?ng-controller=(?P<quote>["'])(?:Live)?PlayerCtrl(?P=quote).*?>''', re.DOTALL) data_id_re = re.compile( r'''data-id=(?P<quote>["'])(?P<id>\w+)(?P=quote)''') content_id_re = re.compile(r'"content(?:I|i)d", "(\w+)"') content_api = "/actions/content/media/{id}" new_content_api = "/action/media/{id}" content_api_schema = validate.Schema({ "Id": validate.text, "Media": { "Link": { "DefaultServiceUrl": validate.url(), validate.optional("ServiceUrl"): validate.any(validate.url(), ""), "SecurePath": validate.text, } } }) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_content_id(self): res = http.get(self.url) # find the contentId content_id_m = self.content_id_re.search(res.text) if content_id_m: return content_id_m.group(1) # find the PlayerCtrl div player_ctrl_m = self.playerctrl_re.search(res.text) if player_ctrl_m: # extract the content id from the player control data player_ctrl_div = player_ctrl_m.group(0) content_id_m = self.data_id_re.search(player_ctrl_div) if content_id_m: return content_id_m.group("id") def _get_hls_url(self, content_id): # make the api url relative to the current domain if "cnnturk" in self.url or "teve2.com.tr" in self.url: self.logger.debug("Using new content API url") api_url = urljoin(self.url, self.new_content_api.format(id=content_id)) else: api_url = urljoin(self.url, self.content_api.format(id=content_id)) apires = http.get(api_url) stream_data = http.json(apires, schema=self.content_api_schema) d = stream_data["Media"]["Link"] return urljoin((d["ServiceUrl"] or d["DefaultServiceUrl"]), d["SecurePath"]) def _get_streams(self): content_id = self._get_content_id() if content_id: self.logger.debug(u"Loading content: {}", content_id) hls_url = self._get_hls_url(content_id) return HLSStream.parse_variant_playlist(self.session, hls_url) else: self.logger.error(u"Could not find the contentId for this stream")
from streamlink.compat import urljoin from streamlink.plugin import Plugin from streamlink.plugin.api import http, validate from streamlink.plugin.api.utils import parse_json from streamlink.stream import AkamaiHDStream, HLSStream _url_re = re.compile(r"http(s)?://(www\.)?livestream.com/") _stream_config_schema = validate.Schema({ "event": { "stream_info": validate.any({ "is_live": bool, "qualities": [{ "bitrate": int, "height": int }], validate.optional("play_url"): validate.url(scheme="http"), validate.optional("m3u8_url"): validate.url( scheme="http", path=validate.endswith(".m3u8") ), }, None) }, validate.optional("playerUri"): validate.text, validate.optional("viewerPlusSwfUrl"): validate.url(scheme="http"), validate.optional("lsPlayerSwfUrl"): validate.text, validate.optional("hdPlayerSwfUrl"): validate.text }) _smil_schema = validate.Schema(validate.union({ "http_base": validate.all( validate.xml_find("{http://www.w3.org/2001/SMIL20/Language}head/" "{http://www.w3.org/2001/SMIL20/Language}meta"
class NimoTV(Plugin): data_url = 'https://m.nimo.tv/{0}' data_re = re.compile(r'<script>var G_roomBaseInfo = ({.*?});</script>') author = None category = None title = None data_schema = validate.Schema( validate.transform(data_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_json), { 'title': str, 'nickname': str, 'game': str, 'liveStreamStatus': int, validate.optional('mStreamPkg'): str, }, )), ) video_qualities = { 250: '240p', 500: '360p', 1000: '480p', 2500: '720p', 6000: '1080p', } _re_appid = re.compile(br'appid=(\d+)') _re_domain = re.compile( br'(https?:\/\/[A-Za-z]{2,3}.hls[A-Za-z\.\/]+)(?:V|&)') _re_id = re.compile(br'id=([^|\\]+)') _re_tp = re.compile(br'tp=(\d+)') def get_author(self): return self.author def get_category(self): return self.category def get_title(self): return self.title def _get_streams(self): username = self.match.group('username') if not username: return headers = {'User-Agent': useragents.ANDROID} data = self.session.http.get( self.data_url.format(username), headers=headers, schema=self.data_schema, ) if data['liveStreamStatus'] == 0: log.info('This stream is currently offline') return mStreamPkg = data.get('mStreamPkg') if not mStreamPkg: log.debug('missing mStreamPkg') return mStreamPkg = bytes.fromhex(mStreamPkg) try: _appid = self._re_appid.search(mStreamPkg).group(1).decode('utf-8') _domain = self._re_domain.search(mStreamPkg).group(1).decode( 'utf-8') _id = self._re_id.search(mStreamPkg).group(1).decode('utf-8') _tp = self._re_tp.search(mStreamPkg).group(1).decode('utf-8') except AttributeError: log.error('invalid mStreamPkg') return params = { 'appid': _appid, 'id': _id, 'tp': _tp, 'u': '0', 't': '100', 'needwm': 1, } url = f'{_domain}{_id}.m3u8' log.debug(f'URL={url}') for k, v in self.video_qualities.items(): _params = params.copy() _params.update({'ratio': k}) if v == '1080p': _params.update({'needwm': 0}) elif v in ('720p', '480p', '360p'): _params.update({'sphd': 1}) log.trace(f'{v} params={_params!r}') # some qualities might not exist, but it will select a different lower quality yield v, HLSStream(self.session, url, params=_params) self.author = data['nickname'] self.category = data['game'] self.title = data['title']
QUALITYS = ["original", "hd", "sd"] QUALITY_WEIGHTS = { "original": 1080, "hd": 720, "sd": 480 } _url_re = re.compile(r"http(s)?://(?P<cdn>\w+\.)?afreeca(tv)?\.com/(?P<username>\w+)(/\d+)?") _channel_schema = validate.Schema( { "CHANNEL": { "RESULT": validate.transform(int), validate.optional("BPWD"): validate.text, validate.optional("BNO"): validate.text, validate.optional("RMD"): validate.text, validate.optional("AID"): validate.text, validate.optional("CDN"): validate.text } }, validate.get("CHANNEL") ) _stream_schema = validate.Schema( { validate.optional("view_url"): validate.url( scheme=validate.any("rtmp", "http") ), "stream_status": validate.text
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 } }) arguments = PluginArguments( PluginArgument( "email", help="The email address used to register with tvplayer.com.", metavar="EMAIL", requires=["password"]), PluginArgument("password", sensitive=True, help="The password for your tvplayer.com account.", metavar="PASSWORD")) @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"): self.logger.debug("Logging in as {0}".format( self.get_option("email"))) 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" )
list, validate.length(1), validate.get(0), dict ) _amf3_array = validate.Schema( validate.any( validate.all( {int: object}, validate.transform(lambda a: list(a.values())), ), list ) ) _recorded_schema = validate.Schema({ validate.optional("stream"): validate.all( _amf3_array, [{ "name": validate.text, "streams": validate.all( _amf3_array, [{ "streamName": validate.text, "bitrate": float, }], ), validate.optional("url"): validate.text, }] ) }) _stream_schema = validate.Schema(
SWF_URL = "http://mips.tv/content/scripts/eplayer.swf" _url_re = re.compile(r"http(s)?://(\w+.)?mips.tv/(?P<channel>[^/&?]+)") _flashvars_re = re.compile(r"'FlashVars', '([^']+)'") _rtmp_re = re.compile(r"redirect=(.+)") _schema = validate.Schema( validate.transform(_flashvars_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_query), { "id": validate.transform(int), validate.optional("s"): validate.text } ) ) ) _rtmp_schema = validate.Schema( validate.transform(_rtmp_re.search), validate.get(1), ) class Mips(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url)