class LineLive(Plugin): _url_re = re.compile( r""" https?://live\.line\.me /channels/(?P<channel>\d+) /broadcast/(?P<broadcast>\d+) """, re.VERBOSE) _api_url = "https://live-api.line-apps.com/app/v3.2/channel/{0}/broadcast/{1}/player_status" _player_status_schema = validate.Schema({ "liveStatus": validate.text, "liveHLSURLs": validate.any( None, { "720": validate.any( None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "480": validate.any( None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "360": validate.any( None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "240": validate.any( None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "144": validate.any( None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), }) }) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _get_streams(self): match = self._url_re.match(self.url) channel = match.group("channel") broadcast = match.group("broadcast") res = self.session.http.get(self._api_url.format(channel, broadcast)) json = self.session.http.json(res, schema=self._player_status_schema) if json["liveStatus"] != "LIVE": return for stream in json["liveHLSURLs"]: url = json["liveHLSURLs"][stream] if url != None: yield "{0}p".format(stream), HLSStream(self.session, url)
def _get_streams(self): try: hls = self.session.http.get(self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//script[@type='application/json'][@id='__NEXT_DATA__']/text()"), str, validate.parse_json(), { "props": { "pageProps": { "type": "live", "url": validate.all( str, validate.transform(lambda url: url.replace("https:////", "https://")), validate.url(path=validate.endswith(".m3u8")), ) } } }, validate.get(("props", "pageProps", "url")), )) except PluginError: return return HLSStream.parse_variant_playlist(self.session, hls)
def _get_streams(self): channel = self.match.groupdict().get("channel") or "lnk" if channel not in self.CHANNEL_MAP: log.error(f"Unknown channel: {channel}") return self.id = self.CHANNEL_MAP.get(channel) self.author, self.category, self.title, hls_url = self.session.http.get( self.API_URL.format(self.id), schema=validate.Schema( validate.parse_json(), {"videoInfo": { "channel": str, "genre": validate.any(None, str), "title": validate.any(None, str), "videoUrl": validate.any( "", validate.url(path=validate.endswith(".m3u8")) ) }}, validate.get("videoInfo"), validate.union_get("channel", "genre", "title", "videoUrl") ) ) if not hls_url: log.error("The stream is not available in your region") return return HLSStream.parse_variant_playlist(self.session, hls_url)
def test_failure_schema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.endswith("invalid"), 1) assert_validationerror( cm.value, """ ValidationError(type): Type of 1 should be str, but is int """)
def test_failure(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.endswith("invalid"), "foo bar baz") assert_validationerror( cm.value, """ ValidationError(endswith): 'foo bar baz' does not end with 'invalid' """)
class LineLive(Plugin): _api_url = "https://live-api.line-apps.com/app/v3.2/channel/{0}/broadcast/{1}/player_status" _player_status_schema = validate.Schema( { "liveStatus": validate.text, "liveHLSURLs": validate.any(None, { "720": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "480": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "360": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "240": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "144": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), }), "archivedHLSURLs": validate.any(None, { "720": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "480": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "360": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "240": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), "144": validate.any(None, validate.url(scheme="http", path=validate.endswith(".m3u8"))), }), }) def _get_live_streams(self, json): for stream in json["liveHLSURLs"]: url = json["liveHLSURLs"][stream] if url is not None: yield "{0}p.".format(stream), HLSStream(self.session, url) def _get_vod_streams(self, json): for stream in json["archivedHLSURLs"]: url = json["archivedHLSURLs"][stream] if url is not None: yield "{0}p.".format(stream), HLSStream(self.session, url) def _get_streams(self): channel = self.match.group("channel") broadcast = self.match.group("broadcast") res = self.session.http.get(self._api_url.format(channel, broadcast)) json = self.session.http.json(res, schema=self._player_status_schema) if json["liveStatus"] == "LIVE": return self._get_live_streams(json) elif json["liveStatus"] == "FINISHED": return self._get_vod_streams(json) return
def _get_streams(self): self.session.http.headers.update( {"Referer": "https://tviplayer.iol.pt/"}) data = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string( ".//script[contains(text(),'.m3u8')]/text()"), validate.text, validate.transform(self._re_jsonData.search), validate.any( None, validate.all( validate.get("json"), validate.parse_json(), { "id": validate.text, "liveType": validate.text, "videoType": validate.text, "videoUrl": validate.url(path=validate.endswith(".m3u8")), validate.optional("channel"): validate.text, })))) if not data: return log.debug("{0!r}".format(data)) if data["liveType"].upper() == "DIRETO" and data["videoType"].upper( ) == "LIVE": geo_path = "live" else: geo_path = "vod" data_geo = self.session.http.get( "https://services.iol.pt/direitos/rights/{0}?id={1}".format( geo_path, data['id']), acceptable_status=(200, 403), schema=validate.Schema( validate.parse_json(), { "code": validate.text, "error": validate.any(None, validate.text), "detail": validate.text, })) log.debug("{0!r}".format(data_geo)) if data_geo["detail"] != "ok": log.error("{0}".format(data_geo['detail'])) return wmsAuthSign = self.session.http.get( "https://services.iol.pt/matrix?userId=", schema=validate.Schema(validate.text)) hls_url = update_qsd(data["videoUrl"], {"wmsAuthSign": wmsAuthSign}) return HLSStream.parse_variant_playlist(self.session, hls_url)
def _get_streams(self): channel = self.match.group("channel") self.session.http.headers.update({"Referer": self.url}) data = self.session.http.post( "https://wap-api.17app.co/api/v1/lives/{0}/viewers/alive".format( channel), data={"liveStreamID": channel}, schema=validate.Schema( validate.parse_json(), validate.any( { "rtmpUrls": [{ validate.optional("provider"): validate.any(int, None), "url": validate.url(path=validate.endswith(".flv")), }] }, { "errorCode": int, "errorMessage": str }, ), ), acceptable_status=(200, 403, 404, 420)) log.trace("{0!r}".format(data)) if data.get("errorCode"): log.error("{0} - {1}".format( data['errorCode'], data['errorMessage'].replace('Something wrong: ', ''))) return flv_url = data["rtmpUrls"][0]["url"] yield "live", HTTPStream(self.session, flv_url) if "wansu-" in flv_url: hls_url = flv_url.replace(".flv", "/playlist.m3u8") else: hls_url = flv_url.replace("live-hdl", "live-hls").replace(".flv", ".m3u8") s = HLSStream.parse_variant_playlist(self.session, hls_url) if not s: yield "live", HLSStream(self.session, hls_url) else: if len(s) == 1: for _n, _s in s.items(): yield "live", _s else: for _s in s.items(): yield _s
class Welt(Plugin): _url_vod = "https://www.welt.de/onward/video/token/{0}" _re_url = re.compile(r"""https?://(\w+\.)?welt\.de/?""", re.IGNORECASE) _re_url_vod = re.compile(r"""mediathek""", re.IGNORECASE) _re_json = re.compile( r""" <script>\s* var\s+funkotron\s*=\s* \{\s* config\s*:\s*(?P<json>\{.+?\})\s* \}\s*;?\s* </script> """, re.VERBOSE | re.DOTALL | re.IGNORECASE) _schema = validate.Schema(validate.transform(_re_json.search), validate.get("json"), validate.transform(parse_json), validate.get("page"), validate.get("content"), validate.get("media"), validate.get(0), validate.get("sources"), validate.map(lambda obj: obj["file"]), validate.filter(_filter_url), validate.get(0)) _schema_url = validate.Schema( validate.url(scheme="https", path=validate.endswith(".m3u8"))) _schema_vod = validate.Schema(validate.transform(parse_json), validate.get("urlWithToken"), _schema_url) @classmethod def can_handle_url(cls, url): return cls._re_url.match(url) is not None def __init__(self, url): Plugin.__init__(self, url) self.url = url self.isVod = self._re_url_vod.search(url) is not None def _get_streams(self): headers = {"User-Agent": useragents.CHROME} hls_url = self.session.http.get(self.url, headers=headers, schema=self._schema) headers["Referer"] = self.url if self.isVod: url = self._url_vod.format(quote(hls_url, safe="")) hls_url = self.session.http.get(url, headers=headers, schema=self._schema_vod) return HLSStream.parse_variant_playlist(self.session, hls_url, headers=headers)
def _get_hls(self, root): schema_live = validate.Schema( validate.xml_xpath_string( ".//*[contains(@data-broadcast,'m3u8')]/@data-broadcast"), str, validate.parse_json(), validate.any(validate.all({"files": list}, validate.get("files")), list), [{ "url": validate.url(path=validate.endswith(".m3u8")) }], validate.get((0, "url")), validate.transform( lambda content_url: update_scheme("https://", content_url))) try: live = schema_live.validate(root) except PluginError: return return HLSStream.parse_variant_playlist(self.session, live)
def get_hls_url(self): self.session.http.cookies.clear() url_parts = self.session.http.get( url=self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//iframe[contains(@src,'embed')]/@src"))) if not url_parts: raise NoStreamsError("Missing url_parts") log.trace(f"url_parts={url_parts}") self.session.http.headers.update({"Referer": self.url}) try: url_ovva = self.session.http.get( url=urljoin(self.url, url_parts), schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//script[@type='text/javascript'][contains(text(),'ovva-player')]/text()"), str, validate.transform(self._re_data.search), validate.get(1), validate.transform(lambda x: b64decode(x).decode()), validate.parse_json(), {"balancer": validate.url()}, validate.get("balancer") )) except (PluginError, TypeError) as err: log.error(f"ovva-player: {err}") return log.debug(f"url_ovva={url_ovva}") url_hls = self.session.http.get( url=url_ovva, schema=validate.Schema( validate.transform(lambda x: x.split("=")), ["302", validate.url(path=validate.endswith(".m3u8"))], validate.get(1))) return url_hls
_csrf_token_re = re.compile("<meta content=\"([^\"]+)\" name=\"csrf-token\"") _hls_playlist_re = re.compile("<meta content=\"([^\"]+.m3u8)\" property=\"og:video\" />") _url_re = re.compile("http(s)?://(\w+\.)?livestation.com") _csrf_token_schema = validate.Schema( 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):
(?: video/(?P<vid>[0-9]+G[0-9A-Za-z]+)| (?P<channel>[0-9]+) ) """, re.VERBOSE) _room_schema = validate.Schema( { "data": { "live_info": { "live_status": int, "stream_items": [{ "title": validate.text, "video": validate.any('', validate.url( scheme="https", path=validate.endswith(".flv") )) }] } } }, validate.get("data") ) _vod_schema = validate.Schema( { "data": { "live_info": { "video": validate.text } }
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" "[@name='httpBase']"), validate.xml_element(attrib={ "content": validate.text
def test_endswith(self): assert validate(endswith("åäö"), "xyzåäö")
) _hls_re = re.compile(r"'(http://.+\.m3u8)'") _rtmp_schema = validate.Schema( validate.xml_findtext("./info/url"), validate.url(scheme="rtmp") ) _hls_schema = validate.Schema( validate.transform(_hls_re.search), validate.any( None, validate.all( validate.get(1), validate.url( scheme="http", path=validate.endswith("m3u8") ) ) ) ) class Streamingvideoprovider(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_hls_stream(self, channel_name): params = { "l": "info", "a": "ajax_video_info",
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")), validate.optional("video_encode_id"): validate.text }]) }) }, validate.get("stream_data")) _login_schema = validate.Schema({ "auth": validate.text, "expires": validate.all(validate.text, validate.transform(parse_timestamp)), "user": { "username": validate.any(validate.text, None), "email": validate.text } })
COOKIE_PARAMS = ("devicetype=desktop&" "preferred-player-odm=hlslink&" "preferred-player-live=hlslink") _id_re = re.compile(r"/(?:program|direkte|serie/[^/]+)/([^/]+)") _url_re = re.compile(r"https?://(tv|radio).nrk.no/") _api_baseurl_re = re.compile(r'''apiBaseUrl:\s*["'](?P<baseurl>[^"']+)["']''') _schema = validate.Schema( validate.transform(_api_baseurl_re.search), validate.any( None, validate.all(validate.get("baseurl"), validate.url(scheme="http")))) _mediaelement_schema = validate.Schema( {"mediaUrl": validate.url(scheme="http", path=validate.endswith(".m3u8"))}) class NRK(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_streams(self): # Get the stream type from the url (tv/radio). stream_type = _url_re.match(self.url).group(1).upper() cookie = {"NRK_PLAYER_SETTINGS_{0}".format(stream_type): COOKIE_PARAMS} # Construct API URL for this program. baseurl = self.session.http.get(self.url, cookies=cookie,
def test_endswith(self): assert validate(endswith(u"åäö"), u"xyzåäö")
(?: video/(?P<vid>[0-9]+G[0-9A-Za-z]+)| (?P<channel>[0-9]+) ) """, re.VERBOSE) _room_schema = validate.Schema( { "data": { "live_info": { "live_status": int, "stream_items": [{ "title": validate.text, "video": validate.any('', validate.url( scheme="https", path=validate.endswith(".flv") )) }] } } }, validate.get("data") ) _vod_schema = validate.Schema( { "data": { "live_info": { "video": validate.text } }
class TestUrlValidator: url = "https://*****:*****@sub.host.tld:1234/path.m3u8?query#fragment" @pytest.mark.parametrize( "params", [ dict(scheme="http"), dict(scheme="https"), dict(netloc="user:[email protected]:1234", username="******", password="******", hostname="sub.host.tld", port=1234), dict(path=validate.endswith(".m3u8")), ], ids=[ "implicit https", "explicit https", "multiple attributes", "subschemas", ], ) def test_success(self, params): assert validate.validate(validate.url(**params), self.url) def test_failure_valid_url(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.url(), "foo") assert_validationerror( cm.value, """ ValidationError(url): 'foo' is not a valid URL """) def test_failure_url_attribute(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.url(invalid=str), self.url) assert_validationerror( cm.value, """ ValidationError(url): Invalid URL attribute 'invalid' """) def test_failure_subschema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.url(hostname="invalid"), self.url) assert_validationerror( cm.value, """ ValidationError(url): Unable to validate URL attribute 'hostname' Context(equality): 'sub.host.tld' does not equal 'invalid' """) def test_failure_schema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.url(), 1) assert_validationerror( cm.value, """ ValidationError(type): Type of 1 should be str, but is int """)
validate.get("login") ) _viewer_token_schema = validate.Schema( { validate.optional("token"): validate.text }, validate.get("token") ) _quality_options_schema = validate.Schema( { "quality_options": validate.all( [{ "quality": validate.any(validate.text, None), "source": validate.url( scheme="https", path=validate.endswith(".mp4") ) }] ) }, validate.get("quality_options") ) def time_to_offset(t): match = _time_re.match(t) if match: offset = int(match.group("hours") or "0") * 60 * 60 offset += int(match.group("minutes") or "0") * 60 offset += int(match.group("seconds") or "0") else:
SWF_URL = "http://play.streamingvideoprovider.com/player2.swf" API_URL = "http://player.webvideocore.net/index.php" _url_re = re.compile( "http(s)?://(\w+\.)?streamingvideoprovider.co.uk/(?P<channel>[^/&?]+)") _hls_re = re.compile(r"'(http://.+\.m3u8)'") _rtmp_schema = validate.Schema(validate.xml_findtext("./info/url"), validate.url(scheme="rtmp")) _hls_schema = validate.Schema( validate.transform(_hls_re.search), validate.any( None, validate.all( validate.get(1), validate.url(scheme="http", path=validate.endswith("m3u8"))))) class Streamingvideoprovider(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_hls_stream(self, channel_name): params = { "l": "info", "a": "ajax_video_info", "file": channel_name, "rid": time() } playlist_url = http.get(API_URL, params=params, schema=_hls_schema)
validate.get("login") ) _viewer_token_schema = validate.Schema( { validate.optional("token"): validate.text }, validate.get("token") ) _quality_options_schema = validate.Schema( { "quality_options": validate.all( [{ "quality": validate.any(validate.text, None), "source": validate.url( scheme="https", path=validate.endswith(".mp4") ) }] ) }, validate.get("quality_options") ) def time_to_offset(t): match = _time_re.match(t) if match: offset = int(match.group("hours") or "0") * 60 * 60 offset += int(match.group("minutes") or "0") * 60 offset += int(match.group("seconds") or "0") else:
_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) streams = {} for url in res["urls"]: parsed = urlparse(url) if parsed.scheme.startswith("rtmp"): params = {"rtmp": url, "pageUrl": self.url, "live": True}
def test_success(self): assert validate.validate(validate.endswith("baz"), "foo bar baz")
}, "start_offset": int, "end_offset": int, }) _viewer_info_schema = validate.Schema( {validate.optional("login"): validate.text}, validate.get("login")) _viewer_token_schema = validate.Schema( {validate.optional("token"): validate.text}, validate.get("token")) _quality_options_schema = validate.Schema( { "quality_options": validate.all([{ "quality": validate.any(validate.text, None), "source": validate.url(scheme="https", path=validate.endswith(".mp4")) }]) }, validate.get("quality_options")) Segment = namedtuple( "Segment", "uri duration title key discontinuity scte35 byterange date map") class TwitchM3U8Parser(M3U8Parser): def parse_tag_ext_x_scte35_out(self, value): self.state["scte35"] = True # unsure if this gets used by Twitch def parse_tag_ext_x_scte35_out_cont(self, value): self.state["scte35"] = True
_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 = http.get(self.url, schema=_schema)