def ytdl_fallback(self): '''Basic support for m3u8 URLs with youtube-dl''' log.debug('Fallback youtube-dl') class YTDL_Logger(object): def debug(self, msg): log.debug(msg) def warning(self, msg): log.warning(msg) def error(self, msg): log.trace(msg) ydl_opts = { 'call_home': False, 'forcejson': True, 'logger': YTDL_Logger(), 'no_color': True, 'noplaylist': True, 'no_warnings': True, 'verbose': False, 'quiet': True, } with youtube_dl.YoutubeDL(ydl_opts) as ydl: try: info = ydl.extract_info(self.url, download=False) except Exception: return if not info or not info.get('formats'): return self.title = info['title'] streams = [] for stream in info['formats']: if stream['protocol'] in ['m3u8', 'm3u8_native' ] and stream['ext'] == 'mp4': log.trace('{0!r}'.format(stream)) name = stream.get('height') or stream.get('width') if name: name = '{0}p'.format(name) streams.append((name, HLSStream(self.session, stream['url'], headers=stream['http_headers']))) if not streams: if ('youtube.com' in self.url and info.get('requested_formats') and len(info.get('requested_formats')) == 2 and MuxedStream.is_usable(self.session)): audio_url = audio_format = video_url = video_format = video_name = None for stream in info.get('requested_formats'): if not stream.get('height'): audio_url = stream.get('url') audio_format = stream.get('format_id') if stream.get('height'): video_url = stream.get('url') video_format = stream.get('format_id') video_name = '{0}p'.format(stream.get('height')) log.debug('MuxedStream: v {video} a {audio} = {name}'.format( audio=audio_format, name=video_name, video=video_format, )) streams.append( (video_name, MuxedStream( self.session, HTTPStream(self.session, video_url, headers=stream['http_headers']), HTTPStream(self.session, audio_url, headers=stream['http_headers'])))) return streams
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 'pluzz.francetv.fr' in self.url: video_re = self._pluzz_video_id_re else: video_re = self._other_video_id_re match = video_re.search(res.text) if match is None: return catalogue = 'Pluzz' video_id = match.group('video_id') if 'catalogue' in match.groupdict(): catalogue = match.group('catalogue') # Retrieve SWF player URL match = self._player_re.search(res.text) swf_url = None if match is not None: player_url = 'http:' + match.group('player') res = http.get(player_url) match = self._swf_re.search(res.text) if match is not None: swf_url = os.path.dirname(player_url) + match.group('swf') res = http.get(self.API_URL.format(video_id, catalogue)) 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 catalogue == 'Pluzz' or '.f4m' in video_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, 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')
def _get_streams(self): data = None m = self._re_url.match(self.url).groupdict() if m['slug_live']: res = self.session.http.get('https://api.pluto.tv/v2/channels') data = self.session.http.json(res, schema=self._schema_media( m['slug_live'])) elif m['slug_series'] and m['slug_episode']: res = self.session.http.get( f'http://api.pluto.tv/v3/vod/slugs/{m["slug_series"]}') data = self.session.http.json( res, schema=validate.Schema( { 'seasons': validate.all([{ 'episodes': self._schema_media(m['slug_episode']) }], validate.filter( lambda k: k['episodes'] is not None)) }, validate.get('seasons'), validate.get(0), validate.any(None, validate.get('episodes'))), ) elif m['slug_movies']: res = self.session.http.get( 'https://api.pluto.tv/v3/vod/categories', params={ 'includeItems': 'true', 'deviceType': 'web' }) data = self.session.http.json( res, schema=validate.Schema( { 'categories': validate.all([{ 'items': self._schema_media(m['slug_movies']) }], validate.filter(lambda k: k['items'] is not None)) }, validate.get('categories'), validate.get(0), validate.any(None, validate.get('items')), ), ) log.trace(f'{data!r}') if data is None or not data.get('stitched'): return self.title = data['name'] stream_url_no_sid = data['stitched']['urls'][0]['url'] device_id = str(uuid4()) stream_url = update_qsd( stream_url_no_sid, { 'deviceId': device_id, 'sid': device_id, 'deviceType': 'web', 'deviceMake': 'Firefox', 'deviceModel': 'Firefox', 'appName': 'web', }) self.session.set_option('ffmpeg-fout', 'mpegts') for q, s in HLSStream.parse_variant_playlist(self.session, stream_url).items(): yield q, MuxedStream(self.session, s)
def _get_streams(self): # Retrieve geolocation data res = self.session.http.get(self.GEO_URL) geo = self.session.http.json(res, schema=self._geo_schema) country_code = geo['reponse']['geo_info']['country_code'] log.debug('Country: {0}'.format(country_code)) # Retrieve URL page and search for video ID res = self.session.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 'sport.francetvinfo.fr' in self.url: match = self._sport_video_id_re.search(res.text) else: match = self._embed_video_id_re.search(res.text) if match is None: return video_id = match.group('video_id') log.debug('Video ID: {0}'.format(video_id)) res = self.session.http.get(self.API_URL.format(video_id)) videos = self.session.http.json(res, schema=self._api_schema) now = time.time() offline = False geolocked = False drm = False expired = False streams = [] for video in videos['videos']: log.trace('{0!r}'.format(video)) 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 res = self.session.http.get(self.TOKEN_URL.format(video_url)) video_url = res.text if '.mpd' in video_url: # Get redirect video URL res = self.session.http.get(res.text) video_url = res.url for bitrate, stream in DASHStream.parse_manifest(self.session, video_url).items(): streams.append((bitrate, stream)) elif '.f4m' in video_url: for bitrate, stream in HDSStream.parse_manifest(self.session, video_url, is_akamai=True, pvswf=self.SWF_PLAYER_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: log.error('Failed to access stream, may be due to offline content') if geolocked: log.error('Failed to access stream, may be due to geo-restricted content') if drm: log.error('Failed to access stream, may be due to DRM-protected content') if expired: log.error('Failed to access stream, may be due to expired content')
def _get_streams(self): info = self._get_stream_info(self.url) if not info: return formats = info.get("fmt_list") streams = {} protected = False for stream_info in info.get("url_encoded_fmt_stream_map", []): if stream_info.get("s"): protected = True continue stream = HTTPStream(self.session, stream_info["url"]) name = formats.get(stream_info["itag"]) or stream_info["quality"] if stream_info.get("stereo3d"): name += "_3d" streams[name] = stream adaptive_streams = {} best_audio_itag = None # Extract audio streams from the DASH format list for stream_info in info.get("adaptive_fmts", []): if stream_info.get("s"): protected = True continue stream_params = dict(parse_qsl(stream_info["url"])) if "itag" not in stream_params: continue itag = int(stream_params["itag"]) # extract any high quality streams only available in adaptive formats adaptive_streams[itag] = stream_info["url"] stream_type, stream_format = stream_info["type"] if stream_type == "audio": stream = HTTPStream(self.session, stream_info["url"]) name = "audio_{0}".format(stream_format) streams[name] = stream # find the best quality audio stream m4a, opus or vorbis if best_audio_itag is None or self.adp_audio[ itag] > self.adp_audio[best_audio_itag]: best_audio_itag = itag if best_audio_itag and adaptive_streams and MuxedStream.is_usable( self.session): aurl = adaptive_streams[best_audio_itag] for itag, name in self.adp_video.items(): if itag in adaptive_streams: vurl = adaptive_streams[itag] streams[name] = MuxedStream(self.session, HTTPStream(self.session, vurl), HTTPStream(self.session, aurl)) hls_playlist = info.get("hlsvp") if hls_playlist: try: hls_streams = HLSStream.parse_variant_playlist( self.session, hls_playlist, headers=HLS_HEADERS, namekey="pixels") streams.update(hls_streams) except IOError as err: self.logger.warning("Failed to extract HLS streams: {0}", err) if not streams and protected: raise PluginError("This plugin does not support protected videos, " "try youtube-dl instead") return streams
def _get_streams(self): self.session.http.headers = {"User-Agent": useragents.CHROME} res = self.session.http.get(self.url) # remap en to english, and ja to japanese rlanguage = { "en": "english", "ja": "japanese" }.get( self.get_option("language").lower(), self.get_option("language").lower()) if "_Incapsula_Resource" in res.text: log.error("This page is protected by Incapsula, please see " "https://github.com/streamlink/streamlink/issues/2088" " for a workaround.") return if "Out of Territory" in res.text: log.error( "The content requested is not available in your territory.") return id_m = self.experience_id_re.search(res.text) experience_id = id_m and int(id_m.group(1)) if experience_id: log.debug(f"Found experience ID: {experience_id}") exp = Experience(self.session, experience_id) if self.get_option("email") and self.get_option("password"): if exp.login(self.get_option("email"), self.get_option("password")): log.info( f"Logged in to Funimation as {self.get_option('email')}" ) else: log.warning("Failed to login") if exp.episode_info: log.debug(f"Found episode: {exp.episode_info['episodeTitle']}") log.debug( f" has languages: {', '.join(exp.episode_info['languages'].keys())}" ) log.debug(f" requested language: {rlanguage}") log.debug(f" current language: {exp.language}") if rlanguage != exp.language: log.debug(f"switching language to: {rlanguage}") exp.set_language(rlanguage) if exp.language != rlanguage: log.warning( f"Requested language {rlanguage} is not available, continuing with {exp.language}" ) else: log.debug(f"New experience ID: {exp.experience_id}") subtitles = None stream_metadata = {} disposition = {} for subtitle in exp.subtitles(): log.debug(f"Subtitles: {subtitle['src']}") if subtitle["src"].endswith( ".vtt") or subtitle["src"].endswith(".srt"): sub_lang = { "en": "eng", "ja": "jpn" }[subtitle["language"]] # pick the first suitable subtitle stream subtitles = subtitles or HTTPStream( self.session, subtitle["src"]) stream_metadata["s:s:0"] = [ "language={0}".format(sub_lang) ] stream_metadata["s:a:0"] = [ "language={0}".format(exp.language_code) ] sources = exp.sources() if 'errors' in sources: for error in sources['errors']: log.error("{0} : {1}".format(error['title'], error['detail'])) return for item in sources["items"]: url = item["src"] if ".m3u8" in url: for q, s in HLSStream.parse_variant_playlist( self.session, url).items(): if self.get_option("mux_subtitles") and subtitles: yield q, MuxedStream(self.session, s, subtitles, metadata=stream_metadata, disposition=disposition) else: yield q, s elif ".mp4" in url: # TODO: fix quality s = HTTPStream(self.session, url) if self.get_option("mux_subtitles") and subtitles: yield self.mp4_quality, MuxedStream( self.session, s, subtitles, metadata=stream_metadata, disposition=disposition) else: yield self.mp4_quality, s else: log.error("Could not find experience ID?!")
def _create_adaptive_streams(self, info, streams): if not MuxedStream.is_usable(self.session): log.info("Cannot use FFMPEG") return streams adaptive_streams = {} best_audio_itag = None adp_video = self.adp_video_h264.copy() vp9 = "vp9" if hostname() in self.stb_vp9_1 or hostname( ) in self.stb_vp9_2 else "" if not vp9: log.debug("STB w/o vp9 4K support detected") if self.get_option("yes-vp9-codecs"): vp9 = "vp9" elif self.get_option("no-vp9-codecs"): vp9 = "" log.info("VP9 Codecs are skipped") if vp9: adp_video.update(self.adp_video_vp9) if self.get_option( "yes-vp9-hdr-codecs") or hostname() in self.stb_vp9_2: adp_video.update(self.adp_video_vp9_hdr) # Extract streams from the DASH format list for stream_info in info.get("formats", []): itag = int(stream_info["format_id"]) if itag not in self.adp_audio and itag not in adp_video: log.debug( "Skipped format:{}, Codec:{}", stream_info["format"], stream_info["acodec"] if stream_info["acodec"] != "none" else stream_info["vcodec"], ) continue # extract any high quality streams only available in adaptive formats and not skipped adaptive_streams[itag] = stream_info["url"] stream_format = stream_info["ext"] if itag in self.adp_audio: if self.get_option( "no-opus-codec") and stream_info["acodec"] == "opus": log.debug("Skipped format:{}, Codec:{}", stream_info["format"], stream_info["acodec"]) continue stream = HTTPStream(self.session, stream_info["url"]) name = "audio_{0}".format(stream_format) streams[name] = stream # find the best quality audio stream m4a, opus or vorbis if best_audio_itag is None or self.adp_audio[ itag] > self.adp_audio[best_audio_itag]: best_audio_itag = itag if best_audio_itag and adaptive_streams and MuxedStream.is_usable( self.session): aurl = adaptive_streams[best_audio_itag] for itag, name in adp_video.items(): if itag in adaptive_streams: vurl = adaptive_streams[itag] log.debug( "MuxedStream: v {video} a {audio} = {name}".format( audio=best_audio_itag, name=name, video=itag, )) streams[name] = MuxedStream(self.session, HTTPStream(self.session, vurl), HTTPStream(self.session, aurl)) return streams
def _get_streams(self): http.headers = {"User-Agent": useragents.CHROME} res = http.get(self.url) rlanguage = self.get_option("language") id_m = self.experience_id_re.search(res.text) experience_id = id_m and int(id_m.group(1)) if experience_id: self.logger.debug("Found experience ID: {0}", experience_id) exp = Experience(experience_id) self.logger.debug("Found episode: {0}", exp.episode_info["episodeTitle"]) self.logger.debug(" has languages: {0}", ", ".join(exp.episode_info["languages"].keys())) self.logger.debug(" requested language: {0}", rlanguage) self.logger.debug(" current language: {0}", exp.language) if rlanguage != exp.language: self.logger.debug("switching language to: {0}", rlanguage) exp.set_language(rlanguage) if exp.language != rlanguage: self.logger.warning( "Requested language {0} is not available, continuing with {1}", rlanguage, exp.language) else: self.logger.debug("New experience ID: {0}", exp.experience_id) subtitles = None stream_metadata = {} disposition = {} for subtitle in exp.subtitles(): self.logger.info("Subtitles: {0}", subtitle["src"]) if subtitle["src"].endswith( ".vtt") or subtitle["src"].endswith(".srt"): sub_lang = {"en": "eng", "ja": "jpn"}[subtitle["language"]] # pick the first suitable subtitle stream subtitles = subtitles or HTTPStream( self.session, subtitle["src"]) stream_metadata["s:s:0"] = [ "language={0}".format(sub_lang) ] stream_metadata["s:a:0"] = [ "language={0}".format(exp.language_code) ] for item in exp.sources()["items"]: url = item["src"] if ".m3u8" in url: for q, s in HLSStream.parse_variant_playlist( self.session, url).items(): if self.get_option("mux_subtitles") and subtitles: yield q, MuxedStream(self.session, s, subtitles, metadata=stream_metadata, disposition=disposition) else: yield q, s elif ".mp4" in url: # TODO: fix quality s = HTTPStream(self.session, url) if self.get_option("mux_subtitles") and subtitles: yield self.mp4_quality, MuxedStream( self.session, s, subtitles, metadata=stream_metadata, disposition=disposition) else: yield self.mp4_quality, s else: self.logger.error("Could not find experience ID?!")
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
def _get_streams(self): self.id = self.session.http.get( self.url, schema=validate.Schema( validate.transform( re.compile(r"\bdata-setup='({.+?})'", re.DOTALL).search), validate.any( None, validate.all( validate.get(1), validate.parse_json(), { "idAsset": validate.any( int, validate.all(str, validate.transform(int))), }, validate.get("idAsset"))), )) if not self.id: return urls = self.session.http.get( self.URL_VIDEOS.format(id=self.id), schema=validate.Schema( validate.transform(ZTNR.translate), validate.transform(list), [(str, validate.url())], ), ) url = next( (url for _, url in urls if urlparse(url).path.endswith(".m3u8")), None) if not url: url = next( (url for _, url in urls if urlparse(url).path.endswith(".mp4")), None) if url: yield "vod", HTTPStream(self.session, url) return streams = HLSStream.parse_variant_playlist(self.session, url).items() if self.options.get("mux-subtitles"): subs = self.session.http.get( self.URL_SUBTITLES.format(id=self.id), schema=validate.Schema( validate.parse_json(), { "page": { "items": [{ "lang": str, "src": validate.url(), }] } }, validate.get(("page", "items")), ), ) if subs: subtitles = { s["lang"]: HTTPStream(self.session, update_scheme("https://", s["src"], force=True)) for s in subs } for quality, stream in streams: yield quality, MuxedStream(self.session, stream, subtitles=subtitles) return yield from streams
def _get_streams(self): http.headers = {"User-Agent": useragents.CHROME} res = http.get(self.url) # remap en to english, and ja to japanese rlanguage = { "en": "english", "ja": "japanese" }.get( self.get_option("language").lower(), self.get_option("language").lower()) if "_Incapsula_Resource" in res.text: self.bypass_incapsula(res) res = http.get(self.url) id_m = self.experience_id_re.search(res.text) experience_id = id_m and int(id_m.group(1)) if experience_id: log.debug("Found experience ID: {0}", experience_id) exp = Experience(experience_id) if self.get_option("email") and self.get_option("password"): if exp.login(self.get_option("email"), self.get_option("password")): log.info("Logged in to Funimation as {0}", self.get_option("email")) else: log.warning("Failed to login") log.debug("Found episode: {0}", exp.episode_info["episodeTitle"]) log.debug(" has languages: {0}", ", ".join(exp.episode_info["languages"].keys())) log.debug(" requested language: {0}", rlanguage) log.debug(" current language: {0}", exp.language) if rlanguage != exp.language: log.debug("switching language to: {0}", rlanguage) exp.set_language(rlanguage) if exp.language != rlanguage: log.warning( "Requested language {0} is not available, continuing with {1}", rlanguage, exp.language) else: log.debug("New experience ID: {0}", exp.experience_id) subtitles = None stream_metadata = {} disposition = {} for subtitle in exp.subtitles(): log.debug("Subtitles: {0}", subtitle["src"]) if subtitle["src"].endswith( ".vtt") or subtitle["src"].endswith(".srt"): sub_lang = {"en": "eng", "ja": "jpn"}[subtitle["language"]] # pick the first suitable subtitle stream subtitles = subtitles or HTTPStream( self.session, subtitle["src"]) stream_metadata["s:s:0"] = [ "language={0}".format(sub_lang) ] stream_metadata["s:a:0"] = [ "language={0}".format(exp.language_code) ] sources = exp.sources() if 'errors' in sources: for error in sources['errors']: log.error("{0} : {1}".format(error['title'], error['detail'])) return for item in sources["items"]: url = item["src"] if ".m3u8" in url: for q, s in HLSStream.parse_variant_playlist( self.session, url).items(): if self.get_option("mux_subtitles") and subtitles: yield q, MuxedStream(self.session, s, subtitles, metadata=stream_metadata, disposition=disposition) else: yield q, s elif ".mp4" in url: # TODO: fix quality s = HTTPStream(self.session, url) if self.get_option("mux_subtitles") and subtitles: yield self.mp4_quality, MuxedStream( self.session, s, subtitles, metadata=stream_metadata, disposition=disposition) else: yield self.mp4_quality, s else: log.error("Could not find experience ID?!")