def _channel_from_login(self, channel): cdata = self.api.users(login=channel) if len(cdata["users"]): return cdata["users"][0] else: raise PluginError("Unable to find channel: {0}".format(channel))
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'] yield from DASHStream.parse_manifest(self.session, playlist).items()
def _channel_from_video_id(self, video_id): vdata = self.api.videos(video_id) if "channel" not in vdata: raise PluginError("Unable to find video: {0}".format(video_id)) return vdata["channel"]
def _get_streams(self): login_session_id = self.get_option("sessionid") login_device_token = self.get_option("devicetoken") if self.options.get("purge_credentials"): self.clear_cookies() self._authed = False log.info("All credentials were successfully removed.") if self._authed: log.debug("Attempting to authenticate using cached cookies") elif not self._authed and login_session_id and login_device_token: self._login_using_session_id_and_device_token( login_session_id, login_device_token) streamer_data = self.get_streamer_data() performers = streamer_data.get("performers") log.trace("{0!r}".format(streamer_data)) if performers: co_hosts = [] # create a list of all available performers for p in performers: co_hosts += [(p["user"]["unique_name"], p["user"]["name"])] log.info("Available hosts: {0}".format(", ".join( ["{0} ({1})".format(k, v) for k, v in co_hosts]))) # control if the host from --pixiv-performer is valid, # if not let the User select a different host if (self.get_option("performer") and not self.get_option("performer") in [v[0] for v in co_hosts]): # print the owner as 0 log.info("0 - {0} ({1})".format( streamer_data["owner"]["user"]["unique_name"], streamer_data["owner"]["user"]["name"])) # print all other performer for i, item in enumerate(co_hosts, start=1): log.info("{0} - {1} ({2})".format(i, item[0], item[1])) try: number = int( self.input_ask("Enter the number you'd like to watch"). split(" ")[0]) if number == 0: # default stream self.set_option("performer", None) else: # other co-hosts self.set_option("performer", co_hosts[number - 1][0]) except FatalPluginError: raise PluginError("Selected performer is invalid.") except (IndexError, ValueError, TypeError): raise PluginError("Input is invalid") # ignore the owner stream, if a performer is selected # or use it when there are no other performers if not self.get_option("performer") or not performers: return self.hls_stream(streamer_data["owner"]["hls_movie"]["url"]) # play a co-host stream if performers and self.get_option("performer"): for p in performers: if p["user"]["unique_name"] == self.get_option("performer"): # if someone goes online at the same time as Streamlink # was used, the hls URL might not be in the JSON data hls_movie = p.get("hls_movie") if hls_movie: return self.hls_stream(hls_movie["url"])
def _get_streams(self): match = url_re.match(self.url) stream_page_scheme = 'https' stream_page_domain = match.group(4) stream_page_path = match.group(5) country_code = CONST_DEFAULT_COUNTRY_CODE # create http session and set headers http_session = http http_session.headers.update(CONST_HEADERS) # get cookies r = http_session.get( urlunparse((stream_page_scheme, stream_page_domain, stream_page_path, '', '', ''))) # redirect to profile page means stream is offline if '/profile/' in r.url: print(colored('\n => Performer is OFFLINE <=', 'yellow', 'on_red')) print(colored('\n => END <= ', 'yellow', 'on_blue')) time.sleep(6) sys.exit() if not r.ok: self.logger.debug('Status code for {0}: {1}', r.url, r.status_code) raise NoStreamsError(self.url) if len(http_session.cookies) == 0: raise PluginError("Can't get a cookies") if urlparse(r.url).netloc != stream_page_domain: # then redirected to regional subdomain country_code = urlparse(r.url).netloc.split('.')[0].lower() # time to set variables baseurl = urlunparse( (stream_page_scheme, urlparse(r.url).netloc, '', '', '', '')) amf_gateway_url = urljoin(baseurl, CONST_AMF_GATEWAY_LOCATION) stream_page_url = urljoin(baseurl, stream_page_path) headers = { 'User-Agent': useragents.CHROME, 'Referer': stream_page_url, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' } data = 'method=getRoomData&args%5B%5D={0}&args%5B%5D=false'.format( stream_page_path) self.logger.debug('DATA: {0}'.format(str(data))) # send request and close http-session r = http_session.post(url=amf_gateway_url, headers=headers, params={CONST_AMF_GATEWAY_PARAM: country_code}, data=data) http_session.close() if r.status_code != 200: raise PluginError('unexpected status code for {0}: {1}', r.url, r.status_code) stream_source_info = amf_msg_schema.validate(json.loads(r.text)) self.logger.debug('source stream info:\n{0}', stream_source_info) if not stream_source_info: return performer = stream_source_info['performerData']['username'] real_name = stream_source_info['performerData']['displayName'] performer_id = stream_source_info['performerData']['userId'] print(colored('\n => Performer => {} <=', 'yellow', 'on_blue')).format(real_name) print(colored('\n => Performer ID => {} <=', 'yellow', 'on_blue')).format(performer_id) urlnoproto = stream_source_info['localData']['videoServerUrl'] urlnoproto = update_scheme('https://', urlnoproto) hls_url = '{0}/hls/stream_{1}/playlist.m3u8'.format( urlnoproto, performer) server = re.sub('https://', '', urlnoproto) print(colored('\n => Server => {} <=', 'yellow', 'on_blue')).format(server) if hls_url: try: for s in HLSStream.parse_variant_playlist( self.session, hls_url, headers=headers).items(): timestamp = str(time.strftime('%d%m%Y-%H%M%S')) path = config.get('folders', 'output_folder_BC') fn = real_name + '_BC_' + timestamp + '.flv' pf = (path + fn) ffmpeg = config.get('files', 'ffmpeg') print( colored('\n => FFMPEG-24/7-REC => {} <=', 'yellow', 'on_red')).format(fn) print command = ( '{} -hide_banner -loglevel panic -i {} -c:v copy -c:a aac -b:a 160k {}' .format(ffmpeg, hls_url, pf)) os.system(command) print(colored(' => END <= ', 'yellow', 'on_blue')) sys.exit() except Exception as e: if '404' in str(e): print( colored('\n => Performer is AWAY or PRIVATE <=', 'yellow', 'on_red')) print(colored('\n => END <= ', 'yellow', 'on_blue')) time.sleep(6) sys.exit()
def _channel_from_video_id(self, video_id): try: self._channel_id, self._channel = self.api.channel_from_video_id(video_id) except PluginError: raise PluginError("Unable to find video: {0}".format(video_id))
def _channel_from_login(self, channel): try: self._channel_id = self.api.channel_from_login(channel) except PluginError: raise PluginError("Unable to find channel: {0}".format(channel))
def parse_manifest(cls, session, url, timeout=60, pvswf=None, is_akamai=False, **request_params): """Parses a HDS manifest and returns its substreams. :param url: The URL to the manifest. :param timeout: How long to wait for data to be returned from from the stream before raising an error. :param is_akamai: force adding of the akamai parameters :param pvswf: URL of player SWF for Akamai HD player verification. """ # private argument, should only be used in recursive calls raise_for_drm = request_params.pop("raise_for_drm", False) if not request_params: request_params = {} request_params["headers"] = request_params.get("headers", {}) request_params["params"] = request_params.get("params", {}) # These params are reserved for internal use request_params.pop("exception", None) request_params.pop("stream", None) request_params.pop("timeout", None) request_params.pop("url", None) if "akamaihd" in url or is_akamai: request_params["params"]["hdcore"] = HDCORE_VERSION request_params["params"]["g"] = cls.cache_buster_string(12) res = session.http.get(url, exception=IOError, **request_params) manifest = session.http.xml(res, "manifest XML", ignore_ns=True, exception=IOError) if manifest.findtext("drmAdditionalHeader"): log.debug(f"Omitting HDS stream protected by DRM: {url}") if raise_for_drm: raise PluginError("{} is protected by DRM".format(url)) log.warning("Some or all streams are unavailable as they are protected by DRM") return {} parsed = urlparse(url) baseurl = manifest.findtext("baseURL") baseheight = manifest.findtext("height") bootstraps = {} streams = {} if not baseurl: baseurl = urljoin(url, os.path.dirname(parsed.path)) if not baseurl.endswith("/"): baseurl += "/" for bootstrap in manifest.findall("bootstrapInfo"): name = bootstrap.attrib.get("id") or "_global" url = bootstrap.attrib.get("url") if url: box = absolute_url(baseurl, url) else: data = base64.b64decode(bytes(bootstrap.text, "utf8")) box = Box.deserialize(BytesIO(data)) bootstraps[name] = box pvtoken = manifest.findtext("pv-2.0") if pvtoken: if not pvswf: raise IOError("This manifest requires the 'pvswf' parameter " "to verify the SWF") params = cls._pv_params(session, pvswf, pvtoken, **request_params) request_params["params"].update(params) child_drm = False for media in manifest.findall("media"): url = media.attrib.get("url") bootstrapid = media.attrib.get("bootstrapInfoId", "_global") href = media.attrib.get("href") if url and bootstrapid: bootstrap = bootstraps.get(bootstrapid) if not bootstrap: continue bitrate = media.attrib.get("bitrate") streamid = media.attrib.get("streamId") height = media.attrib.get("height") if height: quality = height + "p" elif bitrate: quality = bitrate + "k" elif streamid: quality = streamid elif baseheight: quality = baseheight + "p" else: quality = "live" metadata = media.findtext("metadata") if metadata: metadata = base64.b64decode(bytes(metadata, "utf8")) metadata = ScriptData.deserialize(BytesIO(metadata)) else: metadata = None stream = HDSStream(session, baseurl, url, bootstrap, metadata=metadata, timeout=timeout, **request_params) streams[quality] = stream elif href: url = absolute_url(baseurl, href) try: child_streams = cls.parse_manifest(session, url, timeout=timeout, is_akamai=is_akamai, raise_for_drm=True, **request_params) except PluginError: child_drm = True child_streams = {} for name, stream in child_streams.items(): # Override stream name if bitrate is available in parent # manifest but not the child one. bitrate = media.attrib.get("bitrate") if bitrate and not re.match(r"^(\d+)k$", name): name = bitrate + "k" streams[name] = stream if child_drm: log.warning("Some or all streams are unavailable as they are protected by DRM") return streams
def streams(self, stream_types=None, sorting_excludes=None): """Attempts to extract available streams. Returns a :class:`dict` containing the streams, where the key is the name of the stream, most commonly the quality and the value is a :class:`Stream` object. The result can contain the synonyms **best** and **worst** which points to the streams which are likely to be of highest and lowest quality respectively. If multiple streams with the same name are found, the order of streams specified in *stream_types* will determine which stream gets to keep the name while the rest will be renamed to "<name>_<stream type>". The synonyms can be fine tuned with the *sorting_excludes* parameter. This can be either of these types: - A list of filter expressions in the format *[operator]<value>*. For example the filter ">480p" will exclude streams ranked higher than "480p" from the list used in the synonyms ranking. Valid operators are >, >=, < and <=. If no operator is specified then equality will be tested. - A function that is passed to filter() with a list of stream names as input. :param stream_types: A list of stream types to return. :param sorting_excludes: Specify which streams to exclude from the best/worst synonyms. .. versionchanged:: 1.4.2 Added *priority* parameter. .. versionchanged:: 1.5.0 Renamed *priority* to *stream_types* and changed behaviour slightly. .. versionchanged:: 1.5.0 Added *sorting_excludes* parameter. .. versionchanged:: 1.6.0 *sorting_excludes* can now be a list of filter expressions or a function that is passed to filter(). """ try: ostreams = self._get_streams() if isinstance(ostreams, dict): ostreams = ostreams.items() # Flatten the iterator to a list so we can reuse it. if ostreams: ostreams = list(ostreams) except NoStreamsError: return {} except (IOError, OSError, ValueError) as err: raise PluginError(err) if not ostreams: return {} if stream_types is None: stream_types = self.default_stream_types(ostreams) # Add streams depending on stream type and priorities sorted_streams = sorted(iterate_streams(ostreams), key=partial(stream_type_priority, stream_types)) streams = {} for name, stream in sorted_streams: stream_type = type(stream).shortname() # Use * as wildcard to match other stream types if "*" not in stream_types and stream_type not in stream_types: continue # drop _alt from any stream names if name.endswith("_alt"): name = name[:-len("_alt")] existing = streams.get(name) if existing: existing_stream_type = type(existing).shortname() if existing_stream_type != stream_type: name = "{0}_{1}".format(name, stream_type) if name in streams: name = "{0}_alt".format(name) num_alts = len( list( filter(lambda n: n.startswith(name), streams.keys()))) # We shouldn't need more than 2 alt streams if num_alts >= 2: continue elif num_alts > 0: name = "{0}{1}".format(name, num_alts + 1) # Validate stream name and discard the stream if it's bad. match = re.match("([A-z0-9_+]+)", name) if match: name = match.group(1) else: self.logger.debug( "The stream '{0}' has been ignored " "since it is badly named.", name) continue # Force lowercase name and replace space with underscore. streams[name.lower()] = stream # Create the best/worst synonmys def stream_weight_only(s): return (self.stream_weight(s)[0] or (len(streams) == 1 and 1)) stream_names = filter(stream_weight_only, streams.keys()) sorted_streams = sorted(stream_names, key=stream_weight_only) if isinstance(sorting_excludes, list): for expr in sorting_excludes: filter_func = stream_sorting_filter(expr, self.stream_weight) sorted_streams = list(filter(filter_func, sorted_streams)) elif callable(sorting_excludes): sorted_streams = list(filter(sorting_excludes, sorted_streams)) final_sorted_streams = OrderedDict() for stream_name in sorted(streams, key=stream_weight_only): final_sorted_streams[stream_name] = streams[stream_name] if len(sorted_streams) > 0: best = sorted_streams[-1] worst = sorted_streams[0] final_sorted_streams["worst"] = streams[worst] final_sorted_streams["best"] = streams[best] return final_sorted_streams
def _get_hls_streams(self, stream_type="live"): log.debug("Getting {0} HLS streams for {1}".format( stream_type, self.channel)) self._authenticate() self._hosted_chain.append(self.channel) if stream_type == "live": if self.options.get("disable_reruns") and self._check_for_rerun(): log.info("Reruns were disabled by command line option") return {} hosted_channel = self._check_for_host() if hosted_channel and self.options.get("disable_hosting"): log.info("hosting was disabled by command line option") elif hosted_channel: log.info("switching to {0}".format(hosted_channel)) if hosted_channel in self._hosted_chain: log.error(u"A loop of hosted channels has been detected, " "cannot find a playable stream. ({0})".format( u" -> ".join(self._hosted_chain + [hosted_channel]))) return {} self.channel = hosted_channel return self._get_hls_streams(stream_type) # only get the token once the channel has been resolved sig, token = self._access_token(stream_type) url = self.usher.channel(self.channel, sig=sig, token=token, fast_bread=True) elif stream_type == "video": sig, token = self._access_token(stream_type) url = self.usher.video(self.video_id, nauthsig=sig, nauth=token) else: log.debug("Unknown HLS stream type: {0}".format(stream_type)) return {} time_offset = self.params.get("t", 0) if time_offset: try: time_offset = hours_minutes_seconds(time_offset) except ValueError: time_offset = 0 try: # If the stream is a VOD that is still being recorded the stream should start at the # beginning of the recording streams = TwitchHLSStream.parse_variant_playlist( self.session, url, start_offset=time_offset, force_restart=not stream_type == "live") except IOError as err: err = str(err) if "404 Client Error" in err or "Failed to parse playlist" in err: return else: raise PluginError(err) try: token = parse_json(token, schema=_token_schema) for name in token["restricted_bitrates"]: if name not in streams: log.warning( "The quality '{0}' is not available since it requires a subscription." .format(name)) except PluginError: pass return streams
def _get_streams(self): match = url_re.match(self.url) stream_page_scheme = 'https' stream_page_domain = match.group(4) stream_page_path = match.group(5) country_code = CONST_DEFAULT_COUNTRY_CODE # create http session and set headers http_session = http http_session.headers.update(CONST_HEADERS) # get cookies r = http_session.get( urlunparse((stream_page_scheme, stream_page_domain, stream_page_path, '', '', ''))) # redirect to profile page means stream is offline if '/profile/' in r.url: raise NoStreamsError(self.url) if not r.ok: self.logger.debug("Status code for {0}: {1}", r.url, r.status_code) raise NoStreamsError(self.url) if len(http_session.cookies) == 0: raise PluginError("Can't get a cookies") if urlparse(r.url).netloc != stream_page_domain: # then redirected to regional subdomain country_code = urlparse(r.url).netloc.split('.')[0].lower() # time to set variables baseurl = urlunparse( (stream_page_scheme, urlparse(r.url).netloc, '', '', '', '')) amf_gateway_url = urljoin(baseurl, CONST_AMF_GATEWAY_LOCATION) stream_page_url = urljoin(baseurl, stream_page_path) headers = { 'User-Agent': useragents.CHROME, 'Referer': stream_page_url, 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' } data = 'method=getRoomData&args%5B%5D={0}&args%5B%5D=false'.format( stream_page_path) self.logger.debug('DATA: {0}'.format(str(data))) # send request and close http-session r = http_session.post(url=amf_gateway_url, headers=headers, params={CONST_AMF_GATEWAY_PARAM: country_code}, data=data) http_session.close() if r.status_code != 200: raise PluginError("unexpected status code for {0}: {1}", r.url, r.status_code) stream_source_info = amf_msg_schema.validate(json.loads(r.text)) self.logger.debug("source stream info:\n{0}", stream_source_info) if not stream_source_info: return urlnoproto = stream_source_info['localData']['videoServerUrl'] urlnoproto = update_scheme('https://', urlnoproto) performer = stream_source_info['performerData']['username'] hls_url = '{0}/hls/stream_{1}/playlist.m3u8'.format( urlnoproto, performer) if hls_url: self.logger.debug('HLS URL: {0}'.format(hls_url)) try: for s in HLSStream.parse_variant_playlist( self.session, hls_url, headers=headers).items(): yield s except Exception as e: if '404' in str(e): self.logger.error('Stream is currently offline or private') else: self.logger.error(str(e)) return
def _get_streams(self): match = url_re.match(self.url) stream_page_scheme = 'https' stream_page_domain = match.group(4) stream_page_path = match.group(5) country_code = CONST_DEFAULT_COUNTRY_CODE is_paid_show = False # create http session and set headers http_session = http http_session.headers.update(CONST_HEADERS) # get swf url and cookies r = http_session.get( urlunparse((stream_page_scheme, stream_page_domain, stream_page_path, '', '', ''))) # redirect to profile page means stream is offline if '/profile/' in r.url: raise NoStreamsError(self.url) if not r.ok: self.logger.debug("Status code for {}: {}", r.url, r.status_code) raise NoStreamsError(self.url) if len(http_session.cookies) == 0: raise PluginError("Can't get a cookies") if urlparse(r.url).netloc != stream_page_domain: # then redirected to regional subdomain country_code = urlparse(r.url).netloc.split('.')[0].lower() # time to set variables baseurl = urlunparse( (stream_page_scheme, urlparse(r.url).netloc, '', '', '', '')) amf_gateway_url = urljoin(baseurl, CONST_AMF_GATEWAY_LOCATION) stream_page_url = urljoin(baseurl, stream_page_path) match = swf_re.search(r.text) if match: swf_url = urljoin(baseurl, match.group()) self.logger.debug("swf url found: {}", swf_url) else: # most likely it means that country/region banned # can try use default swf-url swf_url = urljoin(baseurl, CONST_DEFAULT_SWF_LOCATION) self.logger.debug("swf url not found. Will try {}", swf_url) # create amf query amf_message = AMFMessage("svDirectAmf.getRoomData", "/1", [stream_page_path, is_paid_show]) amf_packet = AMFPacket(version=0) amf_packet.messages.append(amf_message) # send request and close http-session r = http_session.post(url=amf_gateway_url, params={CONST_AMF_GATEWAY_PARAM: country_code}, data=bytes(amf_packet.serialize())) http_session.close() if r.status_code != 200: raise PluginError("unexpected status code for {}: {}", r.url, r.status_code) amf_response = AMFPacket.deserialize(BytesIO(r.content)) if len(amf_response.messages ) != 1 or amf_response.messages[0].target_uri != "/1/onResult": raise PluginError("unexpected response from amf gate") stream_source_info = amf_msg_schema.validate( amf_response.messages[0].value) self.logger.debug("source stream info:\n{}", stream_source_info) stream_params = { "live": True, "realtime": True, "flashVer": CONST_FLASH_VER, "swfUrl": swf_url, "tcUrl": stream_source_info['localData']['NC_ConnUrl'], "rtmp": stream_source_info['localData']['NC_ConnUrl'], "pageUrl": stream_page_url, "playpath": "%s?uid=%s" % (''.join(('stream_', stream_page_path)), self._get_stream_uid(stream_source_info['userData']['username'])), "conn": [ "S:{0}".format(stream_source_info['userData']['username']), "S:{0}".format( stream_source_info['localData']['NC_AccessKey']), "B:0", "S:{0}".format(stream_source_info['localData']['dataKey']) ] } self.logger.debug("Stream params:\n{}", stream_params) stream = RTMPStream(self.session, stream_params) return {'live': stream}
def _get_streams(self): self.session.http.headers.update({'User-Agent': useragents.FIREFOX}) log.debug('Version 2018-07-12') log.info('This is a custom plugin. ') match = self._url_re.match(self.url) username = match.group('username') user_id = match.group('user_id') servers = self._get_servers() chat_servers = servers['chat_servers'] message, php_message = self._websocket_data(username, chat_servers) if user_id and not username: data = self._php_fallback(username, user_id, php_message) else: log.debug('Attempting to use WebSocket data') data = self._dict_re.search(message) if data is None: raise NoStreamsError(self.url) data = parse_json(data.group('data'), schema=self._data_schema) vs = data['vs'] ok_vs = [0, 90] if vs not in ok_vs: if vs == 2: log.info('Model is currently away') elif vs == 12: log.info('Model is currently in a private show') elif vs == 13: log.info('Model is currently in a group show') elif vs == 127: log.info('Model is currently offline') else: log.error('Stream status: {0}'.format(vs)) raise NoStreamsError(self.url) log.debug('VS: {0}'.format(vs)) nm = data['nm'] uid = data['uid'] uid_video = uid + 100000000 camserver = data['u']['camserv'] server, server_type = self._get_camserver(servers, camserver) if server is None and not user_id: fallback_data = self._php_fallback(username, user_id, php_message) camserver = fallback_data['u']['camserv'] server, server_type = self._get_camserver(servers, camserver) log.info('Username: {0}'.format(nm)) log.info('User ID: {0}'.format(uid)) if not server: raise PluginError('Missing video server') log.debug('Video server: {0}'.format(server)) log.debug('Video server_type: {0}'.format(server_type)) if server_type == 'h5video_servers': DASH_VIDEO_URL = 'https://{0}.myfreecams.com/NxServer/ngrp:mfc_{1}.f4v_desktop/manifest.mpd'.format( server, uid_video) HLS_VIDEO_URL = 'https://{0}.myfreecams.com/NxServer/ngrp:mfc_{1}.f4v_mobile/playlist.m3u8'.format( server, uid_video) elif server_type == 'wzobs_servers': DASH_VIDEO_URL = '' HLS_VIDEO_URL = 'https://{0}.myfreecams.com/NxServer/ngrp:mfc_a_{1}.f4v_mobile/playlist.m3u8'.format( server, uid_video) elif server_type == 'ngvideo_servers': raise PluginError('ngvideo_servers are not supported.') else: raise PluginError('Unknow server type.') log.debug('HLS URL: {0}'.format(HLS_VIDEO_URL)) for s in HLSStream.parse_variant_playlist(self.session, HLS_VIDEO_URL).items(): yield s if DASH_VIDEO_URL and self.get_option('dash'): log.debug('DASH URL: {0}'.format(DASH_VIDEO_URL)) for s in DASHStream.parse_manifest(self.session, DASH_VIDEO_URL).items(): yield s
def _get_streams(self): page = http.get(self.url, schema=_schema) if not page: return pubkey_pem = get_public_key(self.cache, urljoin(self.url, page["clientlibs"])) if not pubkey_pem: raise PluginError("Unable to get public key") flashvars = page["flashvars"] params = {"cashPath": int(time.time() * 1000)} res = http.get(urljoin(self.url, flashvars["country"]), params=params) if not res: return language = http.xml(res, schema=_language_schema) api_params = {} for key in ("ss_id", "mv_id", "device_cd", "ss1_prm", "ss2_prm", "ss3_prm"): if flashvars.get(key, ""): api_params[key] = flashvars[key] aeskey = number.long_to_bytes(random.getrandbits(8 * 32), 32) params = { "s": flashvars["s"], "c": language, "e": self.url, "d": aes_encrypt(aeskey, json.dumps(api_params)), "a": rsa_encrypt(pubkey_pem, aeskey) } res = http.get(urljoin(self.url, flashvars["init"]), params=params) if not res: return rtn = http.json(res, schema=_init_schema) if not rtn: return init_data = parse_json(aes_decrypt(aeskey, rtn)) parsed = urlparse(init_data["play_url"]) if parsed.scheme != "https" or not parsed.path.startswith( "/i/") or not parsed.path.endswith("/master.m3u8"): return hlsstream_url = init_data["play_url"] streams = HLSStream.parse_variant_playlist(self.session, hlsstream_url) if "caption_url" in init_data: if self.get_option("mux_subtitles") and FFMPEGMuxer.is_usable( self.session): res = http.get(init_data["caption_url"]) srt = http.xml(res, ignore_ns=True, schema=_xml_to_srt_schema) subfiles = [] metadata = {} for i, lang, srt in ((i, s[0], s[1]) for i, s in enumerate(srt)): subfile = tempfile.TemporaryFile() subfile.write(srt.encode("utf8")) subfile.seek(0) subfiles.append(FileStream(self.session, fileobj=subfile)) metadata["s:s:{0}".format(i)] = [ "language={0}".format(lang) ] for n, s in streams.items(): yield n, MuxedStream(self.session, s, *subfiles, maps=list(range(0, len(metadata) + 1)), metadata=metadata) return else: self.logger.info("Subtitles: {0}".format( init_data["caption_url"])) for s in streams.items(): yield s