def reload_playlist(self): if self.closed: return self.reader.buffer.wait_free() log.debug("Reloading playlist") res = self.session.http.get(self.stream.url, exception=StreamError, retries=self.playlist_reload_retries, **self.reader.request_params) try: playlist = self._reload_playlist(res.text, res.url) except ValueError as err: raise StreamError(err) if playlist.is_master: raise StreamError("Attempted to play a variant playlist, use " "'hls://{0}' instead".format(self.stream.url)) if playlist.iframes_only: raise StreamError("Streams containing I-frames only is not playable") media_sequence = playlist.media_sequence or 0 sequences = [Sequence(media_sequence + i, s) for i, s in enumerate(playlist.segments)] self.playlist_reload_time = self._playlist_reload_time(playlist, sequences) if sequences: self.process_sequences(playlist, sequences)
def create_decryptor(self, key: Key, num: int) -> AES: if key.method != "AES-128": raise StreamError(f"Unable to decrypt cipher {key.method}") if not self.key_uri_override and not key.uri: raise StreamError("Missing URI to decryption key") if self.key_uri_override: p = urlparse(key.uri) formatter = Formatter({ "url": lambda: key.uri, "scheme": lambda: p.scheme, "netloc": lambda: p.netloc, "path": lambda: p.path, "query": lambda: p.query, }) key_uri = formatter.format(self.key_uri_override) else: key_uri = key.uri if self.key_uri != key_uri: res = self.session.http.get(key_uri, exception=StreamError, retries=self.retries, **self.reader.request_params) res.encoding = "binary/octet-stream" self.key_data = res.content self.key_uri = key_uri iv = key.iv or self.num_to_iv(num) # Pad IV if needed iv = b"\x00" * (16 - len(iv)) + iv return AES.new(self.key_data, AES.MODE_CBC, iv)
def create_decryptor(self, key, sequence): if key.method != "AES-128": raise StreamError("Unable to decrypt cipher {0}", key.method) if not key.uri: raise StreamError("Missing URI to decryption key") if self.key_uri != key.uri: custom_uri = self.reader.stream.session.options.get("custom-uri") if custom_uri: uri = custom_uri else: uri = key.uri res = self.session.http.get(uri, exception=StreamError, retries=self.retries, **self.reader.request_params) self.key_data = res.content self.key_uri = key.uri iv = key.iv or num_to_iv(sequence) # Pad IV if needed iv = b"\x00" * (16 - len(iv)) + iv if _android_ssl: return enc(self.key_data, iv) elif _oscrypto: return AES(self.key_data, iv) else: return AES.new(self.key_data, AES.MODE_CBC, iv)
def reload_playlist(self): if self.closed: # pragma: no cover return self.reader.buffer.wait_free() log.debug("Reloading playlist") res = self._fetch_playlist() try: playlist = self._reload_playlist(res) except ValueError as err: raise StreamError(err) if playlist.is_master: raise StreamError( f"Attempted to play a variant playlist, use 'hls://{self.stream.url}' instead" ) if playlist.iframes_only: raise StreamError( "Streams containing I-frames only are not playable") media_sequence = playlist.media_sequence or 0 sequences = [ Sequence(media_sequence + i, s) for i, s in enumerate(playlist.segments) ] self.playlist_reload_time = self._playlist_reload_time( playlist, sequences) if sequences: self.process_sequences(playlist, sequences)
def create_decryptor(self, key, sequence): if key.method != "AES-128": raise StreamError("Unable to decrypt cipher {0}", key.method) if not self.key_uri_override and not key.uri: raise StreamError("Missing URI to decryption key") if self.key_uri_override: p = urlparse(key.uri) key_uri = LazyFormatter.format( self.key_uri_override, url=key.uri, scheme=p.scheme, netloc=p.netloc, path=p.path, query=p.query, ) else: key_uri = key.uri if self.key_uri != key_uri: res = self.session.http.get(key_uri, exception=StreamError, retries=self.retries, **self.reader.request_params) res.encoding = "binary/octet-stream" self.key_data = res.content self.key_uri = key_uri iv = key.iv or num_to_iv(sequence) # Pad IV if needed iv = b"\x00" * (16 - len(iv)) + iv return AES.new(self.key_data, AES.MODE_CBC, iv)
def _create_video_clip(self, chunks, start_offset, stop_offset): playlist_duration = stop_offset - start_offset playlist_offset = 0 playlist_streams = [] playlist_tags = [] for chunk in chunks: chunk_url = chunk["url"] chunk_length = chunk["length"] chunk_start = playlist_offset chunk_stop = chunk_start + chunk_length chunk_stream = HTTPStream(self.session, chunk_url) if chunk_start <= start_offset <= chunk_stop: try: headers = extract_flv_header_tags(chunk_stream) except IOError as err: raise StreamError("Error while parsing FLV: {0}", err) if not headers.metadata: raise StreamError("Missing metadata tag in the first chunk") metadata = headers.metadata.data.value keyframes = metadata.get("keyframes") if not keyframes: if chunk["upkeep"] == "fail": raise StreamError("Unable to seek into muted chunk, try another timestamp") else: raise StreamError("Missing keyframes info in the first chunk") keyframe_offset = None keyframe_offsets = keyframes.get("filepositions") keyframe_times = [playlist_offset + t for t in keyframes.get("times")] for time, offset in zip(keyframe_times, keyframe_offsets): if time > start_offset: break keyframe_offset = offset if keyframe_offset is None: raise StreamError("Unable to find a keyframe to seek to " "in the first chunk") chunk_headers = dict(Range="bytes={0}-".format(int(keyframe_offset))) chunk_stream = HTTPStream(self.session, chunk_url, headers=chunk_headers) playlist_streams.append(chunk_stream) for tag in headers: playlist_tags.append(tag) elif start_offset <= chunk_start < stop_offset: playlist_streams.append(chunk_stream) playlist_offset += chunk_length return FLVPlaylist(self.session, playlist_streams, tags=playlist_tags, duration=playlist_duration)
def _check_cmd(self): if not self.cmd: raise StreamError("`cmd' attribute not set") cmd = which(self.cmd) if not cmd: raise StreamError("Unable to find `{0}' command".format(self.cmd)) return cmd
def reload_playlist(self): if self.closed: return self.reader.buffer.wait_free() log.debug("Reloading playlist") if self.stream.channel: parsed = urlparse(self.stream.url) if self.stream._first_netloc is None: # save the first netloc self.stream._first_netloc = parsed.netloc # always use the first saved netloc new_stream_url = parsed._replace( netloc=self.stream._first_netloc).geturl() else: new_stream_url = self.stream.url try: res = self.session.http.get(new_stream_url, exception=StreamError, retries=self.playlist_reload_retries, **self.reader.request_params) except StreamError as err: if (hasattr(self.stream, "watch_timeout") and any( x in str(err) for x in ("403 Client Error", "502 Server Error"))): self.stream.watch_timeout = 0 self.playlist_reload_time = 0 log.debug( f"Force reloading the channel playlist on error: {err}") return raise err try: playlist = hls_playlist.load(res.text, res.url) except ValueError as err: raise StreamError(err) if playlist.is_master: raise StreamError("Attempted to play a variant playlist, use " "'hls://{0}' instead".format(self.stream.url)) if playlist.iframes_only: raise StreamError( "Streams containing I-frames only is not playable") media_sequence = playlist.media_sequence or 0 sequences = [ Sequence(media_sequence + i, s) for i, s in enumerate(playlist.segments) ] if sequences: self.process_sequences(playlist, sequences)
def open(self): if self.is_usable(self.session): process = self.spawn(self.parameters, self.arguments) # Wait 0.5 seconds to see if program exited prematurely time.sleep(0.5) if not process.poll() is None: if hasattr(self.stderr, "name"): raise StreamError(("Error while executing subprocess, " "error output logged to: {0}").format(self.stderr.name)) else: raise StreamError("Error while executing subprocess") return StreamProcessIO(self.session, process, process.stdout, timeout=self.timeout) else: raise StreamError("{0} is not installed or not supported on your system".format(os.path.basename(self.cmd)))
def spawn(self, parameters=None, arguments=None, stderr=None, timeout=None, short_option_prefix="-", long_option_prefix="--"): """ Spawn the process defined in `cmd` parameters is converted to options the short and long option prefixes if a list is given as the value, the parameter is repeated with each value If timeout is set the spawn will block until the process returns or the timeout expires. :param parameters: optional parameters :param arguments: positional arguments :param stderr: where to redirect stderr to :param timeout: timeout for short lived process :param long_option_prefix: option prefix, default - :param short_option_prefix: long option prefix, default -- :return: spawned process """ stderr = stderr or self.stderr cmd = self.bake(self._check_cmd(), parameters, arguments, short_option_prefix, long_option_prefix) self.logger.debug("Spawning command: {0}", subprocess.list2cmdline(cmd)) try: process = subprocess.Popen(cmd, stderr=stderr, stdout=subprocess.PIPE) except (OSError, IOError) as err: raise StreamError("Failed to start process: {0} ({1})".format( self._check_cmd(), str(err))) if timeout: elapsed = 0 while elapsed < timeout and not process.poll(): time.sleep(0.25) elapsed += 0.25 # kill after the timeout has expired and the process still hasn't ended if not process.poll(): try: self.logger.debug( "Process timeout expired ({0}s), killing process". format(timeout)) process.kill() except Exception: pass process.wait() return process
def open(self): if not CAN_DECRYPT: raise StreamError( "pyCrypto needs to be installed to decrypt this stream") reader = BeatStreamReader(self) reader.open() return reader
def open(self): process = self.spawn(self.parameters, self.arguments) # Wait 0.5 seconds to see if program exited prematurely time.sleep(0.5) if not process.poll() is None: if hasattr(self.stderr, "name"): raise StreamError( ("Error while executing subprocess, " "error output logged to: {0}").format(self.stderr.name)) else: raise StreamError("Error while executing subprocess") return StreamProcessIO(self.session, process, process.stdout, timeout=self.timeout)
def pkcs7_decode(paddedData, keySize=16): ''' Remove the PKCS#7 padding ''' # Use ord + [-1:] to support both python 2 and 3 val = ord(paddedData[-1:]) if val > keySize: raise StreamError("Input is not padded or padding is corrupt, got padding size of {0}".format(val)) return paddedData[:-val]
def open(self): if self.session.options.get("rtmp-proxy"): if not self._supports_param("socks"): raise StreamError("Installed rtmpdump does not support --socks argument") self.parameters["socks"] = self.session.options.get("rtmp-proxy") if "jtv" in self.parameters and not self._supports_param("jtv"): raise StreamError("Installed rtmpdump does not support --jtv argument") if "weeb" in self.parameters and not self._supports_param("weeb"): raise StreamError("Installed rtmpdump does not support --weeb argument") if self.redirect: self._check_redirect() self.parameters["flv"] = "-" return StreamProcess.open(self)
def handshake(self, fd): try: self.flv = FLV(fd) except FLVError as err: raise StreamError(str(err)) self.buffer.write(self.flv.header.serialize()) log.debug("Attempting to handshake") for i, tag in enumerate(self.flv): if i == 10: raise StreamError( "No OnEdge metadata in FLV after 10 tags, probably not a AkamaiHD stream" ) self.process_tag(tag, exception=StreamError) if self.completed_handshake: log.debug("Handshake successful") break
def create_decryptor(self, key, sequence): if key.method != "AES-128": raise StreamError("Unable to decrypt cipher {0}", key.method) if not key.uri: raise StreamError("Missing URI to decryption key") if self.key_uri != key.uri: res = self.session.http.get(key.uri, exception=StreamError, retries=self.retries, **self.reader.request_params) self.key_data = res.content self.key_uri = key.uri iv = key.iv or num_to_iv(sequence) # Pad IV if needed iv = b"\x00" * (16 - len(iv)) + iv return AES.new(self.key_data, AES.MODE_CBC, iv)
def url(self): # If the watch timeout has passed then refresh the playlist from the API if int(time.time()) >= self.watch_timeout: for stream in self._get_stream_data(): if stream["quality"] == self.quality: self.watch_timeout = int(time.time()) + stream["watch-timeout"] self._url = stream["url"] return self._url raise StreamError("cannot refresh FilmOn HLS Stream playlist") else: return self._url
def _generate_session_token(self, data64): swfdata = base64.decodestring(bytes(data64, "ascii")) md5 = hashlib.md5() md5.update(swfdata) hash = md5.hexdigest() if hash in self.TokenGenerators: generator = self.TokenGenerators[hash](self) return generator.generate() else: raise StreamError( ("No token generator available for hash '{0}'").format(hash))
def cached(self, sequence: Sequence.num, byterange: ByteRange) -> Tuple[int, int]: if byterange.offset is not None: bytes_start = byterange.offset elif self.offset is not None and self.sequence == sequence - 1: bytes_start = self.offset else: raise StreamError("Missing BYTERANGE offset") bytes_end = self._calc_end(bytes_start, byterange.range) self.sequence = sequence self.offset = bytes_end + 1 return bytes_start, bytes_end
def _supports_param(self, param, timeout=5.0): try: rtmpdump = self.spawn(dict(help=True), timeout=timeout, stderr=subprocess.PIPE) except StreamError as err: raise StreamError("Error while checking rtmpdump compatibility: {0}".format(err.message)) for line in rtmpdump.stderr.readlines(): m = re.match(r"^--(\w+)", str(line, "ascii")) if not m: continue if m.group(1) == param: return True return False
def generate(self): if not self.stream.swf: raise StreamError("A SWF URL is required to create session token") res = self.stream.session.http.get(self.stream.swf, exception=StreamError) data = swfdecompress(res.content) md5 = hashlib.md5() md5.update(data) data = bytes(self.stream.sessionid, "ascii") + md5.digest() sig = hmac.new(b"foo", data, hashlib.sha1) b64 = base64.encodestring(sig.digest()) token = str(b64, "ascii").replace("\n", "") return token
def fetch_module_info(self): self.logger.debug("Fetching module info") conn = create_ums_connection("channel", self.stream.channel_id, self.stream.page_url, self.stream.password, exception=StreamError) try: result = conn.process_packets(invoked_method="moduleInfo", timeout=10) except (IOError, librtmp.RTMPError) as err: raise StreamError("Failed to get module info: {0}".format(err)) finally: conn.close() result = _module_info_schema.validate(result) return _channel_schema.validate(result, "module info")
def open(self): self.guid = cache_bust_string(12) self.islive = None self.sessionid = None self.flv = None self.buffer = Buffer() self.completed_handshake = False url = self.StreamURLFormat.format(host=self.host, streamname=self.streamname) params = self._create_params(seek=self.seek) log.debug(f"Opening host={self.host} streamname={self.streamname}") try: res = self.session.http.get(url, stream=True, params=params) self.fd = StreamIOIterWrapper(res.iter_content(8192)) except Exception as err: raise StreamError(str(err)) self.handshake(self.fd) return self
def test_stream_failure_no_output_open(self, mock_console: Mock, mock_log: Mock): output = Mock() stream = Mock(__str__=lambda _: "fake-stream", open=Mock(side_effect=StreamError("failure"))) formatter = Formatter({}) with patch("streamlink_cli.main.output", Mock()), \ patch("streamlink_cli.main.create_output", return_value=output): output_stream(stream, formatter) self.assertEqual(mock_log.error.call_args_list, [ call( "Try 1/2: Could not open stream fake-stream (Could not open stream: failure)" ), call( "Try 2/2: Could not open stream fake-stream (Could not open stream: failure)" ), ]) self.assertEqual(mock_console.exit.call_args_list, [ call("Could not open stream fake-stream, tried 2 times, exiting") ]) self.assertFalse(output.open.called, "Does not open the output on stream error")
def create_decryptor(self, key, sequence): #self.logger.debug('key.method, key.uri: %s, %s'%(key.method, key.uri)) if key.method != "AES-128": raise StreamError("Unable to decrypt cipher {0}", key.method) if not key.uri: raise StreamError("Missing URI to decryption key") if self.key_uri != key.uri: zoom_key = self.reader.stream.session.options.get("zoom-key") zuom_key = self.reader.stream.session.options.get("zuom-key") livecam_key = self.reader.stream.session.options.get("livecam-key") saw_key = self.reader.stream.session.options.get("saw-key") your_key = self.reader.stream.session.options.get("your-key") mama_key = self.reader.stream.session.options.get("mama-key") if zoom_key: uri = 'http://www.zoomtv.me/k.php?q='+base64.urlsafe_b64encode(zoom_key+base64.urlsafe_b64encode(key.uri)) elif zuom_key: uri = 'http://www.zuom.xyz/k.php?q='+base64.urlsafe_b64encode(zuom_key+base64.urlsafe_b64encode(key.uri)) elif livecam_key: h = urlparse.urlparse(urllib.unquote(livecam_key)).netloc q = urlparse.urlparse(urllib.unquote(livecam_key)).query uri = 'http://%s/kaes?q='%h+base64.urlsafe_b64encode(q+base64.b64encode(key.uri)) elif saw_key: if 'foxsportsgo' in key.uri: _tmp = key.uri.split('/') uri = urljoin(saw_key,'/m/fream?p='+_tmp[-4]+'&k='+_tmp[-1]) elif 'nlsk.neulion' in key.uri: _tmp = key.uri.split('?') uri = urljoin(saw_key,'/m/stream?'+_tmp[-1]) elif 'nlsk' in key.uri: _tmp = key.uri.split('?') uri = 'http://bile.level303.club/m/stream?'+_tmp[-1] elif 'nhl.com' in key.uri: _tmp = key.uri.split('/') uri = urljoin(saw_key,'/m/streams?ci='+_tmp[-3]+'&k='+_tmp[-1]) else: uri = key.uri elif mama_key: if 'nlsk' in key.uri: _tmp = key.uri.split('&url=') uri = 'http://mamahd.in/nba?url=' + _tmp[-1] elif your_key: if 'mlb.com' in key.uri: _tmp = key.uri.split('?') uri = urljoin(your_key,'/mlb/get_key/'+_tmp[-1]) elif 'espn3/auth' in key.uri: _tmp = key.uri.split('?') uri = urljoin(your_key,'/ncaa/get_key/'+_tmp[-1]) elif 'nhl.com' in key.uri: _tmp = key.uri.split('nhl.com/') uri = urljoin(your_key,'/nhl/get_key/'+_tmp[-1]) else: uri = key.uri else: uri = key.uri res = self.session.http.get(uri, exception=StreamError, retries=self.retries, **self.reader.request_params) self.key_data = res.content self.key_uri = key.uri iv = key.iv or num_to_iv(sequence) # Pad IV if needed iv = b"\x00" * (16 - len(iv)) + iv if _android_ssl: return enc(self.key_data, iv) elif _oscrypto: return AES(self.key_data, iv) else: return AES.new(self.key_data, AES.MODE_CBC, iv)
def create_decryptor(self, key, sequence): if key.method != "AES-128": raise StreamError("Unable to decrypt cipher {0}", key.method) if not self.key_uri_override and not key.uri: raise StreamError("Missing URI to decryption key") key_uri = self.key_uri_override if self.key_uri_override else key.uri if self.key_uri != key_uri: zoom_key = self.reader.stream.session.options.get("zoom-key") zuom_key = self.reader.stream.session.options.get("zuom-key") livecam_key = self.reader.stream.session.options.get("livecam-key") saw_key = self.reader.stream.session.options.get("saw-key") your_key = self.reader.stream.session.options.get("your-key") mama_key = self.reader.stream.session.options.get("mama-key") #custom_uri = self.reader.stream.session.options.get("custom-uri") if zoom_key: uri = 'http://www.zoomtv.me/k.php?q=' + base64.urlsafe_b64encode( zoom_key + base64.urlsafe_b64encode(key_uri)) elif zuom_key: uri = 'http://www.zuom.xyz/k.php?q=' + base64.urlsafe_b64encode( zuom_key + base64.urlsafe_b64encode(key_uri)) elif livecam_key: h = urlparse.urlparse(urllib.unquote(livecam_key)).netloc q = urlparse.urlparse(urllib.unquote(livecam_key)).query uri = 'https://%s/kaesv2?sqa=' % h + base64.urlsafe_b64encode( q + base64.b64encode(key_uri)) elif saw_key: if 'foxsportsgo' in key_uri: _tmp = key_uri.split('/') uri = urljoin(saw_key, '/m/fream?p=' + _tmp[-4] + '&k=' + _tmp[-1]) elif 'nlsk.neulion' in key_uri: _tmp = key_uri.split('?') uri = urljoin(saw_key, '/m/stream?' + _tmp[-1]) elif 'nlsk' in key_uri: _tmp = key_uri.split('?') uri = 'http://bile.level303.club/m/stream?' + _tmp[-1] elif 'nhl.com' in key_uri: _tmp = key_uri.split('/') uri = urljoin(saw_key, '/m/streams?ci=' + _tmp[-3] + '&k=' + _tmp[-1]) else: uri = key_uri elif mama_key: if 'nlsk' in key_uri: _tmp = key_uri.split('&url=') uri = 'http://mamahd.in/nba?url=' + _tmp[-1] elif your_key: if re.search( r'playback\.svcs\.mlb\.com|mlb-ws-mf\.media\.mlb\.com|mf\.svc\.nhl\.com', key_uri, re.IGNORECASE) != None: _ip = your_key.split('?')[1] uri = re.sub( r'playback\.svcs\.mlb\.com|mlb-ws-mf\.media\.mlb\.com|mf\.svc\.nhl\.com', _ip, key_uri, re.IGNORECASE) elif 'mlb.com' in key_uri: _tmp = key_uri.split('?') uri = urljoin(your_key, '/mlb/get_key/' + _tmp[-1]) elif 'espn3/auth' in key_uri: _tmp = key_uri.split('?') uri = urljoin(your_key, '/ncaa/get_key/' + _tmp[-1]) elif 'nhl.com' in key_uri: _tmp = key_uri.split('nhl.com/') uri = urljoin(your_key, '/nhl/get_key/' + _tmp[-1]) else: uri = key_uri #elif custom_uri: #uri = custom_uri else: uri = key_uri #xbmc.log('[StreamLink_Proxy] using key uri %s'%str(uri)) res = self.session.http.get(uri, exception=StreamError, retries=self.retries, **self.reader.request_params) res.encoding = "binary/octet-stream" self.key_data = res.content self.key_uri = key_uri iv = key.iv or num_to_iv(sequence) # Pad IV if needed iv = b"\x00" * (16 - len(iv)) + iv if _android_ssl: return enc(self.key_data, iv) elif _oscrypto: return AES(self.key_data, iv) else: return AES.new(self.key_data, AES.MODE_CBC, iv)
def reload_playlist(self): if self.closed: return self.reader.buffer.wait_free() log.debug("Reloading playlist") # felix add update headers cookies ''' add option to self.session.options.options then update self.session.http.headers before access self.stream.url eg: headers.update(session.headers) ''' request_params = dict(self.reader.request_params) headers = request_params.pop("headers", {}) cookies = request_params.pop("cookies", {}) if self.token_uri_override: p = urlparse(self.token_uri_override) key_uri = LazyFormatter.format( self.token_uri_override, url=self.token_uri_override, scheme=p.scheme, netloc=p.netloc, path=p.path, query=p.query, ) try: token = self.session.cache.get(key_uri) if not token: res = self.session.http.get( key_uri, exception=StreamError, retries=self.playlist_reload_retries, **self.reader.request_params) token = self.session.http.json(res) self.session.cache.set( key_uri, token, expires=self.session.options.get('hls-token-period')) log.debug( f"reload_playlist save to cache {key_uri} {token}") else: log.debug( f"reload_playlist load from cache {key_uri} {token}") except BaseException as e: log.warning(e) token = {} log.debug(f"reload_playlist {token}") token_headers = token.pop("headers", {}) token_cookies = token.pop("cookies", {}) headers.update(token_headers) cookies.update(token_cookies) request_params["headers"] = headers if cookies: cookiejar = cookiejar_from_dict(cookies) request_params["cookies"] = cookiejar res = self.session.http.get(self.stream.url, exception=StreamError, retries=self.playlist_reload_retries, **request_params) try: playlist = self._reload_playlist(res.text, res.url) except ValueError as err: raise StreamError(err) if playlist.is_master: raise StreamError("Attempted to play a variant playlist, use " "'hls://{0}' instead".format(self.stream.url)) if playlist.iframes_only: raise StreamError( "Streams containing I-frames only is not playable") media_sequence = playlist.media_sequence or 0 sequences = [ Sequence(media_sequence + i, s) for i, s in enumerate(playlist.segments) ] self.playlist_reload_time = self._playlist_reload_time( playlist, sequences) if sequences: self.process_sequences(playlist, sequences)
def uncached(self, byterange: ByteRange) -> Tuple[int, int]: bytes_start = byterange.offset if bytes_start is None: raise StreamError("Missing BYTERANGE offset") return bytes_start, self._calc_end(bytes_start, byterange.range)