def clips(self, clipname): query = """{{ clip(slug: "{clipname}") {{ broadcaster {{ displayName }} title game {{ name }} videoQualities {{ quality sourceURL }} }} }}""".format(clipname=clipname) return self.call_gql( {"query": query}, schema=validate.Schema( { "data": { "clip": validate.any( None, validate.all( { "broadcaster": validate.all( {"displayName": validate.text}, validate.get("displayName")), "title": validate.text, "game": validate.all({"name": validate.text}, validate.get("name")), "videoQualities": [ validate.all( { "quality": validate.all( validate.text, validate.transform( lambda q: "{0}p". format(q))), "sourceURL": validate.url() }, validate.union( (validate.get("quality"), validate.get("sourceURL")))) ] }, validate.union( (validate.get("broadcaster"), validate.get("title"), validate.get("game"), validate.get("videoQualities"))))) } }, validate.get("data"), validate.get("clip")))
class TestUnionSchema: upper = validate.transform(str.upper) def test_dict_success(self): schema = validate.union({ "foo": str, "bar": self.upper, validate.optional("baz"): int, }) assert validate.validate(schema, "value") == { "foo": "value", "bar": "VALUE" } def test_dict_failure(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.union({"foo": int}), "value") assert_validationerror( cm.value, """ ValidationError(UnionSchema): Could not validate union Context(dict): Unable to validate union 'foo' Context(type): Type of 'value' should be int, but is str """) @pytest.mark.parametrize( "schema, expected", [ (validate.union([str, upper]), ["value", "VALUE"]), (validate.union((str, upper)), ("value", "VALUE")), (validate.union({str, upper}), {"value", "VALUE"}), (validate.union(frozenset( (str, upper))), frozenset(("value", "VALUE"))), ], ids=[ "list", "tuple", "set", "frozenset", ], ) def test_sequence(self, schema, expected): result = validate.validate(schema, "value") assert result == expected def test_failure_schema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.union(None), None) assert_validationerror( cm.value, """ ValidationError(UnionSchema): Could not validate union Context: Invalid union type: NoneType """)
def _get_streams(self): self.id, self.title = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.union(( validate.xml_xpath_string( ".//script[@class='dacast-video'][@id]/@id"), validate.xml_xpath_string(".//head/title[1]/text()"), )))) if not self.id: return if re.match(r"\w+_\w+_\w+", self.id): provider = "dacast" else: provider = "universe" data = self.session.http.get( f"https://playback.dacast.com/content/access?contentId={self.id}&provider={provider}", acceptable_status=(200, 400, 403, 404), schema=validate.Schema( validate.parse_json(), validate.any( {"error": str}, {"hls": validate.url()}, ))) if data.get("error"): log.error(data["error"]) return return HLSStream.parse_variant_playlist(self.session, data["hls"])
def access_token(self, endpoint, asset): return self.call("/api/{0}/{1}/access_token".format(endpoint, asset), private=True, schema=validate.Schema( { "token": validate.text, "sig": validate.text }, validate.union((validate.get("sig"), validate.get("token")))))
def test_failure_schema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.union(None), None) assert_validationerror( cm.value, """ ValidationError(UnionSchema): Could not validate union Context: Invalid union type: NoneType """)
def test_dict_success(self): schema = validate.union({ "foo": str, "bar": self.upper, validate.optional("baz"): int, }) assert validate.validate(schema, "value") == { "foo": "value", "bar": "VALUE" }
def test_dict_failure(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.union({"foo": int}), "value") assert_validationerror( cm.value, """ ValidationError(UnionSchema): Could not validate union Context(dict): Unable to validate union 'foo' Context(type): Type of 'value' should be int, but is str """)
def channel_from_video_id(self, video_id): return self.call("/kraken/videos/{0}".format(video_id), schema=validate.Schema( { "channel": { "_id": validate.any(int, validate.text), "name": validate.text } }, validate.get("channel"), validate.union(( validate.all(validate.get("_id"), validate.transform(int)), validate.all(validate.get("name"), validate.transform(lambda n: n.lower())) )) ))
class ard_live(Plugin): swf_url = "http://live.daserste.de/lib/br-player/swf/main.swf" _url_re = re.compile(r"https?://(www.)?daserste.de/", re.I) _player_re = re.compile(r'''dataURL\s*:\s*(?P<q>['"])(?P<url>.*?)(?P=q)''') _player_url_schema = validate.Schema( validate.transform(_player_re.search), validate.any(None, validate.all(validate.get("url"), validate.text))) _livestream_schema = validate.Schema( validate.xml_findall(".//assets"), validate.filter(lambda x: x.attrib.get("type") != "subtitles"), validate.get(0), validate.xml_findall(".//asset"), [ validate.union({ "url": validate.xml_findtext("./fileName"), "bitrate": validate.xml_findtext("./bitrateVideo") }) ]) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _get_streams(self): data_url = self.session.http.get(self.url, schema=self._player_url_schema) if data_url: res = self.session.http.get(urljoin(self.url, data_url)) stream_info = self.session.http.xml(res, schema=self._livestream_schema) for stream in stream_info: url = stream["url"] try: if ".m3u8" in url: for s in HLSStream.parse_variant_playlist( self.session, url, name_key="bitrate").items(): yield s elif ".f4m" in url: for s in HDSStream.parse_manifest( self.session, url, pvswf=self.swf_url, is_akamai=True).items(): yield s elif ".mp4" in url: yield "{0}k".format(stream["bitrate"]), HTTPStream( self.session, url) except IOError as err: self.logger.warning("Error parsing stream: {0}", err)
def _get_streams(self): hls_url, self.title = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.union(( validate.xml_xpath_string( ".//video/source[@src][@type='application/x-mpegURL'][1]/@src" ), validate.xml_xpath_string(".//head/title[1]/text()"), )))) if not hls_url: return return HLSStream.parse_variant_playlist(self.session, hls_url, headers={"Referer": self.url})
def stream_rerun(self, channel_id): return self.call("/kraken/streams/{0}".format(channel_id), schema=validate.Schema( { "stream": validate.any(None, validate.all({ "stream_type": validate.text, "broadcast_platform": validate.text, "channel": validate.all({ "broadcaster_software": validate.text }, validate.get("broadcaster_software")) }, validate.union(( validate.get("stream_type"), validate.get("broadcast_platform"), validate.get("channel") )))) }, validate.get("stream") ))
def _schema_consent(data): schema_consent = validate.Schema( validate.parse_html(), validate.any( validate.xml_find(".//form[@action='https://consent.youtube.com/s']"), validate.all( validate.xml_xpath(".//form[@action='https://consent.youtube.com/save']"), validate.filter(lambda elem: elem.xpath(".//input[@type='hidden'][@name='set_ytc'][@value='true']")), validate.get(0), ) ), validate.union(( validate.get("action"), validate.xml_xpath(".//input[@type='hidden']"), )), ) return schema_consent.validate(data)
def hosted_channel(self, channel_id): return self.call("/hosts", subdomain="tmi", include_logins=1, host=channel_id, schema=validate.Schema( { "hosts": [{ "host_id": int, "target_id": int, "target_login": validate.text, "target_display_name": validate.text }] }, validate.get("hosts"), validate.get(0), validate.union(( validate.get("host_id"), validate.get("target_id"), validate.get("target_login"), validate.get("target_display_name") )) ))
def _get_streams(self): stream_id, has_token, hls_url, dash_url = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_find(".//*[@data-video-id]"), validate.union(( validate.get("data-video-id"), validate.all( validate.get("data-video-has-token"), validate.transform(lambda val: val and val != "false"), ), validate.get("data-vjs-clip-hls-url"), validate.get("data-vjs-clip-dash-url"), )), ), ) if dash_url and has_token: token = self._get_stream_token(stream_id, "dash") parsed = urlsplit(dash_url) dash_url = urlunsplit( parsed._replace(path=f"{token}{parsed.path}")) return DASHStream.parse_manifest( self.session, dash_url, headers={"Referer": self.url}, ) if not hls_url: return if has_token: token = self._get_stream_token(stream_id, "hls") hls_url = f"{hls_url}?{token}" return HLSStream.parse_variant_playlist( self.session, hls_url, headers={"Referer": self.url}, )
def access_token(self, is_live, channel_or_vod): request = { "operationName": "PlaybackAccessToken", "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "0828119ded1c13477966434e15800ff57ddacf13ba1911c129dc2200705b0712" } }, "variables": { "isLive": is_live, "login": channel_or_vod if is_live else "", "isVod": not is_live, "vodID": channel_or_vod if not is_live else "", "playerType": "embed" } } subschema = validate.any(None, validate.all( { "value": validate.text, "signature": validate.text, "__typename": "PlaybackAccessToken" }, validate.union(( validate.get("signature"), validate.get("value") )) )) return self.call_gql(request, schema=validate.Schema( {"data": validate.any( validate.all( {"streamPlaybackAccessToken": subschema}, validate.get("streamPlaybackAccessToken") ), validate.all( {"videoPlaybackAccessToken": subschema}, validate.get("videoPlaybackAccessToken") ) )}, validate.get("data") ))
class Mixer(Plugin): api_url = "https://mixer.com/api/v1/{type}/{id}" _vod_schema = validate.Schema( { "state": "AVAILABLE", "vods": [{ "baseUrl": validate.url(), "data": validate.any(None, {"Height": int}), "format": validate.text }] }, validate.get("vods"), validate.filter(lambda x: x["format"] in ("raw", "hls")), [ validate.union({ "url": validate.get("baseUrl"), "format": validate.get("format"), "height": validate.all(validate.get("data"), validate.get("Height")) }) ]) @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _get_api_res(self, api_type, api_id): try: res = self.session.http.get( self.api_url.format(type=api_type, id=api_id)) return res except Exception as e: if "404" in str(e): self.logger.error("invalid {0} - {1}".format(api_type, api_id)) elif "429" in str(e): self.logger.error( "Too Many Requests, API rate limit exceeded.") raise NoStreamsError(self.url) def _get_vod_stream(self, vod_id): res = self._get_api_res("recordings", vod_id) for sdata in self.session.http.json(res, schema=self._vod_schema): if sdata["format"] == "hls": hls_url = urljoin(sdata["url"], "manifest.m3u8") yield "{0}p".format(sdata["height"]), HLSStream( self.session, hls_url) def _get_live_stream(self, channel): res = self._get_api_res("channels", channel) channel_info = self.session.http.json(res) if not channel_info["online"]: return user_id = channel_info["id"] hls_url = self.api_url.format(type="channels", id="{0}/manifest.m3u8".format(user_id)) for s in HLSStream.parse_variant_playlist(self.session, hls_url).items(): yield s def _get_streams(self): params = dict(parse_qsl(urlparse(self.url).query)) vod_id = params.get("vod") match = _url_re.match(self.url) channel = match.group("channel") if vod_id: self.logger.debug("Looking for VOD {0} from channel: {1}", vod_id, channel) return self._get_vod_stream(vod_id) else: self.logger.debug("Looking for channel: {0}", channel) return self._get_live_stream(channel)
}) _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 }), validate.get("content") ), "videos": validate.all( validate.xml_findall("{http://www.w3.org/2001/SMIL20/Language}body/" "{http://www.w3.org/2001/SMIL20/Language}switch/" "{http://www.w3.org/2001/SMIL20/Language}video"), [ validate.all( validate.xml_element(attrib={ "src": validate.text, "system-bitrate": validate.all( validate.text, validate.transform(int) ) }), validate.transform( lambda e: (e.attrib["src"], e.attrib["system-bitrate"]) ) ) ], ) }))
)? """, re.VERBOSE) _file_re = re.compile("\"?file\"?:\s+['\"]([^'\"]+)['\"]") _swf_url_re = re.compile("swfobject.embedSWF\(\"([^\"]+)\",") _schema = validate.Schema( validate.union({ "urls": validate.all( validate.transform(_file_re.findall), validate.map(unquote), [validate.url()] ), "swf": validate.all( validate.transform(_swf_url_re.search), validate.any( None, validate.all( validate.get(1), validate.url( scheme="http", path=validate.endswith("swf") ) ) ) ) }) ) class Aliez(Plugin): @classmethod def can_handle_url(self, url):
)? (?: (?P<minutes>\d+)m )? (?: (?P<seconds>\d+)s )? """, re.VERBOSE) _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") )
_url_re = re.compile( r""" https?://(\w+\.)?aliez.\w+/ (?:live/[^/]+|video/\d+/[^/]+) """, re.VERBOSE) _file_re = re.compile(r"\"?file\"?:\s+['\"]([^'\"]+)['\"]") _swf_url_re = re.compile(r"swfobject.embedSWF\(\"([^\"]+)\",") _schema = validate.Schema( validate.union({ "urls": validate.all(validate.transform(_file_re.findall), validate.map(unquote), [validate.url()]), "swf": validate.all( validate.transform(_swf_url_re.search), validate.any( None, validate.all( validate.get(1), validate.url(scheme="http", path=validate.endswith("swf"))))) })) class Aliez(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_streams(self): res = self.session.http.get(self.url, schema=_schema)
_url_re = re.compile(r"http(s)?://(\w+\.)?seemeplay.ru/") _player_re = re.compile( r""" SMP.(channel|video).player.init\({ \s+file:\s+"([^"]+)" """, re.VERBOSE) _schema = validate.Schema( validate.transform(_player_re.search), validate.any( None, validate.union({ "type": validate.get(1), "url": validate.all( validate.get(2), validate.url(scheme="http"), ), }))) class SeeMePlay(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _get_streams(self): res = http.get(self.url, schema=_schema) if not res: return
def test_union(self): assert validate(union((get("foo"), get("bar"))), {"foo": "alpha", "bar": "beta"}) == ("alpha", "beta")
validate.union({ "export_url": validate.all(validate.transform(_live_export_re.search), validate.any( None, validate.get(1), )), "video_flashvars": validate.all( validate.transform(_video_flashvars_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_query), { "_111pix_serverURL": validate.url(scheme="rtmp"), "en_flash_providerName": validate.text }))), "history_video": validate.all( validate.transform(_history_re.search), validate.any( None, validate.all(validate.get(1), validate.url(scheme="http")))), "standby_video": validate.all( validate.transform(_replay_json_re.search), validate.any( None, validate.all(validate.get(1), validate.transform(parse_json), [{ "streamName": validate.url(scheme="http") }]))) }))
from streamlink.stream import HLSStream, HTTPStream _url_re = re.compile(r"http(s)?://(\w+\.)?seemeplay.ru/") _player_re = re.compile(r""" SMP.(channel|video).player.init\({ \s+file:\s+"([^"]+)" """, re.VERBOSE) _schema = validate.Schema( validate.transform(_player_re.search), validate.any( None, validate.union({ "type": validate.get(1), "url": validate.all( validate.get(2), validate.url(scheme="http"), ), }) ) ) class SeeMePlay(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _get_streams(self): res = http.get(self.url, schema=_schema) if not res:
/ (?P<video_type>[bcv])(?:ideo)? / (?P<video_id>\d+) )? (?: / (?P<clip_name>[\w]+) )? """, re.VERBOSE) _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: [{
_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 ) }) ) _language_schema = validate.Schema(
validate.union({ "export_url": validate.all( validate.transform(_live_export_re.search), validate.any( None, validate.get(1), ) ), "video_flashvars": validate.all( validate.transform(_video_flashvars_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_query), { "_111pix_serverURL": validate.url(scheme="rtmp"), "en_flash_providerName": validate.text } ) ) ), "history_video": validate.all( validate.transform(_history_re.search), validate.any( None, validate.all( validate.get(1), validate.url(scheme="http") ) ) ), "standby_video": validate.all( validate.transform(_replay_json_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_json), [{ "streamName": validate.url(scheme="http") }] ) ) ) })
class Rtve(Plugin): secret_key = base64.b64decode("eWVMJmRhRDM=") url_re = re.compile(r""" https?://(?:www\.)?rtve\.es/(?:directo|infantil|noticias|television|deportes|alacarta|drmn)/.*?/? """, re.VERBOSE) cdn_schema = validate.Schema( validate.transform(partial(parse_xml, invalid_char_entities=True)), validate.xml_findall(".//preset"), [ validate.union({ "quality": validate.all(validate.getattr("attrib"), validate.get("type")), "urls": validate.all( validate.xml_findall(".//url"), [validate.getattr("text")] ) }) ] ) subtitles_api = "http://www.rtve.es/api/videos/{id}/subtitulos.json" subtitles_schema = validate.Schema({ "page": { "items": [{ "src": validate.url(), "lang": validate.text }] } }, validate.get("page"), validate.get("items")) video_api = "http://www.rtve.es/api/videos/{id}.json" video_schema = validate.Schema({ "page": { "items": [{ "qualities": [{ "preset": validate.text, "height": int }] }] } }, validate.get("page"), validate.get("items"), validate.get(0)) arguments = PluginArguments( PluginArgument( "mux-subtitles", action="store_true", help=""" Automatically mux available subtitles in to the output stream. """ ) ) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def __init__(self, url): Plugin.__init__(self, url) self.session.http.headers = {"User-Agent": useragents.SAFARI_8} self.zclient = ZTNRClient(self.secret_key, self.session) def _get_content_id(self): res = self.session.http.get(self.url) for div in itertags(res.text, "div"): if div.attributes.get("data-id"): return int(div.attributes.get("data-id")) else: log.error("Failed to get content_id") def _get_subtitles(self, content_id): res = self.session.http.get(self.subtitles_api.format(id=content_id)) return self.session.http.json(res, schema=self.subtitles_schema) def _get_quality_map(self, content_id): res = self.session.http.get(self.video_api.format(id=content_id)) data = self.session.http.json(res, schema=self.video_schema) qmap = {} for item in data["qualities"]: qname = {"MED": "Media", "HIGH": "Alta", "ORIGINAL": "Original"}.get(item["preset"], item["preset"]) qmap[qname] = u"{0}p".format(item["height"]) return qmap def _get_streams(self): streams = [] content_id = self._get_content_id() if content_id: log.debug(f"Found content with id: {content_id}") stream_data = self.zclient.get_cdn_list(content_id, schema=self.cdn_schema) quality_map = None for stream in stream_data: for url in stream["urls"]: if ".m3u8" in url: try: streams.extend(HLSStream.parse_variant_playlist(self.session, url).items()) except (IOError, OSError) as err: log.error(str(err)) elif ((url.endswith("mp4") or url.endswith("mov") or url.endswith("avi")) and self.session.http.head(url, raise_for_status=False).status_code == 200): if quality_map is None: # only make the request when it is necessary quality_map = self._get_quality_map(content_id) # rename the HTTP sources to match the HLS sources quality = quality_map.get(stream["quality"], stream["quality"]) streams.append((quality, HTTPStream(self.session, url))) subtitles = None if self.get_option("mux_subtitles"): subtitles = self._get_subtitles(content_id) if subtitles: substreams = {} for i, subtitle in enumerate(subtitles): substreams[subtitle["lang"]] = HTTPStream(self.session, subtitle["src"]) for q, s in streams: yield q, MuxedStream(self.session, s, subtitles=substreams) else: for s in streams: yield s
"_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") ), "videos": validate.all( validate.xml_findall("body/seq/video"), [validate.get("src")] ) }) ) class ard_mediathek(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url)
"_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")), "videos": validate.all(validate.xml_findall("body/seq/video"), [validate.get("src")]) })) class ard_mediathek(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _get_http_streams(self, info): name = QUALITY_MAP.get(info["_quality"], "vod") urls = info["_stream"] if not isinstance(info["_stream"], list): urls = [urls]
class BBCiPlayer(Plugin): url_re = re.compile( r"""https?://(?:www\.)?bbc.co.uk/iplayer/ ( episode/(?P<episode_id>\w+)| live/(?P<channel_name>\w+) ) """, re.VERBOSE) vpid_re = re.compile(r'"ident_id"\s*:\s*"(\w+)"') tvip_re = re.compile(r'event_master_brand=(\w+?)&') account_locals_re = re.compile(r'window.bbcAccount.locals\s*=\s*(\{.*?});') swf_url = "http://emp.bbci.co.uk/emp/SMPf/1.18.3/StandardMediaPlayerChromelessFlash.swf" hash = base64.b64decode( b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==") api_url = ( "http://open.live.bbc.co.uk/mediaselector/5/select/" "version/2.0/mediaset/{platform}/vpid/{vpid}/atk/{vpid_hash}/asn/1/") platforms = ("pc", "iptv-all") config_url = "http://www.bbc.co.uk/idcta/config" auth_url = "https://account.bbc.com/signin" config_schema = validate.Schema( validate.transform(parse_json), { "signin_url": validate.url(), "identity": { "cookieAgeDays": int, "accessTokenCookieName": validate.text, "idSignedInCookieName": validate.text } }) mediaselector_schema = validate.Schema( validate.transform(partial(parse_xml, ignore_ns=True)), validate.union({ "hds": validate.xml_findall( ".//media[@kind='video']//connection[@transferFormat='hds']"), "hls": validate.xml_findall( ".//media[@kind='video']//connection[@transferFormat='hls']") }), { validate.text: validate.all( [ validate.all(validate.getattr("attrib"), validate.get("href")) ], validate.transform(lambda x: list(set(x))) # unique ) }) options = PluginOptions({"password": None, "username": None}) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None @classmethod def _hash_vpid(cls, vpid): return sha1(cls.hash + str(vpid).encode("utf8")).hexdigest() def find_vpid(self, url, res=None): self.logger.debug("Looking for vpid on {0}", url) # Use pre-fetched page if available res = res or http.get(url) m = self.vpid_re.search(res.text) return m and m.group(1) def find_tvip(self, url): self.logger.debug("Looking for tvip on {0}", url) res = http.get(url) m = self.tvip_re.search(res.text) return m and m.group(1) def mediaselector(self, vpid): for platform in self.platforms: url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid), platform=platform) stream_urls = http.get(url, schema=self.mediaselector_schema) for surl in stream_urls.get("hls"): for s in HLSStream.parse_variant_playlist(self.session, surl).items(): yield s for surl in stream_urls.get("hds"): for s in HDSStream.parse_manifest(self.session, surl).items(): yield s def login(self, ptrt_url, context="tvandiplayer"): # get the site config, to find the signin url config = http.get(self.config_url, params=dict(ptrt=ptrt_url), schema=self.config_schema) res = http.get(config["signin_url"], params=dict(userOrigin=context, context=context), headers={"Referer": self.url}) m = self.account_locals_re.search(res.text) if m: auth_data = parse_json(m.group(1)) res = http.post(self.auth_url, params=dict(context=auth_data["userOrigin"], ptrt=auth_data["ptrt"]["value"], userOrigin=auth_data["userOrigin"], nonce=auth_data["nonce"]), data=dict(jsEnabled="false", attempts=0, username=self.get_option("username"), password=self.get_option("password"))) # redirects to ptrt_url on successful login if res.url == ptrt_url: return res else: self.logger.error( "Could not authenticate, could not find the authentication nonce" ) def _get_streams(self): self.logger.info( "A TV License is required to watch BBC iPlayer streams, see the BBC website for more " "information: https://www.bbc.co.uk/iplayer/help/tvlicence") page_res = None if self.get_option("username"): page_res = self.login(self.url) if not page_res: self.logger.error( "Could not authenticate, check your username and password") return m = self.url_re.match(self.url) episode_id = m.group("episode_id") channel_name = m.group("channel_name") if episode_id: self.logger.debug("Loading streams for episode: {0}", episode_id) vpid = self.find_vpid(self.url, res=page_res) if vpid: self.logger.debug("Found VPID: {0}", vpid) for s in self.mediaselector(vpid): yield s else: self.logger.error("Could not find VPID for episode {0}", episode_id) elif channel_name: self.logger.debug("Loading stream for live channel: {0}", channel_name) tvip = self.find_tvip(self.url) if tvip: self.logger.debug("Found TVIP: {0}", tvip) for s in self.mediaselector(tvip): yield s
class DeutscheWelle(Plugin): default_channel = "1" url_re = re.compile(r"https?://(?:www\.)?dw\.com/") channel_re = re.compile(r'''<a.*?data-id="(\d+)".*?class="ici"''') live_stream_div = re.compile( r''' <div\s+class="mediaItem"\s+data-channel-id="(\d+)".*?>.*? <input\s+type="hidden"\s+name="file_name"\s+value="(.*?)"\s*>.*?<div ''', re.DOTALL | re.VERBOSE) smil_api_url = "http://www.dw.com/smil/{}" html5_api_url = "http://www.dw.com/html5Resource/{}" vod_player_type_re = re.compile( r'<input type="hidden" name="player_type" value="(?P<stream_type>.+?)">' ) stream_vod_data_re = re.compile( r'<input\s+type="hidden"\s+name="file_name"\s+value="(?P<stream_url>.+?)">.*?' r'<input\s+type="hidden"\s+name="media_id"\s+value="(?P<stream_id>\d+)">', re.DOTALL) smil_schema = validate.Schema( validate.union({ "base": validate.all(validate.xml_find(".//meta"), validate.xml_element(attrib={"base": validate.text}), validate.get("base")), "streams": validate.all(validate.xml_findall(".//switch/*"), [ validate.all( validate.getattr("attrib"), { "src": validate.text, "system-bitrate": validate.all( validate.text, validate.transform(int), ), validate.optional("width"): validate.all(validate.text, validate.transform(int)) }) ]) })) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _create_stream(self, url, quality=None): if url.startswith('rtmp://'): return (quality, RTMPStream(self.session, {'rtmp': url})) if url.endswith('.m3u8'): return HLSStream.parse_variant_playlist(self.session, url).items() return (quality, HTTPStream(self.session, url)) def _get_live_streams(self, page): # check if a different language has been selected qs = dict(parse_qsl(urlparse(self.url).query)) channel = qs.get("channel") if not channel: m = self.channel_re.search(page.text) channel = m and m.group(1) self.logger.debug("Using sub-channel ID: {0}", channel) # extract the streams from the page, mapping between channel-id and stream url media_items = self.live_stream_div.finditer(page.text) stream_map = dict([mi.groups((1, 2)) for mi in media_items]) stream_url = stream_map.get(str(channel) or self.default_channel) if stream_url: return self._create_stream(stream_url) def _get_vod_streams(self, stream_type, page): m = self.stream_vod_data_re.search(page.text) if m is None: return stream_url, stream_id = m.groups() if stream_type == "video": stream_api_id = "v-{}".format(stream_id) default_quality = "vod" elif stream_type == "audio": stream_api_id = "a-{}".format(stream_id) default_quality = "audio" else: return # Retrieve stream embedded in web page yield self._create_stream(stream_url, default_quality) # Retrieve streams using API res = self.session.http.get(self.smil_api_url.format(stream_api_id)) videos = self.session.http.xml(res, schema=self.smil_schema) for video in videos['streams']: url = videos["base"] + video["src"] if url == stream_url or url.replace("_dwdownload.", ".") == stream_url: continue if video["system-bitrate"] > 0: # If width is available, use it to select the best stream # amongst those with same bitrate quality = "{}k".format( (video["system-bitrate"] + video.get("width", 0)) // 1000) else: quality = default_quality yield self._create_stream(url, quality) def _get_streams(self): res = self.session.http.get(self.url) m = self.vod_player_type_re.search(res.text) if m is None: return stream_type = m.group("stream_type") if stream_type == "dwlivestream": return self._get_live_streams(res) return self._get_vod_streams(stream_type, res)
from streamlink.plugin import Plugin from streamlink.plugin.api import http, validate from streamlink.stream import HLSStream STREAM_INFO_URL = "https://api.periscope.tv/api/v2/getAccessPublic" STATUS_GONE = 410 STATUS_UNAVAILABLE = (STATUS_GONE,) _url_re = re.compile(r"http(s)?://(www\.)?(periscope|pscp)\.tv/[^/]+/(?P<broadcast_id>[\w\-\=]+)") _stream_schema = validate.Schema( validate.any( None, validate.union({ "hls_url": validate.all( {"hls_url": validate.url(scheme="http")}, validate.get("hls_url") ), }), validate.union({ "replay_url": validate.all( {"replay_url": validate.url(scheme="http")}, validate.get("replay_url") ), }), ), ) class Periscope(Plugin): @classmethod def can_handle_url(cls, url):
""", re.VERBOSE) _swf_player_re = re.compile( r'swfobject.embedSWF\("(/\d+/swf/[0-9A-Za-z]+\.swf)"') _schema = validate.Schema( validate.any( validate.all(u"<error></error>", validate.transform(lambda x: None)), validate.all( validate.transform(lambda s: s.split(";")), validate.length(3), validate.union({ "server": validate.all(validate.get(0), validate.text), "token": validate.all( validate.get(1), validate.text, validate.startswith(":mvnkey-"), validate.transform(lambda s: s[len(":mvnkey-"):])), "ingest": validate.all(validate.get(2), validate.text) })))) class VaughnLive(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _get_streams(self): res = http.get(self.url) match = _swf_player_re.search(res.text)
CHANNEL_INFO = "https://beam.pro/api/v1/channels/{0}" CHANNEL_MANIFEST = "https://beam.pro/api/v1/channels/{0}/manifest.smil" _assets_schema = validate.Schema( validate.union({ "base": validate.all( validate.xml_find("./head/meta"), validate.get("base"), validate.url(scheme="rtmp") ), "videos": validate.all( validate.xml_findall(".//video"), [ validate.union({ "src": validate.all( validate.get("src"), validate.text ), "height": validate.all( validate.get("height"), validate.text, validate.transform(int) ) }) ] ) }) ) class Beam(Plugin):
CHANNEL_INFO = "https://beam.pro/api/v1/channels/{0}" CHANNEL_MANIFEST = "https://beam.pro/api/v1/channels/{0}/manifest.smil" _assets_schema = validate.Schema( validate.union({ "base": validate.all( validate.xml_find("./head/meta"), validate.get("base"), validate.url(scheme="rtmp") ), "videos": validate.all( validate.xml_findall(".//video"), [ validate.union({ "src": validate.all( validate.get("src"), validate.text ), "height": validate.all( validate.get("height"), validate.text, validate.transform(int) ) }) ] ) }) ) class Beam(Plugin): @classmethod
class AnimeLab(Plugin): url_re = re.compile(r"https?://(?:www\.)?animelab\.com/player/") login_url = "https://www.animelab.com/login" video_collection_re = re.compile(r"VideoCollection\((\[.*?\])\);") playlist_position_re = re.compile(r"playlistPosition\s*=\s*(\d+);") video_collection_schema = validate.Schema( validate.union({ "position": validate.all( validate.transform(playlist_position_re.search), validate.any( None, validate.all(validate.get(1), validate.transform(int)) ) ), "playlist": validate.all( validate.transform(video_collection_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_json) ) ) ) }) ) arguments = PluginArguments( PluginArgument( "email", requires=["password"], metavar="EMAIL", help="The email address used to register with animelab.com." ), PluginArgument( "password", sensitive=True, metavar="PASSWORD", help="A animelab.com account password to use with --animelab-email." ) ) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def login(self, email, password): self.logger.debug("Attempting to log in as {0}", email) res = http.post(self.login_url, data=dict(email=email, password=password), allow_redirects=False, raise_for_status=False) loc = res.headers.get("Location", "") if "geoblocked" in loc.lower(): self.logger.error("AnimeLab is not available in your territory") elif res.status_code >= 400: self.logger.error("Failed to login to AnimeLab, check your email/password combination") else: return True return False def _get_streams(self): email, password = self.get_option("email"), self.get_option("password") if not email or not password: self.logger.error("AnimeLab requires authentication, use --animelab-email " "and --animelab-password to set your email/password combination") return if self.login(email, password): self.logger.info("Successfully logged in as {0}", email) video_collection = http.get(self.url, schema=self.video_collection_schema) if video_collection["playlist"] is None or video_collection["position"] is None: return data = video_collection["playlist"][video_collection["position"]] self.logger.debug("Found {0} version {1} hard-subs", data["language"]["name"], "with" if data["hardSubbed"] else "without") for video in data["videoInstances"]: if video["httpUrl"]: q = video["videoQuality"]["description"] s = HTTPStream(self.session, video["httpUrl"]) yield q, s
def test_union(self): assert validate(union((get("foo"), get("bar"))), { "foo": "alpha", "bar": "beta" }) == ("alpha", "beta")