class Garena(Plugin): API_INFO = "https://garena.live/api/channel_info_get" API_STREAM = "https://garena.live/api/channel_stream_get" _info_schema = validate.Schema({ "reply": validate.any({ "channel_id": int, }, None), "result": validate.text }) _stream_schema = validate.Schema({ "reply": validate.any( { "streams": [{ "url": validate.text, "resolution": int, "bitrate": int, "format": int }] }, None), "result": validate.text }) @classmethod def can_handle_url(self, url): return _url_re.match(url) def _post_api(self, api, payload, schema): res = http.post(api, json=payload) data = http.json(res, schema=schema) if data["result"] == "success": post_data = data["reply"] return post_data def _get_streams(self): match = _url_re.match(self.url) if match.group("alias"): payload = {"alias": match.group("alias")} info_data = self._post_api(self.API_INFO, payload, self._info_schema) channel_id = info_data["channel_id"] elif match.group("channel_id"): channel_id = int(match.group("channel_id")) if channel_id: payload = {"channel_id": channel_id} stream_data = self._post_api(self.API_STREAM, payload, self._stream_schema) for stream in stream_data["streams"]: n = "{0}p".format(stream["resolution"]) if stream["format"] == 3: s = HLSStream(self.session, stream["url"]) yield n, s
class NineAnime(Plugin): _episode_info_url = "//9anime.to/ajax/episode/info" _info_schema = validate.Schema({ "grabber": validate.url(), "params": { "id": validate.text, "token": validate.text, "options": validate.text, } }) _streams_schema = validate.Schema({ "token": validate.text, "error": None, "data": [{ "label": validate.text, "file": validate.url(), "type": "mp4" }] }) _url_re = re.compile(r"https?://9anime.to/watch/(?:[^.]+?\.)(\w+)/(\w+)") @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def add_scheme(self, url): # update the scheme for the grabber url if required if url.startswith("//"): url = "{0}:{1}".format(urlparse(self.url).scheme, url) return url def _get_streams(self): match = self._url_re.match(self.url) film_id, episode_id = match.groups() headers = {"Referer": self.url, "User-Agent": useragents.FIREFOX} # Get the info about the Episode, including the Grabber API URL info_res = http.get(self.add_scheme(self._episode_info_url), params=dict(update=0, film=film_id, id=episode_id), headers=headers) info = http.json(info_res, schema=self._info_schema) # Get the data about the streams from the Grabber API grabber_url = self.add_scheme(info["grabber"]) stream_list_res = http.get(grabber_url, params=info["params"], headers=headers) stream_data = http.json(stream_list_res, schema=self._streams_schema) for stream in stream_data["data"]: yield stream["label"], HTTPStream(self.session, stream["file"])
class Euronews(Plugin): _url_re = re.compile(r"http(?:s)?://(\w+)\.?euronews.com/(live|.*)") _re_vod = re.compile( r'<meta\s+property="og:video"\s+content="(http.*?)"\s*/>') _live_api_url = "http://{0}.euronews.com/api/watchlive.json" _live_schema = validate.Schema({u"url": validate.url()}) _stream_api_schema = validate.Schema({ u'status': u'ok', u'primary': validate.url(), validate.optional(u'backup'): validate.url() }) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) def _get_vod_stream(self): """ Find the VOD video url :return: video url """ res = http.get(self.url) video_urls = self._re_vod.findall(res.text) if len(video_urls): return dict(vod=HTTPStream(self.session, video_urls[0])) def _get_live_streams(self, subdomain): """ Get the live stream in a particular language :param subdomain: :return: """ res = http.get(self._live_api_url.format(subdomain)) live_res = http.json(res, schema=self._live_schema) api_res = http.get(live_res[u"url"]) stream_data = http.json(api_res, schema=self._stream_api_schema) return HLSStream.parse_variant_playlist(self.session, stream_data[u'primary']) def _get_streams(self): """ Find the streams for euronews :return: """ match = self._url_re.match(self.url) subdomain, path = match.groups() if path == "live": return self._get_live_streams(subdomain) else: return self._get_vod_stream()
class ovvaTV(Plugin): url_re = re.compile( r"https?://(?:www\.)?ovva.tv/(?:ua/)?tvguide/.*?/online") iframe_re = re.compile( r"iframe .*?src=\"((?:https?:)?//(?:\w+\.)?ovva.tv/[^\"]+)\"", re.DOTALL) data_re = re.compile(r"ovva\(\'(.*?)\'\);") ovva_data_schema = validate.Schema({"url": validate.url()}, validate.get("url")) ovva_redirect_schema = validate.Schema( validate.all(validate.transform(lambda x: x.split("=")), ['302', validate.url()], validate.get(1))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def find_iframe(self, res): for url in self.iframe_re.findall(res.text): if url.startswith("//"): p = urlparse(self.url) return "{0}:{1}".format(p.scheme, url) else: return url def _get_streams(self): http.headers = {"User-Agent": useragents.ANDROID} res = http.get(self.url) iframe_url = self.find_iframe(res) if iframe_url: self.logger.debug("Found iframe: {0}", iframe_url) res = http.get(iframe_url, headers={"Referer": self.url}) data = self.data_re.search(res.text) if data: try: ovva_url = parse_json(b64decode( data.group(1)).decode("utf8"), schema=self.ovva_data_schema) stream_url = http.get(ovva_url, schema=self.ovva_redirect_schema) except PluginError as e: self.logger.error("Could not find stream URL: {0}", e) else: return HLSStream.parse_variant_playlist( self.session, stream_url) else: self.logger.error("Could not find player data.")
class QQ(Plugin): """Livecli Plugin for live.qq.com""" _data_schema = validate.Schema({"data": { "hls_url": validate.text }}, validate.get("data", {}), validate.get("hls_url")) api_url = "http://live.qq.com/api/h5/room?room_id={0}" _data_re = re.compile(r"""(?P<data>{.+})""") _url_re = re.compile(r"""https?://(m\.)?live\.qq\.com/(?P<room_id>\d+)""") @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) def _get_streams(self): match = self._url_re.match(self.url) if not match: return room_id = match.group("room_id") res = http.get(self.api_url.format(room_id)) data = self._data_re.search(res.text) if not data: return try: hls_url = parse_json(data.group("data"), schema=self._data_schema) except Exception: raise NoStreamsError(self.url) self.logger.debug("URL={0}".format(hls_url)) return {"live": HLSStream(self.session, hls_url)}
class RaiPlay(Plugin): url_re = re.compile(r"https?://(?:www\.)?raiplay\.it/dirette/(\w+)/?") stream_re = re.compile(r"data-video-url.*?=.*?\"([^\"]+)\"") stream_schema = validate.Schema( validate.all( validate.transform(stream_re.search), validate.any(None, validate.all(validate.get(1), validate.url())))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): http.headers.update({"User-Agent": useragents.FIREFOX}) channel = self.url_re.match(self.url).group(1) self.logger.debug("Found channel: {0}", channel) stream_url = http.get(self.url, schema=self.stream_schema) if stream_url: try: return HLSStream.parse_variant_playlist( self.session, stream_url) except Exception as e: if "Missing #EXTM3U header" in str(e): raise PluginError( "The streaming of this content is available in Italy only." ) raise e
def test_parse_xml_validate(self): expected = ET.Element("test", {"foo": "bar"}) actual = parse_xml(u"""<test foo="bar"/>""", schema=validate.Schema( xml_element(tag="test", attrib={"foo": text}))) self.assertEqual(expected.tag, actual.tag) self.assertEqual(expected.attrib, actual.attrib)
class Streamable(Plugin): url_re = re.compile(r"https?://(?:www\.)?streamable\.com/(.+)") meta_re = re.compile(r'''var\s*videoObject\s*=\s*({.*});''') config_schema = validate.Schema( validate.transform(meta_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_json), { "files": { validate.text: { "url": validate.url(), "width": int, "height": int, "bitrate": int } } }))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): data = http.get(self.url, schema=self.config_schema) for info in data["files"].values(): stream_url = update_scheme(self.url, info["url"]) # pick the smaller of the two dimensions, for landscape v. portrait videos res = min(info["width"], info["height"]) yield "{0}p".format(res), HTTPStream(self.session, stream_url)
class TV8(Plugin): """ Support for the live stream on www.tv8.com.tr """ url_re = re.compile(r"https?://www.tv8.com.tr/canli-yayin") player_config_re = re.compile( r""" configPlayer.source.media.push[ ]*\( [ ]*\{[ ]*'src':[ ]*"(.*?)", [ ]*type:[ ]*"application/x-mpegURL"[ ]*}[ ]*\); """, re.VERBOSE) player_config_schema = validate.Schema( validate.transform(player_config_re.search), validate.any(None, validate.all(validate.get(1), validate.url()))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): res = http.get(self.url) stream_url = self.player_config_schema.validate(res.text) if stream_url: return HLSStream.parse_variant_playlist(self.session, stream_url)
class Filmon(Plugin): url_re = re.compile( r"""https?://(?:\w+\.)?filmon.(?:tv|com)/ (?: (tv|channel)/(?P<channel>[^/]+)| vod/view/(?P<vod_id>\d+)-| group/ ) """, re.VERBOSE) _channel_id_re = re.compile(r'''channel_id\s*?=\s*["']?(\d+)["']''') _channel_id_schema = validate.Schema( validate.transform(_channel_id_re.search), validate.any(None, validate.get(1))) quality_weights = {"high": 720, "low": 480} def __init__(self, url): super(Filmon, self).__init__(url) self.api = FilmOnAPI() @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) vod_id = url_m and url_m.group("vod_id") if vod_id: self.logger.debug("vod_id: {0}".format(vod_id)) data = self.api.vod(vod_id) for _, stream in data["streams"].items(): yield stream["quality"], FilmOnHLS(self.session, vod_id=vod_id, quality=stream["quality"]) else: channel = http.get(self.url, schema=self._channel_id_schema) if channel: self.logger.debug("channel_id: {0}".format(channel)) else: channel = url_m and url_m.group("channel") self.logger.debug("channel: {0}".format(channel)) data = self.api.channel(channel) for stream in data["streams"]: yield stream["quality"], FilmOnHLS(self.session, channel=channel, quality=stream["quality"])
class CDNBG(Plugin): url_re = re.compile( r""" https?://(?:www\.)?(?: tv\.bnt\.bg/\w+(?:/\w+)?| bitelevision\.com/live| nova\.bg/live| kanal3\.bg/live| bgonair\.bg/tvonline| tvevropa\.com/na-zhivo| bloombergtv.bg/video )/? """, re.VERBOSE) iframe_re = re.compile( r"iframe .*?src=\"((?:https?:)?//(?:\w+\.)?cdn.bg/live[^\"]+)\"", re.DOTALL) sdata_re = re.compile( r"sdata\.src.*?=.*?(?P<q>[\"'])(?P<url>http.*?)(?P=q)") hls_file_re = re.compile( r"(src|file): (?P<q>[\"'])(?P<url>(https?:)?//.+?m3u8.*?)(?P=q)") hls_src_re = re.compile(r"video src=(?P<url>http[^ ]+m3u8[^ ]*)") stream_schema = validate.Schema( validate.any( validate.all(validate.transform(sdata_re.search), validate.get("url")), validate.all(validate.transform(hls_file_re.search), validate.get("url")), validate.all(validate.transform(hls_src_re.search), validate.get("url")), )) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def find_iframe(self, res): p = urlparse(self.url) for url in self.iframe_re.findall(res.text): if "googletagmanager" not in url: if url.startswith("//"): return "{0}:{1}".format(p.scheme, url) else: return url def _get_streams(self): http.headers = {"User-Agent": useragents.CHROME} res = http.get(self.url) iframe_url = self.find_iframe(res) if iframe_url: self.logger.debug("Found iframe: {0}", iframe_url) res = http.get(iframe_url, headers={"Referer": self.url}) stream_url = update_scheme(self.url, self.stream_schema.validate(res.text)) return HLSStream.parse_variant_playlist( self.session, stream_url, headers={"User-Agent": useragents.CHROME})
class ard_live(Plugin): swf_url = "http://live.daserste.de/lib/br-player/swf/main.swf" _url_re = re.compile(r"https?://(www.)?daserste.de/", re.I) _player_re = re.compile(r'''dataURL\s*:\s*(?P<q>['"])(?P<url>.*?)(?P=q)''') _player_url_schema = validate.Schema( validate.transform(_player_re.search), validate.any(None, validate.all(validate.get("url"), validate.text))) _livestream_schema = validate.Schema( validate.xml_findall(".//assets"), validate.filter(lambda x: x.attrib.get("type") != "subtitles"), validate.get(0), validate.xml_findall(".//asset"), [ validate.union({ "url": validate.xml_findtext("./fileName"), "bitrate": validate.xml_findtext("./bitrateVideo") }) ]) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _get_streams(self): data_url = http.get(self.url, schema=self._player_url_schema) if data_url: res = http.get(urljoin(self.url, data_url)) stream_info = http.xml(res, schema=self._livestream_schema) for stream in stream_info: url = stream["url"] try: if ".m3u8" in url: for s in HLSStream.parse_variant_playlist( self.session, url, name_key="bitrate").items(): yield s elif ".f4m" in url: for s in HDSStream.parse_manifest( self.session, url, pvswf=self.swf_url, is_akamai=True).items(): yield s elif ".mp4" in url: yield "{0}k".format(stream["bitrate"]), HTTPStream( self.session, url) except IOError as err: self.logger.warning("Error parsing stream: {0}", err)
class Cam4(Plugin): _url_re = re.compile(r'https?://([a-z]+\.)?cam4.com/.+') _video_data_re = re.compile( r"flashData: (?P<flash_data>{.*}), hlsUrl: '(?P<hls_url>.+?)'") _flash_data_schema = validate.Schema( validate.all( validate.transform(parse_json), validate.Schema({ 'playerUrl': validate.url(), 'flashVars': validate.Schema({ 'videoPlayUrl': validate.text, 'videoAppUrl': validate.url(scheme='rtmp') }) }))) @classmethod def can_handle_url(cls, url): return Cam4._url_re.match(url) def _get_streams(self): res = http.get(self.url, headers={'User-Agent': useragents.ANDROID}) match = self._video_data_re.search(res.text) if match is None: return hls_streams = HLSStream.parse_variant_playlist( self.session, match.group('hls_url'), headers={'Referer': self.url}) for s in hls_streams.items(): yield s rtmp_video = self._flash_data_schema.validate( match.group('flash_data')) rtmp_stream = RTMPStream( self.session, { 'rtmp': rtmp_video['flashVars']['videoAppUrl'], 'playpath': rtmp_video['flashVars']['videoPlayUrl'], 'swfUrl': rtmp_video['playerUrl'] }) yield 'live', rtmp_stream
def test_parse_qsd(self): self.assertEqual({ "test": "1", "foo": "bar" }, parse_qsd("test=1&foo=bar", schema=validate.Schema({ "test": validate.text, "foo": "bar" })))
def test_parse_json(self): self.assertEqual({}, parse_json("{}")) self.assertEqual({"test": 1}, parse_json("""{"test": 1}""")) self.assertEqual({"test": 1}, parse_json("""{"test": 1}""", schema=validate.Schema({"test": 1}))) self.assertRaises(PluginError, parse_json, """{"test: 1}""") self.assertRaises(IOError, parse_json, """{"test: 1}""", exception=IOError) self.assertRaises(PluginError, parse_json, """{"test: 1}""" * 10)
class ovvaTV(Plugin): url_re = re.compile(r"https?://(?:www\.)?1plus1\.video/tvguide/embed/[^/]") data_re = re.compile(r"""ovva-player["'],["'](.*?)["']\)};""") next_date_re = re.compile( r"""<div\sclass=["']o-message-timer['"]\sdata-timer=["'](\d+)["']""") ovva_data_schema = validate.Schema({"balancer": validate.url()}, validate.get("balancer")) ovva_redirect_schema = validate.Schema( validate.all(validate.transform(lambda x: x.split("=")), ['302', validate.url()], validate.get(1))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): res = http.get(self.url) data = self.data_re.search(res.text) next_date = self.next_date_re.search(res.text) if data: try: ovva_url = parse_json(b64decode(data.group(1)).decode("utf8"), schema=self.ovva_data_schema) stream_url = http.get(ovva_url, schema=self.ovva_redirect_schema) except PluginError as e: self.logger.error("Could not find stream URL: {0}", e) else: return HLSStream.parse_variant_playlist( self.session, stream_url) elif next_date: self.logger.info("The broadcast will be available at {0}".format( datetime.fromtimestamp(int( next_date.group(1))).strftime('%Y-%m-%d %H:%M:%S'))) else: self.logger.error("Could not find player data.")
class LiveMe(Plugin): url_re = re.compile(r"https?://(www.)?liveme\.com/live\.html\?videoid=(\d+)") api_url = "https://live.ksmobile.net/live/queryinfo" api_schema = validate.Schema(validate.all({ "status": "200", "data": { "video_info": { "videosource": validate.any('', validate.url()), "hlsvideosource": validate.any('', validate.url()), } } }, validate.get("data"))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _random_t(self, t): return "".join(random.choice("ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678") for _ in range(t)) def _make_stream(self, url): if url and url.endswith("flv"): return HTTPStream(self.session, url) elif url and url.endswith("m3u8"): return HLSStream(self.session, url) def _get_streams(self): url_params = dict(parse_qsl(urlparse(self.url).query)) video_id = url_params.get("videoid") if video_id: vali = '{0}l{1}m{2}'.format(self._random_t(4), self._random_t(4), self._random_t(5)) data = { 'userid': 1, 'videoid': video_id, 'area': '', 'h5': 1, 'vali': vali } self.logger.debug("Found Video ID: {0}".format(video_id)) res = http.post(self.api_url, data=data) data = http.json(res, schema=self.api_schema) hls = self._make_stream(data["video_info"]["hlsvideosource"]) video = self._make_stream(data["video_info"]["videosource"]) if hls: yield "live", hls if video: yield "live", video
class Turkuvaz(Plugin): """ Plugin to support ATV/A2TV Live streams from www.atv.com.tr and www.a2tv.com.tr """ _url_re = re.compile( r"""https?://(?:www.)? (?:(atvavrupa).tv| (atv|a2tv|ahaber|aspor|minikago|minikacocuk|anews).com.tr) /webtv/(live-broadcast|canli-yayin)""", re.VERBOSE) _hls_url = "http://trkvz-live.ercdn.net/{channel}/{channel}.m3u8" _token_url = "http://videotoken.tmgrup.com.tr/webtv/secure" _token_schema = validate.Schema( validate.all({ "Success": True, "Url": validate.url(), }, validate.get("Url"))) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _get_streams(self): url_m = self._url_re.match(self.url) domain = url_m.group(1) or url_m.group(2) # remap the domain to channel channel = { "atv": "atvhd", "ahaber": "ahaberhd", "aspor": "asporhd", "anews": "anewshd", "minikacocuk": "minikagococuk" }.get(domain, domain) hls_url = self._hls_url.format(channel=channel) # get the secure HLS URL res = http.get(self._token_url, params="url={0}".format(hls_url), headers={ "Referer": self.url, "User-Agent": useragents.CHROME }) secure_hls_url = http.json(res, schema=self._token_schema) self.logger.debug("Found HLS URL: {0}".format(secure_hls_url)) return HLSStream.parse_variant_playlist(self.session, secure_hls_url)
class RadioNet(Plugin): _url_re = re.compile(r"https?://(\w+)\.radio\.(net|at|de|dk|es|fr|it|pl|pt|se)") _stream_data_re = re.compile(r'\bstation\s*:\s*(\{.+\}),?\s*') _stream_schema = validate.Schema( validate.transform(_stream_data_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(parse_json), { 'stationType': validate.text, 'streamUrls': validate.all([{ 'bitRate': int, 'streamUrl': validate.url() }]) }, ) ) ) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) def _get_streams(self): streams = http.get(self.url, schema=self._stream_schema) if streams is None: return # Ignore non-radio streams (podcasts...) if streams['stationType'] != 'radio_station': return stream_urls = [] for stream in streams['streamUrls']: if stream['streamUrl'] in stream_urls: continue if stream['bitRate'] > 0: bitrate = '{}k'.format(stream['bitRate']) else: bitrate = 'live' yield bitrate, HTTPStream(self.session, stream['streamUrl']) stream_urls.append(stream['streamUrl'])
class TVRPlus(Plugin): url_re = re.compile(r"https?://(?:www\.)tvrplus.ro/live-") hls_file_re = re.compile( r"""src:\s?(?P<q>["'])(?P<url>http.+?m3u8.*?)(?P=q)""") stream_schema = validate.Schema( validate.all(validate.transform(hls_file_re.search), validate.any(None, validate.get("url"))), ) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): stream_url = self.stream_schema.validate(http.get(self.url).text) if stream_url: headers = {"Referer": self.url} return HLSStream.parse_variant_playlist(self.session, stream_url, headers=headers)
class TVRBy(Plugin): url_re = re.compile(r"""https?://(?:www\.)?tvr.by/televidenie/belarus""") file_re = re.compile( r"""(?P<q1>["']?)file(?P=q1)\s*:\s*(?P<q2>["'])(?P<url>(?:http.+?m3u8.*?|rtmp://.*?))(?P=q2)""" ) stream_schema = validate.Schema( validate.all(validate.transform(file_re.finditer), validate.transform(list), [validate.get("url")]), ) def __init__(self, url): # ensure the URL ends with a / if not url.endswith("/"): url += "/" super(TVRBy, self).__init__(url) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): res = http.get(self.url) stream_urls = self.stream_schema.validate(res.text) self.logger.debug("Found {0} stream URL{1}", len(stream_urls), "" if len(stream_urls) == 1 else "s") for stream_url in stream_urls: if "m3u8" in stream_url: for _, s in HLSStream.parse_variant_playlist( self.session, stream_url).items(): yield "live", s if stream_url.startswith("rtmp://"): a = stream_url.split("///") s = RTMPStream( self.session, { "rtmp": a[0], "playpath": "live", "swfVfy": "http://www.tvr.by/plugines/uppod/uppod.swf", "pageUrl": self.url }) yield "live", s
class INE(Plugin): url_re = re.compile(r"""https://streaming.ine.com/play\#?/ ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/? (.*?)""", re.VERBOSE) play_url = "https://streaming.ine.com/play/{vid}/watch" js_re = re.compile(r'''script type="text/javascript" src="(https://content.jwplatform.com/players/.*?)"''') jwplayer_re = re.compile(r'''jwConfig\s*=\s*(\{.*\});''', re.DOTALL) setup_schema = validate.Schema( validate.transform(jwplayer_re.search), validate.any( None, validate.all( validate.get(1), validate.transform(json.loads), {"playlist": [ {"sources": [{"file": validate.text, "type": validate.text}]} ]} ) ) ) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): vid = self.url_re.match(self.url).group(1) self.logger.debug("Found video ID: {0}", vid) page = http.get(self.play_url.format(vid=vid)) js_url_m = self.js_re.search(page.text) if js_url_m: js_url = js_url_m.group(1) self.logger.debug("Loading player JS: {0}", js_url) res = http.get(js_url) data = self.setup_schema.validate(res.text) for source in data["playlist"][0]["sources"]: if source["type"] == "hls": return HLSStream.parse_variant_playlist(self.session, "https:" + source["file"])
class RTBF(Plugin): """Livecli Plugin for RTBF""" _url_re = re.compile(r"""https?://(?:www\.)rtbf\.be/auvio/""") api_live_url = "https://www.rtbf.be/embed/d/ajax/refresh?id={0}" _live_schema = validate.Schema( { "data": validate.any( None, [], { "streamUrlHls": validate.text, }, ), }, validate.get("data"), ) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) def _get_live_stream(self, live_id): res = http.get(self.api_live_url.format(live_id)) live_data = http.json(res, schema=self._live_schema) if not live_data: self.logger.debug("No data found.") return for s in HLSStream.parse_variant_playlist( self.session, live_data["streamUrlHls"]).items(): yield s def _get_streams(self): params = dict(parse_qsl(urlparse(self.url).query)) live_id = params.get("lid") if live_id: return self._get_live_stream(live_id)
class RaiPlay(Plugin): url_re = re.compile(r"https?://(?:www\.)?raiplay\.it/dirette/(\w+)/?") stream_re = re.compile(r"data-video-url.*?=.*?\"([^\"]+)\"") stream_schema = validate.Schema( validate.all( validate.transform(stream_re.search), validate.any( None, validate.all(validate.get(1), validate.url()) ) ) ) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): channel = self.url_re.match(self.url).group(1) self.logger.debug("Found channel: {0}", channel) stream_url = http.get(self.url, schema=self.stream_schema) if stream_url: return HLSStream.parse_variant_playlist(self.session, stream_url)
class CinerGroup(Plugin): """ Support for the live stream on www.showtv.com.tr """ url_re = re.compile( r"""https?://(?:www.)? (?: showtv.com.tr/canli-yayin(/showtv)?| haberturk.com/canliyayin| showmax.com.tr/canliyayin| showturk.com.tr/canli-yayin/showturk| bloomberght.com/tv| haberturk.tv/canliyayin )/?""", re.VERBOSE) stream_re = re.compile( r"""div .*? data-ht=(?P<quote>["'])(?P<data>.*?)(?P=quote)""", re.DOTALL) stream_data_schema = validate.Schema( validate.transform(stream_re.search), validate.any( None, validate.all( validate.get("data"), validate.transform(unquote), validate.transform(lambda x: x.replace(""", '"')), validate.transform(json.loads), {"ht_stream_m3u8": validate.url()}, validate.get("ht_stream_m3u8")))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): res = http.get(self.url) stream_url = self.stream_data_schema.validate(res.text) if stream_url: return HLSStream.parse_variant_playlist(self.session, stream_url)
class FilmOnAPI(object): channel_url = "http://www.filmon.com/api-v2/channel/{0}?protocol=hls" vod_url = "http://www.filmon.com/vod/info/{0}" stream_schema = { "quality": validate.text, "url": validate.url(), "watch-timeout": int } api_schema = validate.Schema( { "data": { "streams": validate.any({validate.text: stream_schema}, [stream_schema]) } }, validate.get("data")) def channel(self, channel): res = http.get(self.channel_url.format(channel)) return http.json(res, schema=self.api_schema) def vod(self, vod_id): res = http.get(self.vod_url.format(vod_id)) return http.json(res, schema=self.api_schema)
class PowerApp(Plugin): url_re = re.compile(r"https?://(?:www.)?powerapp.com.tr/tv/(\w+)") api_url = "http://api.powergroup.com.tr/Channels/{0}/?appRef=iPowerWeb&apiVersion=11" api_schema = validate.Schema( validate.all( { "errorCode": 0, "response": { "channel_stream_url": validate.url() } }, validate.get("response"))) @classmethod def can_handle_url(cls, url): return cls.url_re.match(url) is not None def _get_streams(self): channel = self.url_re.match(self.url).group(1) res = http.get(self.api_url.format(channel)) data = http.json(res, schema=self.api_schema) return HLSStream.parse_variant_playlist(self.session, data["channel_stream_url"])
"domains": [ "camsoda.com", ], "geo_blocked": [], "notes": "", "live": True, "vod": False, "last_update": "2017-11-18", } _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 Camsoda(Plugin): API_URL_USER = "******" API_URL_VIDEO = "https://www.camsoda.com/api/v1/video/vtoken/{0}?username=guest_{1}"
class Zattoo(Plugin): API_HELLO = '{0}/zapi/session/hello' API_LOGIN = '******' API_CHANNELS = '{0}/zapi/v2/cached/channels/{1}?details=False' API_WATCH = '{0}/zapi/watch' API_WATCH_REC = '{0}/zapi/watch/recording/{1}' API_WATCH_VOD = '{0}/zapi/avod/videos/{1}/watch' _url_re = re.compile(r''' https?:// (?P<base_url> zattoo\.com | tvonline\.ewe\.de | nettv\.netcologne\.de )/ (?: (?:ondemand/)?(?:watch/(?:[^/\s]+)(?:/[^/]+/(?P<recording_id>\d+))) | watch/(?P<channel>[^/\s]+) | ondemand/watch/(?P<vod_id>[^-]+)- ) ''', re.VERBOSE) _app_token_re = re.compile(r"""window\.appToken\s+=\s+'([^']+)'""") _channels_schema = validate.Schema({ 'success': int, 'channel_groups': [{ 'channels': [ { 'display_alias': validate.text, 'cid': validate.text }, ] }]}, validate.get('channel_groups'), ) options = PluginOptions({ 'email': None, 'password': None, 'purge_credentials': None }) def __init__(self, url): super(Zattoo, self).__init__(url) self._session_attributes = Cache(filename='plugin-cache.json', key_prefix='zattoo:attributes') self._authed = self._session_attributes.get('beaker.session.id') and self._session_attributes.get('pzuid') and self._session_attributes.get('power_guide_hash') self._uuid = self._session_attributes.get('uuid') self._expires = self._session_attributes.get('expires', 946684800) self.base_url = 'https://{0}'.format(Zattoo._url_re.match(url).group('base_url')) self.headers = { 'User-Agent': useragents.CHROME, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest', 'Referer': self.base_url } @classmethod def can_handle_url(cls, url): return Zattoo._url_re.match(url) def _hello(self): self.logger.debug('_hello ...') headers = { 'User-Agent': useragents.CHROME, 'Referer': self.base_url } res = requests.get("{0}/login".format(self.base_url), headers=headers) match = self._app_token_re.search(res.text) app_token = match.group(1) hello_url = self.API_HELLO.format(self.base_url) if self._uuid: __uuid = self._uuid else: __uuid = str(uuid.uuid4()) self._session_attributes.set('uuid', __uuid, expires=3600 * 24) params = { 'client_app_token': app_token, 'uuid': __uuid, 'lang': 'en', 'format': 'json' } res = http.post(hello_url, headers=self.headers, data=params) return res def _login(self, email, password, _hello): self.logger.debug('_login ... Attempting login as {0}'.format(email)) login_url = self.API_LOGIN.format(self.base_url) params = { 'login': email, 'password': password, 'remember': 'true' } res = http.post(login_url, headers=self.headers, data=params, cookies=_hello.cookies) data = http.json(res) self._authed = data['success'] if self._authed: self.logger.debug('New Session Data') self._session_attributes.set('beaker.session.id', res.cookies.get('beaker.session.id'), expires=3600 * 24) self._session_attributes.set('pzuid', res.cookies.get('pzuid'), expires=3600 * 24) self._session_attributes.set('power_guide_hash', data['session']['power_guide_hash'], expires=3600 * 24) return self._authed else: return None def _watch(self): self.logger.debug('_watch ...') match = self._url_re.match(self.url) if not match: self.logger.debug('_watch ... no match') return channel = match.group('channel') vod_id = match.group('vod_id') recording_id = match.group('recording_id') cookies = { 'beaker.session.id': self._session_attributes.get('beaker.session.id'), 'pzuid': self._session_attributes.get('pzuid') } watch_url = [] if channel: params, watch_url = self._watch_live(channel, cookies) elif vod_id: params, watch_url = self._watch_vod(vod_id) elif recording_id: params, watch_url = self._watch_recording(recording_id) if not watch_url: self.logger.debug('Missing watch_url') return res = [] try: res = http.post(watch_url, headers=self.headers, data=params, cookies=cookies) except Exception as e: if '404 Client Error' in str(e): self.logger.error('Unfortunately streaming is not permitted in this country or this channel does not exist.') elif '402 Client Error: Payment Required' in str(e): self.logger.error('Paid subscription required for this channel.') self.logger.info('If paid subscription exist, use --zattoo-purge-credentials to start a new session.') else: self.logger.error(str(e)) return self.logger.debug('Found post data') data = http.json(res) if data['success']: for hls_url in data['stream']['watch_urls']: for s in HLSStream.parse_variant_playlist(self.session, hls_url['url']).items(): yield s def _watch_live(self, channel, cookies): self.logger.debug('_watch_live ... Channel: {0}'.format(channel)) watch_url = self.API_WATCH.format(self.base_url) channels_url = self.API_CHANNELS.format(self.base_url, self._session_attributes.get('power_guide_hash')) res = http.get(channels_url, headers=self.headers, cookies=cookies) data = http.json(res, schema=self._channels_schema) c_list = [] for d in data: for c in d['channels']: c_list.append(c) cid = [] zattoo_list = [] for c in c_list: zattoo_list.append(c['display_alias']) if c['display_alias'] == channel: cid = c['cid'] self.logger.debug('Available zattoo channels in this country: {0}'.format(', '.join(sorted(zattoo_list)))) if not cid: cid = channel self.logger.debug('CHANNEL ID: {0}'.format(cid)) params = { 'cid': cid, 'https_watch_urls': True, 'stream_type': 'hls' } return params, watch_url def _watch_recording(self, recording_id): self.logger.debug('_watch_recording ...') watch_url = self.API_WATCH_REC.format(self.base_url, recording_id) params = { 'https_watch_urls': True, 'stream_type': 'hls' } return params, watch_url def _watch_vod(self, vod_id): self.logger.debug('_watch_vod ...') watch_url = self.API_WATCH_VOD.format(self.base_url, vod_id) params = { 'https_watch_urls': True, 'stream_type': 'hls' } return params, watch_url def _get_streams(self): email = self.get_option('email') password = self.get_option('password') if self.options.get('purge_credentials'): self._session_attributes.set('beaker.session.id', None, expires=0) self._session_attributes.set('expires', None, expires=0) self._session_attributes.set('power_guide_hash', None, expires=0) self._session_attributes.set('pzuid', None, expires=0) self._session_attributes.set('uuid', None, expires=0) self._authed = False self.logger.info('All credentials were successfully removed.') if not self._authed and (not email and not password): self.logger.error('A login for Zattoo is required, use --zattoo-email EMAIL --zattoo-password PASSWORD to set them') return if self._authed: if self._expires < time.time(): # login after 24h expires = time.time() + 3600 * 24 self._session_attributes.set('expires', expires, expires=3600 * 24) self._authed = False if not self._authed: __hello = self._hello() if not self._login(email, password, __hello): self.logger.error('Failed to login, check your username/password') return return self._watch()
"last_update": "", "broken": True, } BALANCER_URL = "http://www.mips.tv:1935/loadbalancer" PLAYER_URL = "http://mips.tv/embedplayer/{0}/1/500/400" SWF_URL = "http://mips.tv/content/scripts/eplayer.swf" _url_re = re.compile(r"http(s)?://(\w+.)?mips.tv/(?P<channel>[^/&?]+)") _flashvars_re = re.compile(r"'FlashVars', '([^']+)'") _rtmp_re = re.compile(r"redirect=(.+)") _schema = validate.Schema( validate.transform(_flashvars_re.search), validate.any( None, validate.all(validate.get(1), validate.transform(parse_query), { "id": validate.transform(int), validate.optional("s"): validate.text }))) _rtmp_schema = validate.Schema( validate.transform(_rtmp_re.search), validate.get(1), ) class Mips(Plugin): @classmethod def can_handle_url(self, url): return _url_re.match(url) @Plugin.broken()