2018-05-30 03:15:11 +02:00
|
|
|
import logging
|
2013-08-08 15:01:32 +02:00
|
|
|
import pkgutil
|
2021-01-12 21:38:45 +01:00
|
|
|
from functools import lru_cache
|
2021-01-09 18:45:04 +01:00
|
|
|
from socket import AF_INET, AF_INET6
|
2022-04-03 20:58:13 +02:00
|
|
|
from typing import Any, Dict, Optional, Tuple, Type
|
2018-05-30 03:15:11 +02:00
|
|
|
|
2017-01-19 10:33:34 +01:00
|
|
|
import requests
|
2021-01-09 18:45:04 +01:00
|
|
|
import requests.packages.urllib3.util.connection as urllib3_connection
|
|
|
|
from requests.packages.urllib3.util.connection import allowed_gai_family
|
2017-08-03 16:46:46 +02:00
|
|
|
|
2020-10-26 14:46:17 +01:00
|
|
|
from streamlink import __version__, plugins
|
|
|
|
from streamlink.exceptions import NoPluginError, PluginError
|
2020-10-13 14:02:59 +02:00
|
|
|
from streamlink.logger import StreamlinkLogger
|
2020-10-26 14:46:17 +01:00
|
|
|
from streamlink.options import Options
|
2021-11-06 12:33:53 +01:00
|
|
|
from streamlink.plugin.api.http_session import HTTPSession
|
|
|
|
from streamlink.plugin.plugin import Matcher, NORMAL_PRIORITY, NO_PRIORITY, Plugin
|
2017-01-20 14:02:14 +01:00
|
|
|
from streamlink.utils.l10n import Localization
|
2021-09-17 16:19:46 +02:00
|
|
|
from streamlink.utils.module import load_module
|
2021-09-14 12:01:25 +02:00
|
|
|
from streamlink.utils.url import update_scheme
|
2013-02-08 02:00:44 +01:00
|
|
|
|
2018-05-30 03:15:11 +02:00
|
|
|
# Ensure that the Logger class returned is Streamslink's for using the API (for backwards compatibility)
|
|
|
|
logging.setLoggerClass(StreamlinkLogger)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2013-05-10 00:57:43 +02:00
|
|
|
|
2020-04-19 21:02:43 +02:00
|
|
|
class PythonDeprecatedWarning(UserWarning):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2020-11-04 08:15:06 +01:00
|
|
|
class Streamlink:
|
2022-04-03 20:58:13 +02:00
|
|
|
"""
|
|
|
|
The Streamlink session is used to load and resolve plugins, and to store options used by plugins and stream implementations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
http: HTTPSession
|
|
|
|
"""
|
|
|
|
An instance of Streamlink's :class:`requests.Session` subclass.
|
|
|
|
Used for any kind of HTTP request made by plugin and stream implementations.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
options: Optional[Dict[str, Any]] = None
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
:param options: Custom options
|
|
|
|
"""
|
2013-02-08 02:00:44 +01:00
|
|
|
|
2021-11-06 12:33:53 +01:00
|
|
|
self.http = HTTPSession()
|
2013-02-08 02:00:44 +01:00
|
|
|
self.options = Options({
|
2021-01-09 18:45:04 +01:00
|
|
|
"interface": None,
|
|
|
|
"ipv4": False,
|
|
|
|
"ipv6": False,
|
2014-04-18 14:20:45 +02:00
|
|
|
"hls-live-edge": 3,
|
2020-12-14 00:56:21 +01:00
|
|
|
"hls-segment-ignore-names": [],
|
2021-11-09 16:18:28 +01:00
|
|
|
"hls-segment-stream-data": False,
|
2017-02-03 18:26:56 +01:00
|
|
|
"hls-playlist-reload-attempts": 3,
|
2020-04-21 03:30:53 +02:00
|
|
|
"hls-playlist-reload-time": "default",
|
2017-07-26 14:08:12 +02:00
|
|
|
"hls-start-offset": 0,
|
|
|
|
"hls-duration": None,
|
2017-01-16 18:57:12 +01:00
|
|
|
"ringbuffer-size": 1024 * 1024 * 16, # 16 MB
|
2014-07-27 15:55:10 +02:00
|
|
|
"stream-segment-attempts": 3,
|
|
|
|
"stream-segment-threads": 1,
|
|
|
|
"stream-segment-timeout": 10.0,
|
|
|
|
"stream-timeout": 60.0,
|
2017-01-12 13:36:30 +01:00
|
|
|
"ffmpeg-ffmpeg": None,
|
2020-11-23 18:29:26 +01:00
|
|
|
"ffmpeg-fout": None,
|
|
|
|
"ffmpeg-video-transcode": None,
|
|
|
|
"ffmpeg-audio-transcode": None,
|
2020-12-16 17:52:10 +01:00
|
|
|
"ffmpeg-copyts": False,
|
2020-12-15 16:57:50 +01:00
|
|
|
"ffmpeg-start-at-zero": False,
|
2020-11-08 05:56:57 +01:00
|
|
|
"mux-subtitles": False,
|
2018-06-22 01:15:21 +02:00
|
|
|
"locale": None,
|
|
|
|
"user-input-requester": None
|
2013-02-08 02:00:44 +01:00
|
|
|
})
|
2018-06-22 01:15:21 +02:00
|
|
|
if options:
|
|
|
|
self.options.update(options)
|
2022-04-17 14:07:46 +02:00
|
|
|
self.plugins: Dict[str, Type[Plugin]] = {}
|
2013-02-08 02:00:44 +01:00
|
|
|
self.load_builtin_plugins()
|
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
def set_option(self, key: str, value: Any):
|
|
|
|
"""
|
|
|
|
Sets general options used by plugins and streams originating from this session object.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
|
|
|
:param key: key of the option
|
|
|
|
:param value: value to set the option to
|
|
|
|
|
|
|
|
|
2014-04-17 00:52:49 +02:00
|
|
|
**Available options**:
|
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
======================== =========================================
|
2021-01-09 18:45:04 +01:00
|
|
|
interface (str) Set the network interface,
|
|
|
|
default: ``None``
|
|
|
|
ipv4 (bool) Resolve address names to IPv4 only.
|
|
|
|
This option overrides ipv6, default: ``False``
|
|
|
|
ipv6 (bool) Resolve address names to IPv6 only.
|
|
|
|
This option overrides ipv4, default: ``False``
|
2014-04-19 01:12:24 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
hls-live-edge (int) How many segments from the end
|
|
|
|
to start live streams on, default: ``3``
|
2014-04-18 14:20:45 +02:00
|
|
|
|
2020-12-14 00:56:21 +01:00
|
|
|
hls-segment-ignore-names (str[]) List of segment names without
|
|
|
|
file endings which should get filtered out,
|
|
|
|
default: ``[]``
|
|
|
|
|
2019-04-08 00:47:22 +02:00
|
|
|
hls-segment-stream-data (bool) Stream HLS segment downloads,
|
|
|
|
default: ``False``
|
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
http-proxy (str) Specify an HTTP proxy to use for
|
2017-01-03 16:41:56 +01:00
|
|
|
all HTTP requests
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
https-proxy (str) Specify an HTTPS proxy to use for
|
2017-01-03 16:41:56 +01:00
|
|
|
all HTTPS requests
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
http-cookies (dict or str) A dict or a semicolon ``;``
|
2017-01-03 16:41:56 +01:00
|
|
|
delimited str of cookies to add to each
|
|
|
|
HTTP request, e.g. ``foo=bar;baz=qux``
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
http-headers (dict or str) A dict or semicolon ``;``
|
2017-01-03 16:41:56 +01:00
|
|
|
delimited str of headers to add to each
|
|
|
|
HTTP request, e.g. ``foo=bar;baz=qux``
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
http-query-params (dict or str) A dict or an ampersand ``&``
|
2017-01-03 16:41:56 +01:00
|
|
|
delimited string of query parameters to
|
|
|
|
add to each HTTP request,
|
|
|
|
e.g. ``foo=bar&baz=qux``
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
http-trust-env (bool) Trust HTTP settings set in the
|
|
|
|
environment, such as environment
|
2022-04-03 20:58:13 +02:00
|
|
|
variables (HTTP_PROXY, etc.) and
|
2017-01-03 16:41:56 +01:00
|
|
|
~/.netrc authentication
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
http-ssl-verify (bool) Verify SSL certificates,
|
|
|
|
default: ``True``
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
http-ssl-cert (str or tuple) SSL certificate to use,
|
|
|
|
can be either a .pem file (str) or a
|
|
|
|
.crt/.key pair (tuple)
|
2014-04-17 00:52:49 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
http-timeout (float) General timeout used by all HTTP
|
|
|
|
requests except the ones covered by
|
|
|
|
other options, default: ``20.0``
|
2014-04-20 17:07:16 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
ringbuffer-size (int) The size of the internal ring
|
|
|
|
buffer used by most stream types,
|
|
|
|
default: ``16777216`` (16MB)
|
2014-04-18 18:21:43 +02:00
|
|
|
|
2017-01-16 11:48:03 +01:00
|
|
|
ffmpeg-ffmpeg (str) Specify the location of the
|
|
|
|
ffmpeg executable use by Muxing streams
|
|
|
|
e.g. ``/usr/local/bin/ffmpeg``
|
2017-01-10 18:44:32 +01:00
|
|
|
|
2017-01-16 11:48:03 +01:00
|
|
|
ffmpeg-verbose (bool) Log stderr from ffmpeg to the
|
|
|
|
console
|
2017-01-10 18:44:32 +01:00
|
|
|
|
2017-01-16 11:48:03 +01:00
|
|
|
ffmpeg-verbose-path (str) Specify the location of the
|
|
|
|
ffmpeg stderr log file
|
2017-01-10 18:44:32 +01:00
|
|
|
|
2020-11-23 18:29:26 +01:00
|
|
|
ffmpeg-fout (str) The output file format
|
|
|
|
when muxing with ffmpeg
|
|
|
|
e.g. ``matroska``
|
|
|
|
|
2017-01-16 11:48:03 +01:00
|
|
|
ffmpeg-video-transcode (str) The codec to use if transcoding
|
|
|
|
video when muxing with ffmpeg
|
|
|
|
e.g. ``h264``
|
2017-01-10 18:44:32 +01:00
|
|
|
|
2017-01-16 11:48:03 +01:00
|
|
|
ffmpeg-audio-transcode (str) The codec to use if transcoding
|
|
|
|
audio when muxing with ffmpeg
|
|
|
|
e.g. ``aac``
|
2017-01-10 18:44:32 +01:00
|
|
|
|
2020-12-16 17:52:10 +01:00
|
|
|
ffmpeg-copyts (bool) When used with ffmpeg, do not shift input timestamps.
|
|
|
|
|
2020-11-23 18:29:26 +01:00
|
|
|
ffmpeg-start-at-zero (bool) When used with ffmpeg and copyts,
|
2022-04-03 20:58:13 +02:00
|
|
|
shift input timestamps, so they start at zero
|
2020-12-15 16:57:50 +01:00
|
|
|
default: ``False``
|
2020-11-23 18:29:26 +01:00
|
|
|
|
2020-11-08 05:56:57 +01:00
|
|
|
mux-subtitles (bool) Mux available subtitles into the
|
|
|
|
output stream.
|
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
stream-segment-attempts (int) How many attempts should be done
|
|
|
|
to download each segment, default: ``3``.
|
2014-07-27 15:55:10 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
stream-segment-threads (int) The size of the thread pool used
|
|
|
|
to download segments, default: ``1``.
|
2014-07-27 15:55:10 +02:00
|
|
|
|
2017-01-03 16:41:56 +01:00
|
|
|
stream-segment-timeout (float) Segment connect and read
|
|
|
|
timeout, default: ``10.0``.
|
|
|
|
|
|
|
|
stream-timeout (float) Timeout for reading data from
|
|
|
|
stream, default: ``60.0``.
|
2017-01-20 14:02:14 +01:00
|
|
|
|
|
|
|
locale (str) Locale setting, in the RFC 1766 format
|
2022-04-03 20:58:13 +02:00
|
|
|
e.g. en_US or es_ES
|
2017-01-20 14:02:14 +01:00
|
|
|
default: ``system locale``.
|
2018-06-22 01:15:21 +02:00
|
|
|
|
|
|
|
user-input-requester (UserInputRequester) instance of UserInputRequester
|
|
|
|
to collect input from the user at runtime. Must be
|
|
|
|
set before the plugins are loaded.
|
|
|
|
default: ``UserInputRequester``.
|
2017-01-03 16:41:56 +01:00
|
|
|
======================== =========================================
|
2014-04-17 00:52:49 +02:00
|
|
|
"""
|
2014-04-18 18:21:43 +02:00
|
|
|
|
2021-01-09 18:45:04 +01:00
|
|
|
if key == "interface":
|
|
|
|
for scheme, adapter in self.http.adapters.items():
|
|
|
|
if scheme not in ("http://", "https://"):
|
|
|
|
continue
|
|
|
|
if not value:
|
|
|
|
adapter.poolmanager.connection_pool_kw.pop("source_address")
|
|
|
|
else:
|
|
|
|
adapter.poolmanager.connection_pool_kw.update(
|
|
|
|
# https://docs.python.org/3/library/socket.html#socket.create_connection
|
|
|
|
source_address=(value, 0)
|
|
|
|
)
|
|
|
|
self.options.set(key, None if not value else value)
|
|
|
|
|
|
|
|
elif key == "ipv4" or key == "ipv6":
|
|
|
|
self.options.set(key, value)
|
|
|
|
if value:
|
|
|
|
self.options.set("ipv6" if key == "ipv4" else "ipv4", False)
|
|
|
|
urllib3_connection.allowed_gai_family = \
|
|
|
|
(lambda: AF_INET) if key == "ipv4" else (lambda: AF_INET6)
|
|
|
|
else:
|
|
|
|
urllib3_connection.allowed_gai_family = allowed_gai_family
|
|
|
|
|
2021-10-28 16:33:21 +02:00
|
|
|
elif key in ("http-proxy", "https-proxy"):
|
2021-10-03 14:35:46 +02:00
|
|
|
self.http.proxies["http"] = update_scheme("https://", value, force=False)
|
2021-10-28 16:33:21 +02:00
|
|
|
self.http.proxies["https"] = self.http.proxies["http"]
|
|
|
|
if key == "https-proxy":
|
|
|
|
log.info("The https-proxy option has been deprecated in favour of a single http-proxy option")
|
utils.url: make update_scheme always update target
Previously, update_scheme only added schemes on scheme-less URLs and
kept target URLs which included a scheme intact.
This is confusing for plugin implementers and it has already been used
incorrectly a lot of times when trying to force the HTTPS protocol on
unencrypted HTTP URLs. Since the method is called update_scheme, it
should do exactly that, in all cases.
However, always updating the target scheme will break those calls of
update_scheme where the scheme was previously only added if needed, eg.
on scheme-less URLs, partial URLs or URL paths.
An example of this are scheme-less input URLs passed to Streamlink's
`Session.resolve_url()`, which has to implicitly set the HTTP scheme.
Always overriding the scheme would turn explicit HTTPS schemes to HTTP,
which can break certain sites/plugins. The various stream-protocol
plugins have the same issue and have to implicitly set a scheme as well.
This change therefore introduces the `force=True` keyword, so that URL
schemes can be set implicitly again in the methods and plugins mentioned
above by setting force to False.
Another effect of forcing the scheme override by default is the pattern
currently used in many plugins, which often update (partial) URLs by
calling `update_scheme(self.url, target)`. Since `self.url` depends on
the user's input and since the input URL's scheme is implicitly set to
HTTP by the Session's URL resolver, target URLs can be downgraded from
HTTPS to HTTP, which is not desired. This is not a problem in cases
where the server will redirect, but it can be a problem where the server
returns an error instead.
These plugins will therefore need to be fixed so that they either set
the URL schemes implicitly or so that they force the according scheme.
Another solution (which will be done eventually) is the change of the
implicit input URL scheme from HTTP to HTTPS in `Session.resolve_url()`
and the various stream-protocol plugins, which will turn all scheme-less
input URLs to HTTPS by default, unless the user explicitly sets an HTTP
URL.
- always update target URL scheme with current URL scheme
- add force=True keyword and do not override scheme if force is False
- set force=False on all implicitly updated URL schemes
- `Session.resolve_url`
- `plugins.{akamaihd,hds,hls,http}`
- parametrize tests, re-order/re-format assertions and add new ones
2021-09-27 16:45:07 +02:00
|
|
|
|
2014-04-17 00:52:49 +02:00
|
|
|
elif key == "http-cookies":
|
|
|
|
if isinstance(value, dict):
|
|
|
|
self.http.cookies.update(value)
|
|
|
|
else:
|
|
|
|
self.http.parse_cookies(value)
|
|
|
|
elif key == "http-headers":
|
|
|
|
if isinstance(value, dict):
|
|
|
|
self.http.headers.update(value)
|
|
|
|
else:
|
|
|
|
self.http.parse_headers(value)
|
|
|
|
elif key == "http-query-params":
|
|
|
|
if isinstance(value, dict):
|
|
|
|
self.http.params.update(value)
|
|
|
|
else:
|
|
|
|
self.http.parse_query_params(value)
|
|
|
|
elif key == "http-trust-env":
|
|
|
|
self.http.trust_env = value
|
|
|
|
elif key == "http-ssl-verify":
|
|
|
|
self.http.verify = value
|
2017-01-19 10:33:34 +01:00
|
|
|
elif key == "http-disable-dh":
|
|
|
|
if value:
|
|
|
|
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':!DH'
|
|
|
|
try:
|
|
|
|
requests.packages.urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST = \
|
|
|
|
requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS.encode("ascii")
|
|
|
|
except AttributeError:
|
|
|
|
# no ssl to disable the cipher on
|
|
|
|
pass
|
2014-04-17 00:52:49 +02:00
|
|
|
elif key == "http-ssl-cert":
|
|
|
|
self.http.cert = value
|
2014-04-20 17:07:16 +02:00
|
|
|
elif key == "http-timeout":
|
|
|
|
self.http.timeout = value
|
session: deprecate options for spec. stream types
Having multiple session options and CLI arguments for different stream
types which are already covered by generic options/arguments is not only
redundant, but also confusing.
A distinction between different stream types does only make sense when
multiple different stream types are available and the user needs to
explicitly set different values for each of them, but since it's not
always clear which stream type is returned when a stream is selected
via the "best" stream name synonym for example, this makes it even more
confusing. And it's redundant as well, since only one stream can be
selected anyway.
- deprecate `{dash,hds,hls}-segment-attempts`
in favor of `stream-segment-attempts`
- deprecate `{dash,hds,hls}-segment-threads`
in favor of `stream-segment-threads`
- deprecate `{dash,hds,hls}-segment-timeout`
in favor of `stream-segment-timeout`
- deprecate `{dash,hds,hls,rtmp,http-stream}-timeout`
in favor of `stream-timeout` (dash/http-stream were never used)
- fix `HTTPStream` and use `stream-timeout` instead of `http-timeout`
- suppress deprecated CLI arguments
- update help texts of generic CLI arguments
- fix docs and add entry to deprecations page
2021-07-30 22:34:22 +02:00
|
|
|
|
2021-11-12 19:20:26 +01:00
|
|
|
# deprecated: {dash,hls}-segment-attempts
|
|
|
|
elif key in ("dash-segment-attempts", "hls-segment-attempts"):
|
session: deprecate options for spec. stream types
Having multiple session options and CLI arguments for different stream
types which are already covered by generic options/arguments is not only
redundant, but also confusing.
A distinction between different stream types does only make sense when
multiple different stream types are available and the user needs to
explicitly set different values for each of them, but since it's not
always clear which stream type is returned when a stream is selected
via the "best" stream name synonym for example, this makes it even more
confusing. And it's redundant as well, since only one stream can be
selected anyway.
- deprecate `{dash,hds,hls}-segment-attempts`
in favor of `stream-segment-attempts`
- deprecate `{dash,hds,hls}-segment-threads`
in favor of `stream-segment-threads`
- deprecate `{dash,hds,hls}-segment-timeout`
in favor of `stream-segment-timeout`
- deprecate `{dash,hds,hls,rtmp,http-stream}-timeout`
in favor of `stream-timeout` (dash/http-stream were never used)
- fix `HTTPStream` and use `stream-timeout` instead of `http-timeout`
- suppress deprecated CLI arguments
- update help texts of generic CLI arguments
- fix docs and add entry to deprecations page
2021-07-30 22:34:22 +02:00
|
|
|
self.options.set("stream-segment-attempts", int(value))
|
2021-11-12 19:20:26 +01:00
|
|
|
# deprecated: {dash,hls}-segment-threads
|
|
|
|
elif key in ("dash-segment-threads", "hls-segment-threads"):
|
session: deprecate options for spec. stream types
Having multiple session options and CLI arguments for different stream
types which are already covered by generic options/arguments is not only
redundant, but also confusing.
A distinction between different stream types does only make sense when
multiple different stream types are available and the user needs to
explicitly set different values for each of them, but since it's not
always clear which stream type is returned when a stream is selected
via the "best" stream name synonym for example, this makes it even more
confusing. And it's redundant as well, since only one stream can be
selected anyway.
- deprecate `{dash,hds,hls}-segment-attempts`
in favor of `stream-segment-attempts`
- deprecate `{dash,hds,hls}-segment-threads`
in favor of `stream-segment-threads`
- deprecate `{dash,hds,hls}-segment-timeout`
in favor of `stream-segment-timeout`
- deprecate `{dash,hds,hls,rtmp,http-stream}-timeout`
in favor of `stream-timeout` (dash/http-stream were never used)
- fix `HTTPStream` and use `stream-timeout` instead of `http-timeout`
- suppress deprecated CLI arguments
- update help texts of generic CLI arguments
- fix docs and add entry to deprecations page
2021-07-30 22:34:22 +02:00
|
|
|
self.options.set("stream-segment-threads", int(value))
|
2021-11-12 19:20:26 +01:00
|
|
|
# deprecated: {dash,hls}-segment-timeout
|
|
|
|
elif key in ("dash-segment-timeout", "hls-segment-timeout"):
|
session: deprecate options for spec. stream types
Having multiple session options and CLI arguments for different stream
types which are already covered by generic options/arguments is not only
redundant, but also confusing.
A distinction between different stream types does only make sense when
multiple different stream types are available and the user needs to
explicitly set different values for each of them, but since it's not
always clear which stream type is returned when a stream is selected
via the "best" stream name synonym for example, this makes it even more
confusing. And it's redundant as well, since only one stream can be
selected anyway.
- deprecate `{dash,hds,hls}-segment-attempts`
in favor of `stream-segment-attempts`
- deprecate `{dash,hds,hls}-segment-threads`
in favor of `stream-segment-threads`
- deprecate `{dash,hds,hls}-segment-timeout`
in favor of `stream-segment-timeout`
- deprecate `{dash,hds,hls,rtmp,http-stream}-timeout`
in favor of `stream-timeout` (dash/http-stream were never used)
- fix `HTTPStream` and use `stream-timeout` instead of `http-timeout`
- suppress deprecated CLI arguments
- update help texts of generic CLI arguments
- fix docs and add entry to deprecations page
2021-07-30 22:34:22 +02:00
|
|
|
self.options.set("stream-segment-timeout", float(value))
|
2021-11-13 11:36:32 +01:00
|
|
|
# deprecated: {hls,dash,http-stream}-timeout
|
|
|
|
elif key in ("dash-timeout", "hls-timeout", "http-stream-timeout"):
|
session: deprecate options for spec. stream types
Having multiple session options and CLI arguments for different stream
types which are already covered by generic options/arguments is not only
redundant, but also confusing.
A distinction between different stream types does only make sense when
multiple different stream types are available and the user needs to
explicitly set different values for each of them, but since it's not
always clear which stream type is returned when a stream is selected
via the "best" stream name synonym for example, this makes it even more
confusing. And it's redundant as well, since only one stream can be
selected anyway.
- deprecate `{dash,hds,hls}-segment-attempts`
in favor of `stream-segment-attempts`
- deprecate `{dash,hds,hls}-segment-threads`
in favor of `stream-segment-threads`
- deprecate `{dash,hds,hls}-segment-timeout`
in favor of `stream-segment-timeout`
- deprecate `{dash,hds,hls,rtmp,http-stream}-timeout`
in favor of `stream-timeout` (dash/http-stream were never used)
- fix `HTTPStream` and use `stream-timeout` instead of `http-timeout`
- suppress deprecated CLI arguments
- update help texts of generic CLI arguments
- fix docs and add entry to deprecations page
2021-07-30 22:34:22 +02:00
|
|
|
self.options.set("stream-timeout", float(value))
|
|
|
|
|
2014-04-17 00:52:49 +02:00
|
|
|
else:
|
|
|
|
self.options.set(key, value)
|
2013-02-08 02:00:44 +01:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
def get_option(self, key: str):
|
|
|
|
"""
|
|
|
|
Returns the current value of the specified option.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
|
|
|
:param key: key of the option
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2014-12-06 16:40:55 +01:00
|
|
|
if key == "http-proxy":
|
|
|
|
return self.http.proxies.get("http")
|
|
|
|
elif key == "https-proxy":
|
|
|
|
return self.http.proxies.get("https")
|
|
|
|
elif key == "http-cookies":
|
|
|
|
return self.http.cookies
|
|
|
|
elif key == "http-headers":
|
|
|
|
return self.http.headers
|
|
|
|
elif key == "http-query-params":
|
|
|
|
return self.http.params
|
|
|
|
elif key == "http-trust-env":
|
|
|
|
return self.http.trust_env
|
|
|
|
elif key == "http-ssl-verify":
|
|
|
|
return self.http.verify
|
|
|
|
elif key == "http-ssl-cert":
|
|
|
|
return self.http.cert
|
|
|
|
elif key == "http-timeout":
|
|
|
|
return self.http.timeout
|
|
|
|
else:
|
|
|
|
return self.options.get(key)
|
2013-02-08 02:00:44 +01:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
def set_plugin_option(self, plugin: str, key: str, value: Any):
|
|
|
|
"""
|
|
|
|
Sets plugin specific options used by plugins originating from this session object.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
|
|
|
:param plugin: name of the plugin
|
|
|
|
:param key: key of the option
|
|
|
|
:param value: value to set the option to
|
|
|
|
"""
|
|
|
|
|
2013-02-08 02:00:44 +01:00
|
|
|
if plugin in self.plugins:
|
|
|
|
plugin = self.plugins[plugin]
|
|
|
|
plugin.set_option(key, value)
|
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
def get_plugin_option(self, plugin: str, key: str):
|
|
|
|
"""
|
|
|
|
Returns the current value of the plugin specific option.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
|
|
|
:param plugin: name of the plugin
|
|
|
|
:param key: key of the option
|
|
|
|
"""
|
|
|
|
|
2013-02-08 02:00:44 +01:00
|
|
|
if plugin in self.plugins:
|
|
|
|
plugin = self.plugins[plugin]
|
|
|
|
return plugin.get_option(key)
|
|
|
|
|
2021-01-12 21:38:45 +01:00
|
|
|
@lru_cache(maxsize=128)
|
2022-04-03 20:58:13 +02:00
|
|
|
def resolve_url(
|
|
|
|
self,
|
|
|
|
url: str,
|
|
|
|
follow_redirect: bool = True
|
|
|
|
) -> Tuple[Type[Plugin], str]:
|
|
|
|
"""
|
|
|
|
Attempts to find a plugin that can use this URL.
|
2013-08-08 15:01:32 +02:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
The default protocol (https) will be prefixed to the URL if not specified.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
Return values of this method are cached via :meth:`functools.lru_cache`.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
|
|
|
:param url: a URL to match against loaded plugins
|
2017-08-03 14:45:00 +02:00
|
|
|
:param follow_redirect: follow redirects
|
2022-04-03 20:58:13 +02:00
|
|
|
:raises NoPluginError: on plugin resolve failure
|
2013-02-08 02:00:44 +01:00
|
|
|
"""
|
2022-04-03 20:58:13 +02:00
|
|
|
|
2021-10-03 14:35:46 +02:00
|
|
|
url = update_scheme("https://", url, force=False)
|
2013-02-08 02:00:44 +01:00
|
|
|
|
2021-06-22 22:25:35 +02:00
|
|
|
matcher: Matcher
|
|
|
|
candidate: Optional[Type[Plugin]] = None
|
|
|
|
priority = NO_PRIORITY
|
2013-02-08 02:00:44 +01:00
|
|
|
for name, plugin in self.plugins.items():
|
2021-06-22 22:25:35 +02:00
|
|
|
if plugin.matchers:
|
|
|
|
for matcher in plugin.matchers:
|
|
|
|
if matcher.priority > priority and matcher.pattern.match(url) is not None:
|
|
|
|
candidate = plugin
|
|
|
|
priority = matcher.priority
|
2021-06-23 09:17:07 +02:00
|
|
|
# TODO: remove deprecated plugin resolver
|
|
|
|
elif hasattr(plugin, "can_handle_url") and callable(plugin.can_handle_url) and plugin.can_handle_url(url):
|
|
|
|
prio = plugin.priority(url) if hasattr(plugin, "priority") and callable(plugin.priority) else NORMAL_PRIORITY
|
|
|
|
if prio > priority:
|
|
|
|
log.info(f"Resolved plugin {name} with deprecated can_handle_url API")
|
|
|
|
candidate = plugin
|
|
|
|
priority = prio
|
2021-06-22 22:25:35 +02:00
|
|
|
|
|
|
|
if candidate:
|
2021-11-10 10:52:12 +01:00
|
|
|
return candidate, url
|
2015-03-24 21:53:42 +01:00
|
|
|
|
2017-08-03 14:45:00 +02:00
|
|
|
if follow_redirect:
|
|
|
|
# Attempt to handle a redirect URL
|
|
|
|
try:
|
2021-11-10 10:52:12 +01:00
|
|
|
# noinspection PyArgumentList
|
2017-08-03 14:45:00 +02:00
|
|
|
res = self.http.head(url, allow_redirects=True, acceptable_status=[501])
|
2015-03-24 21:53:42 +01:00
|
|
|
|
2017-08-03 14:45:00 +02:00
|
|
|
# Fall back to GET request if server doesn't handle HEAD.
|
|
|
|
if res.status_code == 501:
|
|
|
|
res = self.http.get(url, stream=True)
|
|
|
|
|
|
|
|
if res.url != url:
|
|
|
|
return self.resolve_url(res.url, follow_redirect=follow_redirect)
|
|
|
|
except PluginError:
|
|
|
|
pass
|
2014-03-15 21:40:18 +01:00
|
|
|
|
2013-02-08 02:00:44 +01:00
|
|
|
raise NoPluginError
|
|
|
|
|
2021-11-10 10:52:12 +01:00
|
|
|
def resolve_url_no_redirect(self, url: str) -> Tuple[Type[Plugin], str]:
|
2022-04-03 20:58:13 +02:00
|
|
|
"""
|
|
|
|
Attempts to find a plugin that can use this URL.
|
2017-01-10 20:16:05 +01:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
The default protocol (https) will be prefixed to the URL if not specified.
|
2017-01-10 20:16:05 +01:00
|
|
|
|
|
|
|
:param url: a URL to match against loaded plugins
|
2022-04-03 20:58:13 +02:00
|
|
|
:raises NoPluginError: on plugin resolve failure
|
2017-01-10 20:16:05 +01:00
|
|
|
"""
|
2022-04-03 20:58:13 +02:00
|
|
|
|
2017-08-03 14:45:00 +02:00
|
|
|
return self.resolve_url(url, follow_redirect=False)
|
2017-01-10 20:16:05 +01:00
|
|
|
|
2021-11-10 10:52:12 +01:00
|
|
|
def streams(self, url: str, **params):
|
2022-04-03 20:58:13 +02:00
|
|
|
"""
|
|
|
|
Attempts to find a plugin and extracts streams from the *url* if a plugin was found.
|
2014-06-06 18:14:46 +02:00
|
|
|
|
2022-04-03 20:58:13 +02:00
|
|
|
:param url: a URL to match against loaded plugins
|
|
|
|
:param params: Additional keyword arguments passed to :meth:`streamlink.plugin.Plugin.streams`
|
|
|
|
:raises NoPluginError: on plugin resolve failure
|
|
|
|
:return: A :class:`dict` of stream names and :class:`streamlink.stream.Stream` instances
|
2014-06-06 18:14:46 +02:00
|
|
|
"""
|
|
|
|
|
2021-11-10 10:52:12 +01:00
|
|
|
pluginclass, resolved_url = self.resolve_url(url)
|
|
|
|
plugin = pluginclass(resolved_url)
|
|
|
|
|
2014-06-06 18:14:46 +02:00
|
|
|
return plugin.streams(**params)
|
|
|
|
|
2013-02-08 02:00:44 +01:00
|
|
|
def get_plugins(self):
|
2013-02-25 04:21:37 +01:00
|
|
|
"""Returns the loaded plugins for the session."""
|
|
|
|
|
2013-02-08 02:00:44 +01:00
|
|
|
return self.plugins
|
|
|
|
|
|
|
|
def load_builtin_plugins(self):
|
|
|
|
self.load_plugins(plugins.__path__[0])
|
|
|
|
|
2021-06-05 22:17:42 +02:00
|
|
|
def load_plugins(self, path: str) -> bool:
|
2022-04-03 20:58:13 +02:00
|
|
|
"""
|
|
|
|
Attempt to load plugins from the path specified.
|
2013-02-25 04:21:37 +01:00
|
|
|
|
|
|
|
:param path: full path to a directory where to look for plugins
|
2021-06-05 22:17:42 +02:00
|
|
|
:return: success
|
2013-02-08 02:00:44 +01:00
|
|
|
"""
|
2022-04-03 20:58:13 +02:00
|
|
|
|
2021-06-05 22:17:42 +02:00
|
|
|
success = False
|
2020-11-26 01:14:39 +01:00
|
|
|
user_input_requester = self.get_option("user-input-requester")
|
2013-02-08 02:00:44 +01:00
|
|
|
for loader, name, ispkg in pkgutil.iter_modules([path]):
|
2018-06-01 00:16:52 +02:00
|
|
|
# set the full plugin module name
|
2020-11-26 01:14:39 +01:00
|
|
|
module_name = f"streamlink.plugins.{name}"
|
2013-05-10 00:57:43 +02:00
|
|
|
try:
|
2020-11-26 01:14:39 +01:00
|
|
|
mod = load_module(module_name, path)
|
|
|
|
except ImportError:
|
|
|
|
log.exception(f"Failed to load plugin {name} from {path}\n")
|
2013-05-10 00:57:43 +02:00
|
|
|
continue
|
2013-02-08 02:00:44 +01:00
|
|
|
|
2020-11-26 01:14:39 +01:00
|
|
|
if not hasattr(mod, "__plugin__") or not issubclass(mod.__plugin__, Plugin):
|
|
|
|
continue
|
2021-06-05 22:17:42 +02:00
|
|
|
success = True
|
2020-11-26 01:14:39 +01:00
|
|
|
plugin = mod.__plugin__
|
|
|
|
plugin.bind(self, name, user_input_requester)
|
2018-06-01 00:16:52 +02:00
|
|
|
if plugin.module in self.plugins:
|
2020-11-26 01:14:39 +01:00
|
|
|
log.debug(f"Plugin {plugin.module} is being overridden by {mod.__file__}")
|
2013-02-08 02:00:44 +01:00
|
|
|
self.plugins[plugin.module] = plugin
|
|
|
|
|
2021-06-05 22:17:42 +02:00
|
|
|
return success
|
|
|
|
|
2013-02-08 02:00:44 +01:00
|
|
|
@property
|
|
|
|
def version(self):
|
|
|
|
return __version__
|
|
|
|
|
2017-01-20 14:02:14 +01:00
|
|
|
@property
|
|
|
|
def localization(self):
|
|
|
|
return Localization(self.get_option("locale"))
|
|
|
|
|
2017-01-12 15:54:55 +01:00
|
|
|
|
2016-09-19 21:46:06 +02:00
|
|
|
__all__ = ["Streamlink"]
|