- Add "DTZ" ruff rule
- Add utility functions to `streamlink.utils.times` which use
"aware" datetimes with explicit timezone information,
and use `isodate`'s local timezone implementation
- Replace all "naive" datetimes without timezone information
- Replace all custom ISO8601 parsers with `isodate`'s implementation
- Add tests for new utility functions
- Raise `StreamlinkDeprecationWarning` when `is_global=True`
- Remove all global plugin arguments and replace them with simple
option lookups on the Streamlink session instance
- Remove global argument detection in custom Sphinx plugins extension
- Remove supported plugins list from custom Sphinx argparse extension
and remove respective setup from `setup_plugin_args` in the CLI module
- Add deprecation note to the docs
- Update tests
- Check whether builtin plugins define global plugin arguments
- Capture deprecation warnings
- Add `StreamlinkWarning` and `StreamlinkDeprecationWarning`
and replace `FutureWarning`s
- Don't include the warning's origin in the warning logger if it's a
subclass of `StreamlinkWarning`
- Update tests
Add support for capturing warnings and logging them via Streamlink's
root logger on the warning log level. Use custom `WarningLogRecord`s
with a custom warning message format, and replace the record's
logger name with "warnings" and use the name of the warning type
as log level name when showing warnings.
This enables having proper `DeprecationWarning`s and `FutureWarning`s
in the code instead of just using the logger and its warning log level,
and those warnings can be filtered via the regular filtering mechanisms.
For example:
```
[warnings][deprecationwarning] Calling this method is deprecated
[warnings][futurewarning] Using this config file path is deprecated
```
Replace `read_stream()` with a new `StreamRunner` class, refactor stream
reads and output writes, as well as the progress thread data feeding,
and move player polling into a separate thread which closes the stream
once the player process gets terminated/killed.
This fixes the player polling issue, or rather the detection of its
broken pipe while reading the stream, as stream read calls can stall
for various reasons, e.g. due to segmented streams or the filtering
of stream data which pauses the stream output and disables any timeouts.
- Implement `StreamRunner` and `PlayerPollThread` classes
in dedicated `streamlink_cli.streamrunner` module
- Remove old `streamlink_cli.main.read_stream()` implementation
- Keep the same log messages
- Make `StreamRunner.run()` raise `OSError` on read/write error and
catch `OSError`s in main module where `console.exit()` gets called
- Remove `Progress.iter()`, as it's not needed anymore
- Add extensive tests with full code coverage
- Merge `setup_options()` and `setup_http_session()` and always
initialize all session options:
It doesn't make sense having this split up into two functions, just
so that some CLI functions can have a partially configured session
with just the HTTP options set
- Move merged session setup logic into the `argparser` module
and replace the dozens of if-statements and `session.set_option()`
calls with a list of "CLI argument -> session option" mappings
- Add tests and update `streamlink_cli.main` tests respectively
- Move to separate module (slim down main module)
- Catch and log errors
- Don't call `sys.exit()` and instead alter flow of `main.main()`
if `--version-check` is set
- Handle `KeyboardInterrupt`
- Add tests
This changes the way how the Streamlink session and other objects like
the plugin cache and logger are stored on each plugin.
Previously, those objects were set as class attributes on every `Plugin`
class via `Plugin.bind()` when loading plugins via the session's
`load_plugins()` method that gets called on initialization.
This meant that whenever a new Streamlink session was initialized,
references to it (including a dict of every loaded plugin) were set
on each `Plugin` class as a class attribute, and Python's garbage
collector could not get rid of this memory when deleting the session
instance that was created last.
Removing `Plugin.bind()`, passing the session via the `Plugin.__init__`
constructor, and setting the cache, logger, etc. on `Plugin` instances
instead (only one gets initialized by `streamlink_cli`), removes those
static references that prevent the garbage collector to work.
Since the plugin "module" name now doesn't get set via `Plugin.bind()`
anymore, it derives its name via `self.__class__.__module__` on its own,
which means a change of the return type of `Streamlink.resolve_url()`
is necessary in order to pass the plugin name to `streamlink_cli`,
so that it can load config files and initialize plugin arguments, etc.
Breaking changes:
- Remove `Plugin.bind()`
- Pass the `session` instance via the Plugin constructor and set the
`module`, `cache` and `logger` on the plugin instance instead.
Derive `module` from the actual module name.
- Change the return type of `Session.resolve_url()` and include the
resolved plugin name in the returned tuple
Other changes:
- Remove `pluginclass.bind()` call from `Session.load_plugins()` and
use the loader's module name directly on the `Session.plugins` dict
- Remove initialization check from `Plugin` cookie methods
- Update streamlink_cli.main module according to breaking changes
- Update tests respectively
- Add explicit plugin initialization test
- Update tests with plugin constructors and custom plugin names
- Move testplugin override module, so that it shares the same module
name as the main testplugin module. Rel `Session.load_plugins()`
- Refactor most session tests and replace unneeded `resolve_url()`
wrappers in favor of calling `session.streams()`
- Add `path` argument to the `Progress` class
- Update formats and add path variable with a min-width of 15
- Truncate paths according to the available space
- Add and update tests
- Remove `streamlink_cli.utils.terminal` module again and move logic
to the `Progress` and `ProgressFormatter` classes
- Replace `output` and `formatter` arguments of the `Progress` class
with a simple `stream` argument
- Rewrite `ProgressFormatter.format()`
- Pre-parse format strings via `string.Formatter.parse()`
and store the parsing results
- Add support for substitutions with dynamic/variable lengths
- Update tests
- Move `is_darwin` from `streamlink_cli.compat` to `streamlink.compat`
- Remove `is_win32` from `streamlink_cli.compat`
- Replace last remaining `unittest.skipIf` test decorators with the
`posix_only`/`windows_only` pytest decorators from the `tests` module
- Remove `is_win32` check when importing from `ctypes` in `named_pipe`
- Split `streamlink_cli.utils.progress` into `progress` + `terminal`
- `terminal`:
Responsible for calculating character output widths and for writing
data to the output text stream
- `progress`:
Responsible for calculating the download speed and for formatting
the progress output
- Use a separate thread for the progress output, with the output being
written in constant time intervals, independent of the data that gets
fed by the stream iterator of the main thread
- Output format changes:
- Remove "prefix" from output format (full path already gets logged)
- Avoid showing download speeds until a time threshold is reached
- Use binary values for file size and download speed values
- Add leading zeros to time units (only ever grow output text size)
- Don't import from alias module in `streamlink_cli.main`
- Split, move and rewrite tests
- Use a custom mapping of parent->child argument group relations
instead of calling `group.add_argument_group()` for creating
a tree structure of argument groups (deprecated in Python 3.11)
- Override `parser.add_argument_group()` and add the `parent` keyword
for being able to have nested argument group defintions
- Update custom `--help` format output
- Update the argparser Sphinx extension
- Fix tests
- Require importlib-metadata as fallback on Python < 3.8
- Add importlib_metadata to streamlink_cli.compat
- List all dependencies in `log_current_versions`
- Update tests
- Change format of Stream string representations and error messages
of `to_url` and `to_manifest_url`
- Don't override `__repr__` in `Stream` subclasses
- Raise TypeError if url or manifest URL is None
- Fix CLI
- Abort passthrough if stream can't be translated to URL
- Remove unneeded streamlink_cli.utils.stream module
- Rewrite stream URL tests
- Move FilmOnHLS stream URL tests to plugin test module (and rewrite)
When running test as root, there is an additional log done to warn the
user that it is running streamlink as root. This causes some logging
tests to fail.
For these logging test, ensure we run with a mocked user euid when not
testing for the root euid.
- Replace collection.OrderedDict with builtins.dict where possible:
Python 3.7+ ensures the correct order in builtins.dict objects and is
no longer an implementation detail of cpython.
- Fix OrderedDict type annotation in streamlink.utils.cache.LRUCache
- Add unit test for streamlink.utils.cache.LRUCache
- Don't raise a KeyboardInterrupt until streamlink_cli has been fully
initialized. This prevents a stack call from being printed to stderr
when streamlink_cli gets interrupted early.
- Restore default signal handlers once streamlink_cli has finished its
initialization, so that KeyboardInterrupt exceptions can be caught to
perform clean up tasks.
- Restore default signal handlers in test immediately after importing
streamlink_cli once as early as possible, to be able to cancel the
test runners regularly via a KeyboardInterrupt.
- Refactor CLI logging tests, which test the log output of the
initialization and need to stop execution at some point.
- drop RTMP stream implementation
- drop RTMP plugin
- drop RTMPDump dependency
- remove stream.streamprocess and utils.{rtmp,swf}
- remove "rtmp" from default stream types list
- remove "rtmp" from player-passthrough options list
- remove all `--rtmp*` CLI args and `rtmp-*` session options
- remove all `--subprocess-*` CLI args and `subprocess` session options,
as these were used only by StreamProcess which is no longer needed
- update tests
- update docs and CLI argument help texts
- drop HDS and AkamaiHD stream implementations
- drop HDS and AkamaiHD plugins
- drop streamlink.packages.flashmedia
- remove stream.flvconcat, stream.playlist and plugins.common_swf
- remove "hds" from default stream types list
- remove all `--hds-*` CLI arguments and `hds-*` session options
- remove unneeded flashmedia license text files
- update tests
- update docs and CLI argument help texts
Breaking change:
Instead of resolving a plugin instance in `Streamlink.resolve_url(url)`
from the provided input URL (which can be redirected), resolve the
plugin class and the resulting URL, and cache the tuple. Also affects
`Streamlink.resolve_url_no_redirect(url)`.
The main reason for this change is streamlink_cli and how the plugin
options get set. The plugin options need to be set on the class before
it gets initialized, so that the instance can access the options in its
constructor method. This also fixes any kind of state stored on the
plugin instance.
- resolve and cache a tuple of the plugin class and resulting URL
- initialize plugin in streamlink_cli after applying its options
- remove plugin variable from global scope in streamlink_cli
- fix, rewrite and add tests
This reverts commit ccdd84bf76
- resolve merge conflicts
- keep stream=False parameter in HLSStreamWriter.fetch_map
- keep updated help text of --twitch-low-latency
- add streamlink_cli.utils.path.replace_path
- refactor streamlink_cli.utils.formatter.Formatter
- rename filename() to path() and return a pathlib.Path object
- format path parts separately
- replace dot and double-dot path parts if not explicit user input
- replace unsupported chars in path parts (like before)
- refactor streamlink_cli.output.FileOutput
- turn filename into a Path object
- create parent directories before creating the file descriptor
- update streamlink_cli.main accordingly
- update and add new tests