class WebTV(Plugin): _url_re = re.compile(r"http(?:s)?://(\w+)\.web.tv/?") _sources_re = re.compile(r'"sources": (\[.*?\]),', re.DOTALL) _sources_schema = validate.Schema([ { "src": validate.any( validate.contains("m3u8"), validate.all( validate.text, validate.transform(lambda x: WebTV.decrypt_stream_url(x)), validate.contains("m3u8") ) ), "type": validate.text, "label": validate.text } ]) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None @staticmethod def decrypt_stream_url(encoded_url): data = base64.b64decode(encoded_url) cipher_text = binascii.unhexlify(data[96:]) decryptor = AES.new(binascii.unhexlify(data[32:96]), AES.MODE_CBC, binascii.unhexlify(data[:32])) return unpad_pkcs5(decryptor.decrypt(cipher_text)).decode("utf8") def _get_streams(self): """ Find the streams for web.tv :return: """ headers = {} res = self.session.http.get(self.url, headers=headers) headers["Referer"] = self.url sources = self._sources_re.findall(res.text) if len(sources): sdata = parse_json(sources[0], schema=self._sources_schema) for source in sdata: log.debug(f"Found stream of type: {source['type']}") if source["type"] == "application/vnd.apple.mpegurl": url = update_scheme(self.url, source["src"]) try: # try to parse the stream as a variant playlist variant = HLSStream.parse_variant_playlist(self.session, url, headers=headers) if variant: yield from variant.items() else: # and if that fails, try it as a plain HLS stream yield 'live', HLSStream(self.session, url, headers=headers) except IOError: log.warning("Could not open the stream, perhaps the channel is offline")
def test_failure_schema(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.contains("invalid"), 1) assert_validationerror( cm.value, """ ValidationError(type): Type of 1 should be str, but is int """)
def test_failure(self): with pytest.raises(validate.ValidationError) as cm: validate.validate(validate.contains("invalid"), "foo bar baz") assert_validationerror( cm.value, """ ValidationError(contains): 'foo bar baz' does not contain 'invalid' """)
class OlympicChannel(Plugin): _url_re = re.compile( r"https?://(\w+\.)olympicchannel.com/../(?P<type>live|video|original-series|films)/?(?:\w?|[-\w]+)" ) _tokenizationApiDomainUrl = """"tokenizationApiDomainUrl" content="/OcsTokenization/api/v1/tokenizedUrl">""" _live_api_path = "/OcsTokenization/api/v1/tokenizedUrl?url={url}&domain={netloc}&_ts={time}" _api_schema = validate.Schema(validate.text, validate.transform(lambda v: json.loads(v)), validate.url()) _video_url_re = re.compile( r""""video_url"\scontent\s*=\s*"(?P<value>[^"]+)""") _video_url_schema = validate.Schema( validate.contains(_tokenizationApiDomainUrl), validate.transform(_video_url_re.search), validate.any(None, validate.get("value")), validate.url()) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) def _get_vod_streams(self): stream_url = self.session.http.get(self.url, schema=self._video_url_schema) return HLSStream.parse_variant_playlist(self.session, stream_url) def _get_live_streams(self): video_url = self.session.http.get(self.url, schema=self._video_url_schema) parsed = urlparse(video_url) api_url = urljoin( self.url, self._live_api_path.format(url=video_url, netloc="{0}://{1}".format( parsed.scheme, parsed.netloc), time=int(time()))) stream_url = self.session.http.get(api_url, schema=self._api_schema) return HLSStream.parse_variant_playlist(self.session, stream_url) def _get_streams(self): match = self._url_re.match(self.url) type_of_stream = match.group('type') if type_of_stream == 'live': return self._get_live_streams() elif type_of_stream in ('video', 'original-series', 'films'): return self._get_vod_streams()
class CeskatelevizeAPI2(object): _player_api = 'https://playlist.ceskatelevize.cz/' _url_re = re.compile(r'http(s)?://([^.]*.)?ceskatelevize.cz') _playlist_info_re = re.compile( r'{\s*"type":\s*"([a-z]+)",\s*"id":\s*"(\w+)"') _playlist_schema = validate.Schema({ "CODE": validate.contains("OK"), "RESULT": { "playlist": [{ "streamUrls": { "main": validate.url(), } }] } }) _ctcomp_re = re.compile( r'data-ctcomp="Video"\sdata-video-id="(?P<val1>[^"]*)"\sdata-ctcomp-data="(?P<val2>[^"]+)">' ) _ctcomp_schema = validate.Schema( validate.text, validate.transform(_ctcomp_re.findall), validate.transform( lambda vl: [{ "video-id": v[0], "ctcomp-data": json.loads(html_unescape(v[1])) } for v in vl])) _playlist_info_schema = validate.Schema({ "type": validate.text, "id": validate.any(validate.text, int), "key": validate.text, "date": validate.text, "requestSource": validate.text, "drm": int, validate.optional("canBePlay"): int, validate.optional("assetId"): validate.text, "quality": validate.text, validate.optional("region"): int }) def __init__(self, session, url, res=None): self.session = session self.url = url self.response = res def _get_streams(self): if self.response is None: infos = self.session.http.get(self.url, schema=self._ctcomp_schema) else: infos = self.session.http.json(self.response, schema=self._ctcomp_schema) if not infos: # playlist infos not found raise PluginError('Cannot find playlist infos!') vod_prio = len(infos) == 2 for info in infos: try: pl = info['ctcomp-data']['source']['playlist'][0] except KeyError: raise PluginError('Cannot find playlist info!') pl = self._playlist_info_schema.validate(pl) if vod_prio and pl['type'] != 'VOD': continue log.trace('{0!r}'.format(info)) if pl['type'] == 'LIVE': data = { "contentType": "live", "items": [{ "id": pl["id"], "assetId": pl["assetId"], "key": pl["key"], "playerType": "dash", "date": pl["date"], "requestSource": pl["requestSource"], "drm": pl["drm"], "quality": pl["quality"], }] } elif pl['type'] == 'VOD': data = { "contentType": "vod", "items": [{ "id": pl["id"], "key": pl["key"], "playerType": "dash", "date": pl["date"], "requestSource": pl["requestSource"], "drm": pl["drm"], "canBePlay": pl["canBePlay"], "quality": pl["quality"], "region": pl["region"] }] } headers = { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", } data = json.dumps(data) response = self.session.http.post(self._player_api, data="data={}".format(quote(data)), headers=headers) json_data = self.session.http.json(response, schema=self._playlist_schema) log.trace('{0!r}'.format(json_data)) playlist = json_data['RESULT']['playlist'][0]['streamUrls']['main'] for s in DASHStream.parse_manifest(self.session, playlist).items(): yield s
class LivespottingTV(Plugin): _url_re = re.compile( r""" (?: https?://livespotting\.tv/ (?:locations\?id=)? (?:[^/].+/)? )(\w+) """, re.VERBOSE) _player_re = re.compile( r"player_id:\s*'(\w+)',\s*livesource_id:\s*'(\w+)'") _URL_PLAYER_CONFIG = "https://player.livespotting.com/v1/config/{player_id}.json" _URL_PLAYER_SHOWROOM = "https://player.livespotting.com/v2/livesource/{livesource_id}?type=showroom" _playlist_schema = validate.Schema({ "id": validate.text, "playlist": validate.url(scheme="http"), "playlist_mode": validate.text, "weather_live_enable": bool, }) _sources_schema = validate.Schema([{ validate.optional("mediaid"): validate.text, validate.optional("title"): validate.text, validate.optional("livestream"): validate.all(validate.url(scheme="http"), validate.contains(".m3u8")), "sources": [{ "file": validate.all(validate.url(scheme="http"), validate.contains(".m3u8")) }], }]) _livesource_schema = validate.Schema({ "id": validate.text, "source": validate.all(validate.url(scheme="http"), validate.contains(".m3u8")), }) @classmethod def can_handle_url(cls, url): return cls._url_re.match(url) is not None def _get_streams(self): source_id = self._url_re.search(self.url).group(1) res = self.session.http.get(self.url) m = self._player_re.search(res.text) if m: _player_id, _source_id = m.groups() if _source_id == source_id: config_url = self._URL_PLAYER_CONFIG.format( player_id=_player_id) log.debug("config_url: {0}".format(config_url)) res = self.session.http.get(config_url) res = self.session.http.json(res, schema=self._playlist_schema) log.debug("playlist_mode: {0}".format(res["playlist_mode"])) log.debug("weather_live_enable: {0}".format( res["weather_live_enable"])) if res["playlist_mode"] == "showroom": playlist_url = self._URL_PLAYER_SHOWROOM.format( livesource_id=_source_id) _schema = self._livesource_schema else: playlist_url = res["playlist"] _schema = self._sources_schema log.debug("playlist_url: {0}".format(playlist_url)) res = self.session.http.get(playlist_url) res = self.session.http.json(res, schema=_schema) log.trace("sources: {0!r}".format(res)) for source in res if isinstance(res, list) else [res]: _id = source.get("mediaid") or _source_id if _id == _source_id: title = source.get("title", "N/A") log.debug("title: {0}".format(title)) if source.get("livestream"): for s in HLSStream.parse_variant_playlist( self.session, source["livestream"]).items(): yield s else: log.debug( "No 'livestream' source found, trying alt. sources" ) sources = source.get("sources") or [{ "file": source["source"] }] for file in sources: for s in HLSStream.parse_variant_playlist( self.session, file["file"]).items(): yield s
def test_success(self): assert validate.validate(validate.contains("bar"), "foo bar baz")