_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")), 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
from streamlink.plugin.api.support_plugin import viasat STREAM_API_URL = "http://playapi.mtgx.tv/v3/videos/stream/{0}" _embed_url_re = re.compile( '<meta itemprop="embedURL" content="http://www.viagame.com/embed/video/([^"]+)"' ) _store_data_re = re.compile("window.fluxData\s*=\s*JSON.parse\(\"(.+)\"\);") _url_re = re.compile("http(s)?://(www\.)?viagame.com/channels/.+") _store_schema = validate.Schema( { "initialStoresData": [{ "instanceName": validate.text, "storeName": validate.text, "initialData": validate.any(dict, list) }] }, validate.get("initialStoresData") ) _match_store_schema = validate.Schema( { "match": { "id": validate.text, "type": validate.text, "videos": [{ "id": validate.text, "play_id": validate.text, }] } },
_param_re = re.compile(r'"param"\s*:\s*"(.+?)"\s*,\s*"time"') _time_re = re.compile(r'"time"\s*:\s*(\d+)') _sign_re = re.compile(r'"sign"\s*:\s*"(.+?)"') _sd_re = re.compile(r'"SD"\s*:\s*"(\d+)"') _hd_re = re.compile(r'"HD"\s*:\s*"(\d+)"') _od_re = re.compile(r'"OD"\s*:\s*"(\d+)"') _room_schema = validate.Schema( { "data": validate.any( validate.text, dict, { "videoinfo": validate.any( validate.text, { "plflag_list": validate.text, "plflag": validate.text } ) } ) }, validate.get("data")) class Pandatv(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url)
(?: show/(?P<vid>[^/&?]+)| (?P<channel>[^/&?]+) ) """, re.VERBOSE) _room_id_re = re.compile(r'"room_id\\*"\s*:\s*(\d+),') _room_id_alt_re = re.compile(r'data-onlineid=(\d+)') _room_id_schema = validate.Schema( validate.all( validate.transform(_room_id_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(int) ) ) ) ) _room_id_alt_schema = validate.Schema( validate.all( validate.transform(_room_id_alt_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(int) )
BLOCK_TYPE_VIEWING_LIMIT = 1 BLOCK_TYPE_NO_SLOTS = 11 _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)
"<embed width=\"486\" height=\"326\" flashvars=\"([^\"]+)\"" ) _live_schema = validate.Schema({ "streams": [{ "name": validate.text, "quality": validate.text, "url": validate.url(scheme="rtmp") }] }) _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 } ) )
) } _url_re = re.compile(r""" http(s)?://(\w+\.)?zdf.de/ """, re.VERBOSE | re.IGNORECASE) _api_json_re = re.compile(r'''data-zdfplayer-jsb=["'](?P<json>{.+?})["']''', re.S) _api_schema = validate.Schema( validate.transform(_api_json_re.search), validate.any( None, validate.all( validate.get("json"), validate.transform(parse_json), { "content": validate.text, "apiToken": validate.text }, ) ) ) _documents_schema = validate.Schema( { "mainVideoContent": { "http://zdf.de/rels/target": { "http://zdf.de/rels/streams/ptmd": validate.text }, }, }
class Pixiv(Plugin): """Plugin for https://sketch.pixiv.net/lives""" _url_re = re.compile( r"https?://sketch\.pixiv\.net/[^/]+(?P<videopage>/lives/\d+)?") _videopage_re = re.compile( r"""["']live-button["']><a\shref=["'](?P<path>[^"']+)["']""") _data_re = re.compile( r"""<script\sid=["']state["']>[^><{]+(?P<data>{[^><]+})</script>""") _post_key_re = re.compile( r"""name=["']post_key["']\svalue=["'](?P<data>[^"']+)["']""") _data_schema = validate.Schema( validate.all( validate.transform(_data_re.search), validate.any( None, validate.all( validate.get("data"), validate.transform(parse_json), validate.get("context"), validate.get("dispatcher"), validate.get("stores"), )))) login_url_get = "https://accounts.pixiv.net/login" login_url_post = "https://accounts.pixiv.net/api/login" options = PluginOptions({"username": None, "password": None}) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def find_videopage(self): self.logger.debug("Not a videopage") res = http.get(self.url) m = self._videopage_re.search(res.text) if not m: self.logger.debug( "No stream path, stream might be offline or invalid url.") raise NoStreamsError(self.url) path = m.group("path") self.logger.debug("Found new path: {0}".format(path)) return urljoin(self.url, path) def _login(self, username, password): res = http.get(self.login_url_get) m = self._post_key_re.search(res.text) if not m: raise PluginError("Missing post_key, no login posible.") post_key = m.group("data") data = { "lang": "en", "source": "sketch", "post_key": post_key, "pixiv_id": username, "password": password, } res = http.post(self.login_url_post, data=data) res = http.json(res) if res["body"].get("success"): return True else: return False def _get_streams(self): http.headers = {"User-Agent": useragents.FIREFOX} login_username = self.get_option("username") login_password = self.get_option("password") if login_username and login_password: self.logger.debug("Attempting login as {0}".format(login_username)) if self._login(login_username, login_password): self.logger.info( "Successfully logged in as {0}".format(login_username)) else: self.logger.info( "Failed to login as {0}".format(login_username)) videopage = self._url_re.match(self.url).group("videopage") if not videopage: self.url = self.find_videopage() data = http.get(self.url, schema=self._data_schema) if not data.get("LiveStore"): self.logger.debug("No video url found, stream might be offline.") return data = data["LiveStore"]["lives"] # get the unknown user-id for _key in data.keys(): video_data = data.get(_key) owner = video_data["owner"] self.logger.info("Owner ID: {0}".format(owner["user_id"])) self.logger.debug("HLS URL: {0}".format(owner["hls_movie"])) for n, s in HLSStream.parse_variant_playlist( self.session, owner["hls_movie"]).items(): yield n, s performers = video_data.get("performers") if performers: for p in performers: self.logger.info("CO-HOST ID: {0}".format(p["user_id"])) hls_url = p["hls_movie"] self.logger.debug("HLS URL: {0}".format(hls_url)) for n, s in HLSStream.parse_variant_playlist( self.session, hls_url).items(): _n = "{0}_{1}".format(n, p["user_id"]) yield _n, s
class App17(Plugin): _url_re = re.compile(r"https://17.live/live/(?P<channel>[^/&?]+)") API_URL = "https://api-dsa.17app.co/api/v1/lives/{0}/viewers/alive" _api_schema = validate.Schema( { "rtmpUrls": [{ validate.optional("provider"): validate.any(int, None), "url": validate.url(), }], }, validate.get("rtmpUrls"), ) @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") self.session.http.headers.update({ 'User-Agent': useragents.CHROME, 'Referer': self.url }) data = '{"liveStreamID":"%s"}' % (channel) try: res = self.session.http.post(self.API_URL.format(channel), data=data) res_json = self.session.http.json(res, schema=self._api_schema) log.trace("{0!r}".format(res_json)) http_url = res_json[0]["url"] except Exception as e: log.info("Stream currently unavailable.") log.debug(str(e)) return https_url = http_url.replace("http:", "https:") yield "live", HTTPStream(self.session, https_url) if 'pull-rtmp' in http_url: rtmp_url = http_url.replace("http:", "rtmp:").replace(".flv", "") stream = RTMPStream(self.session, { "rtmp": rtmp_url, "live": True, "pageUrl": self.url, }) yield "live", stream if 'wansu-' in http_url: hls_url = http_url.replace(".flv", "/playlist.m3u8") else: hls_url = http_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: yield from s.items()
https?://(?:\w+\.)?arte\.tv/(?:guide/)? (?P<language>[a-z]{2})/ (?: (?:videos/)?(?P<video_id>(?!RC\-|videos)[^/]+?)/.+ | # VOD (?:direct|live) # Live TV ) """, re.VERBOSE) _video_schema = validate.Schema({ "videoJsonPlayer": { "VSR": validate.any( [], { validate.text: { "height": int, "mediaType": validate.text, "url": validate.text, "versionShortLibelle": validate.text }, }, ) } }) class ArteTV(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _create_stream(self, stream, language):
_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}, validate.get("sequence")))) _vod_playlist_schema = validate.Schema({ "duration": float, "fragments": [[int, float]], "template": validate.text }) _vod_manifest_schema = validate.Schema({ "alternates": [{ "height": int, "template": validate.text, validate.optional("failover"): [validate.text] }] })
CHANNEL_INFO_URL = "http://api.plu.cn/tga/streams/%s" QQ_STREAM_INFO_URL = "http://info.zb.qq.com/?cnlid=%d&cmd=2&stream=%d&system=1&sdtfrom=113" PLU_STREAM_INFO_URL = "http://star.api.plu.cn/live/GetLiveUrl?roomId=%d" _quality_re = re.compile(r"\d+x(\d+)$") _url_re = re.compile( r"http://star\.longzhu\.(?:tv|com)/(m\/)?(?P<domain>[a-z0-9]+)") _channel_schema = validate.Schema( { "data": validate.any( None, { "channel": validate.any( None, { "id": validate.all(validate.text, validate.transform(int)), "vid": int }) }) }, 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(
API_URL = "https://api.live.bilibili.com/room/v1/Room/playUrl" ROOM_API = "https://api.live.bilibili.com/room/v1/Room/room_init?id={}" SHOW_STATUS_OFFLINE = 0 SHOW_STATUS_ONLINE = 1 SHOW_STATUS_ROUND = 2 STREAM_WEIGHTS = {"source": 1080} _url_re = re.compile( r""" http(s)?://live.bilibili.com /(?P<channel>[^/]+) """, re.VERBOSE) _room_id_schema = validate.Schema( {"data": validate.any(None, { "room_id": int, "live_status": int })}, validate.get("data")) _room_stream_list_schema = validate.Schema( {"data": validate.any(None, {"durl": [{ "url": validate.url() }]})}, validate.get("data")) class Bilibili(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) @classmethod def stream_weight(cls, stream):
class RUtube(Plugin): ''' https://rutube.ru/feeds/live/ ''' api_play = 'https://rutube.ru/api/play/options/{0}/?format=json&no_404=true&referer={1}' _url_re = re.compile( r'''https?://(\w+\.)?rutube\.ru/(?:play|video)/(?:embed/)?(?P<id>[a-z0-9]+)''' ) _video_schema = validate.Schema( validate.any( { 'live_streams': { validate.text: [{ 'url': validate.text, }] }, 'video_balancer': { validate.text: validate.text, }, }, {})) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _get_streams(self): log.debug('Version 2018-07-01') log.info('This is a custom plugin. ' 'For support visit https://github.com/back-to/plugins') hls_urls = [] hds_urls = [] http.headers.update({'User-Agent': useragents.FIREFOX}) match = self._url_re.match(self.url) if match is None: return video_id = match.group('id') log.debug('video_id: {0}'.format(video_id)) res = http.get(self.api_play.format(video_id, self.url)) data = http.json(res, schema=self._video_schema) live_data = data.get('live_streams') vod_data = data.get('video_balancer') if live_data: log.debug('Found live_data') for d in live_data['hls']: hls_urls.append(d['url']) for e in live_data['hds']: hds_urls.append(e['url']) elif vod_data: log.debug('Found vod_data') hls_urls.append(vod_data['m3u8']) hds_urls.append(vod_data['default']) else: log.error('This video is not available in your region.') raise NoStreamsError(self.url) for hls_url in hls_urls: log.debug('HLS URL: {0}'.format(hls_url)) for s in HLSStream.parse_variant_playlist(self.session, hls_url).items(): yield s for hds_url in hds_urls: log.debug('HDS URL: {0}'.format(hds_url)) for s in HDSStream.parse_manifest(self.session, hds_url).items(): yield s
_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",
_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):
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 } )
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' _url_re = re.compile(r'https?://play\.sbs\.co\.kr/onair/pc/index.html') _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 ''' ) ) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None 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'])
"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")), [{ "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
class DLive(Plugin): _re_url = re.compile( r""" https?://(?:www\.)?dlive\.tv/ (?: (?:p/(?P<video>[^/]+)) | (?P<channel>[^/]+) ) """, re.VERBOSE) _re_videoPlaybackUrl = re.compile(r'"playbackUrl"\s*:\s*"([^"]+\.m3u8)"') _schema_userByDisplayName = validate.Schema( { "data": { "userByDisplayName": { "livestream": validate.any(None, {"title": validate.text}), "username": validate.text } } }, validate.get("data"), validate.get("userByDisplayName")) _schema_videoPlaybackUrl = validate.Schema( validate.transform(_re_videoPlaybackUrl.search), validate.any( None, validate.all( validate.get(1), validate.transform(unquote_plus), validate.transform( lambda url: bytes(url, "utf-8").decode("unicode_escape")), validate.url()))) @classmethod def can_handle_url(cls, url): return cls._re_url.match(url) @classmethod def stream_weight(cls, key): weight = QUALITY_WEIGHTS.get(key) if weight: return weight, "dlive" return Plugin.stream_weight(key) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.author = None self.title = None match = self._re_url.match(self.url) self.video = match.group("video") self.channel = match.group("channel") def get_author(self): return self.author def get_title(self): return self.title def _get_streams_video(self): log.debug("Getting video HLS streams for {0}".format(self.video)) try: hls_url = self.session.http.get( self.url, schema=self._schema_videoPlaybackUrl) if hls_url is None: return except PluginError: return return HLSStream.parse_variant_playlist(self.session, hls_url) def _get_streams_live(self): log.debug("Getting live HLS streams for {0}".format(self.channel)) try: data = json.dumps({ "query": """query {{ userByDisplayName(displayname:"{displayname}") {{ livestream {{ title }} username }} }}""".format(displayname=self.channel) }) res = self.session.http.post("https://graphigo.prd.dlive.tv/", data=data) res = self.session.http.json(res, schema=self._schema_userByDisplayName) if res["livestream"] is None: return except PluginError: return self.author = self.channel self.title = res["livestream"]["title"] hls_url = "https://live.prd.dlive.tv/hls/live/{0}.m3u8".format( res["username"]) return HLSStream.parse_variant_playlist(self.session, hls_url) def _get_streams(self): if self.video: return self._get_streams_video() elif self.channel: return self._get_streams_live()
API_SECRET = "95acd7f6cc3392f3" SHOW_STATUS_ONLINE = 1 SHOW_STATUS_OFFLINE = 2 STREAM_WEIGHTS = { "source": 1080 } _url_re = re.compile(r""" http(s)?://live.bilibili.com /(?P<channel>[^/]+) """, re.VERBOSE) _room_id_schema = validate.Schema( { "data": validate.any(None, { "room_id": int }) }, validate.get("data") ) class Bilibili(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], "Bilibili"
class Pluzz(Plugin): GEO_URL = 'http://geo.francetv.fr/ws/edgescape.json' API_URL = 'http://sivideo.webservices.francetelevisions.fr/tools/getInfosOeuvre/v2/?idDiffusion={0}' PLAYER_GENERATOR_URL = 'https://sivideo.webservices.francetelevisions.fr/assets/staticmd5/getUrl?id=jquery.player.7.js' TOKEN_URL = 'http://hdfauthftv-a.akamaihd.net/esi/TA?url={0}' _url_re = re.compile(r'https?://((?:www)\.france\.tv/.+\.html|www\.(ludo|zouzous)\.fr/heros/[\w-]+|(sport|france3-regions)\.francetvinfo\.fr/.+?/(tv/direct)?)') _pluzz_video_id_re = re.compile(r'data-main-video="(?P<video_id>.+?)"') _jeunesse_video_id_re = re.compile(r'playlist: \[{.*?,"identity":"(?P<video_id>.+?)@(?P<catalogue>Ludo|Zouzous)"') _f3_regions_video_id_re = re.compile(r'"http://videos\.francetv\.fr/video/(?P<video_id>.+?)@Regions"') _sport_video_id_re = re.compile(r'data-video="(?P<video_id>.+?)"') _player_re = re.compile(r'src="(?P<player>//staticftv-a\.akamaihd\.net/player/jquery\.player.+?-[0-9a-f]+?\.js)"></script>') _swf_re = re.compile(r'//staticftv-a\.akamaihd\.net/player/bower_components/player_flash/dist/FranceTVNVPVFlashPlayer\.akamai-[0-9a-f]+\.swf') _hds_pv_data_re = re.compile(r"~data=.+?!") _mp4_bitrate_re = re.compile(r'.*-(?P<bitrate>[0-9]+k)\.mp4') _geo_schema = validate.Schema({ 'reponse': { 'geo_info': { 'country_code': validate.text } } }) _api_schema = validate.Schema({ 'videos': validate.all( [{ 'format': validate.any( None, validate.text ), 'url': validate.any( None, validate.url(), ), 'statut': validate.text, 'drm': bool, 'geoblocage': validate.any( None, [validate.all(validate.text)] ), 'plages_ouverture': validate.all( [{ 'debut': validate.any( None, int ), 'fin': validate.any( None, int ) }] ) }] ), 'subtitles': validate.any( [], validate.all( [{ 'type': validate.text, 'url': validate.url(), 'format': validate.text }] ) ) }) _player_schema = validate.Schema({'result': validate.url()}) options = PluginOptions({ "mux_subtitles": False }) @classmethod def can_handle_url(cls, url): return Pluzz._url_re.match(url) def _get_streams(self): # Retrieve geolocation data res = http.get(self.GEO_URL) geo = http.json(res, schema=self._geo_schema) country_code = geo['reponse']['geo_info']['country_code'] # Retrieve URL page and search for video ID res = http.get(self.url) if 'france.tv' in self.url: match = self._pluzz_video_id_re.search(res.text) elif 'ludo.fr' in self.url or 'zouzous.fr' in self.url: match = self._jeunesse_video_id_re.search(res.text) elif 'france3-regions.francetvinfo.fr' in self.url: match = self._f3_regions_video_id_re.search(res.text) elif 'sport.francetvinfo.fr' in self.url: match = self._sport_video_id_re.search(res.text) if match is None: return video_id = match.group('video_id') # Retrieve SWF player URL swf_url = None res = http.get(self.PLAYER_GENERATOR_URL) player_url = update_scheme(self.url, http.json(res, schema=self._player_schema)['result']) res = http.get(player_url) match = self._swf_re.search(res.text) if match is not None: swf_url = update_scheme(self.url, match.group(0)) res = http.get(self.API_URL.format(video_id)) videos = http.json(res, schema=self._api_schema) now = time.time() offline = False geolocked = False drm = False expired = False streams = [] for video in videos['videos']: video_url = video['url'] # Check whether video format is available if video['statut'] != 'ONLINE': offline = offline or True continue # Check whether video format is geo-locked if video['geoblocage'] is not None and country_code not in video['geoblocage']: geolocked = geolocked or True continue # Check whether video is DRM-protected if video['drm']: drm = drm or True continue # Check whether video format is expired available = False for interval in video['plages_ouverture']: available = (interval['debut'] or 0) <= now <= (interval['fin'] or sys.maxsize) if available: break if not available: expired = expired or True continue # TODO: add DASH streams once supported if '.mpd' in video_url: continue if '.f4m' in video_url or 'france.tv' in self.url: res = http.get(self.TOKEN_URL.format(video_url)) video_url = res.text if '.f4m' in video_url and swf_url is not None: for bitrate, stream in HDSStream.parse_manifest(self.session, video_url, is_akamai=True, pvswf=swf_url).items(): # HDS videos with data in their manifest fragment token # doesn't seem to be supported by HDSStream. Ignore such # stream (but HDS stream having only the hdntl parameter in # their manifest token will be provided) pvtoken = stream.request_params['params'].get('pvtoken', '') match = self._hds_pv_data_re.search(pvtoken) if match is None: streams.append((bitrate, stream)) elif '.m3u8' in video_url: for stream in HLSStream.parse_variant_playlist(self.session, video_url).items(): streams.append(stream) # HBB TV streams are not provided anymore by France Televisions elif '.mp4' in video_url and '/hbbtv/' not in video_url: match = self._mp4_bitrate_re.match(video_url) if match is not None: bitrate = match.group('bitrate') else: # Fallback bitrate (seems all France Televisions MP4 videos # seem have such bitrate) bitrate = '1500k' streams.append((bitrate, HTTPStream(self.session, video_url))) if self.get_option("mux_subtitles") and videos['subtitles'] != []: substreams = {} for subtitle in videos['subtitles']: # TTML subtitles are available but not supported by FFmpeg if subtitle['format'] == 'ttml': continue substreams[subtitle['type']] = HTTPStream(self.session, subtitle['url']) for quality, stream in streams: yield quality, MuxedStream(self.session, stream, subtitles=substreams) else: for stream in streams: yield stream if offline: self.logger.error('Failed to access stream, may be due to offline content') if geolocked: self.logger.error('Failed to access stream, may be due to geo-restricted content') if drm: self.logger.error('Failed to access stream, may be due to DRM-protected content') if expired: self.logger.error('Failed to access stream, may be due to expired content')
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}, validate.get("sequence") ) ) ) _vod_playlist_schema = validate.Schema({ "duration": float, "fragments": [[int, float]], "template": validate.text }) _vod_manifest_schema = validate.Schema({ "alternates": [{ "height": int, "template": validate.text, validate.optional("failover"): [validate.text] }]
class Reuters(Plugin): _url_re = re.compile(r'https?://(.*?\.)?reuters\.(com|tv)') _id_re = re.compile(r'(/l/|id=)(?P<id>.*?)(/|\?|$)') _iframe_url = 'https://www.reuters.tv/l/{0}/?nonav=true' _hls_re = re.compile(r'''(?<!')https://[^"';!<>]+\.m3u8''') _json_re = re.compile(r'''(?P<data>{.*});''') _data_schema = validate.Schema( validate.transform(_json_re.search), validate.any( None, validate.all( validate.get('data'), validate.transform(parse_json), { 'title': validate.text, 'items': [{ 'title': validate.text, 'type': validate.text, 'resources': [{ 'mimeType': validate.text, 'uri': validate.url(), validate.optional('protocol'): validate.text, validate.optional('entityType'): validate.text, }] }], }))) def __init__(self, url): super(Reuters, self).__init__(url) self.title = None @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def get_title(self): if not self.title: self._get_data() return self.title def _get_data(self): res = self.session.http.get(self.url) for script in itertags(res.text, 'script'): if script.attributes.get( 'type' ) == 'text/javascript' and '#rtvIframe' in script.text: m = self._id_re.search(self.url) if m and m.group('id'): log.debug('ID: {0}'.format(m.group('id'))) res = self.session.http.get( self._iframe_url.format(m.group('id'))) for script in itertags(res.text, 'script'): if script.attributes.get( 'type') == 'text/javascript' and 'RTVJson' in script.text: data = self._data_schema.validate(script.text) if not data: continue self.title = data['title'] for item in data['items']: if data['title'] == item['title']: log.trace('{0!r}'.format(item)) log.debug('Type: {0}'.format(item['type'])) for res in item['resources']: if res['mimeType'] == 'application/x-mpegURL': return res['uri'] # fallback for title in itertags(res.text, 'title'): self.title = title.text m = self._hls_re.search(res.text) if not m: log.error('Unsupported PageType.') return return m.group(0) def _get_streams(self): hls_url = self._get_data() if not hls_url: return log.debug('URL={0}'.format(hls_url)) return HLSStream.parse_variant_playlist(self.session, hls_url)
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 Picarto(Plugin): url_re = re.compile( r''' https?://(?:www\.)?picarto\.tv/ (?:(?P<po>streampopout|videopopout)/)? (?P<user>[^&?/]+) (?:\?tab=videos&id=(?P<vod_id>\d+))? ''', re.VERBOSE) channel_schema = validate.Schema({ 'channel': validate.any( None, { 'stream_name': validate.text, 'title': validate.text, 'online': bool, 'private': bool, 'categories': [{ 'label': validate.text }], }), 'getLoadBalancerUrl': { 'origin': validate.text }, 'getMultiStreams': validate.any( None, { 'multistream': bool, 'streams': [{ 'name': validate.text, 'online': bool, }], }), }) vod_schema = validate.Schema( { 'data': { 'video': validate.any( None, { 'id': validate.text, 'title': validate.text, 'file_name': validate.text, 'channel': { 'name': validate.text }, }), }, }, validate.get('data'), validate.get('video')) HLS_URL = 'https://{origin}.picarto.tv/stream/hls/{file_name}/index.m3u8' author = None category = None title = None @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def get_author(self): return self.author def get_category(self): return self.category def get_title(self): return self.title def get_live(self, username): res = self.session.http.get( 'https://ptvintern.picarto.tv/api/channel/detail/{0}'.format( username)) channel_data = self.session.http.json(res, schema=self.channel_schema) log.trace('channel_data={0!r}'.format(channel_data)) if not channel_data['channel'] or not channel_data['getMultiStreams']: log.debug('Missing channel or streaming data') return if channel_data['channel']['private']: log.info('This is a private stream') return if channel_data['getMultiStreams']['multistream']: msg = 'Found multistream: ' i = 1 for user in channel_data['getMultiStreams']['streams']: msg += user['name'] msg += ' (online)' if user['online'] else ' (offline)' if i < len(channel_data['getMultiStreams']['streams']): msg += ', ' i += 1 log.info(msg) if not channel_data['channel']['online']: log.error('User is not online') return self.author = username self.category = channel_data['channel']['categories'][0]['label'] self.title = channel_data['channel']['title'] return HLSStream.parse_variant_playlist( self.session, self.HLS_URL.format( file_name=channel_data['channel']['stream_name'], origin=channel_data['getLoadBalancerUrl']['origin'])) def get_vod(self, vod_id): data = { 'query': ('query ($videoId: ID!) {\n' ' video(id: $videoId) {\n' ' id\n' ' title\n' ' file_name\n' ' channel {\n' ' name\n' ' }' ' }\n' '}\n'), 'variables': { 'videoId': vod_id }, } res = self.session.http.post('https://ptvintern.picarto.tv/ptvapi', json=data) vod_data = self.session.http.json(res, schema=self.vod_schema) log.trace('vod_data={0!r}'.format(vod_data)) if not vod_data: log.debug('Missing video data') return self.author = vod_data['channel']['name'] self.category = 'VOD' self.title = vod_data['title'] return HLSStream.parse_variant_playlist( self.session, self.HLS_URL.format(file_name=vod_data['file_name'], origin='recording-eu-1')) def _get_streams(self): m = self.url_re.match(self.url).groupdict() if (m['po'] == 'streampopout' or not m['po']) and m['user'] and not m['vod_id']: log.debug('Type=Live') return self.get_live(m['user']) elif m['po'] == 'videopopout' or (m['user'] and m['vod_id']): log.debug('Type=VOD') vod_id = m['vod_id'] if m['vod_id'] else m['user'] return self.get_vod(vod_id)
"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 AfreecaTV(Plugin): login_url = "https://member.afreecatv.com:8111/login/LoginAction.php" arguments = PluginArguments( PluginArgument( "username", requires=["password"], metavar="USERNAME", help="The username used to register with afreecatv.com."
class Mjunoon(Plugin): login_url = 'https://cdn2.mjunoon.tv:9191/v2/auth/login' stream_url = 'https://cdn2.mjunoon.tv:9191/v2/streaming-url' is_live_channel_re = re.compile(r'"isLiveBroadcast":\s*"(true|undefined)"') main_chunk_js_url_re = re.compile( r'<script src="(/static/js/main\.\w+\.chunk\.js)"></script>') js_credentials_re = re.compile( r'data:{email:"(?P<email>.*?)",password:"******"}') js_cipher_data_re = re.compile( r'createDecipheriv\("(?P<algorithm>.*?)","(?P<key>.*?)","(?P<iv>.*?)"\)' ) token_schema = validate.Schema({ 'token': str, 'token_type': str, 'expires_in': int, }) encrypted_data_schema = validate.Schema({ 'eData': str, }, validate.get('eData')) stream_schema = validate.Schema( { 'data': { 'live_stream_url': validate.url(), 'channel_name': str, 'meta_title': validate.any(None, str), 'genres': validate.all( validate.transform(lambda x: x.split(',')[0]), str, ), }, }, validate.get('data')) encryption_algorithm = { 'aes-256-cbc': AES.MODE_CBC, } def get_data(self): js_data = {} res = self.session.http.get(self.url) m = self.is_live_channel_re.search(res.text) if not m: return if m.group(1) == "true": js_data['type'] = 'channel' else: js_data['type'] = 'episode' m = self.main_chunk_js_url_re.search(res.text) if not m: log.error('Failed to get main chunk JS URL') return res = self.session.http.get(urljoin(self.url, m.group(1))) m = self.js_credentials_re.search(res.text) if not m: log.error('Failed to get credentials') return js_data['credentials'] = m.groupdict() m = self.js_cipher_data_re.search(res.text) if not m: log.error('Failed to get cipher data') return js_data['cipher_data'] = m.groupdict() return js_data def decrypt_data(self, cipher_data, encrypted_data): cipher = AES.new( bytes(cipher_data['key'], 'utf-8'), self.encryption_algorithm[cipher_data['algorithm']], bytes(cipher_data['iv'], 'utf-8'), ) return unpad(cipher.decrypt(binascii.unhexlify(encrypted_data)), 16, 'pkcs7') def get_stream(self, slug, js_data): token_data = {} token_data['token'] = self.cache.get('token') token_data['token_type'] = self.cache.get('token_type') if token_data['token'] and token_data['token_type']: log.debug('Using cached token') else: log.debug('Getting new token') res = self.session.http.post( self.login_url, json=js_data['credentials'], ) token_data = self.session.http.json(res, schema=self.token_schema) log.debug(f'Token={token_data["token"]}') self.cache.set('token', token_data['token'], expires=token_data['expires_in']) self.cache.set('token_type', token_data['token_type'], expires=token_data['expires_in']) headers = { 'Authorization': f'{token_data["token_type"]} {token_data["token"]}' } data = { 'slug': slug, 'type': js_data['type'], } res = self.session.http.post( self.stream_url, headers=headers, json=data, ) encrypted_data = self.session.http.json( res, schema=self.encrypted_data_schema) stream_data = parse_json( self.decrypt_data(js_data['cipher_data'], encrypted_data), schema=self.stream_schema, ) self.author = stream_data['channel_name'] self.category = stream_data['genres'] self.title = stream_data['meta_title'] return stream_data['live_stream_url'] def _get_streams(self): slug = self.match.group(1) log.debug(f'Slug={slug}') js_data = self.get_data() if not js_data: return log.debug(f'JS data={js_data}') hls_url = self.get_stream(slug, js_data) log.debug(f'HLS URL={hls_url}') return HLSStream.parse_variant_playlist(self.session, hls_url)
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): return _url_re.match(url)
class PlayTV(Plugin): FORMATS_URL = 'http://playtv.fr/player/initialize/{0}/' API_URL = 'http://playtv.fr/player/play/{0}/?format={1}&language={2}&bitrate={3}' _url_re = re.compile(r'http://playtv\.fr/television/(?P<channel>[^/]+)/?') _formats_schema = validate.Schema({ 'streams': validate.any( [], { validate.text: validate.Schema({ validate.text: { 'bitrates': validate.all([ validate.Schema({ 'value': int }) ]) } }) } ) }) _api_schema = validate.Schema({ 'url': validate.url() }) @classmethod def can_handle_url(cls, url): return PlayTV._url_re.match(url) def _get_streams(self): match = self._url_re.match(self.url) channel = match.group('channel') res = http.get(self.FORMATS_URL.format(channel)) streams = http.json(res, schema=self._formats_schema)['streams'] if streams == []: self.logger.error('Channel may be geo-restricted, not directly provided by PlayTV or not freely available') return for language in streams: for protocol, bitrates in list(streams[language].items()): # - Ignore non-supported protocols (RTSP, DASH) # - Ignore deprecated Flash (RTMPE) streams (PlayTV doesn't provide anymore a Flash player) if protocol in ['rtsp', 'flash', 'dash']: continue for bitrate in bitrates['bitrates']: if bitrate['value'] == 0: continue api_url = self.API_URL.format(channel, protocol, language, bitrate['value']) res = http.get(api_url) video_url = http.json(res, schema=self._api_schema)['url'] bs = '{0}k'.format(bitrate['value']) if protocol == 'hls': for _, stream in HLSStream.parse_variant_playlist(self.session, video_url).items(): yield bs, stream elif protocol == 'hds': for _, stream in HDSStream.parse_manifest(self.session, video_url).items(): yield bs, stream
import re from streamlink.plugin import Plugin from streamlink.plugin.api import http, validate from streamlink.plugin.api import useragents from streamlink.stream import HLSStream _url_re = re.compile(r"https?://(www\.)?ok\.ru/live/\d+") _vod_re = re.compile(r";(?P<hlsurl>[^;]+video\.m3u8.+?)\\"") _schema = validate.Schema( validate.transform(_vod_re.search), validate.any( None, validate.all( validate.get("hlsurl"), validate.url() ) ) ) class OK_live(Plugin): """ Support for ok.ru live stream: http://www.ok.ru/live/ """ @classmethod def can_handle_url(cls, url): return _url_re.match(url) is not None def _get_streams(self): headers = {
class Kugou(Plugin): _roomid_re = re.compile(r"roomId:\s*'(\d+)'") _room_stream_list_schema = validate.Schema( {"data": validate.any(None, {"httpflv": validate.url()})}, validate.get("httpflv_room_stream_list_schema")) _stream_hv_schema = validate.Schema( validate.any( None, [{ "httpshls": [validate.url()], "httpsflv": [validate.url()], }], )) _stream_data_schema = validate.Schema({ "msg": validate.text, "code": int, "data": { "status": int, "vertical": _stream_hv_schema, "horizontal": _stream_hv_schema, "roomId": int, } }) def _get_streams(self): res = self.session.http.get(self.url) m = self._roomid_re.search(res.text) if m: room_id = m.group(1) else: room_id = self.match.group("room_id") res = self.session.http.get( "https://fx2.service.kugou.com/video/pc/live/pull/v3/streamaddr", params={ "ch": "fx", "version": "1.0", # 1=rtmp, 2=httpflv, 3=hls, 5=httpsflv, 6=httpshls "streamType": "1-2-5-6", "ua": "fx-flash", "kugouId": "0", "roomId": room_id, "_": int(time.time()), }) stream_data_json = self.session.http.json( res, schema=self._stream_data_schema) log.trace("{0!r}".format(stream_data_json)) if stream_data_json["code"] != 0 or stream_data_json["data"][ "status"] != 1: return h = stream_data_json["data"]["horizontal"] v = stream_data_json["data"]["vertical"] stream_data = h[0] if h else v[0] if stream_data.get("httpshls"): for hls_url in stream_data["httpshls"]: s = HLSStream.parse_variant_playlist(self.session, hls_url) if not s: yield "live", HLSStream(self.session, hls_url) else: for _s in s.items(): yield _s if stream_data.get("httpsflv"): for http_url in stream_data["httpsflv"]: yield "live", HTTPStream(self.session, http_url)
from streamlink.plugin.api import http, validate 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:
class TestDict: def test_simple(self): schema = {"foo": "FOO", "bar": str} value = {"foo": "FOO", "bar": "BAR", "baz": "BAZ"} result = validate.validate(schema, value) assert result == {"foo": "FOO", "bar": "BAR"} assert result is not value @pytest.mark.parametrize("value, expected", [ ({ "foo": "foo" }, { "foo": "foo" }), ({ "bar": "bar" }, {}), ], ids=[ "existing", "missing", ]) def test_optional(self, value, expected): assert validate.validate({validate.optional("foo"): "foo"}, value) == expected @pytest.mark.parametrize( "schema, value, expected", [ ( { str: { int: str } }, { "foo": { 1: "foo" } }, { "foo": { 1: "foo" } }, ), ( { validate.all(str, "foo"): str }, { "foo": "foo" }, { "foo": "foo" }, ), ( { validate.any(int, str): str }, { "foo": "foo" }, { "foo": "foo" }, ), ( { validate.transform(lambda s: s.upper()): str }, { "foo": "foo" }, { "FOO": "foo" }, ), ( { validate.union((str, )): str }, { "foo": "foo" }, { ("foo", ): "foo" }, ), ], ids=[ "type", "AllSchema", "AnySchema", "TransformSchema", "UnionSchema", ], ) def test_keys(self, schema, value, expected): assert validate.validate(schema, value) == expected def test_failure_key(self): with pytest.raises(validate.ValidationError) as cm: validate.validate({str: int}, {"foo": 1, 2: 3}) assert_validationerror( cm.value, """ ValidationError(dict): Unable to validate key Context(type): Type of 2 should be str, but is int """) def test_failure_key_value(self): with pytest.raises(validate.ValidationError) as cm: validate.validate({str: int}, {"foo": "bar"}) assert_validationerror( cm.value, """ ValidationError(dict): Unable to validate value Context(type): Type of 'bar' should be int, but is str """) def test_failure_notfound(self): with pytest.raises(validate.ValidationError) as cm: validate.validate({"foo": "bar"}, {"baz": "qux"}) assert_validationerror( cm.value, """ ValidationError(dict): Key 'foo' not found in {'baz': 'qux'} """) def test_failure_value(self): with pytest.raises(validate.ValidationError) as cm: validate.validate({"foo": "bar"}, {"foo": 1}) assert_validationerror( cm.value, """ ValidationError(dict): Unable to validate value of key 'foo' Context(equality): 1 does not equal 'bar' """) def test_failure_schema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate({}, 1) assert_validationerror( cm.value, """ ValidationError(type): Type of 1 should be dict, but is int """)
from streamlink.stream import HLSStream from streamlink.plugin.api import useragents from streamlink.utils import update_scheme HUYA_URL = "http://m.huya.com/%s" _url_re = re.compile(r'http(s)?://(www\.)?huya.com/(?P<channel>[^/]+)', re.VERBOSE) _hls_re = re.compile(r'^\s*<video\s+id="html5player-video"\s+src="(?P<url>[^"]+)"', re.MULTILINE) _hls_schema = validate.Schema( validate.all( validate.transform(_hls_re.search), validate.any( None, validate.all( validate.get('url'), validate.transform(str) ) ) ) ) class Huya(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_streams(self): match = _url_re.match(self.url) channel = match.group("channel")
def schema(self): return validate.any( "foo", str, lambda data: data is not None, )
"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):
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 = self.session.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 = self.session.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
} }, 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, } ) _viewer_info_schema = validate.Schema( { validate.optional("login"): validate.text }, validate.get("login") ) _viewer_token_schema = validate.Schema(
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.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): return _url_re.match(url) def _get_streams(self): match = _url_re.match(self.url)
import re from streamlink.plugin import Plugin from streamlink.plugin.api import http, validate YOUTUBE_URL = "https://www.youtube.com/watch?v={0}" _url_re = re.compile(r'http(s)?://www\.skai.gr/.*') _youtube_id = re.compile(r'<span\s+itemprop="contentUrl"\s+href="(.*)"></span>', re.MULTILINE) _youtube_url_schema = validate.Schema( validate.all( validate.transform(_youtube_id.search), validate.any( None, validate.all( validate.get(1), validate.text ) ) ) ) class Skai(Plugin): @classmethod def can_handle_url(cls, url): return _url_re.match(url) def _get_streams(self): channel_id = http.get(self.url, schema=_youtube_url_schema) if channel_id: return self.session.streams(YOUTUBE_URL.format(channel_id))
"h264_aac_ts_http_m3u8_http": ("HLS", HLSStream.parse_variant_playlist) } _url_re = re.compile(r""" http(s)?://(\w+\.)?zdf.de/ """, re.VERBOSE | re.IGNORECASE) _api_json_re = re.compile(r'''data-zdfplayer-jsb=["'](?P<json>{.+?})["']''', re.S) _api_schema = validate.Schema( validate.transform(_api_json_re.search), validate.any( None, validate.all( validate.get("json"), validate.transform(parse_json), { "content": validate.text, "apiToken": validate.text }, ))) _documents_schema = validate.Schema({ "mainVideoContent": { "http://zdf.de/rels/target": { "http://zdf.de/rels/streams/ptmd-template": validate.text }, }, }) _schema = validate.Schema({ "priorityList": [{
from streamlink.plugin import Plugin from streamlink.plugin.api import StreamMapper, http, validate from streamlink.stream import HDSStream, HLSStream, RTMPStream from streamlink.utils import rtmpparse STREAM_API_URL = "https://playapi.mtgx.tv/v3/videos/stream/{0}" _swf_url_re = re.compile(r"data-flashplayer-url=\"([^\"]+)\"") _player_data_re = re.compile(r"window.fluxData\s*=\s*JSON.parse\(\"(.+)\"\);") _stream_schema = validate.Schema( validate.any( None, validate.all({"msg": validate.text}), validate.all({ "streams": validate.all( {validate.text: validate.any(validate.text, int, None)}, validate.filter(lambda k, v: isinstance(v, validate.text)) ) }, validate.get("streams")) ) ) class Viasat(Plugin): """Streamlink Plugin for Viasat""" _iframe_re = re.compile(r"""<iframe.+src=["'](?P<url>[^"']+)["'].+allowfullscreen""") _image_re = re.compile(r"""<meta\sproperty=["']og:image["']\scontent=".+/(?P<stream_id>\d+)/[^/]+\.jpg""") _url_re = re.compile(r"""https?://(?:www\.)? (?:
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)
_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") ), 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)
class Filmon(Plugin): url_re = re.compile(r"""(?x)https?://(?:www\.)?filmon\.(?:tv|com)/(?: (?: index/popout\? | (?:tv/)channel/(?:export\?)? | tv/(?!channel/) | channel/ | (?P<is_group>group/) )(?:channel_id=)?(?P<channel>[-_\w]+) | vod/view/(?P<vod_id>\d+)- )""") _channel_id_re = re.compile( r"""channel_id\s*=\s*(?P<quote>['"]?)(?P<value>\d+)(?P=quote)""") _channel_id_schema = validate.Schema( validate.transform(_channel_id_re.search), validate.any(None, validate.get("value"))) quality_weights = {"high": 720, "low": 480} TIME_CHANNEL = 60 * 60 * 24 * 365 def __init__(self, url): super(Filmon, self).__init__(url) parsed = urlparse(self.url) if parsed.path.startswith("/channel/"): self.url = urlunparse( parsed._replace(path=parsed.path.replace("/channel/", "/tv/"))) self.api = FilmOnAPI(self.session) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None @classmethod def stream_weight(cls, key): weight = cls.quality_weights.get(key) if weight: return weight, "filmon" return Plugin.stream_weight(key) def _get_streams(self): url_m = self.url_re.match(self.url) channel = url_m and url_m.group("channel") vod_id = url_m and url_m.group("vod_id") is_group = url_m and url_m.group("is_group") if vod_id: data = self.api.vod(vod_id) for _, stream in data["streams"].items(): streams = HLSStream.parse_variant_playlist( self.session, stream["url"]) if not streams: yield stream["quality"], HLSStream(self.session, stream["url"]) else: yield from streams.items() else: if channel and not channel.isdigit(): _id = self.cache.get(channel) if _id is None: _id = self.session.http.get(self.url, schema=self._channel_id_schema) log.debug(f"Found channel ID: {_id}") # do not cache a group url if _id and not is_group: self.cache.set(channel, _id, expires=self.TIME_CHANNEL) else: log.debug(f"Found cached channel ID: {_id}") else: _id = channel if _id is None: raise PluginError( "Unable to find channel ID: {0}".format(channel)) try: data = self.api.channel(_id) for stream in data["streams"]: yield stream["quality"], FilmOnHLS( self.session, channel=_id, quality=stream["quality"]) except Exception: if channel and not channel.isdigit(): self.cache.set(channel, None, expires=0) log.debug(f"Reset cached channel: {channel}") raise
"web_medium": 10, "web_high": 11, "web_hd": 12 } _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):
class UStreamTVWsClient(WebsocketClient): API_URL = "wss://r{0}-1-{1}-{2}-ws-{3}.ums.services.video.ibm.com/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, } }
API_URL = "https://www.zhanqi.tv/api/static/v2.1/room/domain/{0}.json" STATUS_ONLINE = 4 STATUS_OFFLINE = 0 _url_re = re.compile(r""" http(s)?://(www\.)?zhanqi.tv /(?P<channel>[^/]+) """, re.VERBOSE) _room_schema = validate.Schema( { "data": validate.any(None, { "status": validate.all( validate.text, validate.transform(int) ), "videoId": validate.text }) }, validate.get("data") ) class Zhanqitv(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_streams(self): match = _url_re.match(self.url)
import random import re import json 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"): validate.Schema({ "chat": validate.Schema ({ "status": validate.any(int, validate.text) }) }) } ) _api_video_schema = validate.Schema( { "token": validate.text, "app": validate.text, "edge_servers": [validate.text], "stream_name": validate.text } )
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_id = parsed.fragment
class LineLive(Plugin): _api_url = "https://live-api.line-apps.com/web/v4.0/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
_url_re = re.compile(""" http(s)?://(www\.)?douyu.com /(?P<channel>[^/]+) """, re.VERBOSE) _room_id_re = re.compile(r'"room_id"\s*:\s*(\d+),') _room_id_alt_re = re.compile(r'data-room_id="(\d+)"') _room_id_schema = validate.Schema( validate.all( validate.transform(_room_id_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(int) ) ) ) ) _room_id_alt_schema = validate.Schema( validate.all( validate.transform(_room_id_alt_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(int) )
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)
import re from streamlink.plugin import Plugin, PluginError, PluginOptions from streamlink.plugin.api import http, validate from streamlink.stream import HLSStream LOGIN_PAGE_URL = "http://www.livestation.com/en/users/new" LOGIN_POST_URL = "http://www.livestation.com/en/sessions.json" _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( {
VIEW_LIVE_API_URL_JP = "http://api.afreecatv.jp/live/view_live.php" _url_re = re.compile( "http(s)?://(\w+\.)?(afreecatv.com.tw|afreeca.tv|afreecatv.jp)/(?P<channel>[\w\-_]+)" ) _url_re_tw = re.compile( "http(s)?://(\w+\.)?(afreecatv.com.tw)/(?P<channel>[\w\-_]+)") _url_re_jp = re.compile( "http(s)?://(\w+\.)?(afreecatv.jp)/(?P<channel>[\w\-_]+)") _flashvars_re = re.compile('<param name="flashvars" value="([^"]+)" />') _flashvars_schema = validate.Schema( validate.transform(_flashvars_re.findall), validate.get(0), validate.transform(parse_query), validate.any({ "s": validate.text, "id": validate.text }, {})) _view_live_schema = validate.Schema( { "channel": { "strm": [{ "bps": validate.text, "purl": validate.url(scheme="rtmp") }] }, }, validate.get("channel"), validate.get("strm")) class AfreecaTV(Plugin): @classmethod def can_handle_url(cls, url):
) RECORDED_URL = "http://tcdn.ustream.tv/video/{0}" RTMP_URL = "rtmp://r{0}-1-{1}-channel-live.ums.ustream.tv:1935/ustream" SWF_URL = "http://static-cdn1.ustream.tv/swf/live/viewer.rsl:505.swf" _module_info_schema = validate.Schema( 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, }],
http(s)?://(\w+\.)? dailymotion.com (?: (/embed)?/(video|live) /(?P<media_id>[^_?/]+) | /(?P<channel_name>[A-Za-z0-9-_]+) ) """, re.VERBOSE) _media_schema = validate.Schema(validate.any( {"error": {"title": validate.text}}, # "stream_chromecast_url": validate.url(), # Chromecast URL is already available in qualities subdict {"qualities": validate.any({ validate.text: validate.all([{ "type": validate.text, "url": validate.url() }]) }) })) _live_id_schema = validate.Schema( { "total": int, "list": validate.any( [], [{"id": validate.text}] ) } )
LAPI_URL = "http://g2.live.360.cn/liveplay?stype=flv&channel={}&bid=huajiao&sn={}&sid={}&_rate=xd&ts={}&r={}&_ostype=flash&_delay=0&_sign=null&_ver=13" _url_re = re.compile(r""" http(s)?://(www\.)?huajiao.com /l/(?P<channel>[^/]+) """, re.VERBOSE) _feed_json_re = re.compile(r'^\s*var\s*feed\s*=\s*(?P<feed>{.*})\s*;', re.MULTILINE) _feed_json_schema = validate.Schema( validate.all( validate.transform(_feed_json_re.search), validate.any( None, validate.all( validate.get('feed'), validate.transform(json.loads) ) ) ) ) class Huajiao(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) def _get_streams(self): match = _url_re.match(self.url) channel = match.group("channel")
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+)"') item_id_re = re.compile(r"_itemId\s+=\s+'(\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: self.logger.debug("Found contentId by contentId regex") 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: self.logger.debug("Found contentId by player data-id regex") return content_id_m.group("id") # find the itemId var item_id_m = self.item_id_re.search(res.text) if item_id_m: self.logger.debug("Found contentId by itemId regex") return item_id_m.group(1) 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")